From 34049247528d76127500f1c3031df0589c4abb92 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 6 Oct 2018 11:39:52 +0100 Subject: [PATCH] Cache Function evaluation results, and use cached results --- src/function/eval.rs | 36 ++- src/function/mod.rs | 754 ++++++++++++++++++++++++++++++------------- src/lib.rs | 24 +- 3 files changed, 571 insertions(+), 243 deletions(-) diff --git a/src/function/eval.rs b/src/function/eval.rs index a5e12e1..1536cb0 100644 --- a/src/function/eval.rs +++ b/src/function/eval.rs @@ -193,7 +193,13 @@ fn evaluate_version( impl Function { pub fn eval(&self, state: &State) -> Result { - match *self { + if let Ok(reader) = state.condition_cache.read() { + if let Some(cached_result) = reader.get(self) { + return Ok(*cached_result); + } + } + + let result = match *self { Function::FilePath(ref f) => evaluate_file_path(state, f), Function::FileRegex(ref p, ref r) => evaluate_file_regex(state, p, r), Function::ActivePath(ref p) => evaluate_active_path(state, p), @@ -202,7 +208,15 @@ impl Function { Function::ManyActive(ref r) => evaluate_many_active(state, r), Function::Checksum(ref path, ref crc) => evaluate_checksum(state, path, *crc), Function::Version(ref p, ref v, ref c) => evaluate_version(state, p, v, *c), + }; + + if let Ok(function_result) = result { + if let Ok(mut writer) = state.condition_cache.write() { + writer.insert(self.clone(), function_result); + } } + + result } } @@ -210,7 +224,7 @@ impl Function { mod tests { use super::*; - use std::fs::{copy, create_dir}; + use std::fs::{copy, create_dir, remove_file}; use std::sync::RwLock; use regex::RegexBuilder; @@ -261,6 +275,7 @@ mod tests { .iter() .map(|(p, v)| (p.to_lowercase(), v.to_string())) .collect(), + condition_cache: RwLock::default(), } } @@ -573,6 +588,23 @@ mod tests { assert!(function.eval(&state).unwrap()); } + #[test] + fn function_eval_should_cache_results_and_use_cached_results() { + let tmp_dir = tempdir().unwrap(); + let data_path = tmp_dir.path().join("Data"); + let state = state(data_path); + + copy(Path::new("Cargo.toml"), &state.data_path.join("Cargo.toml")).unwrap(); + + let function = Function::FilePath(PathBuf::from("Cargo.toml")); + + assert!(function.eval(&state).unwrap()); + + remove_file(&state.data_path.join("Cargo.toml")).unwrap(); + + assert!(function.eval(&state).unwrap()); + } + #[test] fn function_version_eval_should_be_true_if_the_path_does_not_exist_and_comparator_is_ne() { let function = diff --git a/src/function/mod.rs b/src/function/mod.rs index 6168800..b916912 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -1,4 +1,6 @@ use std::fmt; +use std::hash::{Hash, Hasher}; +use std::mem::discriminant; use std::path::PathBuf; use regex::Regex; @@ -7,7 +9,7 @@ use unicase::eq; pub mod eval; pub mod parse; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ComparisonOperator { Equal, NotEqual, @@ -86,6 +88,45 @@ impl PartialEq for Function { impl Eq for Function {} +impl Hash for Function { + fn hash(&self, state: &mut H) { + use Function::*; + match self { + FilePath(p) => { + p.to_string_lossy().to_lowercase().hash(state); + } + FileRegex(p, r) => { + p.to_string_lossy().to_lowercase().hash(state); + r.as_str().to_lowercase().hash(state); + } + ActivePath(p) => { + p.to_string_lossy().to_lowercase().hash(state); + } + ActiveRegex(r) => { + r.as_str().to_lowercase().hash(state); + } + Many(p, r) => { + p.to_string_lossy().to_lowercase().hash(state); + r.as_str().to_lowercase().hash(state); + } + ManyActive(r) => { + r.as_str().to_lowercase().hash(state); + } + Checksum(p, c) => { + p.to_string_lossy().to_lowercase().hash(state); + c.hash(state); + } + Version(p, v, c) => { + p.to_string_lossy().to_lowercase().hash(state); + v.to_lowercase().hash(state); + c.hash(state); + } + } + + discriminant(self).hash(state); + } +} + #[cfg(test)] mod tests { use super::*; @@ -94,281 +135,528 @@ mod tests { Regex::new(string).unwrap() } - #[test] - fn function_fmt_for_file_path_should_format_correctly() { - let function = Function::FilePath("subdir/Blank.esm".into()); + mod fmt { + use super::*; - assert_eq!("file(\"subdir/Blank.esm\")", &format!("{}", function)); + #[test] + fn function_fmt_for_file_path_should_format_correctly() { + let function = Function::FilePath("subdir/Blank.esm".into()); + + assert_eq!("file(\"subdir/Blank.esm\")", &format!("{}", function)); + } + + #[test] + fn function_fmt_for_file_regex_should_format_correctly() { + let function = Function::FileRegex("subdir".into(), regex("Blank.*")); + + assert_eq!("file(\"subdir/Blank.*\")", &format!("{}", function)); + } + + #[test] + fn function_fmt_for_active_path_should_format_correctly() { + let function = Function::ActivePath("Blank.esm".into()); + + assert_eq!("active(\"Blank.esm\")", &format!("{}", function)); + } + + #[test] + fn function_fmt_for_active_regex_should_format_correctly() { + let function = Function::ActiveRegex(regex("Blank.*")); + + assert_eq!("active(\"Blank.*\")", &format!("{}", function)); + } + + #[test] + fn function_fmt_for_many_should_format_correctly() { + let function = Function::Many("subdir".into(), regex("Blank.*")); + + assert_eq!("many(\"subdir/Blank.*\")", &format!("{}", function)); + } + + #[test] + fn function_fmt_for_many_active_should_format_correctly() { + let function = Function::ManyActive(regex("Blank.*")); + + assert_eq!("many_active(\"Blank.*\")", &format!("{}", function)); + } + + #[test] + fn function_fmt_for_checksum_should_format_correctly() { + let function = Function::Checksum("subdir/Blank.esm".into(), 0xDEADBEEF); + + assert_eq!( + "checksum(\"subdir/Blank.esm\", DEADBEEF)", + &format!("{}", function) + ); + } + + #[test] + fn function_fmt_for_version_should_format_correctly() { + let function = Function::Version( + "subdir/Blank.esm".into(), + "1.2a".into(), + ComparisonOperator::Equal, + ); + + assert_eq!( + "version(\"subdir/Blank.esm\", \"1.2a\", ==)", + &format!("{}", function) + ); + } } - #[test] - fn function_fmt_for_file_regex_should_format_correctly() { - let function = Function::FileRegex("subdir".into(), regex("Blank.*")); + mod eq { + use super::*; - assert_eq!("file(\"subdir/Blank.*\")", &format!("{}", function)); + #[test] + fn function_eq_for_file_path_should_check_pathbuf() { + assert_eq!( + Function::FilePath("Blank.esm".into()), + Function::FilePath("Blank.esm".into()) + ); + + assert_ne!( + Function::FilePath("Blank.esp".into()), + Function::FilePath("Blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_file_path_should_be_case_insensitive_on_pathbuf() { + assert_eq!( + Function::FilePath("Blank.esm".into()), + Function::FilePath("blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_file_regex_should_check_pathbuf_and_regex() { + assert_eq!( + Function::FileRegex("subdir".into(), regex("blank.*")), + Function::FileRegex("subdir".into(), regex("blank.*")) + ); + + assert_ne!( + Function::FileRegex("subdir".into(), regex("blank.*")), + Function::FileRegex("other".into(), regex("blank.*")) + ); + assert_ne!( + Function::FileRegex("subdir".into(), regex("blank.*")), + Function::FileRegex("subdir".into(), regex(".*")) + ); + } + + #[test] + fn function_eq_for_file_regex_should_be_case_insensitive_on_pathbuf_and_regex() { + assert_eq!( + Function::FileRegex("subdir".into(), regex("blank.*")), + Function::FileRegex("Subdir".into(), regex("Blank.*")) + ); + } + + #[test] + fn function_eq_for_active_path_should_check_pathbuf() { + assert_eq!( + Function::ActivePath("Blank.esm".into()), + Function::ActivePath("Blank.esm".into()) + ); + + assert_ne!( + Function::ActivePath("Blank.esp".into()), + Function::ActivePath("Blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_active_path_should_be_case_insensitive_on_pathbuf() { + assert_eq!( + Function::ActivePath("Blank.esm".into()), + Function::ActivePath("blank.esm".into()) + ); + } + + #[test] + fn function_eq_active_path_should_not_be_equal_to_file_path_with_same_pathbuf() { + assert_ne!( + Function::ActivePath("Blank.esm".into()), + Function::FilePath("Blank.esm".into()) + ); + } + + #[test] + fn function_eq_for_active_regex_should_check_regex() { + assert_eq!( + Function::ActiveRegex(regex("blank.*")), + Function::ActiveRegex(regex("blank.*")) + ); + + assert_ne!( + Function::ActiveRegex(regex("blank.*")), + Function::ActiveRegex(regex(".*")) + ); + } + + #[test] + fn function_eq_for_active_regex_should_be_case_insensitive_on_regex() { + assert_eq!( + Function::ActiveRegex(regex("blank.*")), + Function::ActiveRegex(regex("Blank.*")) + ); + } + + #[test] + fn function_eq_for_many_should_check_pathbuf_and_regex() { + assert_eq!( + Function::Many("subdir".into(), regex("blank.*")), + Function::Many("subdir".into(), regex("blank.*")) + ); + + assert_ne!( + Function::Many("subdir".into(), regex("blank.*")), + Function::Many("subdir".into(), regex(".*")) + ); + assert_ne!( + Function::Many("subdir".into(), regex("blank.*")), + Function::Many("other".into(), regex("blank.*")) + ); + } + + #[test] + fn function_eq_for_many_should_be_case_insensitive_on_pathbuf_and_regex() { + assert_eq!( + Function::FileRegex("subdir".into(), regex("blank.*")), + Function::FileRegex("Subdir".into(), regex("Blank.*")) + ); + } + + #[test] + fn function_eq_many_should_not_be_equal_to_file_regex_with_same_pathbuf_and_regex() { + assert_ne!( + Function::Many("subdir".into(), regex("blank.*")), + Function::FileRegex("subdir".into(), regex("blank.*")) + ); + } + + #[test] + fn function_eq_for_many_active_should_check_regex() { + assert_eq!( + Function::ManyActive(regex("blank.*")), + Function::ManyActive(regex("blank.*")) + ); + + assert_ne!( + Function::ManyActive(regex("blank.*")), + Function::ManyActive(regex(".*")) + ); + } + + #[test] + fn function_eq_for_many_active_should_be_case_insensitive_on_regex() { + assert_eq!( + Function::ManyActive(regex("blank.*")), + Function::ManyActive(regex("Blank.*")) + ); + } + + #[test] + fn function_eq_many_active_should_not_be_equal_to_active_regex_with_same_regex() { + assert_ne!( + Function::ManyActive(regex("blank.*")), + Function::ActiveRegex(regex("blank.*")) + ); + } + + #[test] + fn function_eq_for_checksum_should_check_pathbuf_and_crc() { + assert_eq!( + Function::Checksum("Blank.esm".into(), 1), + Function::Checksum("Blank.esm".into(), 1) + ); + + assert_ne!( + Function::Checksum("Blank.esm".into(), 1), + Function::Checksum("Blank.esm".into(), 2) + ); + assert_ne!( + Function::Checksum("Blank.esm".into(), 1), + Function::Checksum("Blank.esp".into(), 1) + ); + } + + #[test] + fn function_eq_for_checksum_should_be_case_insensitive_on_pathbuf() { + assert_eq!( + Function::Checksum("Blank.esm".into(), 1), + Function::Checksum("blank.esm".into(), 1) + ); + } + + #[test] + fn function_eq_for_version_should_check_pathbuf_version_and_comparator() { + assert_eq!( + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal) + ); + + assert_ne!( + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), + Function::Version("Blank.esp".into(), "1".into(), ComparisonOperator::Equal) + ); + assert_ne!( + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), + Function::Version("Blank.esm".into(), "2".into(), ComparisonOperator::Equal) + ); + assert_ne!( + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::NotEqual) + ); + } + + #[test] + fn function_eq_for_version_should_be_case_insensitive_on_pathbuf_and_version() { + assert_eq!( + Function::Version("Blank.esm".into(), "A".into(), ComparisonOperator::Equal), + Function::Version("blank.esm".into(), "a".into(), ComparisonOperator::Equal) + ); + } } - #[test] - fn function_fmt_for_active_path_should_format_correctly() { - let function = Function::ActivePath("Blank.esm".into()); + mod hash { + use super::*; - assert_eq!("active(\"Blank.esm\")", &format!("{}", function)); - } + use std::collections::hash_map::DefaultHasher; - #[test] - fn function_fmt_for_active_regex_should_format_correctly() { - let function = Function::ActiveRegex(regex("Blank.*")); + fn hash(function: Function) -> u64 { + let mut hasher = DefaultHasher::new(); + function.hash(&mut hasher); + hasher.finish() + } - assert_eq!("active(\"Blank.*\")", &format!("{}", function)); - } + #[test] + fn function_hash_file_path_should_hash_pathbuf() { + let function1 = Function::FilePath("Blank.esm".into()); + let function2 = Function::FilePath("Blank.esm".into()); - #[test] - fn function_fmt_for_many_should_format_correctly() { - let function = Function::Many("subdir".into(), regex("Blank.*")); + assert_eq!(hash(function1), hash(function2)); - assert_eq!("many(\"subdir/Blank.*\")", &format!("{}", function)); - } + let function1 = Function::FilePath("Blank.esm".into()); + let function2 = Function::FilePath("Blank.esp".into()); - #[test] - fn function_fmt_for_many_active_should_format_correctly() { - let function = Function::ManyActive(regex("Blank.*")); + assert_ne!(hash(function1), hash(function2)); + } - assert_eq!("many_active(\"Blank.*\")", &format!("{}", function)); - } + #[test] + fn function_hash_file_path_should_be_case_insensitive() { + let function1 = Function::FilePath("Blank.esm".into()); + let function2 = Function::FilePath("blank.esm".into()); - #[test] - fn function_fmt_for_checksum_should_format_correctly() { - let function = Function::Checksum("subdir/Blank.esm".into(), 0xDEADBEEF); + assert_eq!(hash(function1), hash(function2)); + } - assert_eq!( - "checksum(\"subdir/Blank.esm\", DEADBEEF)", - &format!("{}", function) - ); - } + #[test] + fn function_hash_file_regex_should_hash_pathbuf_and_regex() { + let function1 = Function::FileRegex("subdir".into(), regex(".*")); + let function2 = Function::FileRegex("subdir".into(), regex(".*")); - #[test] - fn function_fmt_for_version_should_format_correctly() { - let function = Function::Version( - "subdir/Blank.esm".into(), - "1.2a".into(), - ComparisonOperator::Equal, - ); + assert_eq!(hash(function1), hash(function2)); - assert_eq!( - "version(\"subdir/Blank.esm\", \"1.2a\", ==)", - &format!("{}", function) - ); - } + let function1 = Function::FileRegex("subdir".into(), regex(".*")); + let function2 = Function::FileRegex("other".into(), regex(".*")); - #[test] - fn function_eq_for_file_path_should_check_pathbuf() { - assert_eq!( - Function::FilePath("Blank.esm".into()), - Function::FilePath("Blank.esm".into()) - ); + assert_ne!(hash(function1), hash(function2)); - assert_ne!( - Function::FilePath("Blank.esp".into()), - Function::FilePath("Blank.esm".into()) - ); - } + let function1 = Function::FileRegex("subdir".into(), regex(".*")); + let function2 = Function::FileRegex("subdir".into(), regex("Blank.*")); - #[test] - fn function_eq_for_file_path_should_be_case_insensitive_on_pathbuf() { - assert_eq!( - Function::FilePath("Blank.esm".into()), - Function::FilePath("blank.esm".into()) - ); - } + assert_ne!(hash(function1), hash(function2)); + } - #[test] - fn function_eq_for_file_regex_should_check_pathbuf_and_regex() { - assert_eq!( - Function::FileRegex("subdir".into(), regex("blank.*")), - Function::FileRegex("subdir".into(), regex("blank.*")) - ); + #[test] + fn function_hash_file_regex_should_be_case_insensitive() { + let function1 = Function::FileRegex("Subdir".into(), regex("Blank.*")); + let function2 = Function::FileRegex("subdir".into(), regex("blank.*")); - assert_ne!( - Function::FileRegex("subdir".into(), regex("blank.*")), - Function::FileRegex("other".into(), regex("blank.*")) - ); - assert_ne!( - Function::FileRegex("subdir".into(), regex("blank.*")), - Function::FileRegex("subdir".into(), regex(".*")) - ); - } + assert_eq!(hash(function1), hash(function2)); + } - #[test] - fn function_eq_for_file_regex_should_be_case_insensitive_on_pathbuf_and_regex() { - assert_eq!( - Function::FileRegex("subdir".into(), regex("blank.*")), - Function::FileRegex("Subdir".into(), regex("Blank.*")) - ); - } + #[test] + fn function_hash_active_path_should_hash_pathbuf() { + let function1 = Function::ActivePath("Blank.esm".into()); + let function2 = Function::ActivePath("Blank.esm".into()); - #[test] - fn function_eq_for_active_path_should_check_pathbuf() { - assert_eq!( - Function::ActivePath("Blank.esm".into()), - Function::ActivePath("Blank.esm".into()) - ); + assert_eq!(hash(function1), hash(function2)); - assert_ne!( - Function::ActivePath("Blank.esp".into()), - Function::ActivePath("Blank.esm".into()) - ); - } + let function1 = Function::ActivePath("Blank.esm".into()); + let function2 = Function::ActivePath("Blank.esp".into()); - #[test] - fn function_eq_for_active_path_should_be_case_insensitive_on_pathbuf() { - assert_eq!( - Function::ActivePath("Blank.esm".into()), - Function::ActivePath("blank.esm".into()) - ); - } + assert_ne!(hash(function1), hash(function2)); + } - #[test] - fn function_eq_active_path_should_not_be_equal_to_file_path_with_same_pathbuf() { - assert_ne!( - Function::ActivePath("Blank.esm".into()), - Function::FilePath("Blank.esm".into()) - ); - } + #[test] + fn function_hash_active_path_should_be_case_insensitive() { + let function1 = Function::ActivePath("Blank.esm".into()); + let function2 = Function::ActivePath("blank.esm".into()); - #[test] - fn function_eq_for_active_regex_should_check_regex() { - assert_eq!( - Function::ActiveRegex(regex("blank.*")), - Function::ActiveRegex(regex("blank.*")) - ); + assert_eq!(hash(function1), hash(function2)); + } - assert_ne!( - Function::ActiveRegex(regex("blank.*")), - Function::ActiveRegex(regex(".*")) - ); - } + #[test] + fn function_hash_file_path_and_active_path_should_not_have_equal_hashes() { + let function1 = Function::FilePath("Blank.esm".into()); + let function2 = Function::ActivePath("Blank.esm".into()); - #[test] - fn function_eq_for_active_regex_should_be_case_insensitive_on_regex() { - assert_eq!( - Function::ActiveRegex(regex("blank.*")), - Function::ActiveRegex(regex("Blank.*")) - ); - } + assert_ne!(hash(function1), hash(function2)); + } - #[test] - fn function_eq_for_many_should_check_pathbuf_and_regex() { - assert_eq!( - Function::Many("subdir".into(), regex("blank.*")), - Function::Many("subdir".into(), regex("blank.*")) - ); + #[test] + fn function_hash_active_regex_should_hash_pathbuf_and_regex() { + let function1 = Function::ActiveRegex(regex(".*")); + let function2 = Function::ActiveRegex(regex(".*")); - assert_ne!( - Function::Many("subdir".into(), regex("blank.*")), - Function::Many("subdir".into(), regex(".*")) - ); - assert_ne!( - Function::Many("subdir".into(), regex("blank.*")), - Function::Many("other".into(), regex("blank.*")) - ); - } + assert_eq!(hash(function1), hash(function2)); - #[test] - fn function_eq_for_many_should_be_case_insensitive_on_pathbuf_and_regex() { - assert_eq!( - Function::FileRegex("subdir".into(), regex("blank.*")), - Function::FileRegex("Subdir".into(), regex("Blank.*")) - ); - } + let function1 = Function::ActiveRegex(regex(".*")); + let function2 = Function::ActiveRegex(regex("Blank.*")); - #[test] - fn function_eq_many_should_not_be_equal_to_file_regex_with_same_pathbuf_and_regex() { - assert_ne!( - Function::Many("subdir".into(), regex("blank.*")), - Function::FileRegex("subdir".into(), regex("blank.*")) - ); - } + assert_ne!(hash(function1), hash(function2)); + } - #[test] - fn function_eq_for_many_active_should_check_regex() { - assert_eq!( - Function::ManyActive(regex("blank.*")), - Function::ManyActive(regex("blank.*")) - ); + #[test] + fn function_hash_active_regex_should_be_case_insensitive() { + let function1 = Function::ActiveRegex(regex("Blank.*")); + let function2 = Function::ActiveRegex(regex("blank.*")); - assert_ne!( - Function::ManyActive(regex("blank.*")), - Function::ManyActive(regex(".*")) - ); - } + assert_eq!(hash(function1), hash(function2)); + } - #[test] - fn function_eq_for_many_active_should_be_case_insensitive_on_regex() { - assert_eq!( - Function::ManyActive(regex("blank.*")), - Function::ManyActive(regex("Blank.*")) - ); - } + #[test] + fn function_hash_many_should_hash_pathbuf_and_regex() { + let function1 = Function::Many("subdir".into(), regex(".*")); + let function2 = Function::Many("subdir".into(), regex(".*")); - #[test] - fn function_eq_many_active_should_not_be_equal_to_active_regex_with_same_regex() { - assert_ne!( - Function::ManyActive(regex("blank.*")), - Function::ActiveRegex(regex("blank.*")) - ); - } + assert_eq!(hash(function1), hash(function2)); - #[test] - fn function_eq_for_checksum_should_check_pathbuf_and_crc() { - assert_eq!( - Function::Checksum("Blank.esm".into(), 1), - Function::Checksum("Blank.esm".into(), 1) - ); + let function1 = Function::Many("subdir".into(), regex(".*")); + let function2 = Function::Many("other".into(), regex(".*")); - assert_ne!( - Function::Checksum("Blank.esm".into(), 1), - Function::Checksum("Blank.esm".into(), 2) - ); - assert_ne!( - Function::Checksum("Blank.esm".into(), 1), - Function::Checksum("Blank.esp".into(), 1) - ); - } + assert_ne!(hash(function1), hash(function2)); - #[test] - fn function_eq_for_checksum_should_be_case_insensitive_on_pathbuf() { - assert_eq!( - Function::Checksum("Blank.esm".into(), 1), - Function::Checksum("blank.esm".into(), 1) - ); - } + let function1 = Function::Many("subdir".into(), regex(".*")); + let function2 = Function::Many("subdir".into(), regex("Blank.*")); - #[test] - fn function_eq_for_version_should_check_pathbuf_version_and_comparator() { - assert_eq!( - Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), - Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal) - ); + assert_ne!(hash(function1), hash(function2)); + } - assert_ne!( - Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), - Function::Version("Blank.esp".into(), "1".into(), ComparisonOperator::Equal) - ); - assert_ne!( - Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), - Function::Version("Blank.esm".into(), "2".into(), ComparisonOperator::Equal) - ); - assert_ne!( - Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal), - Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::NotEqual) - ); - } + #[test] + fn function_hash_many_should_be_case_insensitive() { + let function1 = Function::Many("Subdir".into(), regex("Blank.*")); + let function2 = Function::Many("subdir".into(), regex("blank.*")); - #[test] - fn function_eq_for_version_should_be_case_insensitive_on_pathbuf_and_version() { - assert_eq!( - Function::Version("Blank.esm".into(), "A".into(), ComparisonOperator::Equal), - Function::Version("blank.esm".into(), "a".into(), ComparisonOperator::Equal) - ); + assert_eq!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_file_regex_and_many_should_not_have_equal_hashes() { + let function1 = Function::FileRegex("subdir".into(), regex(".*")); + let function2 = Function::Many("subdir".into(), regex(".*")); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_many_active_should_hash_pathbuf_and_regex() { + let function1 = Function::ManyActive(regex(".*")); + let function2 = Function::ManyActive(regex(".*")); + + assert_eq!(hash(function1), hash(function2)); + + let function1 = Function::ManyActive(regex(".*")); + let function2 = Function::ManyActive(regex("Blank.*")); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_many_active_should_be_case_insensitive() { + let function1 = Function::ManyActive(regex("Blank.*")); + let function2 = Function::ManyActive(regex("blank.*")); + + assert_eq!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_active_regex_and_many_active_should_not_have_equal_hashes() { + let function1 = Function::ActiveRegex(regex(".*")); + let function2 = Function::ManyActive(regex(".*")); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_checksum_should_hash_pathbuf_and_regex() { + let function1 = Function::Checksum("subdir".into(), 1); + let function2 = Function::Checksum("subdir".into(), 1); + + assert_eq!(hash(function1), hash(function2)); + + let function1 = Function::Checksum("subdir".into(), 1); + let function2 = Function::Checksum("other".into(), 1); + + assert_ne!(hash(function1), hash(function2)); + + let function1 = Function::Checksum("subdir".into(), 1); + let function2 = Function::Checksum("subdir".into(), 2); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_checksum_should_be_case_insensitive() { + let function1 = Function::Checksum("Blank.esm".into(), 1); + let function2 = Function::Checksum("Blank.esm".into(), 1); + + assert_eq!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_version_should_hash_pathbuf_and_version_and_comparator() { + let function1 = + Function::Version("Blank.esm".into(), "1.2a".into(), ComparisonOperator::Equal); + let function2 = + Function::Version("Blank.esm".into(), "1.2a".into(), ComparisonOperator::Equal); + + assert_eq!(hash(function1), hash(function2)); + + let function1 = + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal); + let function2 = + Function::Version("Blank.esp".into(), "1".into(), ComparisonOperator::Equal); + + assert_ne!(hash(function1), hash(function2)); + + let function1 = + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal); + let function2 = + Function::Version("Blank.esm".into(), "2".into(), ComparisonOperator::Equal); + + assert_ne!(hash(function1), hash(function2)); + + let function1 = + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::Equal); + let function2 = + Function::Version("Blank.esm".into(), "1".into(), ComparisonOperator::NotEqual); + + assert_ne!(hash(function1), hash(function2)); + } + + #[test] + fn function_hash_version_should_be_case_insensitive() { + let function1 = + Function::Version("Blank.esm".into(), "1.2a".into(), ComparisonOperator::Equal); + let function2 = + Function::Version("Blank.esm".into(), "1.2A".into(), ComparisonOperator::Equal); + + assert_eq!(hash(function1), hash(function2)); + } } } diff --git a/src/lib.rs b/src/lib.rs index ed3a039..82edaff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,15 +86,22 @@ impl GameType { pub struct State { game_type: GameType, + /// Game Data folder path. data_path: PathBuf, + /// Path to the LOOT executable, used to resolve conditions that use the "LOOT" path. loot_path: PathBuf, - active_plugins: HashSet, // Lowercased plugin filenames. - crc_cache: RwLock>, // Lowercased paths. - plugin_versions: HashMap, // Lowercased plugin filenames and their versions as found in description fields. + /// Lowercased plugin filenames. + active_plugins: HashSet, + /// Lowercased paths. + crc_cache: RwLock>, + /// Lowercased plugin filenames and their versions as found in description fields. + plugin_versions: HashMap, + /// Conditions that have already been evaluated, and their results. + condition_cache: RwLock>, } -// Compound conditions joined by 'or' -#[derive(Clone, Debug, Default, PartialEq, Eq)] +/// Compound conditions joined by 'or' +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Expression(Vec); impl Expression { @@ -124,8 +131,8 @@ impl fmt::Display for Expression { } } -// Conditions joined by 'and' -#[derive(Clone, Debug, Default, PartialEq, Eq)] +/// Conditions joined by 'and' +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] struct CompoundCondition(Vec); impl CompoundCondition { @@ -154,7 +161,7 @@ impl fmt::Display for CompoundCondition { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] enum Condition { Function(Function), InvertedFunction(Function), @@ -219,6 +226,7 @@ mod tests { active_plugins: HashSet::new(), crc_cache: RwLock::default(), plugin_versions: HashMap::default(), + condition_cache: RwLock::default(), } }