Add support for product_version() conditions

This checks the product version field of executables instead of their
file version field.
This commit is contained in:
Oliver Hamlet
2018-10-23 21:12:40 +01:00
parent 07737cebe8
commit 0c0e8a6639
3 changed files with 208 additions and 6 deletions
+60 -5
View File
@@ -173,18 +173,30 @@ fn get_version(state: &State, file_path: &Path) -> Result<Option<Version>, Error
if state.game_type.is_plugin_filename(file_path) {
Ok(None)
} else {
Version::read_file_version(file_path).map(|v| Some(v))
Version::read_file_version(file_path).map(Some)
}
}
fn evaluate_version(
fn get_product_version(file_path: &Path) -> Result<Option<Version>, Error> {
if file_path.exists() {
Version::read_product_version(file_path).map(Some)
} else {
Ok(None)
}
}
fn evaluate_version<F>(
state: &State,
file_path: &Path,
given_version: &str,
comparator: ComparisonOperator,
) -> Result<bool, Error> {
read_version: F,
) -> Result<bool, Error>
where
F: Fn(&State, &Path) -> Result<Option<Version>, Error>,
{
let file_path = resolve_path(state, file_path);
let actual_version = match get_version(state, &file_path)? {
let actual_version = match read_version(state, &file_path)? {
Some(v) => v,
None => {
return Ok(comparator == ComparisonOperator::NotEqual
@@ -223,7 +235,10 @@ impl Function {
Function::Many(p, r) => evaluate_many(state, p, r),
Function::ManyActive(r) => evaluate_many_active(state, r),
Function::Checksum(path, crc) => evaluate_checksum(state, path, *crc),
Function::Version(p, v, c) => evaluate_version(state, p, v, *c),
Function::Version(p, v, c) => evaluate_version(state, p, v, *c, get_version),
Function::ProductVersion(p, v, c) => {
evaluate_version(state, p, v, *c, |_, p| get_product_version(p))
}
};
if self.is_slow() {
@@ -902,4 +917,44 @@ mod tests {
assert!(function.eval(&state).unwrap());
}
#[test]
fn function_version_eval_should_read_executable_file_version() {
let function = Function::Version(
"loot_api.dll".into(),
"0.13.8.0".into(),
ComparisonOperator::Equal,
);
let state = state("tests/loot_api_win32");
assert!(function.eval(&state).unwrap());
}
#[test]
fn function_product_version_eval_should_read_executable_product_version() {
let function =
Function::Version("7za.exe".into(), "18.05".into(), ComparisonOperator::Equal);
let state = state("tests/7z");
assert!(function.eval(&state).unwrap());
}
#[test]
fn get_product_version_should_return_ok_none_if_the_path_does_not_exist() {
assert!(get_product_version(Path::new("missing")).unwrap().is_none());
}
#[test]
fn get_product_version_should_return_ok_some_if_the_path_is_an_executable() {
let version = get_product_version(Path::new("tests/7z/7za.exe"))
.unwrap()
.unwrap();
assert_eq!(Version::from("18.05"), version);
}
#[test]
fn get_product_version_should_error_if_the_path_is_not_an_executable() {
assert!(get_product_version(Path::new("Cargo.toml")).is_err());
}
}
+115
View File
@@ -43,6 +43,7 @@ pub enum Function {
ManyActive(Regex),
Checksum(PathBuf, u32),
Version(PathBuf, String, ComparisonOperator),
ProductVersion(PathBuf, String, ComparisonOperator),
}
impl fmt::Display for Function {
@@ -57,6 +58,9 @@ impl fmt::Display for Function {
ManyActive(r) => write!(f, "many_active(\"{}\")", r),
Checksum(p, c) => write!(f, "checksum(\"{}\", {:02X?})", p.display(), c),
Version(p, v, c) => write!(f, "version(\"{}\", \"{}\", {})", p.display(), v, c),
ProductVersion(p, v, c) => {
write!(f, "product_version(\"{}\", \"{}\", {})", p.display(), v, c)
}
}
}
}
@@ -81,6 +85,9 @@ impl PartialEq for Function {
(Version(p1, v1, c1), Version(p2, v2, c2)) => {
c1 == c2 && eq(&v1, &v2) && eq(&p1.to_string_lossy(), &p2.to_string_lossy())
}
(ProductVersion(p1, v1, c1), ProductVersion(p2, v2, c2)) => {
c1 == c2 && eq(&v1, &v2) && eq(&p1.to_string_lossy(), &p2.to_string_lossy())
}
_ => false,
}
}
@@ -121,6 +128,11 @@ impl Hash for Function {
v.to_lowercase().hash(state);
c.hash(state);
}
ProductVersion(p, v, c) => {
p.to_string_lossy().to_lowercase().hash(state);
v.to_lowercase().hash(state);
c.hash(state);
}
}
discriminant(self).hash(state);
@@ -203,6 +215,20 @@ mod tests {
&format!("{}", function)
);
}
#[test]
fn function_fmt_for_product_version_should_format_correctly() {
let function = Function::ProductVersion(
"../TESV.exe".into(),
"1.2a".into(),
ComparisonOperator::Equal,
);
assert_eq!(
"product_version(\"../TESV.exe\", \"1.2a\", ==)",
&format!("{}", function)
);
}
}
mod eq {
@@ -419,6 +445,39 @@ mod tests {
Function::Version("blank.esm".into(), "a".into(), ComparisonOperator::Equal)
);
}
#[test]
fn function_eq_for_product_version_should_check_pathbuf_version_and_comparator() {
assert_eq!(
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal),
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal)
);
assert_ne!(
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal),
Function::ProductVersion("Blank.esp".into(), "1".into(), ComparisonOperator::Equal)
);
assert_ne!(
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal),
Function::ProductVersion("Blank.esm".into(), "2".into(), ComparisonOperator::Equal)
);
assert_ne!(
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal),
Function::ProductVersion(
"Blank.esm".into(),
"1".into(),
ComparisonOperator::NotEqual
)
);
}
#[test]
fn function_eq_for_product_version_should_be_case_insensitive_on_pathbuf_and_version() {
assert_eq!(
Function::ProductVersion("Blank.esm".into(), "A".into(), ComparisonOperator::Equal),
Function::ProductVersion("blank.esm".into(), "a".into(), ComparisonOperator::Equal)
);
}
}
mod hash {
@@ -658,5 +717,61 @@ mod tests {
assert_eq!(hash(function1), hash(function2));
}
#[test]
fn function_hash_product_version_should_hash_pathbuf_and_version_and_comparator() {
let function1 = Function::ProductVersion(
"Blank.esm".into(),
"1.2a".into(),
ComparisonOperator::Equal,
);
let function2 = Function::ProductVersion(
"Blank.esm".into(),
"1.2a".into(),
ComparisonOperator::Equal,
);
assert_eq!(hash(function1), hash(function2));
let function1 =
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal);
let function2 =
Function::ProductVersion("Blank.esp".into(), "1".into(), ComparisonOperator::Equal);
assert_ne!(hash(function1), hash(function2));
let function1 =
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal);
let function2 =
Function::ProductVersion("Blank.esm".into(), "2".into(), ComparisonOperator::Equal);
assert_ne!(hash(function1), hash(function2));
let function1 =
Function::ProductVersion("Blank.esm".into(), "1".into(), ComparisonOperator::Equal);
let function2 = Function::ProductVersion(
"Blank.esm".into(),
"1".into(),
ComparisonOperator::NotEqual,
);
assert_ne!(hash(function1), hash(function2));
}
#[test]
fn function_hash_product_version_should_be_case_insensitive() {
let function1 = Function::ProductVersion(
"Blank.esm".into(),
"1.2a".into(),
ComparisonOperator::Equal,
);
let function2 = Function::ProductVersion(
"Blank.esm".into(),
"1.2A".into(),
ComparisonOperator::Equal,
);
assert_eq!(hash(function1), hash(function2));
}
}
}
+33 -1
View File
@@ -228,6 +228,15 @@ impl Function {
Function::Version(path, version, comparator)
}
} |
delimited!(
fix_error!(ParsingError, tag!("product_version(")),
call!(parse_version_args),
fix_error!(ParsingError, tag!(")"))
) => {
|(path, version, comparator)| {
Function::ProductVersion(path, version, comparator)
}
} |
delimited!(
fix_error!(ParsingError, tag!("checksum(")),
call!(parse_checksum_args),
@@ -419,7 +428,7 @@ mod tests {
assert_eq!("1.2", version);
assert_eq!(ComparisonOperator::Equal, comparator);
}
_ => panic!("Expected a checksum function"),
_ => panic!("Expected a version function"),
}
}
@@ -427,4 +436,27 @@ mod tests {
fn function_parse_should_error_if_the_version_path_is_outside_the_game_directory() {
assert!(Function::parse("version(\"../../Cargo.toml\", \"1.2\", ==)".into()).is_err());
}
#[test]
fn function_parse_should_parse_a_product_version_equals_function() {
let result = Function::parse("product_version(\"Cargo.toml\", \"1.2\", ==)".into())
.unwrap()
.1;
match result {
Function::ProductVersion(path, version, comparator) => {
assert_eq!(Path::new("Cargo.toml"), path);
assert_eq!("1.2", version);
assert_eq!(ComparisonOperator::Equal, comparator);
}
_ => panic!("Expected a product version function"),
}
}
#[test]
fn function_parse_should_error_if_the_product_version_path_is_outside_the_game_directory() {
assert!(
Function::parse("product_version(\"../../Cargo.toml\", \"1.2\", ==)".into()).is_err()
);
}
}