update
This commit is contained in:
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
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") != "B27"
|
||||
or spec.get("candidate") != "P15-083"
|
||||
or spec.get("test") != "TC176-stream-runtime"
|
||||
):
|
||||
raise SystemExit("invalid B27 stream fixture spec")
|
||||
if set(spec.get("codecs", {})) != {"lzma", "deflate", "zstd"}:
|
||||
raise SystemExit("B27 codec set changed")
|
||||
return spec
|
||||
|
||||
|
||||
def make_source(root: Path, spec: dict[str, object]) -> bytes:
|
||||
root.mkdir(parents=True)
|
||||
source_spec = spec["source"]
|
||||
content = b"".join(
|
||||
f"P15-083-{index % 64:02d}:alpha-beta-gamma-delta:{(index * 17) % 256:02x}\n".encode("ascii")
|
||||
for index in range(source_spec["line_count"])
|
||||
)
|
||||
if len(content) != source_spec["size"] or sha256_bytes(content) != source_spec["sha256"]:
|
||||
raise SystemExit("B27 deterministic payload identity changed")
|
||||
payload = root / "payload.bin"
|
||||
payload.write_bytes(content)
|
||||
os.utime(payload, (0, 0))
|
||||
os.utime(root, (0, 0))
|
||||
return content
|
||||
|
||||
|
||||
def run(argv: list[str], cwd: Path) -> None:
|
||||
completed = subprocess.run(
|
||||
argv,
|
||||
cwd=cwd,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise SystemExit(
|
||||
f"command failed ({completed.returncode}): {' '.join(argv)}\n{completed.stdout}"
|
||||
)
|
||||
|
||||
|
||||
def build_codec(
|
||||
codec: str,
|
||||
codec_spec: dict[str, object],
|
||||
spec: dict[str, object],
|
||||
source_dir: Path,
|
||||
images: Path,
|
||||
) -> list[dict[str, object]]:
|
||||
valid = images / f"{codec}-valid.erofs"
|
||||
run(["mkfs.erofs", *codec_spec["mkfs_args"], str(valid), str(source_dir)], images)
|
||||
valid_data = valid.read_bytes()
|
||||
if (
|
||||
len(valid_data) != codec_spec["expected_image_size"]
|
||||
or sha256_bytes(valid_data) != codec_spec["expected_image_sha256"]
|
||||
):
|
||||
raise SystemExit(f"{codec} valid image is not the G04 reproducible fixture")
|
||||
|
||||
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"]:
|
||||
raise SystemExit(f"{codec} pcluster padding/stream boundary changed")
|
||||
|
||||
tail = bytes.fromhex(spec["tail_bytes_hex"])
|
||||
if not tail or any(value == 0 for value in tail) or leading <= len(tail):
|
||||
raise SystemExit("B27 trailing mutation is not nonzero or lacks pcluster room")
|
||||
variants: dict[str, bytearray] = {}
|
||||
tail_data = bytearray(valid_data)
|
||||
tail_data[start + leading - len(tail) : end] = stream + tail
|
||||
variants["tail"] = tail_data
|
||||
|
||||
truncated_data = bytearray(valid_data)
|
||||
truncated_data[start + leading : end] = b"\0" + stream[:-1]
|
||||
variants["truncated"] = truncated_data
|
||||
|
||||
corruption_start = codec_spec["corruption_start"]
|
||||
if not (codec_spec["gate"]["partial_consumed"] < corruption_start < len(stream)):
|
||||
raise SystemExit(f"{codec} corruption is not after partial oracle consumption")
|
||||
corrupt_data = bytearray(valid_data)
|
||||
corrupt_data[start + leading : end] = (
|
||||
stream[:corruption_start] + b"\0" * (len(stream) - corruption_start)
|
||||
)
|
||||
if corrupt_data == valid_data:
|
||||
raise SystemExit(f"{codec} corruption mutation changed no bytes")
|
||||
variants["corrupt"] = corrupt_data
|
||||
|
||||
records = [
|
||||
{
|
||||
"class": "valid",
|
||||
"codec": codec,
|
||||
"expected_errno": 0,
|
||||
"path": valid.name,
|
||||
"sha256": sha256_path(valid),
|
||||
}
|
||||
]
|
||||
for kind, data in variants.items():
|
||||
path = images / f"{codec}-{kind}.erofs"
|
||||
path.write_bytes(data)
|
||||
records.append(
|
||||
{
|
||||
"class": kind,
|
||||
"codec": codec,
|
||||
"expected_errno": 0 if kind == "corrupt-partial" else 97,
|
||||
"path": path.name,
|
||||
"sha256": sha256_path(path),
|
||||
}
|
||||
)
|
||||
return records
|
||||
|
||||
|
||||
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 B27 output: {args.output}")
|
||||
if shutil.which("mkfs.erofs") is None:
|
||||
raise SystemExit("mkfs.erofs is required")
|
||||
args.output.mkdir(parents=True)
|
||||
source_dir = args.output / "source"
|
||||
images = args.output / "images"
|
||||
images.mkdir()
|
||||
make_source(source_dir, spec)
|
||||
records = []
|
||||
for codec in sorted(spec["codecs"]):
|
||||
records.extend(
|
||||
build_codec(codec, spec["codecs"][codec], spec, source_dir, images)
|
||||
)
|
||||
index = {
|
||||
"batch": "B27",
|
||||
"candidate": "P15-083",
|
||||
"fixture_count": len(records),
|
||||
"fixtures": records,
|
||||
"schema": 1,
|
||||
"source_sha256": spec["source"]["sha256"],
|
||||
"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()
|
||||
Reference in New Issue
Block a user