P1: file parsers, sample sheet, discovery, CLI inspect

Readers for IDAT v3, BPM (locus entries v4-v8), CSV manifest, EGT
(data blocks 5-9) and GTC v3-v5 (plus a layout-preserving GTC writer);
sample-sheet validation (IN-001..IN-014), IDAT discovery and pairing
(IN-020..IN-022), message catalogue and exit codes (CLI-014/016),
JSON Lines logging (CLI-007/008), atomic writes (CLI-011), run manifest,
and `simurg inspect`.

Validation (docs/validation/P1.md): GTC, BPM and EGT readers agree
exactly with Illumina BeadArrayFiles on GSA v4 / GDA v1 files; GTCs
round-trip byte-identically; BPM and CSV manifests are consistent.
This commit is contained in:
kemal committed 2026-09-27 16:56:39 +01:00
1 parent 0a421dffc9
commit 5a20e7131c
40 files changed
+4742

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
from __future__ import annotations
import gzip
import numpy as np
import pytest
from hypothesis import given
from hypothesis import strategies as st
from simurg.core.errors import FormatError
from simurg.formats.binutil import BinaryReader, BinaryWriter, load_bytes
@given(st.text(max_size=400))
def test_string_roundtrip(s: str) -> None:
w = BinaryWriter()
w.string(s)
r = BinaryReader(w.getvalue(), "E_GTC_CORRUPT")
assert r.string() == s
assert r.remaining() == 0
@pytest.mark.parametrize("n", [0, 1, 127, 128, 16383, 16384, 300_000])
def test_varint_boundaries(n: int) -> None:
w = BinaryWriter()
w.string(b"x" * n)
r = BinaryReader(w.getvalue(), "E_GTC_CORRUPT")
assert r.varint_length() == n
def test_invalid_utf8_is_replaced_not_raised() -> None: # IN-033, EDGE-002
w = BinaryWriter()
w.string(b"ok\xff\xfe")
assert BinaryReader(w.getvalue(), "E_GTC_CORRUPT").string() == "ok��"
def test_string_past_eof_is_format_error() -> None:
buf = bytes([50]) + b"short"
with pytest.raises(FormatError) as e:
BinaryReader(buf, "E_GTC_CORRUPT", "x.gtc").string()
assert e.value.code == "E_GTC_CORRUPT"
def test_bad_varint_is_format_error() -> None:
with pytest.raises(FormatError):
BinaryReader(b"\xff\xff\xff\xff\xff\xff", "E_BPM_FORMAT").varint_length()
def test_array_bounds_and_seek() -> None:
r = BinaryReader(np.arange(4, dtype="<i4").tobytes(), "E_IDAT_FORMAT")
assert r.array("<i4", 4).tolist() == [0, 1, 2, 3]
r.seek(0)
with pytest.raises(FormatError):
r.array("<i4", 5)
with pytest.raises(FormatError):
r.seek(100)
def test_load_bytes_gzip(tmp_path) -> None: # type: ignore[no-untyped-def]
p = tmp_path / "a.bin.gz"
p.write_bytes(gzip.compress(b"hello"))
assert bytes(load_bytes(p)) == b"hello"
q = tmp_path / "b.bin"
q.write_bytes(b"world")
assert bytes(load_bytes(q)) == b"world"
e = tmp_path / "empty.bin"
e.write_bytes(b"")
assert bytes(load_bytes(e)) == b""