532 lines
19 KiB
Bash
Executable File
532 lines
19 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
: "${PRE15_DUT:?PRE15_DUT is required}"
|
|
: "${PRE15_ROOT:?PRE15_ROOT is required}"
|
|
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
|
|
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
|
|
. "$PRE15_LIB_DIR/runner.sh"
|
|
|
|
gate=$PRE15_DUT/tests/pre15/gates/P15-006.sh
|
|
input=$PRE15_DUT/tests/pre15/gates/P15-006-input.json
|
|
oracle=$PRE15_DUT/tests/pre15/fixtures/B07-map-oracle.json
|
|
artifacts=$PRE15_RUN_DIR/artifacts
|
|
baseline=6673f51152a5195a8a8903aa801f820abce7936e
|
|
b07b=d58f131cfe855728969aa3695d34a26a61b90277
|
|
|
|
for tool in cc git python3 sha256sum; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B07c host tool: $tool"
|
|
done
|
|
|
|
pre15_record_fixture b07c-gate "$gate"
|
|
pre15_record_fixture b07c-input "$input"
|
|
pre15_record_fixture b07c-oracle "$oracle"
|
|
for source in internal.h data.c zmap.c zdata.c super.c; do
|
|
pre15_record_fixture "b07c-$source" "$PRE15_DUT/src/$source"
|
|
done
|
|
|
|
mkdir -p "$artifacts"
|
|
pre15_target_reached
|
|
|
|
if "$gate" --base "$baseline" --oracle "$oracle" \
|
|
--output "$artifacts/baseline" >"$artifacts/baseline.stdout" \
|
|
2>"$artifacts/baseline.stderr"; then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B07c frozen P15-006 baseline replay failed'
|
|
fi
|
|
|
|
if python3 - "$gate" "$artifacts/P15-006-B07c.py" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
marker = "<<'PY'\n"
|
|
start = source.index(marker) + len(marker)
|
|
end = source.rindex("\nPY\n")
|
|
program = source[start:end]
|
|
|
|
|
|
def replace_once(old: str, new: str) -> None:
|
|
global program
|
|
if program.count(old) != 1:
|
|
raise SystemExit(f"P15-006 derivation marker count changed: {old[:70]!r}")
|
|
program = program.replace(old, new)
|
|
|
|
|
|
replace_once(
|
|
'''if candidate_mode and not re.search(
|
|
r"^erofs_map_blocks_legacy\\s*\\(", data_source, re.MULTILINE
|
|
):
|
|
raise SystemExit("candidate map adapter has no file-local legacy target")
|
|
''',
|
|
'''if candidate_mode and re.search(
|
|
r"^erofs_map_blocks_legacy\\s*\\(", data_source, re.MULTILINE
|
|
):
|
|
raise SystemExit("B07c candidate still has the legacy scatter adapter")
|
|
''',
|
|
)
|
|
replace_once(
|
|
'''for key, expected_hash in SPEC["protected_function_sha256"].items():
|
|
filename, function = key.split(":", 1)
|
|
text = data_source if filename == "data.c" else zmap_source
|
|
actual_hash = sha256_bytes(extract_function(text, function).encode("utf-8"))
|
|
protected_hashes[key] = actual_hash
|
|
if actual_hash != expected_hash:
|
|
raise SystemExit(f"protected producer/GEOM body changed: {key}")
|
|
''',
|
|
'''for key, expected_hash in SPEC["protected_function_sha256"].items():
|
|
filename, function = key.split(":", 1)
|
|
text = data_source if filename == "data.c" else zmap_source
|
|
actual_function = (
|
|
"z_erofs_map_blocks"
|
|
if key == "zmap.c:z_erofs_map_blocks_iter"
|
|
else function
|
|
)
|
|
actual_hash = sha256_bytes(extract_function(text, actual_function).encode("utf-8"))
|
|
protected_hashes[key] = actual_hash
|
|
allowed = {
|
|
"data.c:erofs_map_blocks_chunk",
|
|
"zmap.c:z_erofs_map_blocks_iter",
|
|
}
|
|
if actual_hash != expected_hash and key not in allowed:
|
|
raise SystemExit(f"protected producer/GEOM body changed: {key}")
|
|
''',
|
|
)
|
|
replace_once(
|
|
'''if candidate_mode:
|
|
legacy_body = extract_function(data_source, "erofs_map_blocks_legacy")
|
|
expected_legacy_hash = SPEC["candidate_legacy_map_sha256"]
|
|
else:
|
|
legacy_body = extract_function(data_source, "erofs_map_blocks")
|
|
expected_legacy_hash = SPEC["baseline_legacy_map_sha256"]
|
|
if sha256_bytes(legacy_body.encode("utf-8")) != expected_legacy_hash:
|
|
raise SystemExit("plain/chunk legacy producer body changed")
|
|
''',
|
|
'''if candidate_mode:
|
|
legacy_body = ""
|
|
expected_legacy_hash = None
|
|
else:
|
|
legacy_body = extract_function(data_source, "erofs_map_blocks")
|
|
expected_legacy_hash = SPEC["baseline_legacy_map_sha256"]
|
|
if expected_legacy_hash is not None and sha256_bytes(legacy_body.encode("utf-8")) != expected_legacy_hash:
|
|
raise SystemExit("plain/chunk legacy producer body changed")
|
|
''',
|
|
)
|
|
replace_once(
|
|
' "z_erofs_map_blocks_iter",\n',
|
|
' "z_erofs_map_blocks",\n',
|
|
)
|
|
replace_once(
|
|
'program += "int z_erofs_map_blocks_iter(struct erofs_sb_info *, struct erofs_inode *, struct erofs_map_blocks *, int);\\n"\n',
|
|
'program += "int z_erofs_map_blocks(struct erofs_sb_info *, struct erofs_inode *, struct erofs_map_blocks *);\\n"\n',
|
|
)
|
|
replace_once(
|
|
'''program += extract_function(data_source, "erofs_inline_tail_start")
|
|
program += extract_function(data_source, "erofs_map_blocks_chunk")
|
|
program += legacy_body
|
|
if candidate_mode:
|
|
program += extract_function(data_source, "erofs_map_blocks")
|
|
''',
|
|
'''program += extract_function(data_source, "erofs_inline_tail_start")
|
|
program += extract_function(data_source, "erofs_map_blocks_chunk")
|
|
if candidate_mode:
|
|
program += extract_function(data_source, "erofs_map_blocks_flatmode")
|
|
program += extract_function(data_source, "erofs_map_blocks")
|
|
else:
|
|
program += legacy_body
|
|
''',
|
|
)
|
|
replace_once(
|
|
'''int z_erofs_map_blocks_iter(struct erofs_sb_info *sbi,
|
|
struct erofs_inode *vi, struct erofs_map_blocks *map, int flags)
|
|
''',
|
|
'''int z_erofs_map_blocks(struct erofs_sb_info *sbi,
|
|
struct erofs_inode *vi, struct erofs_map_blocks *map)
|
|
''',
|
|
)
|
|
replace_once(
|
|
''' (void)sbi;
|
|
if (flags != 0)
|
|
return (EINVAL);
|
|
++gate_adapter_calls;
|
|
''',
|
|
''' (void)sbi;
|
|
++gate_adapter_calls;
|
|
''',
|
|
)
|
|
replace_once(
|
|
''' adapter_program += extract_function(data_source, "erofs_inline_tail_start")
|
|
adapter_program += extract_function(data_source, "erofs_map_blocks_chunk")
|
|
adapter_program += legacy_body
|
|
adapter_program += extract_function(data_source, "erofs_map_blocks")
|
|
''',
|
|
''' adapter_program += extract_function(data_source, "erofs_inline_tail_start")
|
|
adapter_program += extract_function(data_source, "erofs_map_blocks_chunk")
|
|
adapter_program += extract_function(data_source, "erofs_map_blocks_flatmode")
|
|
adapter_program += extract_function(data_source, "erofs_map_blocks")
|
|
''',
|
|
)
|
|
|
|
Path(sys.argv[2]).write_text(program + "\n", encoding="ascii")
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B07c could not derive the current-consumer replay'
|
|
fi
|
|
|
|
if python3 "$artifacts/P15-006-B07c.py" "$PRE15_ROOT" "$input" \
|
|
worktree '' "$artifacts/candidate" "$oracle" \
|
|
>"$artifacts/candidate.stdout" 2>"$artifacts/candidate.stderr"; then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B07c common consumer replay failed'
|
|
fi
|
|
|
|
if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$oracle" \
|
|
"$artifacts/baseline" "$artifacts/candidate" "$artifacts/consumer-check.json" \
|
|
"$b07b" <<'PY'
|
|
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
root = Path(sys.argv[1])
|
|
dut = Path(sys.argv[2])
|
|
oracle = json.loads(Path(sys.argv[3]).read_text(encoding="ascii"))
|
|
baseline = Path(sys.argv[4])
|
|
candidate = Path(sys.argv[5])
|
|
output = Path(sys.argv[6])
|
|
b07b = sys.argv[7]
|
|
|
|
|
|
def load(directory: Path, name: str) -> dict:
|
|
return json.loads((directory / name).read_text(encoding="ascii"))
|
|
|
|
|
|
def extract_function(source: str, name: str) -> str:
|
|
match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
|
|
if not match:
|
|
raise SystemExit(f"missing function: {name}")
|
|
start = source.rfind("\n\n", 0, match.start()) + 2
|
|
brace = source.find("{", match.end())
|
|
depth = 0
|
|
state = "code"
|
|
index = brace
|
|
while index < len(source):
|
|
char = source[index]
|
|
following = source[index + 1] if index + 1 < len(source) else ""
|
|
if state == "code":
|
|
if char == "/" and following == "*":
|
|
state = "block"
|
|
index += 2
|
|
continue
|
|
if char == "/" and following == "/":
|
|
state = "line"
|
|
index += 2
|
|
continue
|
|
if char == '"':
|
|
state = "string"
|
|
elif char == "'":
|
|
state = "character"
|
|
elif char == "{":
|
|
depth += 1
|
|
elif char == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[start : index + 1]
|
|
elif state == "block" and char == "*" and following == "/":
|
|
state = "code"
|
|
index += 2
|
|
continue
|
|
elif state == "line" and char == "\n":
|
|
state = "code"
|
|
elif state in {"string", "character"}:
|
|
if char == "\\":
|
|
index += 2
|
|
continue
|
|
if (state == "string" and char == '"') or (
|
|
state == "character" and char == "'"
|
|
):
|
|
state = "code"
|
|
index += 1
|
|
raise SystemExit(f"unterminated function: {name}")
|
|
|
|
|
|
def function_names(source: str) -> set[str]:
|
|
return set(re.findall(
|
|
r"^([A-Za-z_][A-Za-z0-9_]*)\s*\(", source, re.MULTILINE
|
|
))
|
|
|
|
|
|
def committed_source(path: str) -> str:
|
|
completed = subprocess.run(
|
|
["git", "-C", str(root), "show", f"{b07b}:repo-pre-15/src/{path}"],
|
|
check=False,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
if completed.returncode != 0:
|
|
raise SystemExit(f"cannot read B07b source {path}: {completed.stderr}")
|
|
return completed.stdout
|
|
|
|
|
|
def replace_once(source: str, old: str, new: str, label: str) -> str:
|
|
if source.count(old) != 1:
|
|
raise SystemExit(f"{label} transform marker count changed")
|
|
return source.replace(old, new)
|
|
|
|
|
|
def require_order(source: str, markers: list[str], label: str) -> None:
|
|
position = -1
|
|
for marker in markers:
|
|
next_position = source.find(marker, position + 1)
|
|
if next_position < 0:
|
|
raise SystemExit(f"{label} is missing ordered marker: {marker}")
|
|
position = next_position
|
|
|
|
|
|
base_result = load(baseline, "result.json")
|
|
base_tuples = load(baseline, "tuples.json")
|
|
base_devices = load(baseline, "devices.json")
|
|
result = load(candidate, "result.json")
|
|
tuples = load(candidate, "tuples.json")
|
|
devices = load(candidate, "devices.json")
|
|
|
|
for label, record in (("baseline", base_result), ("candidate", result)):
|
|
if record["status"] != "GO" or record["failures"]:
|
|
raise SystemExit(f"{label} P15-006 replay did not GO: {record!r}")
|
|
if result["map_case_count"] != 80 or result["device_case_count"] != 13:
|
|
raise SystemExit("P15-006 corpus denominator changed")
|
|
if result["oracle_equal"] is not True or base_result["oracle_equal"] is not True:
|
|
raise SystemExit("frozen B07 oracle comparison failed")
|
|
if result["model_sha256"] != oracle["model_sha256"]:
|
|
raise SystemExit("independent P15-006 model digest changed")
|
|
if result["tuple_bytes_sha256"] != oracle["tuple_bytes_sha256"]:
|
|
raise SystemExit("full tuple stream differs from the frozen B07 oracle")
|
|
if result["adapter_probe"]["status"] != "PASS":
|
|
raise SystemExit("common compressed adapter probe failed")
|
|
|
|
base_records = {record["id"]: record for record in base_tuples["records"]}
|
|
records = {record["id"]: record for record in tuples["records"]}
|
|
if set(base_records) != set(records) or len(records) != 80:
|
|
raise SystemExit("B07 tuple ID set changed")
|
|
tuple_diffs = [
|
|
case_id for case_id in records
|
|
if records[case_id]["tuple_hex"] != base_records[case_id]["tuple_hex"]
|
|
]
|
|
if tuple_diffs:
|
|
raise SystemExit(f"B07c full tuple differences: {tuple_diffs!r}")
|
|
if (
|
|
devices["records"] != base_devices["records"]
|
|
or devices["status"] != "PASS"
|
|
or base_devices["status"] != "PASS"
|
|
):
|
|
raise SystemExit("B07c device-resolution records changed")
|
|
|
|
sources = {
|
|
name: (dut / "src" / name).read_text(encoding="utf-8")
|
|
for name in ("internal.h", "data.c", "super.c", "zdata.c", "zmap.c")
|
|
}
|
|
old = {name: committed_source(name) for name in sources}
|
|
joined = "\n".join(sources.values())
|
|
if re.search(r"\berofs_map_blocks_legacy\b|\bz_erofs_map_blocks_iter\b", joined):
|
|
raise SystemExit("legacy map interface remains")
|
|
|
|
common = extract_function(sources["data.c"], "erofs_map_blocks")
|
|
read_data = extract_function(sources["data.c"], "erofs_read_data")
|
|
read_uio = extract_function(sources["data.c"], "erofs_read_uio")
|
|
zread = extract_function(sources["zdata.c"], "z_erofs_do_read")
|
|
metabox = extract_function(sources["super.c"], "erofs_init_metabox_inode")
|
|
backend = extract_function(sources["zmap.c"], "z_erofs_map_blocks")
|
|
|
|
if "z_erofs_map_blocks(sbi, vi, &next)" not in common:
|
|
raise SystemExit("common entry does not own compressed dispatch")
|
|
if backend.count("EROFS_GET_BLOCKS_FIEMAP") != 2:
|
|
raise SystemExit("compressed backend did not preserve FIEMAP semantics")
|
|
if sources["data.c"].count("erofs_map_blocks(sbi, vi, &map)") != 2:
|
|
raise SystemExit("data consumers did not both migrate")
|
|
if zread.count("erofs_map_blocks(sbi, vi, &map)") != 1:
|
|
raise SystemExit("z_erofs_do_read did not migrate")
|
|
if metabox.count("erofs_map_blocks(sbi, sbi->metabox_en, &map)") != 1:
|
|
raise SystemExit("metabox validation did not migrate")
|
|
|
|
require_order(
|
|
read_data,
|
|
[
|
|
"map = (struct erofs_map_blocks) { .m_la = loff + done };",
|
|
"error = erofs_map_blocks(sbi, vi, &map);",
|
|
"if (error != 0)",
|
|
"if (map.m_llen == 0)",
|
|
"want = MIN((size_t)map.m_llen, len - done);",
|
|
"(map.m_flags & EROFS_MAP_MAPPED) == 0",
|
|
"(map.m_flags & EROFS_MAP_META) != 0",
|
|
"erofs_read_metadata(sbi, vi->nid, map.m_pa",
|
|
"erofs_read_physical(sbi, map.m_deviceid, map.m_pa",
|
|
"erofs_put_metabuf(&buf);",
|
|
"erofs_brelse(blk);",
|
|
],
|
|
"erofs_read_data",
|
|
)
|
|
require_order(
|
|
read_uio,
|
|
[
|
|
"map = (struct erofs_map_blocks) { .m_la = uio->uio_offset };",
|
|
"error = erofs_map_blocks(sbi, vi, &map);",
|
|
"if (error != 0)",
|
|
"if (map.m_llen == 0)",
|
|
"want = MIN((size_t)map.m_llen, (size_t)uio->uio_resid);",
|
|
"(map.m_flags & EROFS_MAP_MAPPED) == 0",
|
|
"error = uiomove(zerobuf, zlen, uio);",
|
|
"(map.m_flags & EROFS_MAP_META) != 0",
|
|
"erofs_read_metadata(sbi, vi->nid, map.m_pa, want",
|
|
"erofs_read_physical(sbi, map.m_deviceid, map.m_pa",
|
|
"error = uiomove(buf.data, want, uio);",
|
|
"erofs_put_metabuf(&buf);",
|
|
"error = uiomove(blk, want, uio);",
|
|
"erofs_brelse(blk);",
|
|
],
|
|
"erofs_read_uio",
|
|
)
|
|
|
|
expected_internal = replace_once(
|
|
old["internal.h"],
|
|
"int z_erofs_map_blocks_iter(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n"
|
|
" struct erofs_map_blocks *map, int flags);",
|
|
"int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n"
|
|
" struct erofs_map_blocks *map);",
|
|
"internal.h",
|
|
)
|
|
if sources["internal.h"] != expected_internal:
|
|
raise SystemExit("B07c internal.h contains changes beyond backend closure")
|
|
|
|
expected_zdata = replace_once(
|
|
old["zdata.c"],
|
|
"\t\tbzero(&map, sizeof(map));\n"
|
|
"\t\tmap.m_la = loff + done;\n"
|
|
"\t\terror = z_erofs_map_blocks_iter(sbi, vi, &map,\n"
|
|
"\t\t EROFS_GET_BLOCKS_FIEMAP);",
|
|
"\t\tmap = (struct erofs_map_blocks) { .m_la = loff + done };\n"
|
|
"\t\terror = erofs_map_blocks(sbi, vi, &map);",
|
|
"zdata.c",
|
|
)
|
|
if sources["zdata.c"] != expected_zdata:
|
|
raise SystemExit("B07c zdata.c contains changes beyond consumer migration")
|
|
|
|
expected_super = replace_once(
|
|
old["super.c"],
|
|
"\t\t\tbzero(&map, sizeof(map));\n"
|
|
"\t\t\tmap.m_la = sbi->metabox_en->size - 1;\n"
|
|
"\t\t\terror = z_erofs_map_blocks_iter(sbi, sbi->metabox_en, &map,\n"
|
|
"\t\t\t EROFS_GET_BLOCKS_FIEMAP);",
|
|
"\t\t\tmap = (struct erofs_map_blocks) {\n"
|
|
"\t\t\t\t.m_la = sbi->metabox_en->size - 1,\n"
|
|
"\t\t\t};\n"
|
|
"\t\t\terror = erofs_map_blocks(sbi, sbi->metabox_en, &map);",
|
|
"super.c",
|
|
)
|
|
if sources["super.c"] != expected_super:
|
|
raise SystemExit("B07c super.c contains changes beyond metabox migration")
|
|
|
|
old_backend = extract_function(old["zmap.c"], "z_erofs_map_blocks_iter")
|
|
expected_backend = replace_once(
|
|
old_backend,
|
|
"z_erofs_map_blocks_iter(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n"
|
|
" struct erofs_map_blocks *map, int flags)",
|
|
"z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,\n"
|
|
" struct erofs_map_blocks *map)",
|
|
"zmap signature",
|
|
)
|
|
expected_backend = expected_backend.replace(
|
|
"z_erofs_map_blocks_ext(sbi, vi, map, flags)",
|
|
"z_erofs_map_blocks_ext(sbi, vi, map,\n\t\t\t EROFS_GET_BLOCKS_FIEMAP)",
|
|
)
|
|
expected_backend = expected_backend.replace(
|
|
"z_erofs_map_blocks_fo(sbi, vi, map, flags)",
|
|
"z_erofs_map_blocks_fo(sbi, vi, map,\n\t\t\t EROFS_GET_BLOCKS_FIEMAP)",
|
|
)
|
|
if backend != expected_backend:
|
|
raise SystemExit("compressed backend changed beyond FIEMAP interface closure")
|
|
expected_zmap = old["zmap.c"].replace(old_backend, expected_backend)
|
|
if sources["zmap.c"] != expected_zmap:
|
|
raise SystemExit("B07c zmap.c contains changes outside the backend wrapper")
|
|
|
|
old_data_functions = function_names(old["data.c"])
|
|
data_functions = function_names(sources["data.c"])
|
|
if old_data_functions - data_functions != {"erofs_map_blocks_legacy"}:
|
|
raise SystemExit("B07c data.c removed functions beyond the legacy adapter")
|
|
if data_functions - old_data_functions:
|
|
raise SystemExit("B07c data.c added an unexpected function")
|
|
changed_data_functions = {
|
|
"erofs_map_blocks",
|
|
"erofs_read_data",
|
|
"erofs_read_uio",
|
|
}
|
|
for function in sorted(data_functions - changed_data_functions):
|
|
if extract_function(old["data.c"], function) != extract_function(
|
|
sources["data.c"], function
|
|
):
|
|
raise SystemExit(f"B07c changed protected data.c function: {function}")
|
|
|
|
cleanup = Counter(
|
|
(record["actual"]["acquire_count"], record["actual"]["release_count"])
|
|
for record in tuples["records"]
|
|
)
|
|
if any(acquire != release for acquire, release in cleanup):
|
|
raise SystemExit(f"metadata cleanup became unbalanced: {cleanup!r}")
|
|
if any(record["actual"]["errno"] < 0 for record in tuples["records"]):
|
|
raise SystemExit("negative errno observed")
|
|
|
|
tuple_bytes = b"".join(
|
|
bytes.fromhex(record["tuple_hex"]) for record in tuples["records"]
|
|
)
|
|
summary = {
|
|
"status": "PASS",
|
|
"final_id": "P15-006",
|
|
"map_cases": 80,
|
|
"producer_cases": 50,
|
|
"compressed_cases": 30,
|
|
"device_cases": 13,
|
|
"full_tuple_diff_count": len(tuple_diffs),
|
|
"tuple_bytes": len(tuple_bytes),
|
|
"tuple_bytes_sha256": hashlib.sha256(tuple_bytes).hexdigest(),
|
|
"model_sha256": result["model_sha256"],
|
|
"legacy_callsite_count": 0,
|
|
"data_consumers": 2,
|
|
"compressed_consumers": 2,
|
|
"backend_fiemap_dispatches": 2,
|
|
"cleanup_distribution": {
|
|
f"{acquire}:{release}": count
|
|
for (acquire, release), count in sorted(cleanup.items())
|
|
},
|
|
"qemu": "NOT_RUN",
|
|
"full_feature_suite": "NOT_RUN",
|
|
}
|
|
with output.open("x", encoding="ascii") as stream:
|
|
json.dump(summary, stream, ensure_ascii=True, indent=2, sort_keys=True)
|
|
stream.write("\n")
|
|
print("B07c PASS full=80 diff=0 device=13 legacy=0 consumers=4")
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B07c consumer boundary check failed'
|
|
fi
|
|
|
|
sha256sum "$artifacts/baseline/result.json" \
|
|
"$artifacts/baseline/tuples.json" "$artifacts/baseline/devices.json" \
|
|
"$artifacts/candidate/result.json" "$artifacts/candidate/tuples.json" \
|
|
"$artifacts/candidate/devices.json" "$artifacts/consumer-check.json" \
|
|
>"$artifacts/SHA256SUMS"
|
|
printf 'B07c PASS common consumers; legacy map interfaces absent\n'
|