Crates.io | havocompare |
lib.rs | havocompare |
version | 0.6.1 |
source | src |
created_at | 2022-10-18 12:47:34.67114 |
updated_at | 2024-06-05 07:06:50.378808 |
description | A flexible rule-based file and folder comparison tool and crate including nice html reporting. Compares CSVs, JSON, text files, pdf-texts and images. |
homepage | https://github.com/VolumeGraphics/havocompare |
repository | https://github.com/VolumeGraphics/havocompare |
max_upload_size | |
id | 690883 |
size | 291,457 |
You have rust? cool! try:
cargo install havocompare
You just want a binary: Check our binary downloads on github-pages
Havocompare was developed with a few design goals in mind. We wanted a human-readable and easily composable
configuration file format.
After a few tries we ended up with the current format, which is a list of rules inside a yaml file.
See the following example config.yaml
:
rules:
- name: "Numerical results csv"
# you can have multiple includes and excludes
pattern_include:
- "**/export_*.csv"
# excludes are optional
pattern_exclude:
- "**/export_1337.csv"
CSV:
comparison_modes:
- Relative: 0.1
- Absolute: 1.0
It creates a new rule named rule including all files matching "export_*.csv" in all sub-folders but exclude "
export_1337.csv".
String cells will be checked for perfect identity, numbers (including numbers with units) will be checked for a relative
deviation smaller than 0.1
AND absolute deviation smaller than 1.0
.
Comparison rules
|nominal - actual| / |nominal| < tolerance
|nominal - actual| < tolerance
0
difference with 0
nominal value is valid for any relative differenceRunning the comparison is super easy, just supply nominal, actual and the config:
./havocompare compare nominal_dir actual_dir config.yaml
The report of the comparison will be written inside the ./report
folder. Differences will also be printed to the
terminal.
Furthermore, if differences are found, the return code will be 1
, if no differences are found, it will be 0
making
integration of
havocompare into a CI system rather easy.
Writing a valid configuration file can be error-prone without auto-completion. We suggest using json schema to validate
your yaml
and even enable auto-completion in IDEs like pycharm. To generate the schema you can call:
./havocompare schema > config_scheme.json
and import the resulting scheme into your IDE.
The comparison_modes
option is required and of type 'list'. It can comprise either a relative numerical ('Relative')
maximum deviation or a maximum
deviation ('Absolute').
You can specify the decimal separator and the field separator. If you don't specify, havocompare will try to guess it
from each csv file.
Note: If delimiters are not specified, even different delimiters between nominal and actual are accepted as long as all
deviations are in bounds.
To ignore specific cells, you can specify an exclusion regex.
The preprocessing steps are done after the file is parsed using the given delimiters (or guessing) but before anything else. Processing order is as written in the list. In the below example, headers will be extracted from the csv-input file, then a column with the title "Column to delete" will be deleted. If any of the preprocessing steps fail, havocompare will exit with an error immediately so use them carefully.
See the following example with all optional parameters set:
rules:
- name: "CSV - Demo all options"
# what files to include - use as many as make sense to reduce duplication in your rules
pattern_include:
- "**/*.csv"
# optional: of all included files, remove the ones matching any exclude pattern
pattern_exclude:
- "**/ignored.csv"
CSV:
# delimiters are optional, if not given, they will be auto-detected.
# auto-detection allows different delimiters for nominal and actual
decimal_separator: '.'
field_delimiter: ';'
# can have Absolute or Relative or both
comparison_modes:
- Absolute: 1.0
- Relative: 0.1
# optional: exclude fields matching the regex from comparison
exclude_field_regex: "Excluded"
# optional: preprocessing of the csv files
preprocessing:
# extracts the headers to the header-fields, makes reports more legible and allows for further processing "ByName".
# While it may fail, there's no penalty for it, as long as you don't rely on it.
- ExtractHeaders
# Sort the table by column 0, beware that the column must only contain numbers / quantities
- SortByColumnNumber: 0
# Delete a column by name, needs `ExtractHeaders` first - delete sets all values to 'DELETED'
- DeleteColumnByName: "Vertex_Position_Y"
- DeleteColumnByNumber: 1
# Sorts are stable, so a second sort will keep the first sort as sub-order.
- SortByColumnName: "Vertex_Position_X"
# Deletes the first row by setting all values to 'DELETED' - meaning that numbering stays constant
- DeleteRowByNumber: 0
# Deletes rows having any element matching the given regex (may delete different lines in nom / act)!
- DeleteRowByRegex: "Vertex_Count"
# Deletes the cell (column, row) by setting the value to 'DELETED'
- DeleteCellByNumber:
column: 0
row: 0
# Deletes the cell (column name, row) by setting the value to 'DELETED'. This needs `ExtractHeaders`
- DeleteCellByName:
column: "Column to delete"
row: 0
Image comparison is done using the image compare
crate.
Specify loads of options here and then filter on threshold.
rules:
- name: "JPG comparison"
pattern_include:
- "**/*.jpg"
# exclude can of course also be specified!
Image:
# Compare images in RGBA-mode, can also be RGB and Gray
# Comparison mode set to Hybrid means we want MSSIM on the Y channel and 2 dim vec diff on UV for color information
RGBA: Hybrid
threshold: 0.9
For plain text comparison the file is read and compared line by line. For each line the normalized Damerau-Levenshtein
distance from the strsim
crate is used. You can ignore single lines which you know are different by specifying an arbitrary number of ignored
lines:
rules:
- name: "HTML-Compare strict"
pattern_exclude:
- "**/*_changed.html"
pattern_include:
- "**/*.html"
PlainText:
# Normalized Damerau-Levenshtein distance
threshold: 1.0
# All lines matching any regex below will be ignored
ignore_lines:
- "stylesheet"
- "next_ignore"
- "[A-Z]*[0-9]"
For PDF text comparison the text will be extracted and written to temporary files. The files will then be compared using the Plain text comparison:
rules:
- name: "PDF-Text-Compare"
pattern_exclude:
- "**/*_changed.pdf"
pattern_include:
- "**/*.pdf"
PDFText:
# Normalized Damerau-Levenshtein distance
threshold: 1.0
# All lines matching any regex below will be ignored
ignore_lines:
- "stylesheet"
- "next_ignore"
- "[A-Z]*[0-9]"
For binary files which cannot otherwise be checked we can also do a simple hash comparison. Currently, we only support SHA-256 but more checks can be added easily.
rules:
- name: "Hash comparison strict"
pattern_exclude:
- "**/*.bin"
Hash:
# Currently we only have Sha256
function: Sha256
For the cases where the pure existence or some metadata are already enough.
rules:
- name: "Metadata comparison"
pattern_exclude:
- "**/*.bin"
FileProperties:
# nom/act file paths must not contain whitespace
forbid_name_regex: "[\\s]"
# files must have their modification timestamp within 3600 seconds
modification_date_tolerance_secs: 3600
# files sizes must be within 1 kb
file_size_tolerance_bytes: 1024
In case you want to run an external comparison tool, you can use this option
rules:
- name: "External checker"
pattern_include:
- "*.pdf"
External:
# this config will call `/usr/bin/pdf-diff --only-images nominal.pdf actual.pdf`
# return code will decide on comparison result
executable: "/usr/bin/pdf-diff"
# optional: add as many extra params as
extra_params:
- "--only-images"
Compares JSON files for different keys in both files and mismatches in values.
ignore_keys
is a list of regexes that are matched against the individual key names, the key value pair is excluded
from the comparison if a regex matches.
The values are not affected by this.
rules:
- name: "Compare JSON files"
pattern_include:
- "**/*.json"
Json:
ignore_keys:
# drop "ignore_this_key" and "ignore_this_keys" with this regex :)
- "ignore_this_key(s?)"
[dev-dependencies]
havocompare = "0.5"
#[test]
// works in 0.5 series
fn integ_test_dirs() {
let result_dir = process_whatever_test_data();
// just write the usual yaml file
let result = havocompare::compare_folders("../tests/data/nominal/integ_test/case", &result_dir, "../tests/data/config.yaml", "../tests/out_report").unwrap;
assert!(result);
}
#[test]
// works starting with 0.5.3 only
fn integ_test_file() {
let result_file = process_generate_image();
// see docs for all options
let compare_mode = ComparisonMode::Image(ImageCompareConfig{threshold: 0.97});
let result = havocompare::compare_files("../tests/data/nominal.png", &result_file, &compare_mode).unwrap;
assert!(result);
}
--open
compare
are now propagated to terminallinked_hash_map::Node<alloc::vec::Vec<u8>, object::Object>
uninitialized'"unwrap
and expect
in the library code in favor of correct error propagation