update
This commit is contained in:
Executable
+448
@@ -0,0 +1,448 @@
|
||||
#!/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"
|
||||
|
||||
baseline=1b4b8abc249d9bbbb75e19b5259c5f46f91909ef
|
||||
artifacts=$PRE15_RUN_DIR/artifacts
|
||||
|
||||
for tool in git python3 sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B30 host tool: $tool"
|
||||
done
|
||||
|
||||
for source in internal.h compress.h decompressor.c decompressor_lz4.c \
|
||||
decompressor_lzma.c decompressor_deflate.c decompressor_zstd.c zdata.c zmap.c; do
|
||||
pre15_record_fixture "b30-$source" "$PRE15_DUT/src/$source"
|
||||
done
|
||||
pre15_record_fixture b30-linux-compress "$PRE15_ROOT/src-linux/compress.h"
|
||||
pre15_record_fixture b30-linux-decompressor "$PRE15_ROOT/src-linux/decompressor.c"
|
||||
pre15_record_fixture b30-case \
|
||||
"$PRE15_DUT/tests/pre15/cases/B30-codec-structure.sh"
|
||||
|
||||
mkdir -p "$artifacts"
|
||||
pre15_target_reached
|
||||
|
||||
if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" \
|
||||
"$artifacts/B30-codec-structure.json" <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
dut = Path(sys.argv[2])
|
||||
baseline = sys.argv[3]
|
||||
report_path = Path(sys.argv[4])
|
||||
src = dut / "src"
|
||||
|
||||
|
||||
def committed(path: str) -> str:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/{path}"],
|
||||
check=False,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise SystemExit(f"cannot read B30 baseline {path}: {completed.stderr}")
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def replace_once(source: str, old: str, new: str, label: str) -> str:
|
||||
count = source.count(old)
|
||||
if count != 1:
|
||||
raise SystemExit(f"B30 baseline anchor count for {label}: {count}")
|
||||
return source.replace(old, new, 1)
|
||||
|
||||
|
||||
expected_changed = {
|
||||
"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/internal.h",
|
||||
"repo-pre-15/src/zdata.c",
|
||||
"repo-pre-15/tests/pre15/cases/B30-codec-structure.sh",
|
||||
}
|
||||
changed = subprocess.run(
|
||||
["git", "-C", str(root), "diff", "--name-only", baseline, "--", "repo-pre-15"],
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.splitlines()
|
||||
changed.extend(
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(root),
|
||||
"ls-files",
|
||||
"--others",
|
||||
"--exclude-standard",
|
||||
"--",
|
||||
"repo-pre-15",
|
||||
],
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.splitlines()
|
||||
)
|
||||
if set(changed) != expected_changed or len(changed) != len(expected_changed):
|
||||
raise SystemExit(f"B30 repo-pre-15 write set mismatch: {changed!r}")
|
||||
|
||||
current = {
|
||||
path: (src / path).read_text(encoding="utf-8")
|
||||
for path in (
|
||||
"internal.h",
|
||||
"compress.h",
|
||||
"decompressor.c",
|
||||
"decompressor_lz4.c",
|
||||
"decompressor_lzma.c",
|
||||
"decompressor_deflate.c",
|
||||
"decompressor_zstd.c",
|
||||
"zdata.c",
|
||||
"zmap.c",
|
||||
)
|
||||
}
|
||||
before = {
|
||||
path: committed(f"src/{path}")
|
||||
for path in current
|
||||
}
|
||||
|
||||
expected = before["internal.h"]
|
||||
expected = replace_once(
|
||||
expected,
|
||||
"struct erofs_sb_lz4_info {\n"
|
||||
"\tuint16_t max_distance_pages;\n"
|
||||
"\tuint16_t max_pclusterblks;\n"
|
||||
"};\n\n",
|
||||
"",
|
||||
"LZ4 state type",
|
||||
)
|
||||
expected = replace_once(
|
||||
expected,
|
||||
"\tstruct erofs_sb_lz4_info lz4;\n",
|
||||
"",
|
||||
"LZ4 state member",
|
||||
)
|
||||
if current["internal.h"] != expected:
|
||||
raise SystemExit("internal.h differs from exact B30 dead-state removal")
|
||||
|
||||
expected = before["compress.h"]
|
||||
expected = replace_once(
|
||||
expected,
|
||||
"struct z_erofs_decompressor {\n"
|
||||
"\tconst char *name;\n"
|
||||
"\tbool supports_subextent;\n"
|
||||
"\t/* Callbacks return zero or a positive FreeBSD errno. */\n"
|
||||
"\tint (*config)(struct erofs_sb_info *, const struct erofs_super_block *,\n"
|
||||
"\t const void *, size_t);\n"
|
||||
"\tint (*decompress)(const struct z_erofs_decompress_req *);\n"
|
||||
"};",
|
||||
"struct z_erofs_decompressor {\n"
|
||||
"\t/* Callbacks return zero or a positive FreeBSD errno. */\n"
|
||||
"\tint (*config)(struct erofs_sb_info *, const struct erofs_super_block *,\n"
|
||||
"\t const void *, size_t);\n"
|
||||
"\tint (*decompress)(const struct z_erofs_decompress_req *);\n"
|
||||
"\tbool supports_subextent;\n"
|
||||
"\tconst char *name;\n"
|
||||
"};",
|
||||
"descriptor fields",
|
||||
)
|
||||
if current["compress.h"] != expected:
|
||||
raise SystemExit("compress.h differs from exact B30 descriptor alignment")
|
||||
|
||||
expected = before["decompressor.c"]
|
||||
for old, new, label in (
|
||||
("\t\tdistance = le16toh(lz4->max_distance);\n", "", "unused cfg distance"),
|
||||
("\t\tmax_pclusterblks = 1;\n\t\tsbi->available_compr_algs", "\t\tsbi->available_compr_algs", "legacy dead local store"),
|
||||
(
|
||||
"\tsbi->lz4.max_pclusterblks = max_pclusterblks;\n"
|
||||
"\tsbi->lz4.max_distance_pages = distance != 0 ?\n"
|
||||
"\t howmany(distance, PAGE_SIZE) + 1 :\n"
|
||||
"\t howmany(UINT16_MAX, PAGE_SIZE) + 1;\n",
|
||||
"",
|
||||
"runtime LZ4 stores",
|
||||
),
|
||||
(
|
||||
"static const struct z_erofs_decompressor z_erofs_shifted_decomp = {\n"
|
||||
"\t.name = \"shifted\",\n"
|
||||
"\t.decompress = z_erofs_transform_plain,\n"
|
||||
"};",
|
||||
"static const struct z_erofs_decompressor z_erofs_shifted_decomp = {\n"
|
||||
"\t.decompress = z_erofs_transform_plain,\n"
|
||||
"\t.name = \"shifted\",\n"
|
||||
"};",
|
||||
"shifted descriptor",
|
||||
),
|
||||
(
|
||||
"static const struct z_erofs_decompressor z_erofs_interlaced_decomp = {\n"
|
||||
"\t.name = \"interlaced\",\n"
|
||||
"\t.decompress = z_erofs_transform_plain,\n"
|
||||
"};",
|
||||
"static const struct z_erofs_decompressor z_erofs_interlaced_decomp = {\n"
|
||||
"\t.decompress = z_erofs_transform_plain,\n"
|
||||
"\t.name = \"interlaced\",\n"
|
||||
"};",
|
||||
"interlaced descriptor",
|
||||
),
|
||||
(
|
||||
"static const struct z_erofs_decompressor z_erofs_lz4_decomp = {\n"
|
||||
"\t.name = \"lz4\",\n"
|
||||
"\t.supports_subextent = 1,\n"
|
||||
"\t.config = z_erofs_load_lz4_config,\n"
|
||||
"\t.decompress = z_erofs_lz4_decompress,\n"
|
||||
"};",
|
||||
"static const struct z_erofs_decompressor z_erofs_lz4_decomp = {\n"
|
||||
"\t.config = z_erofs_load_lz4_config,\n"
|
||||
"\t.decompress = z_erofs_lz4_decompress,\n"
|
||||
"\t.supports_subextent = 1,\n"
|
||||
"\t.name = \"lz4\",\n"
|
||||
"};",
|
||||
"LZ4 descriptor",
|
||||
),
|
||||
(
|
||||
"static const struct z_erofs_decompressor * const z_erofs_decomp[] = {\n"
|
||||
"\t[Z_EROFS_COMPRESSION_LZ4] = &z_erofs_lz4_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_LZMA] = &z_erofs_lzma_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_DEFLATE] = &z_erofs_deflate_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_ZSTD] = &z_erofs_zstd_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_SHIFTED] = &z_erofs_shifted_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_INTERLACED] = &z_erofs_interlaced_decomp,\n"
|
||||
"};",
|
||||
"static const struct z_erofs_decompressor * const z_erofs_decomp[] = {\n"
|
||||
"\t[Z_EROFS_COMPRESSION_SHIFTED] = &z_erofs_shifted_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_INTERLACED] = &z_erofs_interlaced_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_LZ4] = &z_erofs_lz4_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_LZMA] = &z_erofs_lzma_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_DEFLATE] = &z_erofs_deflate_decomp,\n"
|
||||
"\t[Z_EROFS_COMPRESSION_ZSTD] = &z_erofs_zstd_decomp,\n"
|
||||
"};",
|
||||
"decompressor table",
|
||||
),
|
||||
):
|
||||
expected = replace_once(expected, old, new, label)
|
||||
if current["decompressor.c"] != expected:
|
||||
raise SystemExit("decompressor.c differs from exact B30 structural edits")
|
||||
|
||||
for path, name, capability in (
|
||||
("decompressor_lzma.c", "lzma", "1"),
|
||||
("decompressor_deflate.c", "deflate", "1"),
|
||||
("decompressor_zstd.c", "zstd", "0"),
|
||||
):
|
||||
expected = before[path]
|
||||
expected = replace_once(
|
||||
expected,
|
||||
f"const struct z_erofs_decompressor z_erofs_{name}_decomp = {{\n"
|
||||
f"\t.name = \"{name}\",\n"
|
||||
f"\t.supports_subextent = {capability},\n"
|
||||
f"\t.config = z_erofs_load_{name}_config,\n"
|
||||
f"\t.decompress = z_erofs_{name}_decompress,\n"
|
||||
"};",
|
||||
f"const struct z_erofs_decompressor z_erofs_{name}_decomp = {{\n"
|
||||
f"\t.config = z_erofs_load_{name}_config,\n"
|
||||
f"\t.decompress = z_erofs_{name}_decompress,\n"
|
||||
f"\t.supports_subextent = {capability},\n"
|
||||
f"\t.name = \"{name}\",\n"
|
||||
"};",
|
||||
f"{name} descriptor",
|
||||
)
|
||||
if current[path] != expected:
|
||||
raise SystemExit(f"{path} differs from exact B30 descriptor reorder")
|
||||
|
||||
if current["decompressor_lz4.c"] != before["decompressor_lz4.c"]:
|
||||
raise SystemExit("B30 changed the independent FreeBSD LZ4 backend")
|
||||
expected = before["zdata.c"]
|
||||
if expected.count("z_erofs_read_extent") != 2:
|
||||
raise SystemExit("B30 zdata baseline rename denominator changed")
|
||||
expected = expected.replace("z_erofs_read_extent", "z_erofs_decode_extent")
|
||||
if current["zdata.c"] != expected:
|
||||
raise SystemExit("zdata.c differs from the exact two-token decode rename")
|
||||
if current["zmap.c"] != before["zmap.c"]:
|
||||
raise SystemExit("B30 changed the zmap record reader")
|
||||
|
||||
descriptor_markers = [
|
||||
"int (*config)(",
|
||||
"int (*decompress)(",
|
||||
"bool supports_subextent;",
|
||||
"const char *name;",
|
||||
]
|
||||
positions = [current["compress.h"].index(marker) for marker in descriptor_markers]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit("FreeBSD descriptor field order drifted")
|
||||
|
||||
|
||||
def initializer_fields(source: str, object_name: str) -> list[tuple[str, str]]:
|
||||
match = re.search(
|
||||
rf"(?:static )?const struct z_erofs_decompressor {object_name} = \{{\n"
|
||||
rf"(?P<body>.*?)\n\}};",
|
||||
source,
|
||||
re.DOTALL,
|
||||
)
|
||||
if match is None:
|
||||
raise SystemExit(f"missing descriptor initializer: {object_name}")
|
||||
return re.findall(r"^\s*\.([a-z_]+)\s*=\s*([^,]+),$", match.group("body"), re.MULTILINE)
|
||||
|
||||
|
||||
descriptors = {
|
||||
"shifted": initializer_fields(current["decompressor.c"], "z_erofs_shifted_decomp"),
|
||||
"interlaced": initializer_fields(current["decompressor.c"], "z_erofs_interlaced_decomp"),
|
||||
"lz4": initializer_fields(current["decompressor.c"], "z_erofs_lz4_decomp"),
|
||||
"lzma": initializer_fields(current["decompressor_lzma.c"], "z_erofs_lzma_decomp"),
|
||||
"deflate": initializer_fields(current["decompressor_deflate.c"], "z_erofs_deflate_decomp"),
|
||||
"zstd": initializer_fields(current["decompressor_zstd.c"], "z_erofs_zstd_decomp"),
|
||||
}
|
||||
expected_descriptors = {
|
||||
"shifted": [("decompress", "z_erofs_transform_plain"), ("name", '"shifted"')],
|
||||
"interlaced": [("decompress", "z_erofs_transform_plain"), ("name", '"interlaced"')],
|
||||
"lz4": [
|
||||
("config", "z_erofs_load_lz4_config"),
|
||||
("decompress", "z_erofs_lz4_decompress"),
|
||||
("supports_subextent", "1"),
|
||||
("name", '"lz4"'),
|
||||
],
|
||||
"lzma": [
|
||||
("config", "z_erofs_load_lzma_config"),
|
||||
("decompress", "z_erofs_lzma_decompress"),
|
||||
("supports_subextent", "1"),
|
||||
("name", '"lzma"'),
|
||||
],
|
||||
"deflate": [
|
||||
("config", "z_erofs_load_deflate_config"),
|
||||
("decompress", "z_erofs_deflate_decompress"),
|
||||
("supports_subextent", "1"),
|
||||
("name", '"deflate"'),
|
||||
],
|
||||
"zstd": [
|
||||
("config", "z_erofs_load_zstd_config"),
|
||||
("decompress", "z_erofs_zstd_decompress"),
|
||||
("supports_subextent", "0"),
|
||||
("name", '"zstd"'),
|
||||
],
|
||||
}
|
||||
if descriptors != expected_descriptors:
|
||||
raise SystemExit(f"backend descriptor initializer mismatch: {descriptors!r}")
|
||||
|
||||
table_match = re.search(
|
||||
r"static const struct z_erofs_decompressor \* const z_erofs_decomp\[\] = \{\n"
|
||||
r"(?P<body>.*?)\n\};",
|
||||
current["decompressor.c"],
|
||||
re.DOTALL,
|
||||
)
|
||||
if table_match is None:
|
||||
raise SystemExit("missing FreeBSD decompressor table")
|
||||
table_order = re.findall(r"\[(Z_EROFS_COMPRESSION_[A-Z0-9_]+)\]", table_match.group("body"))
|
||||
expected_order = [
|
||||
"Z_EROFS_COMPRESSION_SHIFTED",
|
||||
"Z_EROFS_COMPRESSION_INTERLACED",
|
||||
"Z_EROFS_COMPRESSION_LZ4",
|
||||
"Z_EROFS_COMPRESSION_LZMA",
|
||||
"Z_EROFS_COMPRESSION_DEFLATE",
|
||||
"Z_EROFS_COMPRESSION_ZSTD",
|
||||
]
|
||||
if table_order != expected_order:
|
||||
raise SystemExit(f"FreeBSD decompressor table order mismatch: {table_order!r}")
|
||||
linux_decompressor = (root / "src-linux/decompressor.c").read_text(encoding="utf-8")
|
||||
linux_table = linux_decompressor[linux_decompressor.index("z_erofs_decomp[] = {") :]
|
||||
linux_order = re.findall(r"\[(Z_EROFS_COMPRESSION_[A-Z0-9_]+)\]", linux_table)[:6]
|
||||
if linux_order != expected_order:
|
||||
raise SystemExit(f"Linux decompressor table anchor drifted: {linux_order!r}")
|
||||
|
||||
callbacks = []
|
||||
for path in sorted(src.glob("decompressor*.c")):
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
match = re.match(r"\s*\.(config|decompress)\s*=\s*([^,]+),", line)
|
||||
if match:
|
||||
callbacks.append((path.name, match.group(1), match.group(2)))
|
||||
expected_callbacks = [
|
||||
("decompressor.c", "decompress", "z_erofs_transform_plain"),
|
||||
("decompressor.c", "decompress", "z_erofs_transform_plain"),
|
||||
("decompressor.c", "config", "z_erofs_load_lz4_config"),
|
||||
("decompressor.c", "decompress", "z_erofs_lz4_decompress"),
|
||||
("decompressor_deflate.c", "config", "z_erofs_load_deflate_config"),
|
||||
("decompressor_deflate.c", "decompress", "z_erofs_deflate_decompress"),
|
||||
("decompressor_lzma.c", "config", "z_erofs_load_lzma_config"),
|
||||
("decompressor_lzma.c", "decompress", "z_erofs_lzma_decompress"),
|
||||
("decompressor_zstd.c", "config", "z_erofs_load_zstd_config"),
|
||||
("decompressor_zstd.c", "decompress", "z_erofs_zstd_decompress"),
|
||||
]
|
||||
if Counter(callbacks) != Counter(expected_callbacks) or len(callbacks) != 10:
|
||||
raise SystemExit(f"G01 decompressor callback multiset changed: {callbacks!r}")
|
||||
|
||||
all_source = "\n".join(current.values())
|
||||
for removed in ("erofs_sb_lz4_info", "max_distance_pages", "sbi->lz4"):
|
||||
if removed in all_source:
|
||||
raise SystemExit(f"removed LZ4 runtime state remains: {removed}")
|
||||
config = current["decompressor.c"][: current["decompressor.c"].index("static int\nz_erofs_transform_plain")]
|
||||
for marker in (
|
||||
"uint32_t max_pclusterblks;",
|
||||
"max_pclusterblks = le16toh(lz4->max_pclusterblks);",
|
||||
"if (max_pclusterblks == 0)",
|
||||
"max_pclusterblks = 1;",
|
||||
"max_pclusterblks >\n\t\t (Z_EROFS_PCLUSTER_MAX_SIZE >> sbi->blkszbits)",
|
||||
"return (EOPNOTSUPP);",
|
||||
"distance = le16toh(dsb->u1.lz4_max_distance);",
|
||||
"if (distance == 0 && !erofs_sb_has_lz4_0padding(sbi))",
|
||||
):
|
||||
if marker not in config:
|
||||
raise SystemExit(f"LZ4 config validation marker missing: {marker!r}")
|
||||
|
||||
if current["zdata.c"].count("z_erofs_decode_extent") != 2:
|
||||
raise SystemExit("zdata decode helper rename denominator changed")
|
||||
if "z_erofs_read_extent" in current["zdata.c"]:
|
||||
raise SystemExit("old zdata decode helper name remains")
|
||||
if current["zmap.c"].count("z_erofs_read_extent") != 4:
|
||||
raise SystemExit("zmap record reader definition/calls changed")
|
||||
if "z_erofs_decode_extent" in current["zmap.c"]:
|
||||
raise SystemExit("zmap record reader was incorrectly renamed")
|
||||
if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", all_source):
|
||||
raise SystemExit("B30 introduced Linux negative errno")
|
||||
|
||||
for path, load_name, decode_name, descriptor_name in (
|
||||
("decompressor_lzma.c", "z_erofs_load_lzma_config", "z_erofs_lzma_decompress", "z_erofs_lzma_decomp"),
|
||||
("decompressor_deflate.c", "z_erofs_load_deflate_config", "z_erofs_deflate_decompress", "z_erofs_deflate_decomp"),
|
||||
("decompressor_zstd.c", "z_erofs_load_zstd_config", "z_erofs_zstd_decompress", "z_erofs_zstd_decomp"),
|
||||
):
|
||||
positions = [current[path].index(name) for name in (load_name, decode_name, descriptor_name)]
|
||||
if positions != sorted(positions):
|
||||
raise SystemExit(f"backend definition order drifted: {path}")
|
||||
|
||||
report = {
|
||||
"status": "PASS",
|
||||
"baseline": baseline,
|
||||
"changed_paths": sorted(changed),
|
||||
"descriptor_fields": ["config", "decompress", "supports_subextent", "name"],
|
||||
"descriptor_table_order": table_order,
|
||||
"callback_field_count": len(callbacks),
|
||||
"callback_target_multiset": sorted(callbacks),
|
||||
"removed_runtime_state": ["erofs_sb_lz4_info", "max_distance_pages", "max_pclusterblks store"],
|
||||
"retained_config_validation": ["max_pclusterblks local", "format cap", "legacy distance branch"],
|
||||
"rename": {"zdata_decode": 2, "zmap_record_reader": 4},
|
||||
"preserved": ["B25 typed errno", "B27 trailing policy", "B28 partial fallback ownership"],
|
||||
}
|
||||
with report_path.open("x", encoding="ascii") as stream:
|
||||
json.dump(report, stream, ensure_ascii=True, indent=2, sort_keys=True)
|
||||
stream.write("\n")
|
||||
print("B30-descriptors PASS fields=4 backends=6 callbacks=10")
|
||||
print("B30-write-only PASS dead-state=2 config-local=1 rename=2/4")
|
||||
PY
|
||||
then
|
||||
:
|
||||
else
|
||||
pre15_dut_fail 'B30 descriptor, dead-state, or rename oracle failed'
|
||||
fi
|
||||
|
||||
sha256sum "$artifacts/B30-codec-structure.json" > "$artifacts/SHA256SUMS"
|
||||
printf 'B30 PASS codec structure aligned without behavior changes\n'
|
||||
Reference in New Issue
Block a user