Files
erofs-freebsd-out-tree/tests/pre15/lib/manifest.sh
T
2026-08-18 09:20:44 +02:00

198 lines
7.3 KiB
Bash

#!/bin/sh
pre15_write_argv_json()
{
pre15_argv_path=$1
shift
python3 - "$pre15_argv_path" "$@" <<'PY'
import json
from pathlib import Path
import sys
path = Path(sys.argv[1])
with path.open("x", encoding="ascii") as output:
json.dump(sys.argv[2:], output, ensure_ascii=True, separators=(",", ":"))
output.write("\n")
PY
}
pre15_write_manifest()
{
python3 - "$PRE15_MANIFEST_PATH" <<'PY'
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import sys
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def relative(path: Path, root: Path) -> str:
return path.relative_to(root).as_posix()
manifest_path = Path(sys.argv[1])
run_dir = manifest_path.parent
argv_path = Path(os.environ["PRE15_ARGV_PATH"])
fixture_path = Path(os.environ["PRE15_FIXTURE_HASHES"])
module_path = Path(os.environ["PRE15_MODULE_HASH"])
stdout_path = Path(os.environ["PRE15_STDOUT_PATH"])
stderr_path = Path(os.environ["PRE15_STDERR_PATH"])
ownership_path = Path(os.environ["PRE15_OWNERSHIP_FILE"])
cleanup_log = Path(os.environ["PRE15_CLEANUP_LOG"])
identity_path = Path(os.environ["PRE15_IDENTITY_PATH"])
fixtures = []
fixture_details = []
if fixture_path.exists():
for raw_line in fixture_path.read_text(encoding="utf-8").splitlines():
if not raw_line:
continue
checksum, label, path = raw_line.split("\t", 2)
fixtures.append(checksum)
fixture_details.append(
{"sha256": checksum, "label": label, "path": path}
)
module_sha256 = None
module_details = None
if module_path.exists() and module_path.stat().st_size:
checksum, path = module_path.read_text(encoding="utf-8").rstrip("\n").split(
"\t", 1
)
module_sha256 = checksum
module_details = {"sha256": checksum, "path": path}
exit_code_text = os.environ["PRE15_EXIT_CODE"]
exit_code = None if exit_code_text == "null" else int(exit_code_text)
identity = json.loads(identity_path.read_text(encoding="ascii"))
manifest = {
"schema_version": 1,
"run_id": os.environ["PRE15_RUN_ID"],
"mode": os.environ["PRE15_MODE"],
"case_id": os.environ["PRE15_CASE_ID"],
"dut_head": os.environ["PRE15_DUT_HEAD"],
"dut_tree": os.environ["PRE15_DUT_TREE"],
"worktree_diff_sha256": os.environ["PRE15_WORKTREE_DIFF_SHA256"],
"freebsd_source": os.environ["PRE15_FREEBSD_SOURCE"],
"linux_source": os.environ["PRE15_LINUX_SOURCE"],
"module_sha256": module_sha256,
"fixture_sha256": fixtures,
"command_argv": json.loads(argv_path.read_text(encoding="ascii")),
"start_utc": os.environ["PRE15_START_UTC"],
"end_utc": os.environ["PRE15_END_UTC"],
"deadline_seconds": int(os.environ["PRE15_DEADLINE"]),
"exit_code": exit_code,
"timed_out": os.environ["PRE15_TIMED_OUT"] == "1",
"target_marker": os.environ["PRE15_TARGET_STATE"],
"status": os.environ["PRE15_STATUS"],
"reason": os.environ["PRE15_REASON"],
"failure_origin": os.environ["PRE15_FAILURE_ORIGIN"],
"cleanup": os.environ["PRE15_CLEANUP_STATUS"],
"stdout": relative(stdout_path, run_dir),
"stderr": relative(stderr_path, run_dir),
"raw_sha256": {
"stdout": digest(stdout_path),
"stderr": digest(stderr_path),
"ownership": digest(ownership_path),
"cleanup": digest(cleanup_log),
},
"ownership_manifest": relative(ownership_path, run_dir),
"cleanup_log": relative(cleanup_log, run_dir),
"identity": identity,
"fixtures": fixture_details,
"module": module_details,
}
with manifest_path.open("x", encoding="ascii") as output:
json.dump(manifest, output, ensure_ascii=True, indent=2, sort_keys=True)
output.write("\n")
PY
}
pre15_validate_manifest()
{
pre15_manifest_to_validate=$1
pre15_schema_to_validate=$2
python3 - "$pre15_manifest_to_validate" "$pre15_schema_to_validate" <<'PY'
from __future__ import annotations
from datetime import datetime
import hashlib
import json
from pathlib import Path
import re
import sys
manifest_path = Path(sys.argv[1])
schema_path = Path(sys.argv[2])
manifest = json.loads(manifest_path.read_text(encoding="ascii"))
schema = json.loads(schema_path.read_text(encoding="ascii"))
missing = sorted(set(schema["required"]) - set(manifest))
if missing:
raise SystemExit(f"manifest missing required keys: {', '.join(missing)}")
if manifest["status"] not in schema["properties"]["status"]["enum"]:
raise SystemExit(f"invalid status: {manifest['status']}")
if manifest["cleanup"] not in schema["properties"]["cleanup"]["enum"]:
raise SystemExit(f"invalid cleanup: {manifest['cleanup']}")
if manifest["target_marker"] not in schema["properties"]["target_marker"]["enum"]:
raise SystemExit(f"invalid target marker: {manifest['target_marker']}")
if not re.fullmatch(r"[0-9a-f]{40}", manifest["dut_head"]):
raise SystemExit("dut_head is not a 40-hex commit")
for key in ("dut_tree", "worktree_diff_sha256"):
if not re.fullmatch(r"[0-9a-f]{40,64}", manifest[key]):
raise SystemExit(f"{key} is not a lowercase hex identity")
if manifest["module_sha256"] is not None and not re.fullmatch(
r"[0-9a-f]{64}", manifest["module_sha256"]
):
raise SystemExit("module_sha256 is neither null nor SHA256")
if not isinstance(manifest["command_argv"], list) or not manifest["command_argv"]:
raise SystemExit("command_argv must be a nonempty array")
if not all(isinstance(item, str) for item in manifest["command_argv"]):
raise SystemExit("command_argv contains a non-string item")
if not isinstance(manifest["fixture_sha256"], list) or not all(
re.fullmatch(r"[0-9a-f]{64}", item) for item in manifest["fixture_sha256"]
):
raise SystemExit("fixture_sha256 contains an invalid digest")
if not isinstance(manifest["deadline_seconds"], int) or manifest["deadline_seconds"] <= 0:
raise SystemExit("deadline_seconds must be positive")
if manifest["exit_code"] is not None and not isinstance(manifest["exit_code"], int):
raise SystemExit("exit_code must be an integer or null")
for key in ("start_utc", "end_utc"):
datetime.fromisoformat(manifest[key].replace("Z", "+00:00"))
if manifest["status"] == "PASS":
if manifest["exit_code"] != 0 or manifest["target_marker"] != "reached":
raise SystemExit("PASS requires exit_code=0 and a reached target")
if manifest["cleanup"] != "PASS":
raise SystemExit("PASS requires successful cleanup")
if manifest["status"] == "DUT_FAIL" and manifest["target_marker"] != "reached":
raise SystemExit("DUT_FAIL requires a reached target")
if manifest["cleanup"] == "FAIL" and manifest["status"] != "RUNNER_FAIL":
raise SystemExit("cleanup failure must classify as RUNNER_FAIL")
run_dir = manifest_path.parent
for key in ("stdout", "stderr", "ownership_manifest", "cleanup_log"):
relative = Path(manifest[key])
if relative.is_absolute() or ".." in relative.parts:
raise SystemExit(f"{key} escapes the run directory")
if not (run_dir / relative).is_file():
raise SystemExit(f"{key} does not exist: {relative}")
hashed_paths = {
"stdout": manifest["stdout"],
"stderr": manifest["stderr"],
"ownership": manifest["ownership_manifest"],
"cleanup": manifest["cleanup_log"],
}
for key, relative_path in hashed_paths.items():
actual = hashlib.sha256((run_dir / relative_path).read_bytes()).hexdigest()
if actual != manifest["raw_sha256"][key]:
raise SystemExit(f"{key} hash mismatch")
PY
}