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]>
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
#!/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()
|