fix(tools): h5rs diff compares every name of a hard-linked object
The path walk skipped the second hard link to an object, so a file that shares one dataset between /x and /y differed from a file holding two identical copies: "</y> exists only in <B>", exit 1, where h5diff exits 0. For a hard-linked group every member was reported the same way. diff now enumerates every path below the start object (a hard link back to an ancestor is recorded but not descended into), so each name is compared. A group whose links cannot be read is now an error instead of an empty group. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -109,6 +109,23 @@ small("attr.h5", attr=True)
|
||||
small("reshaped.h5", shape=(4, 3))
|
||||
small("int.h5", dtype="i4")
|
||||
|
||||
# One object under two names (a hard link) against two separate copies.
|
||||
with h5py.File(os.path.join(out, "hardlinked.h5"), "w") as f:
|
||||
f["x"] = np.arange(5)
|
||||
f["y"] = f["x"]
|
||||
g = f.create_group("g")
|
||||
g["d"] = np.arange(3)
|
||||
g.create_group("s")["e"] = np.arange(2)
|
||||
f["h"] = g
|
||||
for name, last in (("copied.h5", 1), ("copied_changed.h5", 9)):
|
||||
with h5py.File(os.path.join(out, name), "w") as f:
|
||||
f["x"] = np.arange(5)
|
||||
f["y"] = np.arange(5)
|
||||
for gname in ("g", "h"):
|
||||
g = f.create_group(gname)
|
||||
g["d"] = np.arange(3)
|
||||
g.create_group("s")["e"] = np.array([0, last if gname == "h" else 1])
|
||||
|
||||
with h5py.File(os.path.join(out, "userblock.h5"), "w", userblock_size=1024) as f:
|
||||
f["d"] = np.arange(10)
|
||||
|
||||
|
||||
@@ -380,6 +380,42 @@ fn diff_exit_codes_match_h5diff() {
|
||||
assert!(s.contains("2 difference(s) found"), "{s}");
|
||||
}
|
||||
|
||||
/// An object hard-linked under two names is compared under both, with
|
||||
/// everything below it, so a file that shares one object between two names
|
||||
/// equals a file that stores two identical copies.
|
||||
#[test]
|
||||
fn diff_compares_every_hard_link_path() {
|
||||
let Some(f) = generate() else { return };
|
||||
let (linked, copied, changed) = (
|
||||
f.p("hardlinked.h5"),
|
||||
f.p("copied.h5"),
|
||||
f.p("copied_changed.h5"),
|
||||
);
|
||||
// A hard-linked dataset: h5diff agrees (exit 0).
|
||||
for (a, b) in [(&linked, &copied), (&copied, &linked)] {
|
||||
let o = h5rs(&["diff", a, b, "/y"]);
|
||||
assert_eq!(code(&o), 0, "{a} {b} /y: {}", stdout(&o));
|
||||
if tool_available("h5diff") {
|
||||
assert_eq!(code(&run("h5diff", &[a, b, "/y"])), 0, "h5diff /y");
|
||||
}
|
||||
// The whole file, including a hard-linked group's members (h5diff
|
||||
// does not descend a group's second name, so it reports those
|
||||
// members as present in one file only and exits 1).
|
||||
let o = h5rs(&["diff", a, b]);
|
||||
assert_eq!(code(&o), 0, "{a} {b}: {}", stdout(&o));
|
||||
assert!(!stdout(&o).contains("exists only"), "{}", stdout(&o));
|
||||
}
|
||||
// A value under the second name is compared, not skipped.
|
||||
let o = h5rs(&["diff", "-r", &linked, &changed]);
|
||||
assert_eq!(code(&o), 1, "{}", stdout(&o));
|
||||
assert!(
|
||||
stdout(&o).contains("dataset: </h/s/e> and </h/s/e>"),
|
||||
"{}",
|
||||
stdout(&o)
|
||||
);
|
||||
assert!(!stdout(&o).contains("</g/s/e>"), "{}", stdout(&o));
|
||||
}
|
||||
|
||||
/// Where h5rs deliberately differs from h5diff: objects that cannot be
|
||||
/// compared (another shape, another datatype class) are a difference.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user