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.
69 lines
2.0 KiB
Python
69 lines
2.0 KiB
Python
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""
|