// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package io import ( "path/filepath" "slices" "testing" "sourcedock.dev/petrbalvin/tensor/internal/core" ) // The boolean fixture bool_enum.h5 under testdata/h5 was written by // hand against the HDF5 1.8 specification, in the shape the reference // library writes for an old-style group with a symbol table, and it // validates against the reference library's own tools: h5dump reads // the dataset back as an H5T_ENUM over H5T_STD_U8LE with the members // FALSE = 0 and TRUE = 1 and the values TRUE, FALSE, TRUE, TRUE, // FALSE. Its layout deliberately differs from what this package's // writer produces: the messages of the dataset's object header sit in // the order datatype, fill value, dataspace, layout, the dataspace // carries no maximum dimensions and the fill value message declares a // defined zero fill, so the pins below prove the reader's tolerance of // a foreign layout rather than a round trip of its own bytes. // TestLoadHDF5ForeignBoolFixture pins the read side: the hand-crafted // boolean enumeration of a foreign layout lands the dataset "flags" // with the core Bool dtype and the values the reference library reads // from the same bytes. func TestLoadHDF5ForeignBoolFixture(t *testing.T) { sets, err := LoadHDF5(h5Fixture(t, "bool_enum.h5")) if err != nil { t.Fatalf("LoadHDF5: %v", err) } if len(sets) != 1 { t.Fatalf("datasets = %d, want 1", len(sets)) } d := sets[0] if d.Path != "/flags" { t.Fatalf("path = %q, want /flags", d.Path) } if s := d.Shape; len(s) != 1 || s[0] != 5 { t.Fatalf("shape = %v, want [5]", s) } if dt := d.Values.Dtype(); dt != core.Bool { t.Fatalf("dtype = %s, want bool", dt) } want := []bool{true, false, true, true, false} if got := d.Values.RawBools()[:5]; !slices.Equal(got, want) { t.Fatalf("values = %v, want %v", got, want) } } // TestSaveHDF5BoolConvention pins the write side against the same // logical values: SaveHDF5 stores them through the boolean enumeration // convention and LoadHDF5 reads them back identically. The pin holds // the two-sided agreement on the convention, not a byte match with the // foreign fixture, whose layout the writer need not reproduce. func TestSaveHDF5BoolConvention(t *testing.T) { want := []bool{true, false, true, true, false} values, err := core.FromBools(want, 5) if err != nil { t.Fatalf("FromBools: %v", err) } path := filepath.Join(t.TempDir(), "bool_convention.h5") if err := SaveHDF5(path, []HDF5Dataset{{Path: "/flags", Shape: []int{5}, Values: values}}, nil); err != nil { t.Fatalf("SaveHDF5: %v", err) } sets, err := LoadHDF5(path) if err != nil { t.Fatalf("LoadHDF5: %v", err) } if len(sets) != 1 { t.Fatalf("datasets = %d, want 1", len(sets)) } d := sets[0] if d.Path != "/flags" { t.Fatalf("path = %q, want /flags", d.Path) } if s := d.Shape; len(s) != 1 || s[0] != 5 { t.Fatalf("shape = %v, want [5]", s) } if dt := d.Values.Dtype(); dt != core.Bool { t.Fatalf("dtype = %s, want bool", dt) } if got := d.Values.RawBools()[:5]; !slices.Equal(got, want) { t.Fatalf("values = %v, want %v", got, want) } }