conformance: compare h5py's big-endian VL values corrected, confirm libhdf5 over-reads per run
The last 3 our-errors and 2 mismatches were documented as not ours but
still counted against us, on a heuristic (any big-endian VL mismatch) and
a fixed list.
- ref.py checks that the installed h5py returns big-endian VL elements
with the file's bytes under a little-endian dtype (writing and reading
a vlen('>f4') in memory) and, if so, relabels them with the file's byte
order before hashing, marking the object `ref_fix`. The values are now
compared: attr_datatypes.hdf5 /@vlen_uint64 and tcomplex_be.h5
/VariableLengthDatasetFloatComplex are identical to ours (h5dump 1.14.6
prints the same (1, 2), (3, 4, 5), (42)).
- ref_bugs.py re-reads each object h5py reads only through a libhdf5 bug
in six processes with different heaps (import order, MALLOC_PERTURB_).
Values the file determines are the same every time; these three change
(6, 6 and 3 distinct results), so they are over-read memory, not data
clawhdf5 could match. compare.py classifies a file `ref-bug` only when
every difference is such an object confirmed in the same run.
- report.py: the ref-bug class, the evidence table, the corrected
objects; test_ref.py covers both (run in the nightly job).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests of the reference side's corrections: `python conformance/test_ref.py`.
|
||||
|
||||
- ref.py compares a big-endian VL sequence by the file's values even though
|
||||
h5py returns them byte-swapped (and records that it corrected them);
|
||||
- ref_bugs.py confirms an object only when its reads disagree.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import h5py
|
||||
import numpy as np
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import ref_bugs # noqa: E402
|
||||
|
||||
|
||||
def ref_objects(path):
|
||||
out = subprocess.run([sys.executable, os.path.join(HERE, "ref.py"), path],
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
return {o["path"]: o for o in json.loads(out)["objects"]}
|
||||
|
||||
|
||||
class BigEndianVlen(unittest.TestCase):
|
||||
def test_be_vlen_compared_by_file_values(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = os.path.join(d, "v.h5")
|
||||
with h5py.File(path, "w") as f:
|
||||
for name, order in (("be", ">"), ("le", "<")):
|
||||
t = np.dtype(order + "f4")
|
||||
ds = f.create_dataset(name, (2,), dtype=h5py.vlen_dtype(t))
|
||||
ds[0] = np.array([1.0, 2.0], dtype=t)
|
||||
ds[1] = np.array([3.0], dtype=t)
|
||||
u = np.dtype(order + "u8")
|
||||
f.attrs.create(name, [np.array([1, 2], dtype=u), np.array([42], dtype=u)],
|
||||
dtype=h5py.vlen_dtype(u))
|
||||
objs = ref_objects(path)
|
||||
be, le = objs["/be"], objs["/le"]
|
||||
# Same values, so the same canonical hash whatever the file's byte order.
|
||||
self.assertEqual(be["hash"], le["hash"])
|
||||
self.assertEqual(objs["/"]["attrs"]["be"]["hash"], objs["/"]["attrs"]["le"]["hash"])
|
||||
self.assertNotIn("ref_fix", le)
|
||||
# And the correction is recorded wherever h5py needed it.
|
||||
import ref
|
||||
if ref.be_vlen_bug():
|
||||
self.assertEqual(be.get("ref_fix"), ["h5py-be-vlen"])
|
||||
self.assertEqual(objs["/"]["attrs"]["be"].get("ref_fix"), ["h5py-be-vlen"])
|
||||
|
||||
|
||||
class RefBugsConfirmation(unittest.TestCase):
|
||||
def run_check(self, outcomes):
|
||||
seq = iter(outcomes)
|
||||
saved = ref_bugs.read_once
|
||||
ref_bugs.read_once = lambda *a: next(seq)
|
||||
try:
|
||||
key = next(iter(ref_bugs.READ_BUGS))
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
p = os.path.join(d, key[0])
|
||||
os.makedirs(os.path.dirname(p))
|
||||
open(p, "wb").close()
|
||||
return ref_bugs.check(d, key)
|
||||
finally:
|
||||
ref_bugs.read_once = saved
|
||||
|
||||
def test_stable_values_are_not_confirmed(self):
|
||||
r = self.run_check(["values a"] * len(ref_bugs.RUNS))
|
||||
self.assertFalse(r["confirmed"])
|
||||
|
||||
def test_changing_values_are_confirmed(self):
|
||||
r = self.run_check(["values a"] * (len(ref_bugs.RUNS) - 1) + ["values b"])
|
||||
self.assertTrue(r["confirmed"])
|
||||
r = self.run_check(["values a"] * (len(ref_bugs.RUNS) - 1) + ["error filter failed"])
|
||||
self.assertTrue(r["confirmed"])
|
||||
|
||||
def test_errors_only_are_not_confirmed(self):
|
||||
# h5py cannot read it at all: nothing it reads, nothing to excuse.
|
||||
r = self.run_check(["error x"] * (len(ref_bugs.RUNS) - 1) + ["error y"])
|
||||
self.assertFalse(r["confirmed"])
|
||||
|
||||
def test_missing_file_is_not_confirmed(self):
|
||||
key = next(iter(ref_bugs.READ_BUGS))
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
self.assertFalse(ref_bugs.check(d, key)["confirmed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user