update
This commit is contained in:
Executable
+667
@@ -0,0 +1,667 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
: "${PRE15_DUT:?PRE15_DUT is required}"
|
||||
: "${PRE15_ROOT:?PRE15_ROOT is required}"
|
||||
: "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}"
|
||||
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
|
||||
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
|
||||
. "$PRE15_LIB_DIR/runner.sh"
|
||||
|
||||
fixture_dir=$PRE15_DUT/tests/pre15/fixtures
|
||||
spec=$fixture_dir/B28-partial.json
|
||||
generator=$fixture_dir/B28-partial-fixtures.py
|
||||
probe=$fixture_dir/B28-partial-probe.c
|
||||
kld_builder=$fixture_dir/B28-build-kld.sh
|
||||
gate_script=$PRE15_DUT/tests/pre15/gates/P15-086.sh
|
||||
gate_input=$PRE15_DUT/tests/pre15/gates/P15-086-input.json
|
||||
gate_result=$PRE15_ROOT/planning/pre15/evidence/20260815T154915Z-G04-G05-P15-086/gate-output/result.json
|
||||
gate_capabilities=$PRE15_ROOT/planning/pre15/evidence/20260815T154915Z-G04-G05-P15-086/gate-output/authorized-capabilities.json
|
||||
artifacts=$PRE15_RUN_DIR/artifacts
|
||||
first=$PRE15_CASE_TMP/fixtures-first
|
||||
second=$PRE15_CASE_TMP/fixtures-second
|
||||
action=${1:-runtime}
|
||||
|
||||
case "$action" in
|
||||
runtime|k2) ;;
|
||||
*) pre15_fail_usage "unknown B28 action: $action" ;;
|
||||
esac
|
||||
|
||||
if test "$action" = k2; then
|
||||
for tool in awk clang cmp diff file nm sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B28 K2 tool: $tool"
|
||||
done
|
||||
pre15_record_fixture b28-case "$PRE15_DUT/tests/pre15/cases/B28-partial.sh"
|
||||
pre15_record_fixture b28-kld-builder "$kld_builder"
|
||||
for source in compress.h decompressor.c decompressor_lz4.c \
|
||||
decompressor_lzma.c decompressor_deflate.c decompressor_zstd.c zdata.c; do
|
||||
pre15_record_fixture "b28-$source" "$PRE15_DUT/src/$source"
|
||||
done
|
||||
mkdir -p "$artifacts"
|
||||
for config in 0 1; do
|
||||
module=$PRE15_CASE_TMP/B28-zstdio$config.ko
|
||||
if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \
|
||||
"$PRE15_FREEBSD_SRC" "$module" \
|
||||
"$PRE15_CASE_TMP/kld-work-$config" "$config" \
|
||||
>"$artifacts/B28-build-zstdio$config.stdout" \
|
||||
2>"$artifacts/B28-build-zstdio$config.stderr"; then
|
||||
pre15_dut_fail "B28 zstdio$config cross-target KLD build failed"
|
||||
fi
|
||||
file "$module" >"$artifacts/B28-zstdio$config-file.txt"
|
||||
sha256sum "$module" >"$artifacts/B28-zstdio$config-sha256.txt"
|
||||
nm -g "$module" | LC_ALL=C sort \
|
||||
>"$artifacts/B28-zstdio$config-nm-global.txt"
|
||||
nm -u "$module" | LC_ALL=C sort \
|
||||
>"$artifacts/B28-zstdio$config-nm-u.txt"
|
||||
done
|
||||
awk '$1 != "U" { print $NF }' "$artifacts/B28-zstdio0-nm-global.txt" \
|
||||
>"$PRE15_CASE_TMP/B28-zstdio0-global-names.txt"
|
||||
awk '$1 != "U" { print $NF }' "$artifacts/B28-zstdio1-nm-global.txt" \
|
||||
>"$PRE15_CASE_TMP/B28-zstdio1-global-names.txt"
|
||||
if ! diff -u "$PRE15_CASE_TMP/B28-zstdio0-global-names.txt" \
|
||||
"$PRE15_CASE_TMP/B28-zstdio1-global-names.txt" \
|
||||
>"$artifacts/B28-config-nm-global.diff"; then
|
||||
pre15_dut_fail 'B28 K2 global symbols differ by ZSTDIO configuration'
|
||||
fi
|
||||
if ! grep -q ' z_erofs_decompress_supports_subextent$' \
|
||||
"$artifacts/B28-zstdio0-nm-global.txt"; then
|
||||
pre15_dut_fail 'B28 K2 partial capability symbol is absent'
|
||||
fi
|
||||
pre15_target_reached
|
||||
printf '%s\n' \
|
||||
'B28 targeted FreeBSD 15 K2 PASS' \
|
||||
'zstdio0/zstdio1 link, defined-global parity, optional Zstd ABI, and partial capability symbol PASS' \
|
||||
'KLD and object files removed by owned case cleanup'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for tool in cc cmp diff file fsck.erofs git mkfs.erofs nm python3 sha256sum tar timeout; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B28 host tool: $tool"
|
||||
done
|
||||
|
||||
pre15_record_fixture b28-case "$PRE15_DUT/tests/pre15/cases/B28-partial.sh"
|
||||
pre15_record_fixture b28-spec "$spec"
|
||||
pre15_record_fixture b28-generator "$generator"
|
||||
pre15_record_fixture b28-probe "$probe"
|
||||
pre15_record_fixture b28-kld-builder "$kld_builder"
|
||||
pre15_record_fixture b28-gate "$gate_script"
|
||||
pre15_record_fixture b28-gate-input "$gate_input"
|
||||
pre15_record_fixture b28-gate-result "$gate_result"
|
||||
pre15_record_fixture b28-gate-capabilities "$gate_capabilities"
|
||||
for source in compress.h decompressor.c decompressor_lz4.c \
|
||||
decompressor_lzma.c decompressor_deflate.c decompressor_zstd.c zdata.c; do
|
||||
pre15_record_fixture "b28-$source" "$PRE15_DUT/src/$source"
|
||||
done
|
||||
mkdir -p "$artifacts"
|
||||
|
||||
if ! timeout -k 5 120 python3 -B "$generator" --spec "$spec" \
|
||||
--output "$first" >"$artifacts/generate-first.stdout" \
|
||||
2>"$artifacts/generate-first.stderr"; then
|
||||
pre15_runner_fail 'B28 first real EROFS fixture generation failed'
|
||||
fi
|
||||
if ! timeout -k 5 120 python3 -B "$generator" --spec "$spec" \
|
||||
--output "$second" >"$artifacts/generate-second.stdout" \
|
||||
2>"$artifacts/generate-second.stderr"; then
|
||||
pre15_runner_fail 'B28 second real EROFS fixture generation failed'
|
||||
fi
|
||||
if ! diff -ru "$first" "$second" >"$artifacts/fixture-repeat.diff"; then
|
||||
pre15_runner_fail 'B28 real EROFS fixtures are not byte reproducible'
|
||||
fi
|
||||
cp "$first/fixture-index.json" "$artifacts/B28-fixture-index.json"
|
||||
|
||||
if test "${PRE15_QEMU_TARGET_ONLY:-0}" = 1; then
|
||||
pre15_target_reached
|
||||
else
|
||||
if ! python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$spec" "$gate_result" \
|
||||
"$gate_capabilities" "$artifacts/B28-source-audit.json" \
|
||||
"$PRE15_CASE_TMP/decision-harness.c" <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
dut = Path(sys.argv[2])
|
||||
spec_path = Path(sys.argv[3])
|
||||
gate_result_path = Path(sys.argv[4])
|
||||
gate_capabilities_path = Path(sys.argv[5])
|
||||
report_path = Path(sys.argv[6])
|
||||
harness_path = Path(sys.argv[7])
|
||||
spec = json.loads(spec_path.read_text(encoding="ascii"))
|
||||
baseline = spec["baseline"]
|
||||
implementation = "7a0d7e4a4047ac6d6130a3be5cb677cb94979fe7"
|
||||
runtime_base = "d09924362eb29819bd393e1e86602eb38c7608c6"
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise SystemExit(message)
|
||||
|
||||
|
||||
def sha256_path(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def committed(commit: str, name: str) -> str:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(root), "show", f"{commit}:repo-pre-15/src/{name}"],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
fail(f"cannot read B28 source {commit}:{name}: {completed.stderr.strip()}")
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def function(source: str, name: str) -> str:
|
||||
marker = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE)
|
||||
if marker is None:
|
||||
fail(f"missing function: {name}")
|
||||
name_line = source.rfind("\n", 0, marker.start()) + 1
|
||||
start = source.rfind("\n", 0, name_line - 1) + 1
|
||||
brace = source.find("{", marker.end())
|
||||
if brace < 0:
|
||||
fail(f"missing function body: {name}")
|
||||
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
|
||||
fail(f"unterminated function: {name}")
|
||||
|
||||
|
||||
if (
|
||||
spec.get("schema") != 1
|
||||
or spec.get("batch") != "B28"
|
||||
or spec.get("candidate") != "P15-086"
|
||||
or spec.get("test") != "TC176-stream-runtime"
|
||||
):
|
||||
fail("B28 fixture identity changed")
|
||||
for name, commit in (
|
||||
("baseline", baseline),
|
||||
("implementation", implementation),
|
||||
("runtime base", runtime_base),
|
||||
):
|
||||
if subprocess.check_output(
|
||||
["git", "-C", str(root), "rev-parse", commit], text=True
|
||||
).strip() != commit:
|
||||
fail(f"B28 {name} identity changed")
|
||||
gate = json.loads(gate_result_path.read_text(encoding="ascii"))
|
||||
capabilities = json.loads(gate_capabilities_path.read_text(encoding="ascii"))
|
||||
if (
|
||||
gate.get("status") != "GO"
|
||||
or gate.get("g04") != "GO"
|
||||
or gate.get("g05") != "GO"
|
||||
or gate.get("b28") != "AUTHORIZED"
|
||||
or gate.get("resolved_base") != "6bf5724619be70f805bbe7d1ba77dd70cdced69f"
|
||||
):
|
||||
fail("frozen P15-086 gate no longer authorizes B28")
|
||||
if capabilities != {
|
||||
"full_fallback": ["zstd"],
|
||||
"partial": ["deflate", "lz4", "lzma"],
|
||||
"persistent_state_added": False,
|
||||
"schema": 1,
|
||||
}:
|
||||
fail("P15-086 per-codec authorization changed")
|
||||
for name, expected in (
|
||||
("gate script", spec["gate"]["script_sha256"]),
|
||||
("gate input", spec["gate"]["input_sha256"]),
|
||||
("gate result", spec["gate"]["result_sha256"]),
|
||||
):
|
||||
path = {
|
||||
"gate script": dut / "tests/pre15/gates/P15-086.sh",
|
||||
"gate input": dut / "tests/pre15/gates/P15-086-input.json",
|
||||
"gate result": gate_result_path,
|
||||
}[name]
|
||||
if sha256_path(path) != expected:
|
||||
fail(f"{name} hash changed")
|
||||
|
||||
source_names = (
|
||||
"compress.h",
|
||||
"decompressor.c",
|
||||
"decompressor_lz4.c",
|
||||
"decompressor_lzma.c",
|
||||
"decompressor_deflate.c",
|
||||
"decompressor_zstd.c",
|
||||
"zdata.c",
|
||||
)
|
||||
baseline_sources = {name: committed(baseline, name) for name in source_names}
|
||||
implemented_sources = {name: committed(implementation, name) for name in source_names}
|
||||
sources = {
|
||||
name: (dut / "src" / name).read_text(encoding="utf-8")
|
||||
for name in source_names
|
||||
}
|
||||
if implemented_sources["decompressor_lz4.c"] != baseline_sources["decompressor_lz4.c"]:
|
||||
fail("B28 changed the already-capable LZ4 backend implementation")
|
||||
if "bool supports_subextent;" not in implemented_sources["compress.h"]:
|
||||
fail("B28 descriptor capability is absent")
|
||||
if implemented_sources["compress.h"].count("z_erofs_decompress_supports_subextent") != 1:
|
||||
fail("B28 capability prototype count changed")
|
||||
if "bool supports_subextent;" not in sources["compress.h"]:
|
||||
fail("current tree lost the B28 descriptor capability")
|
||||
if sources["compress.h"].count("z_erofs_decompress_supports_subextent") != 1:
|
||||
fail("current B28 capability prototype count changed")
|
||||
|
||||
def read_descriptor_decisions(source_set: dict[str, str]) -> dict[str, bool]:
|
||||
decisions = {}
|
||||
for codec in ("lzma", "deflate", "zstd"):
|
||||
value = re.search(
|
||||
rf"const struct z_erofs_decompressor z_erofs_{codec}_decomp = \{{.*?\.supports_subextent = ([01]),",
|
||||
source_set[f"decompressor_{codec}.c"],
|
||||
re.DOTALL,
|
||||
)
|
||||
if value is None:
|
||||
fail(f"missing {codec} descriptor capability")
|
||||
decisions[codec] = value.group(1) == "1"
|
||||
lz4 = re.search(
|
||||
r"static const struct z_erofs_decompressor z_erofs_lz4_decomp = \{.*?\.supports_subextent = ([01]),",
|
||||
source_set["decompressor.c"],
|
||||
re.DOTALL,
|
||||
)
|
||||
if lz4 is None:
|
||||
fail("missing LZ4 descriptor capability")
|
||||
decisions["lz4"] = lz4.group(1) == "1"
|
||||
return decisions
|
||||
|
||||
|
||||
implemented_decisions = read_descriptor_decisions(implemented_sources)
|
||||
if implemented_decisions != {
|
||||
"lz4": True, "lzma": True, "deflate": True, "zstd": False
|
||||
}:
|
||||
fail(f"B28 implementation decisions differ from G04/G05: {implemented_decisions}")
|
||||
descriptor_decisions = read_descriptor_decisions(sources)
|
||||
if descriptor_decisions != {
|
||||
"lz4": True, "lzma": True, "deflate": True, "zstd": False
|
||||
}:
|
||||
fail(f"current partial descriptor decisions changed: {descriptor_decisions}")
|
||||
expected_zstd = committed(runtime_base, "decompressor_zstd.c")
|
||||
if function(sources["decompressor_zstd.c"], "z_erofs_zstd_finish") != \
|
||||
function(expected_zstd, "z_erofs_zstd_finish"):
|
||||
fail("current Zstd completion policy differs from the accepted transform")
|
||||
|
||||
unchanged_zdata = (
|
||||
"z_erofs_extent_cache_match",
|
||||
"z_erofs_extent_cache_copy",
|
||||
"z_erofs_extent_cache_publish",
|
||||
"z_erofs_extent_cache_init",
|
||||
"z_erofs_extent_cache_fini",
|
||||
"z_erofs_extent_cache_eligible",
|
||||
"z_erofs_read_data",
|
||||
"z_erofs_read_uio",
|
||||
)
|
||||
baseline_zdata = baseline_sources["zdata.c"]
|
||||
for name in unchanged_zdata:
|
||||
if function(implemented_sources["zdata.c"], name) != function(baseline_zdata, name):
|
||||
fail(f"B28 changed unrelated zdata function {name}")
|
||||
implemented_decode_length = function(implemented_sources["zdata.c"], "z_erofs_decode_length")
|
||||
implemented_read_extent = function(implemented_sources["zdata.c"], "z_erofs_read_extent")
|
||||
implemented_do_read = function(implemented_sources["zdata.c"], "z_erofs_do_read")
|
||||
decode_length = function(sources["zdata.c"], "z_erofs_decode_length")
|
||||
decode_extent = function(sources["zdata.c"], "z_erofs_decode_extent")
|
||||
do_read = function(sources["zdata.c"], "z_erofs_do_read")
|
||||
if decode_length != implemented_decode_length:
|
||||
fail("current tree changed the B28 decode-length transform")
|
||||
if decode_extent[decode_extent.index("{") :] != implemented_read_extent[
|
||||
implemented_read_extent.index("{") :
|
||||
]:
|
||||
fail("current tree changed B28 provider or decoded-buffer ownership")
|
||||
for anchor in (
|
||||
"z_erofs_decompress_supports_subextent(map)",
|
||||
"*partial = (map->m_flags & EROFS_MAP_PARTIAL_REF) != 0;",
|
||||
"*decoded_len = end;",
|
||||
"*partial = true;",
|
||||
):
|
||||
if anchor not in implemented_decode_length:
|
||||
fail(f"B28 decode-length anchor missing: {anchor}")
|
||||
for anchor in (
|
||||
"erofs_read_metadata(",
|
||||
"erofs_read_physical(",
|
||||
"erofs_put_metabuf(&buf);",
|
||||
"erofs_brelse(compressed);",
|
||||
"free(decoded, M_EROFS);",
|
||||
"*bufp = decoded;",
|
||||
):
|
||||
if implemented_read_extent.count(anchor) != function(baseline_zdata, "z_erofs_read_extent").count(anchor):
|
||||
fail(f"B28 changed provider or cleanup count: {anchor}")
|
||||
if not (
|
||||
implemented_do_read.index("z_erofs_extent_cache_copy")
|
||||
< implemented_do_read.index("z_erofs_decode_length")
|
||||
< implemented_do_read.index("z_erofs_read_extent")
|
||||
< implemented_do_read.index("cache_eligible && !partial")
|
||||
< implemented_do_read.index("z_erofs_extent_cache_publish")
|
||||
):
|
||||
fail("B28 cache/decode/publication order changed")
|
||||
if "partial, &decoded" not in implemented_do_read:
|
||||
fail("B28 does not pass the selected partial mode to extent decode")
|
||||
if not (
|
||||
do_read.index("z_erofs_decode_length")
|
||||
< do_read.index("cache_eligible = !partial")
|
||||
< do_read.index("z_erofs_decode_extent")
|
||||
):
|
||||
fail("current tree no longer excludes partial decodes from cache admission")
|
||||
if "partial, &decoded" not in do_read:
|
||||
fail("current tree does not pass B28 partial mode to extent decode")
|
||||
|
||||
changed = set(
|
||||
subprocess.check_output(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(root),
|
||||
"diff-tree",
|
||||
"--no-commit-id",
|
||||
"--name-only",
|
||||
"-r",
|
||||
implementation,
|
||||
],
|
||||
text=True,
|
||||
).splitlines()
|
||||
)
|
||||
expected_write_set = {
|
||||
"repo-pre-15/src/compress.h",
|
||||
"repo-pre-15/src/decompressor.c",
|
||||
"repo-pre-15/src/decompressor_deflate.c",
|
||||
"repo-pre-15/src/decompressor_lzma.c",
|
||||
"repo-pre-15/src/decompressor_zstd.c",
|
||||
"repo-pre-15/src/zdata.c",
|
||||
"repo-pre-15/tests/pre15/cases/B28-partial.sh",
|
||||
"repo-pre-15/tests/pre15/fixtures/B28-build-kld.sh",
|
||||
"repo-pre-15/tests/pre15/fixtures/B28-partial-fixtures.py",
|
||||
"repo-pre-15/tests/pre15/fixtures/B28-partial-probe.c",
|
||||
"repo-pre-15/tests/pre15/fixtures/B28-partial.json",
|
||||
}
|
||||
if changed != expected_write_set:
|
||||
fail(f"B28 exact write set differs: {sorted(changed ^ expected_write_set)}")
|
||||
if any(re.search(r"return\s*\(\s*-E[A-Z0-9_]+", source) for source in sources.values()):
|
||||
fail("negative Linux errno entered B28")
|
||||
|
||||
harness = r'''#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#define EINTEGRITY 97
|
||||
#define EOVERFLOW 84
|
||||
#define EROFS_MAP_PARTIAL_REF 0x20
|
||||
typedef uint64_t erofs_off_t;
|
||||
struct erofs_map_blocks {
|
||||
uint64_t m_llen;
|
||||
unsigned int m_flags;
|
||||
unsigned int m_algorithmformat;
|
||||
};
|
||||
static bool z_erofs_decompress_supports_subextent(const struct erofs_map_blocks *map)
|
||||
{
|
||||
return map->m_algorithmformat <= 2;
|
||||
}
|
||||
''' + decode_length + r'''
|
||||
static int failures;
|
||||
static void check(const char *name, struct erofs_map_blocks map, uint64_t off,
|
||||
size_t want, int expected_error, size_t expected_length, bool expected_partial)
|
||||
{
|
||||
size_t length = 0;
|
||||
bool partial = false;
|
||||
int error = z_erofs_decode_length(&map, off, want, &length, &partial);
|
||||
if (error != expected_error || (!error &&
|
||||
(length != expected_length || partial != expected_partial))) {
|
||||
fprintf(stderr, "%s error=%d length=%zu partial=%d\n", name,
|
||||
error, length, partial);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
check("lz4 prefix", (struct erofs_map_blocks){1038906, 0, 0}, 0,
|
||||
4096, 0, 4096, true);
|
||||
check("lzma middle", (struct erofs_map_blocks){151552, 0, 1}, 8192,
|
||||
4096, 0, 12288, true);
|
||||
check("deflate cross", (struct erofs_map_blocks){22878, 0, 2}, 3584,
|
||||
4096, 0, 7680, true);
|
||||
check("zstd prefix", (struct erofs_map_blocks){151552, 0, 3}, 0,
|
||||
4096, 0, 151552, false);
|
||||
check("lzma tail", (struct erofs_map_blocks){151552, 0, 1}, 147456,
|
||||
4096, 0, 151552, false);
|
||||
check("zstd partial ref", (struct erofs_map_blocks){151552,
|
||||
EROFS_MAP_PARTIAL_REF, 3}, 0, 4096, 0, 4096, true);
|
||||
check("shifted fallback", (struct erofs_map_blocks){65536, 0, 4}, 0,
|
||||
4096, 0, 65536, false);
|
||||
check("bad range", (struct erofs_map_blocks){4096, 0, 0}, 4090,
|
||||
16, EINTEGRITY, 0, false);
|
||||
return failures != 0;
|
||||
}
|
||||
'''
|
||||
harness_path.write_text(harness, encoding="ascii")
|
||||
report = {
|
||||
"baseline": baseline,
|
||||
"batch": "B28",
|
||||
"cache_hit_before_decode": True,
|
||||
"candidate": "P15-086",
|
||||
"descriptor_decisions": descriptor_decisions,
|
||||
"frozen_gate_full_fallback": ["zstd"],
|
||||
"current_full_fallback": ["shifted", "interlaced", "unknown", "zstd"],
|
||||
"current_partial": ["deflate", "lz4", "lzma"],
|
||||
"gate": "GO",
|
||||
"implementation": implementation,
|
||||
"partial_cache_publication": False,
|
||||
"partial_reference_preserved": True,
|
||||
"persistent_state_added": False,
|
||||
"positive_errno": True,
|
||||
"provider_and_cleanup_unchanged": True,
|
||||
"runtime_base": runtime_base,
|
||||
"status": "PASS",
|
||||
"test": "TC176-stream-runtime",
|
||||
"write_set": sorted(changed),
|
||||
}
|
||||
report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii")
|
||||
print(json.dumps(report, indent=2, sort_keys=True))
|
||||
PY
|
||||
then
|
||||
pre15_runner_fail 'B28 gate/source/write-set audit failed'
|
||||
fi
|
||||
|
||||
if ! cc -std=c11 -Wall -Wextra -Werror "$PRE15_CASE_TMP/decision-harness.c" \
|
||||
-o "$PRE15_CASE_TMP/decision-harness" \
|
||||
>"$artifacts/decision-harness-build.stdout" \
|
||||
2>"$artifacts/decision-harness-build.stderr" || \
|
||||
! "$PRE15_CASE_TMP/decision-harness" \
|
||||
>"$artifacts/decision-harness.stdout" \
|
||||
2>"$artifacts/decision-harness.stderr"; then
|
||||
pre15_dut_fail 'B28 extracted decode-length harness failed'
|
||||
fi
|
||||
cp "$gate_result" "$artifacts/P15-086-result.json"
|
||||
cp "$gate_capabilities" "$artifacts/P15-086-capabilities.json"
|
||||
sha256sum "$artifacts/P15-086-result.json" \
|
||||
"$artifacts/P15-086-capabilities.json" \
|
||||
"$artifacts/B28-fixture-index.json" \
|
||||
"$artifacts/B28-source-audit.json" >"$artifacts/SHA256SUMS"
|
||||
fi
|
||||
pre15_target_reached
|
||||
|
||||
if test "${PRE15_MODE:-host}" != qemu; then
|
||||
printf '%s\n' \
|
||||
'TC176 B28 host partial-subextent subset PASS' \
|
||||
'B28 LZ4/LZMA/Deflate partial policy and Zstd full fallback, cache exclusion, arithmetic, real fixtures, and cleanup PASS' \
|
||||
'QEMU runtime NOT_RUN in host mode' \
|
||||
'Full feature suite NOT_RUN'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for tool in awk clang scp; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B28 QEMU tool: $tool"
|
||||
done
|
||||
: "${PRE15_QEMU_CONTROL_PATH:?QEMU control path is required}"
|
||||
: "${PRE15_QEMU_SSH_KEY:?QEMU SSH key is required}"
|
||||
: "${PRE15_QEMU_SSH_PORT:?QEMU SSH port is required}"
|
||||
: "${PRE15_QEMU_SSH_USER:?QEMU SSH user is required}"
|
||||
|
||||
fixture_archive=$PRE15_CASE_TMP/B28-fixtures.tar.gz
|
||||
module=$PRE15_CASE_TMP/B28-erofs-zstdio1.ko
|
||||
if ! /bin/sh "$kld_builder" "$PRE15_DUT" "$PRE15_FREEBSD_SRC" "$module" \
|
||||
"$PRE15_CASE_TMP/kld-work" 1 >"$artifacts/B28-kld-build.stdout" \
|
||||
2>"$artifacts/B28-kld-build.stderr"; then
|
||||
pre15_dut_fail 'B28 cross-target zstdio1 KLD build failed'
|
||||
fi
|
||||
pre15_record_module "$module"
|
||||
file "$module" >"$artifacts/B28-kld-file.txt"
|
||||
sha256sum "$module" >"$artifacts/B28-kld-sha256.txt"
|
||||
nm -u "$module" | LC_ALL=C sort >"$artifacts/B28-kld-nm-u.txt"
|
||||
tar -C "$first" -czf "$fixture_archive" .
|
||||
|
||||
pre15_scp()
|
||||
{
|
||||
timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" scp -O -q \
|
||||
-o BatchMode=yes -o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \
|
||||
-o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \
|
||||
-i "$PRE15_QEMU_SSH_KEY" -P "$PRE15_QEMU_SSH_PORT" \
|
||||
"$1" "$PRE15_QEMU_SSH_USER@127.0.0.1:$2"
|
||||
}
|
||||
|
||||
pre15_guest_ssh_bounded()
|
||||
{
|
||||
timeout -k 5 "${PRE15_GUEST_COMMAND_TIMEOUT:-60}" ssh -n -q \
|
||||
-o BatchMode=yes -o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \
|
||||
-o "ControlPath=$PRE15_QEMU_CONTROL_PATH" \
|
||||
-p "$PRE15_QEMU_SSH_PORT" \
|
||||
"$PRE15_QEMU_SSH_USER@127.0.0.1" "$@"
|
||||
}
|
||||
|
||||
if ! pre15_scp "$fixture_archive" /root/B28-fixtures.tar.gz || \
|
||||
! pre15_scp "$probe" /root/B28-partial-probe.c || \
|
||||
! pre15_scp "$module" /root/B28-erofs-zstdio1.ko; then
|
||||
pre15_infra_blocked 'could not transfer B28 module, probe, or fixtures'
|
||||
fi
|
||||
if ! pre15_guest_ssh_bounded \
|
||||
'rm -rf /root/B28-fixtures && mkdir /root/B28-fixtures && tar -xzf /root/B28-fixtures.tar.gz -C /root/B28-fixtures && cc -O2 -Wall -Wextra -Werror -o /root/B28-partial-probe /root/B28-partial-probe.c'; then
|
||||
pre15_infra_blocked 'could not prepare B28 guest fixtures/probe'
|
||||
fi
|
||||
if pre15_guest_ssh_bounded kldstat -q -m erofs >/dev/null 2>&1; then
|
||||
pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
|
||||
fi
|
||||
if ! pre15_guest_ssh_bounded kldload /root/B28-erofs-zstdio1.ko \
|
||||
>"$artifacts/B28-kldload.stdout" 2>"$artifacts/B28-kldload.stderr"; then
|
||||
if grep -q 'module already loaded or in kernel' \
|
||||
"$artifacts/B28-kldload.stderr"; then
|
||||
pre15_infra_blocked 'guest kernel already owns the erofs.1 interface'
|
||||
fi
|
||||
if grep -Eq 'link_elf|symbol|not defined' "$artifacts/B28-kldload.stderr"; then
|
||||
pre15_infra_blocked 'guest kernel lacks the planned ZSTDIO provider ABI'
|
||||
fi
|
||||
pre15_dut_fail 'B28 exact-source zstdio1 KLD failed to load'
|
||||
fi
|
||||
pre15_own_guest_kld B28-erofs-zstdio1.ko 'B28 exact-source KLD'
|
||||
|
||||
attach_mount()
|
||||
{
|
||||
image=$1
|
||||
label=$2
|
||||
B28_MD=$(pre15_guest_ssh_bounded mdconfig -a -t vnode \
|
||||
-f "/root/B28-fixtures/images/$image") || \
|
||||
pre15_dut_fail "$label md attach failed"
|
||||
case "$B28_MD" in
|
||||
md[0-9]*) ;;
|
||||
*) pre15_runner_fail "unexpected B28 md unit: $B28_MD" ;;
|
||||
esac
|
||||
pre15_own_guest_md "$B28_MD" "$label md"
|
||||
B28_MOUNT=/mnt/pre15-b28-$label
|
||||
pre15_guest_ssh_bounded mkdir -p "$B28_MOUNT"
|
||||
pre15_guest_ssh_bounded mount -t erofs -o ro "/dev/$B28_MD" "$B28_MOUNT" || \
|
||||
pre15_dut_fail "$label mount failed"
|
||||
pre15_own_guest_mount "$B28_MOUNT" "$label mount"
|
||||
}
|
||||
|
||||
valid_assertions=0
|
||||
corrupt_range_assertions=0
|
||||
full_corruption_assertions=0
|
||||
while IFS='|' read -r codec source logical_offset logical_length decision; do
|
||||
reference=/root/B28-fixtures/sources/$source/payload.bin
|
||||
attach_mount "$codec-valid.erofs" "$codec-valid"
|
||||
for range in '0 4096' '3584 4096' '8192 4096'; do
|
||||
set -- $range
|
||||
offset=$((logical_offset + $1))
|
||||
pre15_guest_ssh_bounded /root/B28-partial-probe range \
|
||||
"$B28_MOUNT/payload.bin" "$reference" "$offset" "$2" || \
|
||||
pre15_dut_fail "$codec valid subextent mismatch at $offset"
|
||||
valid_assertions=$((valid_assertions + 1))
|
||||
done
|
||||
tail_offset=$((logical_offset + logical_length - 4096))
|
||||
pre15_guest_ssh_bounded /root/B28-partial-probe range \
|
||||
"$B28_MOUNT/payload.bin" "$reference" "$tail_offset" 4096 || \
|
||||
pre15_dut_fail "$codec valid tail fallback mismatch"
|
||||
valid_assertions=$((valid_assertions + 1))
|
||||
|
||||
attach_mount "$codec-corrupt.erofs" "$codec-corrupt"
|
||||
if test "$decision" = GO; then
|
||||
pre15_guest_ssh_bounded /root/B28-partial-probe range \
|
||||
"$B28_MOUNT/payload.bin" "$reference" "$logical_offset" 4096 || \
|
||||
pre15_dut_fail "$codec range-before-corruption partial decode failed"
|
||||
else
|
||||
pre15_guest_ssh_bounded /root/B28-partial-probe range-errno \
|
||||
"$B28_MOUNT/payload.bin" "$logical_offset" 4096 97 || \
|
||||
pre15_dut_fail "$codec did not use exact full fallback"
|
||||
fi
|
||||
corrupt_range_assertions=$((corrupt_range_assertions + 1))
|
||||
pre15_guest_ssh_bounded /root/B28-partial-probe full-errno \
|
||||
"$B28_MOUNT/payload.bin" 97 || \
|
||||
pre15_dut_fail "$codec full corruption did not return EINTEGRITY"
|
||||
full_corruption_assertions=$((full_corruption_assertions + 1))
|
||||
done <<'EOF'
|
||||
deflate|stream|114326|22878|GO
|
||||
lz4|lz4|0|1038906|GO
|
||||
lzma|stream|0|151552|GO
|
||||
zstd|stream|0|151552|FULL_FALLBACK
|
||||
EOF
|
||||
if test "$valid_assertions" -ne 16 || \
|
||||
test "$corrupt_range_assertions" -ne 4 || \
|
||||
test "$full_corruption_assertions" -ne 4; then
|
||||
pre15_runner_fail "B28 assertion count mismatch: valid=$valid_assertions corrupt-range=$corrupt_range_assertions full-corruption=$full_corruption_assertions"
|
||||
fi
|
||||
|
||||
pre15_guest_ssh_bounded dmesg >"$artifacts/B28-dmesg.txt"
|
||||
printf '%s\n' \
|
||||
'TC176 B28 QEMU partial-subextent subset PASS' \
|
||||
'valid-ranges=16 partial-before-corruption=4 full-corruption=4 errno=97' \
|
||||
'Full feature suite NOT_RUN'
|
||||
Reference in New Issue
Block a user