Files
clawhdf5/conformance/test_ref.py
T
osobhandClaude Opus 5.5 5c44630ea2 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]>
2026-09-27 22:26:32 -05:00

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()