This commit is contained in:
2026-08-18 09:20:44 +02:00
commit b826cd721a
522 changed files with 93730 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def sha256_path(path: Path) -> str:
return sha256_bytes(path.read_bytes())
def load_spec(path: Path) -> dict[str, object]:
spec = json.loads(path.read_text(encoding="ascii"))
if (
spec.get("schema") != 1
or spec.get("batch") != "B28"
or spec.get("candidate") != "P15-086"
or spec.get("test") != "TC176-stream-runtime"
):
raise SystemExit("invalid B28 partial fixture spec")
if set(spec.get("codecs", {})) != {"lz4", "lzma", "deflate", "zstd"}:
raise SystemExit("B28 codec set changed")
return spec
def make_sources(root: Path, spec: dict[str, object]) -> dict[str, bytes]:
sources: dict[str, bytes] = {}
lz4_spec = spec["sources"]["lz4"]
sources["lz4"] = b"".join(
bytes([segment["byte"]]) * segment["length"]
for segment in lz4_spec["segments"]
)
stream_spec = spec["sources"]["stream"]
sources["stream"] = b"".join(
f"P15-086-{index % 64:02d}:alpha-beta-gamma-delta:{(index * 17) % 256:02x}\n".encode(
"ascii"
)
for index in range(stream_spec["line_count"])
)
for name, content in sources.items():
expected = spec["sources"][name]
if len(content) != expected["size"] or sha256_bytes(content) != expected["sha256"]:
raise SystemExit(f"B28 {name} source identity changed")
directory = root / name
directory.mkdir(parents=True)
payload = directory / "payload.bin"
payload.write_bytes(content)
os.utime(payload, (0, 0))
os.utime(directory, (0, 0))
return sources
def run(argv: list[str], cwd: Path, expected: set[int] = {0}) -> subprocess.CompletedProcess[str]:
completed = subprocess.run(
argv,
cwd=cwd,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=120,
)
if completed.returncode not in expected:
raise SystemExit(
f"command failed ({completed.returncode}): {' '.join(argv)}\n{completed.stdout}"
)
return completed
def verify_fsck(image: Path, expected: bytes, label: str) -> None:
with tempfile.TemporaryDirectory(prefix=f"b28-{label}-fsck-") as temporary:
destination = Path(temporary)
run(["fsck.erofs", f"--extract={destination}", str(image)], image.parent)
if (destination / "payload.bin").read_bytes() != expected:
raise SystemExit(f"B28 {label} fsck extraction mismatch")
def build_codec(
codec: str,
codec_spec: dict[str, object],
sources: dict[str, bytes],
source_root: Path,
images: Path,
) -> list[dict[str, object]]:
valid = images / f"{codec}-valid.erofs"
run(
[
"mkfs.erofs",
*codec_spec["mkfs_args"],
str(valid),
str(source_root / codec_spec["source"]),
],
images,
)
valid_data = valid.read_bytes()
actual_image_sha256 = sha256_bytes(valid_data)
source = sources[codec_spec["source"]]
verify_fsck(valid, source, f"{codec}-valid")
extent = codec_spec["extent"]
start = extent["physical_offset"]
end = start + extent["physical_length"]
block = valid_data[start:end]
leading = next((index for index, value in enumerate(block) if value), len(block))
stream = block[leading:]
if (
leading != extent["leading_zero_bytes"]
or len(stream) != extent["stream_bytes"]
or not (extent["prefix_consumed"] < codec_spec["corruption_start"] < len(stream))
):
raise SystemExit(f"{codec} pcluster or corruption boundary changed")
corrupted_data = bytearray(valid_data)
corruption = start + leading + codec_spec["corruption_start"]
corrupted_data[corruption:end] = b"\0" * (end - corruption)
if corrupted_data == valid_data:
raise SystemExit(f"{codec} corruption mutation changed no bytes")
corrupted = images / f"{codec}-corrupt.erofs"
corrupted.write_bytes(corrupted_data)
with tempfile.TemporaryDirectory(prefix=f"b28-{codec}-corrupt-") as temporary:
failed = run(
["fsck.erofs", f"--extract={temporary}", str(corrupted)],
images,
set(range(1, 256)),
)
return [
{
"class": "valid",
"codec": codec,
"expected_errno": 0,
"gate_image_sha256": codec_spec["gate_image_sha256"],
"path": valid.name,
"sha256": actual_image_sha256,
},
{
"class": "corrupt",
"codec": codec,
"expected_full_errno": 97,
"expected_prefix_errno": 0 if codec_spec["decision"] == "GO" else 97,
"fsck_exit": failed.returncode,
"path": corrupted.name,
"sha256": sha256_path(corrupted),
},
]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--spec", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
spec = load_spec(args.spec)
if args.output.exists():
raise SystemExit(f"refusing existing B28 output: {args.output}")
for tool in ("mkfs.erofs", "fsck.erofs"):
if shutil.which(tool) is None:
raise SystemExit(f"{tool} is required")
args.output.mkdir(parents=True)
source_root = args.output / "sources"
images = args.output / "images"
images.mkdir()
sources = make_sources(source_root, spec)
records = []
for codec in sorted(spec["codecs"]):
records.extend(
build_codec(codec, spec["codecs"][codec], sources, source_root, images)
)
(args.output / "spec.json").write_bytes(args.spec.read_bytes())
index = {
"batch": "B28",
"candidate": "P15-086",
"fixture_count": len(records),
"fixtures": records,
"schema": 1,
"source_sha256": {
name: sha256_bytes(content) for name, content in sorted(sources.items())
},
"status": "READY",
"test": "TC176-stream-runtime",
}
(args.output / "fixture-index.json").write_text(
json.dumps(index, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
print(json.dumps(index, indent=2, sort_keys=True))
if __name__ == "__main__":
main()