Merge pull request #751 from matthiasbeyer/libimagstore/store-tests-succeeding-hook-tests
libimagstore/store: tests succeeding hook tests
This commit is contained in:
commit
513a9bd066
3 changed files with 249 additions and 64 deletions
|
@ -1,5 +1,9 @@
|
|||
use toml::Value;
|
||||
|
||||
use libimagerror::into::IntoError;
|
||||
|
||||
use store::Result;
|
||||
|
||||
/// Check whether the configuration is valid for the store
|
||||
///
|
||||
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
|
||||
|
@ -42,31 +46,41 @@ use toml::Value;
|
|||
/// You have been warned!
|
||||
///
|
||||
///
|
||||
pub fn config_is_valid(config: &Option<Value>) -> bool {
|
||||
pub fn config_is_valid(config: &Option<Value>) -> Result<()> {
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::io::stderr;
|
||||
use error::StoreErrorKind as SEK;
|
||||
|
||||
if config.is_none() {
|
||||
return true;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str) -> bool {
|
||||
/// Check whether the config has a key with a string array.
|
||||
/// The `key` is the key which is checked
|
||||
/// The `kind` is the error kind which is used as `cause` if there is an error, so we can
|
||||
/// indicate via error type which key is missing
|
||||
fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str,
|
||||
kind: SEK) -> Result<()> {
|
||||
v.get(key)
|
||||
.map_or_else(|| {
|
||||
write!(stderr(), "Required key '{}' is not in store config", key).ok();
|
||||
false
|
||||
}, |t| match *t {
|
||||
Value::Array(ref a) => a.iter().all(|elem| {
|
||||
match *elem {
|
||||
Value::String(_) => true,
|
||||
_ => false,
|
||||
.ok_or_else(|| {
|
||||
warn!("Required key '{}' is not in store config", key);
|
||||
SEK::ConfigKeyMissingError.into_error_with_cause(Box::new(kind.into_error()))
|
||||
})
|
||||
.and_then(|t| match *t {
|
||||
Value::Array(ref a) => {
|
||||
a.iter().fold(Ok(()), |acc, elem| {
|
||||
acc.and_then(|_| {
|
||||
if is_match!(*elem, Value::String(_)) {
|
||||
Ok(())
|
||||
} else {
|
||||
let cause = Box::new(kind.into_error());
|
||||
Err(SEK::ConfigTypeError.into_error_with_cause(cause))
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
},
|
||||
_ => {
|
||||
write!(stderr(), "Key '{}' in store config should contain an array", key)
|
||||
.ok();
|
||||
false
|
||||
warn!("Key '{}' in store config should contain an array", key);
|
||||
Err(SEK::ConfigTypeError.into_error_with_cause(Box::new(kind.into_error())))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -82,59 +96,64 @@ pub fn config_is_valid(config: &Option<Value>) -> bool {
|
|||
section: &str,
|
||||
key: &str,
|
||||
f: F)
|
||||
-> bool
|
||||
-> Result<()>
|
||||
where F: Fn(&Value) -> bool
|
||||
{
|
||||
store_config.get(section) // The store config has the section `section`
|
||||
.map_or_else(|| {
|
||||
write!(stderr(), "Store config expects section '{}' to be present, but isn't.",
|
||||
section).ok();
|
||||
false
|
||||
}, |section_table| {
|
||||
match *section_table { // which is
|
||||
.ok_or_else(|| {
|
||||
warn!("Store config expects section '{}' to be present, but isn't.", section);
|
||||
SEK::ConfigKeyMissingError.into_error()
|
||||
})
|
||||
.and_then(|section_table| match *section_table { // which is
|
||||
Value::Table(ref section_table) => // a table
|
||||
section_table
|
||||
.iter() // which has values,
|
||||
.all(|(inner_key, cfg)| { // and all of these values
|
||||
section_table.iter().fold(Ok(()), |acc, (inner_key, cfg)| {
|
||||
acc.and_then(|_| {
|
||||
match *cfg {
|
||||
Value::Table(ref hook_config) => { // are tables
|
||||
hook_config.get(key) // with a key
|
||||
// fullfilling this constraint
|
||||
.map_or(false, |hook_aspect| f(&hook_aspect))
|
||||
// with a key
|
||||
let hook_aspect_is_valid = try!(hook_config.get(key)
|
||||
.map(|hook_aspect| f(&hook_aspect))
|
||||
.ok_or(SEK::ConfigKeyMissingError.into_error())
|
||||
);
|
||||
|
||||
if !hook_aspect_is_valid {
|
||||
Err(SEK::ConfigTypeError.into_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
write!(stderr(), "Store config expects '{}' to be in '{}.{}', but isn't.",
|
||||
key, section, inner_key).ok();
|
||||
false
|
||||
warn!("Store config expects '{}' to be in '{}.{}', but isn't.",
|
||||
key, section, inner_key);
|
||||
Err(SEK::ConfigKeyMissingError.into_error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
_ => {
|
||||
write!(stderr(), "Store config expects '{}' to be a Table, but isn't.",
|
||||
section).ok();
|
||||
false
|
||||
}
|
||||
warn!("Store config expects '{}' to be a Table, but isn't.", section);
|
||||
Err(SEK::ConfigTypeError.into_error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
match *config {
|
||||
Some(Value::Table(ref t)) => {
|
||||
has_key_with_string_ary(t, "store-unload-hook-aspects") &&
|
||||
try!(has_key_with_string_ary(t, "store-unload-hook-aspects", SEK::ConfigKeyUnloadAspectsError));
|
||||
|
||||
has_key_with_string_ary(t, "pre-create-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "post-create-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "pre-retrieve-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "post-retrieve-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "pre-update-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "post-update-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "pre-delete-hook-aspects") &&
|
||||
has_key_with_string_ary(t, "post-delete-hook-aspects") &&
|
||||
try!(has_key_with_string_ary(t, "pre-create-hook-aspects", SEK::ConfigKeyPreCreateAspectsError));
|
||||
try!(has_key_with_string_ary(t, "post-create-hook-aspects", SEK::ConfigKeyPostCreateAspectsError));
|
||||
try!(has_key_with_string_ary(t, "pre-retrieve-hook-aspects", SEK::ConfigKeyPreRetrieveAspectsError));
|
||||
try!(has_key_with_string_ary(t, "post-retrieve-hook-aspects", SEK::ConfigKeyPostRetrieveAspectsError));
|
||||
try!(has_key_with_string_ary(t, "pre-update-hook-aspects", SEK::ConfigKeyPreUpdateAspectsError));
|
||||
try!(has_key_with_string_ary(t, "post-update-hook-aspects", SEK::ConfigKeyPostUpdateAspectsError));
|
||||
try!(has_key_with_string_ary(t, "pre-delete-hook-aspects", SEK::ConfigKeyPreDeleteAspectsError));
|
||||
try!(has_key_with_string_ary(t, "post-delete-hook-aspects", SEK::ConfigKeyPostDeleteAspectsError));
|
||||
|
||||
// The section "hooks" has maps which have a key "aspect" which has a value of type
|
||||
// String
|
||||
check_all_inner_maps_have_key_with(t, "hooks", "aspect",
|
||||
|asp| is_match!(asp, &Value::String(_))) &&
|
||||
try!(check_all_inner_maps_have_key_with(t, "hooks", "aspect",
|
||||
|asp| is_match!(asp, &Value::String(_))));
|
||||
|
||||
// The section "aspects" has maps which have a key "parllel" which has a value of type
|
||||
// Boolean
|
||||
|
@ -142,8 +161,8 @@ pub fn config_is_valid(config: &Option<Value>) -> bool {
|
|||
|asp| is_match!(asp, &Value::Boolean(_)))
|
||||
}
|
||||
_ => {
|
||||
write!(stderr(), "Store config is no table").ok();
|
||||
false
|
||||
warn!("Store config is no table");
|
||||
Err(SEK::ConfigTypeError.into_error())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,19 @@ pub struct CustomErrorData {}
|
|||
|
||||
generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData,
|
||||
ConfigurationError => "Store Configuration Error",
|
||||
ConfigTypeError => "Store configuration type error",
|
||||
ConfigKeyMissingError => "Configuration Key missing",
|
||||
|
||||
ConfigKeyUnloadAspectsError => "Config Key 'store-unload-hook-aspects' caused an error",
|
||||
ConfigKeyPreCreateAspectsError => "Config Key 'pre-create-hook-aspects' caused an error",
|
||||
ConfigKeyPostCreateAspectsError => "Config Key 'post-create-hook-aspects' caused an error",
|
||||
ConfigKeyPreRetrieveAspectsError => "Config Key 'pre-retrieve-hook-aspect' caused an error",
|
||||
ConfigKeyPostRetrieveAspectsError => "Config Key 'post-retrieve-hook-aspec' caused an error",
|
||||
ConfigKeyPreUpdateAspectsError => "Config Key 'pre-update-hook-aspects' caused an error",
|
||||
ConfigKeyPostUpdateAspectsError => "Config Key 'post-update-hook-aspects' caused an error",
|
||||
ConfigKeyPreDeleteAspectsError => "Config Key 'pre-delete-hook-aspects' caused an error",
|
||||
ConfigKeyPostDeleteAspectsError => "Config Key 'post-delete-hook-aspects' caused an error",
|
||||
|
||||
CreateStoreDirDenied => "Creating store directory implicitely denied",
|
||||
FileError => "File Error",
|
||||
IoError => "IO Error",
|
||||
|
|
|
@ -213,9 +213,7 @@ impl Store {
|
|||
use configuration::*;
|
||||
|
||||
debug!("Validating Store configuration");
|
||||
if !config_is_valid(&store_config) {
|
||||
return Err(SE::new(SEK::ConfigurationError, None));
|
||||
}
|
||||
let _ = try!(config_is_valid(&store_config).map_err_into(SEK::ConfigurationError));
|
||||
|
||||
debug!("Building new Store object");
|
||||
if !location.exists() {
|
||||
|
@ -2158,7 +2156,7 @@ mod store_tests {
|
|||
|
||||
use super::Store;
|
||||
|
||||
fn get_store() -> Store {
|
||||
pub fn get_store() -> Store {
|
||||
Store::new(PathBuf::from("/"), None).unwrap()
|
||||
}
|
||||
|
||||
|
@ -2352,3 +2350,158 @@ mod store_tests {
|
|||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod store_hook_tests {
|
||||
|
||||
mod succeeding_hook {
|
||||
use hook::Hook;
|
||||
use hook::accessor::HookDataAccessor;
|
||||
use hook::accessor::HookDataAccessorProvider;
|
||||
use hook::position::HookPosition;
|
||||
|
||||
use self::accessor::SucceedingHookAccessor as DHA;
|
||||
|
||||
use toml::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SucceedingHook {
|
||||
position: HookPosition,
|
||||
accessor: DHA,
|
||||
}
|
||||
|
||||
impl SucceedingHook {
|
||||
|
||||
pub fn new(pos: HookPosition) -> SucceedingHook {
|
||||
SucceedingHook { position: pos.clone(), accessor: DHA::new(pos) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Hook for SucceedingHook {
|
||||
fn name(&self) -> &'static str { "testhook_succeeding" }
|
||||
fn set_config(&mut self, _: &Value) { }
|
||||
}
|
||||
|
||||
impl HookDataAccessorProvider for SucceedingHook {
|
||||
|
||||
fn accessor(&self) -> HookDataAccessor {
|
||||
use hook::position::HookPosition as HP;
|
||||
use hook::accessor::HookDataAccessor as HDA;
|
||||
|
||||
match self.position {
|
||||
HP::StoreUnload |
|
||||
HP::PreCreate |
|
||||
HP::PreRetrieve |
|
||||
HP::PreDelete |
|
||||
HP::PostDelete => HDA::StoreIdAccess(&self.accessor),
|
||||
HP::PostCreate |
|
||||
HP::PostRetrieve |
|
||||
HP::PreUpdate |
|
||||
HP::PostUpdate => HDA::MutableAccess(&self.accessor),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub mod accessor {
|
||||
use hook::result::HookResult;
|
||||
use hook::accessor::MutableHookDataAccessor;
|
||||
use hook::accessor::NonMutableHookDataAccessor;
|
||||
use hook::accessor::StoreIdAccessor;
|
||||
use hook::position::HookPosition;
|
||||
use store::FileLockEntry;
|
||||
use storeid::StoreId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SucceedingHookAccessor(HookPosition);
|
||||
|
||||
impl SucceedingHookAccessor {
|
||||
|
||||
pub fn new(position: HookPosition) -> SucceedingHookAccessor {
|
||||
SucceedingHookAccessor(position)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl StoreIdAccessor for SucceedingHookAccessor {
|
||||
|
||||
fn access(&self, id: &StoreId) -> HookResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl MutableHookDataAccessor for SucceedingHookAccessor {
|
||||
|
||||
fn access_mut(&self, fle: &mut FileLockEntry) -> HookResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl NonMutableHookDataAccessor for SucceedingHookAccessor {
|
||||
|
||||
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use hook::position::HookPosition as HP;
|
||||
use storeid::StoreId;
|
||||
use store::Store;
|
||||
|
||||
use self::succeeding_hook::SucceedingHook;
|
||||
|
||||
fn get_store_with_config() -> Store {
|
||||
use toml::Parser;
|
||||
|
||||
let cfg = Parser::new(mini_config()).parse().unwrap();
|
||||
println!("Config parsed: {:?}", cfg);
|
||||
Store::new(PathBuf::from("/"), Some(cfg.get("store").cloned().unwrap())).unwrap()
|
||||
}
|
||||
|
||||
fn mini_config() -> &'static str {
|
||||
r#"
|
||||
[store]
|
||||
store-unload-hook-aspects = [ "test" ]
|
||||
pre-create-hook-aspects = [ "test" ]
|
||||
post-create-hook-aspects = [ "test" ]
|
||||
pre-move-hook-aspects = [ "test" ]
|
||||
post-move-hook-aspects = [ "test" ]
|
||||
pre-retrieve-hook-aspects = [ "test" ]
|
||||
post-retrieve-hook-aspects = [ "test" ]
|
||||
pre-update-hook-aspects = [ "test" ]
|
||||
post-update-hook-aspects = [ "test" ]
|
||||
pre-delete-hook-aspects = [ "test" ]
|
||||
post-delete-hook-aspects = [ "test" ]
|
||||
|
||||
[store.aspects.test]
|
||||
parallel = false
|
||||
mutable_hooks = true
|
||||
|
||||
[store.hooks.testhook_succeeding]
|
||||
aspect = "test"
|
||||
"#
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_create() {
|
||||
let mut store = get_store_with_config();
|
||||
let pos = HP::PreCreate;
|
||||
let hook = SucceedingHook::new(pos.clone());
|
||||
|
||||
assert!(store.register_hook(pos, "test", Box::new(hook)).map_err(|e| println!("{:?}", e)).is_ok());
|
||||
|
||||
let pb = StoreId::new_baseless(PathBuf::from("test")).unwrap();
|
||||
assert!(store.create(pb.clone()).is_ok());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue