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 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())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue