fix(tools): h5rs diff names its options as h5diff does
-c meant "list at most N differences" in h5rs, but in h5diff -c is --compare (a flag) and the count is -n/--count=N, so a script moved over from h5diff behaved differently: `h5diff -r -c 2 A B` exits 2 (the 2 is taken as a file name) while h5rs exited 1. The count is now -n/--count, -c/--compare is accepted (h5rs always lists objects that are not comparable), and the --count=N, --delta=D, --relative=R forms are accepted; exit codes equal h5diff's on 7 cases. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
+3
-2
@@ -128,8 +128,9 @@
|
|||||||
- `h5rs stat FILE` reports h5stat's object, link, rank, layout, filter,
|
- `h5rs stat FILE` reports h5stat's object, link, rank, layout, filter,
|
||||||
attribute, raw-data and file-size figures (equal to h5stat's on the test
|
attribute, raw-data and file-size figures (equal to h5stat's on the test
|
||||||
files); metadata space is one figure, not broken down.
|
files); metadata space is one figure, not broken down.
|
||||||
- `h5rs diff [-r] [-q] [-d D] [-p R] [--follow-symlinks] A B [OBJ1 [OBJ2]]`
|
- `h5rs diff [-r] [-q] [-n N] [-d D] [-p R] [--follow-symlinks] A B [OBJ1
|
||||||
compares objects, kinds, datatypes, shapes, attributes, values and link
|
[OBJ2]]` (option names as h5diff's: `-c` is `--compare`, the count is
|
||||||
|
`-n`/`--count=N`) compares objects, kinds, datatypes, shapes, attributes, values and link
|
||||||
targets; exit status 0/1/2 as h5diff's. Soft links are compared by
|
targets; exit status 0/1/2 as h5diff's. Soft links are compared by
|
||||||
target path, as h5diff's default, or with `--follow-symlinks` by the
|
target path, as h5diff's default, or with `--follow-symlinks` by the
|
||||||
objects they lead to (external links are never followed). Every path is
|
objects they lead to (external links are never followed). Every path is
|
||||||
|
|||||||
@@ -141,8 +141,13 @@ $ h5rs diff -r a.h5 b.h5 /d # list every differing element
|
|||||||
$ h5rs diff -d 0.001 a.h5 b.h5 # |a - b| > 0.001 is a difference
|
$ h5rs diff -d 0.001 a.h5 b.h5 # |a - b| > 0.001 is a difference
|
||||||
$ h5rs diff -p 0.01 a.h5 b.h5 # |a - b| / |a| > 1% is a difference
|
$ h5rs diff -p 0.01 a.h5 b.h5 # |a - b| / |a| > 1% is a difference
|
||||||
$ h5rs diff --follow-symlinks a.h5 b.h5 /lnk # what the soft link /lnk leads to
|
$ h5rs diff --follow-symlinks a.h5 b.h5 /lnk # what the soft link /lnk leads to
|
||||||
|
$ h5rs diff -r -n 10 a.h5 b.h5 # list at most 10 differing elements per object
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The options are named as h5diff's: `-n N`/`--count=N` limits the listed
|
||||||
|
elements, `-c`/`--compare` (list objects that are not comparable) is
|
||||||
|
accepted and always in effect, and `--delta=D`, `--relative=R` work too.
|
||||||
|
|
||||||
Integers are compared in integer arithmetic, with or without a tolerance, so
|
Integers are compared in integer arithmetic, with or without a tolerance, so
|
||||||
64-bit values beyond 2^53 lose no precision (`-d 0` tells 2^60 from
|
64-bit values beyond 2^53 lose no precision (`-d 0` tells 2^60 from
|
||||||
2^60 + 1). A relative tolerance below the f64 epsilon (2.2e-16) compares
|
2^60 + 1). A relative tolerance below the f64 epsilon (2.2e-16) compares
|
||||||
|
|||||||
@@ -11,30 +11,37 @@ pub struct Out<'a> {
|
|||||||
/// The arguments after the subcommand name.
|
/// The arguments after the subcommand name.
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
cmd: &'static str,
|
cmd: &'static str,
|
||||||
rest: std::vec::IntoIter<String>,
|
rest: std::collections::VecDeque<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
pub fn new(cmd: &'static str, rest: Vec<String>) -> Self {
|
pub fn new(cmd: &'static str, rest: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cmd,
|
cmd,
|
||||||
rest: rest.into_iter(),
|
rest: rest.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
#[allow(clippy::should_implement_trait)]
|
||||||
pub fn next(&mut self) -> Option<String> {
|
pub fn next(&mut self) -> Option<String> {
|
||||||
self.rest.next()
|
self.rest.pop_front()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Put an option and its value back, to be read next (for the
|
||||||
|
/// `--name=value` form).
|
||||||
|
pub fn push_front(&mut self, name: String, value: String) {
|
||||||
|
self.rest.push_front(value);
|
||||||
|
self.rest.push_front(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The value after an option such as `--max-bytes`.
|
/// The value after an option such as `--max-bytes`.
|
||||||
pub fn value(&mut self) -> Option<String> {
|
pub fn value(&mut self) -> Option<String> {
|
||||||
self.rest.next()
|
self.rest.pop_front()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A numeric option value.
|
/// A numeric option value.
|
||||||
pub fn number<T: std::str::FromStr>(&mut self) -> Option<T> {
|
pub fn number<T: std::str::FromStr>(&mut self) -> Option<T> {
|
||||||
self.rest.next().and_then(|s| s.parse().ok())
|
self.rest.pop_front().and_then(|s| s.parse().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Report a usage problem; exit status 2.
|
/// Report a usage problem; exit status 2.
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ soft link is compared as a link, as h5diff does, unless --follow-symlinks).
|
|||||||
-d, --delta D numbers differ only when |a - b| > D
|
-d, --delta D numbers differ only when |a - b| > D
|
||||||
-p, --relative R numbers differ only when |a - b| / |a| > R (an R below
|
-p, --relative R numbers differ only when |a - b| / |a| > R (an R below
|
||||||
the f64 epsilon, 2.2e-16, compares exactly, as h5diff)
|
the f64 epsilon, 2.2e-16, compares exactly, as h5diff)
|
||||||
-c, --count N list at most N differing elements per object with -r
|
-n, --count N list at most N differing elements per object with -r
|
||||||
|
-c, --compare list objects that are not comparable (always done;
|
||||||
|
accepted for h5diff compatibility)
|
||||||
--max-bytes N largest dataset read (default 1 GiB); a larger one is an error
|
--max-bytes N largest dataset read (default 1 GiB); a larger one is an error
|
||||||
|
|
||||||
Two NaNs compare equal. Unlike h5diff, objects that cannot be compared
|
Two NaNs compare equal. Unlike h5diff, objects that cannot be compared
|
||||||
@@ -139,7 +141,9 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
|||||||
}
|
}
|
||||||
_ => return args.usage_error(out, "--relative needs a number >= 0", USAGE),
|
_ => return args.usage_error(out, "--relative needs a number >= 0", USAGE),
|
||||||
},
|
},
|
||||||
"-c" | "--count" => match args.number() {
|
// h5diff's -c: h5rs always lists them.
|
||||||
|
"-c" | "--compare" => {}
|
||||||
|
"-n" | "--count" => match args.number() {
|
||||||
Some(n) => opts.count = n,
|
Some(n) => opts.count = n,
|
||||||
None => return args.usage_error(out, "--count needs a number", USAGE),
|
None => return args.usage_error(out, "--count needs a number", USAGE),
|
||||||
},
|
},
|
||||||
@@ -151,6 +155,14 @@ pub fn run(args: &mut Args, out: &mut Out) -> std::io::Result<i32> {
|
|||||||
writeln!(out.o, "{USAGE}")?;
|
writeln!(out.o, "{USAGE}")?;
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
// h5diff's --count=N, --delta=D, --relative=R forms.
|
||||||
|
s if s.starts_with("--") && s.contains('=') => {
|
||||||
|
let (k, v) = s.split_once('=').unwrap_or((s, ""));
|
||||||
|
if !matches!(k, "--count" | "--delta" | "--relative" | "--max-bytes") {
|
||||||
|
return args.usage_error(out, &format!("unknown option {a}"), USAGE);
|
||||||
|
}
|
||||||
|
args.push_front(k.to_string(), v.to_string());
|
||||||
|
}
|
||||||
s if s.starts_with('-') && s.len() > 1 => {
|
s if s.starts_with('-') && s.len() > 1 => {
|
||||||
return args.usage_error(out, &format!("unknown option {a}"), USAGE);
|
return args.usage_error(out, &format!("unknown option {a}"), USAGE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,6 +380,42 @@ fn diff_exit_codes_match_h5diff() {
|
|||||||
assert!(s.contains("2 difference(s) found"), "{s}");
|
assert!(s.contains("2 difference(s) found"), "{s}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The option names are h5diff's: -c is --compare (a flag), the count is
|
||||||
|
/// -n/--count, and the --name=value forms are accepted.
|
||||||
|
#[test]
|
||||||
|
fn diff_options_are_named_like_h5diff() {
|
||||||
|
let Some(f) = generate() else { return };
|
||||||
|
let (b, changed) = (f.p("base.h5"), f.p("changed.h5"));
|
||||||
|
let cases: Vec<(Vec<&str>, i32)> = vec![
|
||||||
|
// "2" is then the first file name, which does not exist.
|
||||||
|
(vec!["-r", "-c", "2", &b, &changed], 2),
|
||||||
|
(vec!["-c", &b, &changed], 1),
|
||||||
|
(vec!["--compare", &b, &b], 0),
|
||||||
|
(vec!["-r", "-n", "1", &b, &changed], 1),
|
||||||
|
(vec!["-r", "--count=1", &b, &changed], 1),
|
||||||
|
(vec!["--delta=0.01", &b, &changed], 0),
|
||||||
|
(vec!["--relative=1e-9", &b, &changed], 1),
|
||||||
|
];
|
||||||
|
let have_h5diff = tool_available("h5diff");
|
||||||
|
for (args, want) in cases {
|
||||||
|
if have_h5diff {
|
||||||
|
assert_eq!(code(&run("h5diff", &args)), want, "h5diff {args:?}");
|
||||||
|
}
|
||||||
|
let o = h5rs(&[&["diff"], args.as_slice()].concat());
|
||||||
|
assert_eq!(code(&o), want, "h5rs diff {args:?}: {}", stdout(&o));
|
||||||
|
}
|
||||||
|
for count in [vec!["-n", "1"], vec!["--count=1"]] {
|
||||||
|
let o = h5rs(&[&["diff", "-r"], count.as_slice(), &[&b, &changed, "/d"]].concat());
|
||||||
|
let s = stdout(&o);
|
||||||
|
assert!(
|
||||||
|
s.contains("[ 0 2 ]") && !s.contains("[ 2 3 ]"),
|
||||||
|
"{count:?}: {s}"
|
||||||
|
);
|
||||||
|
assert!(s.contains("2 difference(s) found"), "{s}");
|
||||||
|
}
|
||||||
|
assert_eq!(code(&h5rs(&["diff", "--bogus=1", &b, &b])), 2);
|
||||||
|
}
|
||||||
|
|
||||||
/// Tolerances on 64-bit integers one apart, far beyond f64's integer
|
/// Tolerances on 64-bit integers one apart, far beyond f64's integer
|
||||||
/// precision: compared exactly, as h5diff does.
|
/// precision: compared exactly, as h5diff does.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user