Merge pull request #751 from matthiasbeyer/libimagstore/store-tests-succeeding-hook-tests

libimagstore/store: tests succeeding hook tests
This commit is contained in:
Matthias Beyer 2016-09-19 22:39:23 +02:00 committed by GitHub
commit 513a9bd066
3 changed files with 249 additions and 64 deletions

View File

@ -1,5 +1,9 @@
use toml::Value; use toml::Value;
use libimagerror::into::IntoError;
use store::Result;
/// Check whether the configuration is valid for the store /// Check whether the configuration is valid for the store
/// ///
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will /// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
@ -42,32 +46,42 @@ use toml::Value;
/// You have been warned! /// 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::collections::BTreeMap;
use std::io::Write; use error::StoreErrorKind as SEK;
use std::io::stderr;
if config.is_none() { 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) v.get(key)
.map_or_else(|| { .ok_or_else(|| {
write!(stderr(), "Required key '{}' is not in store config", key).ok(); warn!("Required key '{}' is not in store config", key);
false SEK::ConfigKeyMissingError.into_error_with_cause(Box::new(kind.into_error()))
}, |t| match *t { })
Value::Array(ref a) => a.iter().all(|elem| { .and_then(|t| match *t {
match *elem { Value::Array(ref a) => {
Value::String(_) => true, a.iter().fold(Ok(()), |acc, elem| {
_ => false, acc.and_then(|_| {
} if is_match!(*elem, Value::String(_)) {
}), Ok(())
_ => { } else {
write!(stderr(), "Key '{}' in store config should contain an array", key) let cause = Box::new(kind.into_error());
.ok(); Err(SEK::ConfigTypeError.into_error_with_cause(cause))
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, section: &str,
key: &str, key: &str,
f: F) f: F)
-> bool -> Result<()>
where F: Fn(&Value) -> bool where F: Fn(&Value) -> bool
{ {
store_config.get(section) // The store config has the section `section` store_config.get(section) // The store config has the section `section`
.map_or_else(|| { .ok_or_else(|| {
write!(stderr(), "Store config expects section '{}' to be present, but isn't.", warn!("Store config expects section '{}' to be present, but isn't.", section);
section).ok(); SEK::ConfigKeyMissingError.into_error()
false })
}, |section_table| { .and_then(|section_table| match *section_table { // which is
match *section_table { // which is Value::Table(ref section_table) => // a table
Value::Table(ref section_table) => // a table section_table.iter().fold(Ok(()), |acc, (inner_key, cfg)| {
section_table acc.and_then(|_| {
.iter() // which has values, match *cfg {
.all(|(inner_key, cfg)| { // and all of these values Value::Table(ref hook_config) => { // are tables
match *cfg { // with a key
Value::Table(ref hook_config) => { // are tables let hook_aspect_is_valid = try!(hook_config.get(key)
hook_config.get(key) // with a key .map(|hook_aspect| f(&hook_aspect))
// fullfilling this constraint .ok_or(SEK::ConfigKeyMissingError.into_error())
.map_or(false, |hook_aspect| f(&hook_aspect)) );
},
_ => { if !hook_aspect_is_valid {
write!(stderr(), "Store config expects '{}' to be in '{}.{}', but isn't.", Err(SEK::ConfigTypeError.into_error())
key, section, inner_key).ok(); } else {
false Ok(())
} }
},
_ => {
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 { match *config {
Some(Value::Table(ref t)) => { 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") && try!(has_key_with_string_ary(t, "pre-create-hook-aspects", SEK::ConfigKeyPreCreateAspectsError));
has_key_with_string_ary(t, "post-create-hook-aspects") && try!(has_key_with_string_ary(t, "post-create-hook-aspects", SEK::ConfigKeyPostCreateAspectsError));
has_key_with_string_ary(t, "pre-retrieve-hook-aspects") && try!(has_key_with_string_ary(t, "pre-retrieve-hook-aspects", SEK::ConfigKeyPreRetrieveAspectsError));
has_key_with_string_ary(t, "post-retrieve-hook-aspects") && try!(has_key_with_string_ary(t, "post-retrieve-hook-aspects", SEK::ConfigKeyPostRetrieveAspectsError));
has_key_with_string_ary(t, "pre-update-hook-aspects") && try!(has_key_with_string_ary(t, "pre-update-hook-aspects", SEK::ConfigKeyPreUpdateAspectsError));
has_key_with_string_ary(t, "post-update-hook-aspects") && try!(has_key_with_string_ary(t, "post-update-hook-aspects", SEK::ConfigKeyPostUpdateAspectsError));
has_key_with_string_ary(t, "pre-delete-hook-aspects") && try!(has_key_with_string_ary(t, "pre-delete-hook-aspects", SEK::ConfigKeyPreDeleteAspectsError));
has_key_with_string_ary(t, "post-delete-hook-aspects") && 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 // The section "hooks" has maps which have a key "aspect" which has a value of type
// String // String
check_all_inner_maps_have_key_with(t, "hooks", "aspect", try!(check_all_inner_maps_have_key_with(t, "hooks", "aspect",
|asp| is_match!(asp, &Value::String(_))) && |asp| is_match!(asp, &Value::String(_))));
// The section "aspects" has maps which have a key "parllel" which has a value of type // The section "aspects" has maps which have a key "parllel" which has a value of type
// Boolean // Boolean
@ -142,8 +161,8 @@ pub fn config_is_valid(config: &Option<Value>) -> bool {
|asp| is_match!(asp, &Value::Boolean(_))) |asp| is_match!(asp, &Value::Boolean(_)))
} }
_ => { _ => {
write!(stderr(), "Store config is no table").ok(); warn!("Store config is no table");
false Err(SEK::ConfigTypeError.into_error())
}, },
} }
} }

View File

@ -6,6 +6,19 @@ pub struct CustomErrorData {}
generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData, generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData,
ConfigurationError => "Store Configuration Error", 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", CreateStoreDirDenied => "Creating store directory implicitely denied",
FileError => "File Error", FileError => "File Error",
IoError => "IO Error", IoError => "IO Error",

View File

@ -213,9 +213,7 @@ impl Store {
use configuration::*; use configuration::*;
debug!("Validating Store configuration"); debug!("Validating Store configuration");
if !config_is_valid(&store_config) { let _ = try!(config_is_valid(&store_config).map_err_into(SEK::ConfigurationError));
return Err(SE::new(SEK::ConfigurationError, None));
}
debug!("Building new Store object"); debug!("Building new Store object");
if !location.exists() { if !location.exists() {
@ -2158,7 +2156,7 @@ mod store_tests {
use super::Store; use super::Store;
fn get_store() -> Store { pub fn get_store() -> Store {
Store::new(PathBuf::from("/"), None).unwrap() 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());
}
}