Files
erofs-freebsd-out-tree/tests/pre15/cases/B08-plain-run.sh
T
2026-08-18 09:20:44 +02:00

908 lines
28 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
map_fixture=$PRE15_DUT/tests/pre15/fixtures/B08-map-runs.json
io_fixture=$PRE15_DUT/tests/pre15/fixtures/B08-io-caps.json
b07c_case=$PRE15_DUT/tests/pre15/cases/B07c-map-consumers.sh
artifacts=$PRE15_RUN_DIR/artifacts
baseline=6673f51152a5195a8a8903aa801f820abce7936e
b07c=55c609db13fc9b0f21a3c7c9ec5cb9574fd18276
for tool in cc git python3 sha256sum; do
command -v "$tool" >/dev/null 2>&1 || \
pre15_infra_blocked "missing B08 host tool: $tool"
done
pre15_record_fixture b08-gate "$gate"
pre15_record_fixture b08-input "$input"
pre15_record_fixture b08-b07-oracle "$oracle"
pre15_record_fixture b08-map-runs "$map_fixture"
pre15_record_fixture b08-io-caps "$io_fixture"
pre15_record_fixture b08-b07c-case "$b07c_case"
pre15_record_fixture b08-data "$PRE15_DUT/src/data.c"
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 'B08 frozen P15-006 baseline replay failed'
fi
if python3 - "$b07c_case" "$artifacts/derive-B07c.py" <<'PY'
from pathlib import Path
import sys
source = Path(sys.argv[1]).read_text(encoding="utf-8")
marker = 'if python3 - "$gate" "$artifacts/P15-006-B07c.py" <<\'PY\'\n'
start = source.index(marker) + len(marker)
end = source.index("\nPY\nthen", start)
Path(sys.argv[2]).write_text(source[start:end] + "\n", encoding="ascii")
PY
then
:
else
pre15_dut_fail 'B08 could not recover the proven B07c replay adapter'
fi
if python3 "$artifacts/derive-B07c.py" "$gate" \
"$artifacts/P15-006-B08.py" >"$artifacts/derive.stdout" \
2>"$artifacts/derive.stderr"; then
:
else
pre15_dut_fail 'B08 could not derive the current map replay'
fi
candidate_rc=0
python3 "$artifacts/P15-006-B08.py" "$PRE15_ROOT" "$input" \
worktree '' "$artifacts/candidate" "$oracle" \
>"$artifacts/candidate.stdout" 2>"$artifacts/candidate.stderr" || \
candidate_rc=$?
if test "$candidate_rc" -ne 1; then
pre15_dut_fail 'B08 old-run oracle did not reject exactly the intended change'
fi
if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$oracle" "$map_fixture" \
"$io_fixture" "$artifacts/baseline" "$artifacts/candidate" \
"$artifacts/B08-result.json" "$b07c" "$artifacts" <<'PY'
from __future__ import annotations
from collections import Counter
import hashlib
import json
from pathlib import Path
import re
import struct
import subprocess
import sys
root = Path(sys.argv[1])
dut = Path(sys.argv[2])
oracle_path = Path(sys.argv[3])
map_fixture_path = Path(sys.argv[4])
io_fixture_path = Path(sys.argv[5])
baseline = Path(sys.argv[6])
candidate = Path(sys.argv[7])
output = Path(sys.argv[8])
b07c = sys.argv[9]
artifacts = Path(sys.argv[10])
def load(path: Path) -> dict:
return json.loads(path.read_text(encoding="ascii"))
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
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}")
name_line = source.rfind("\n", 0, match.start()) + 1
start = source.rfind("\n", 0, name_line - 1) + 1
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_data() -> str:
completed = subprocess.run(
["git", "-C", str(root), "show", f"{b07c}:repo-pre-15/src/data.c"],
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if completed.returncode != 0:
raise SystemExit(f"cannot read B07c data.c: {completed.stderr}")
return completed.stdout
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
def compile_and_run(name: str, program: str) -> list[str]:
source_path = artifacts / f"{name}.c"
binary_path = artifacts / name
source_path.write_text(program, encoding="ascii")
compiled = subprocess.run(
[
"cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror",
str(source_path), "-o", str(binary_path),
],
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(artifacts / f"{name}.compile.stdout").write_text(
compiled.stdout, encoding="utf-8"
)
(artifacts / f"{name}.compile.stderr").write_text(
compiled.stderr, encoding="utf-8"
)
if compiled.returncode != 0:
raise SystemExit(f"{name} compilation failed")
executed = subprocess.run(
[str(binary_path)],
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(artifacts / f"{name}.stdout").write_text(
executed.stdout, encoding="utf-8"
)
(artifacts / f"{name}.stderr").write_text(
executed.stderr, encoding="utf-8"
)
if executed.returncode != 0:
raise SystemExit(f"{name} execution failed")
return executed.stdout.splitlines()
oracle = load(oracle_path)
map_fixture = load(map_fixture_path)
io_fixture = load(io_fixture_path)
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")
if sha256_bytes(oracle_path.read_bytes()) != map_fixture["b07_oracle_sha256"]:
raise SystemExit("B08 map fixture is not anchored to the frozen B07 oracle")
if map_fixture["schema"] != 1 or map_fixture["candidate"] != "P15-074":
raise SystemExit("invalid B08 map fixture identity")
if io_fixture["schema"] != 1 or io_fixture["candidate"] != "P15-074":
raise SystemExit("invalid B08 I/O fixture identity")
if (
base_result["status"] != "GO"
or base_result["oracle_equal"] is not True
or base_result["map_case_count"] != 80
or base_result["device_case_count"] != 13
):
raise SystemExit("frozen B07 control replay did not GO")
if result["status"] != "STOP" or result["map_case_count"] != 80:
raise SystemExit("B08 candidate did not expose the intended old-run delta")
if result["device_case_count"] != 13 or result["model_sha256"] != oracle["model_sha256"]:
raise SystemExit("B08 candidate changed the independent corpus denominator")
if result["adapter_probe"]["status"] != "PASS":
raise SystemExit("B08 common map adapter probe failed")
if devices["status"] != "PASS" or devices["records"] != base_devices["records"]:
raise SystemExit("B08 changed one of the 13 frozen device records")
tuple_fields = (
"m_la", "m_pa", "m_llen", "m_plen", "m_deviceid", "m_flags",
"m_algorithmformat", "errno", "acquire_count", "release_count",
)
tuple_layout = map_fixture["b07_tuple_layout"]
if tuple_layout != oracle["tuple_byte_layout"]:
raise SystemExit("B08 tuple byte layout drifted")
oracle_records = {record["id"]: record for record in oracle["records"]}
actual_records = {record["id"]: record for record in tuples["records"]}
base_records = {record["id"]: record for record in base_tuples["records"]}
transforms = {record["id"]: record for record in map_fixture["b07_transforms"]}
if len(oracle_records) != 80 or set(actual_records) != set(oracle_records):
raise SystemExit("B08 map tuple ID set changed")
if set(base_records) != set(oracle_records):
raise SystemExit("B08 baseline tuple ID set changed")
expected_bytes = bytearray()
actual_bytes = bytearray()
changed_ids = []
for record in oracle["records"]:
case_id = record["id"]
if base_records[case_id]["tuple_hex"] != record["tuple_hex"]:
raise SystemExit(f"frozen B07 baseline drifted: {case_id}")
expected = dict(zip(
tuple_fields,
struct.unpack(tuple_layout, bytes.fromhex(record["tuple_hex"])),
strict=True,
))
if case_id in transforms:
expected["m_llen"] = transforms[case_id]["m_llen"]
expected["m_plen"] = transforms[case_id]["m_plen"]
expected_tuple = struct.pack(
tuple_layout, *(expected[field] for field in tuple_fields)
)
actual_tuple = bytes.fromhex(actual_records[case_id]["tuple_hex"])
if actual_tuple != expected_tuple:
raise SystemExit(f"B08 transformed tuple mismatch: {case_id}")
if actual_tuple != bytes.fromhex(record["tuple_hex"]):
changed_ids.append(case_id)
expected_bytes.extend(expected_tuple)
actual_bytes.extend(actual_tuple)
if set(changed_ids) != set(transforms):
raise SystemExit(f"B08 changed unexpected B07 tuples: {changed_ids!r}")
transformed_hash = sha256_bytes(bytes(expected_bytes))
if bytes(actual_bytes) != bytes(expected_bytes):
raise SystemExit("B08 full transformed tuple stream mismatch")
if transformed_hash != map_fixture["expected_transformed_tuple_sha256"]:
raise SystemExit("B08 transformed tuple fixture digest mismatch")
allowed_failures = []
for failure in result["failures"]:
if failure.get("field") == "frozen-byte-oracle" and "id" not in failure:
continue
if failure.get("id") in transforms and failure.get("field") in {
"m_llen", "m_plen"
}:
continue
allowed_failures.append(failure)
if allowed_failures:
raise SystemExit(f"B08 old oracle reported unrelated failures: {allowed_failures!r}")
data_source = (dut / "src/data.c").read_text(encoding="utf-8")
old_data = committed_data()
expected_preamble = old_data[:old_data.index("static erofs_off_t\n")].replace(
"#include <sys/systm.h>\n",
"#include <sys/systm.h>\n#include <sys/_maxphys.h>\n",
)
if data_source[:data_source.index("static erofs_off_t\n")] != expected_preamble:
raise SystemExit("B08 data.c preamble changed beyond the MAXPHYS header")
old_functions = function_names(old_data)
data_functions = function_names(data_source)
if data_functions != old_functions:
raise SystemExit("B08 added or removed a data.c function")
changed_functions = {
"erofs_map_blocks_flatmode", "erofs_read_data", "erofs_read_uio"
}
for function in sorted(data_functions - changed_functions):
if extract_function(data_source, function) != extract_function(
old_data, function
):
raise SystemExit(f"B08 changed out-of-scope data.c function: {function}")
for function in changed_functions:
if extract_function(data_source, function) == extract_function(
old_data, function
):
raise SystemExit(f"B08 did not change required function: {function}")
flatmode = extract_function(data_source, "erofs_map_blocks_flatmode")
read_data = extract_function(data_source, "erofs_read_data")
read_uio = extract_function(data_source, "erofs_read_uio")
if flatmode.count("map->m_llen = remain;") != 1:
raise SystemExit("plain mapping does not expose the EOF run")
if flatmode.count("map->m_llen = MIN(remain, tail_start - loff);") != 1:
raise SystemExit("pre-inline mapping does not stop at the inline tail")
if "MAXPHYS" in flatmode:
raise SystemExit("B08 incorrectly capped the map contract")
if read_data.count("(uint64_t)MAXPHYS") != 1:
raise SystemExit("erofs_read_data lacks one MAXPHYS cap")
if read_uio.count("(uint64_t)MAXPHYS") != 1:
raise SystemExit("erofs_read_uio lacks one MAXPHYS cap")
require_order(
read_data,
[
"if (erofs_inode_is_data_compressed(vi->datalayout))",
"return (z_erofs_read_data(sbi, vi, loff, len, bufp));",
"error = erofs_map_blocks(sbi, vi, &map);",
"MIN(map.m_llen, (uint64_t)MAXPHYS)",
"erofs_read_physical(sbi, map.m_deviceid, map.m_pa",
"erofs_brelse(blk);",
],
"erofs_read_data",
)
require_order(
read_uio,
[
"if (erofs_inode_is_data_compressed(vi->datalayout))",
"return (z_erofs_read_uio(sbi, vi, uio));",
"error = erofs_map_blocks(sbi, vi, &map);",
"MIN(map.m_llen, (uint64_t)MAXPHYS)",
"erofs_read_physical(sbi, map.m_deviceid, map.m_pa",
"error = uiomove(blk, want, uio);",
"erofs_brelse(blk);",
],
"erofs_read_uio",
)
map_common = r'''
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#define EOPNOTSUPP 45
#define EOVERFLOW 84
#define EINTEGRITY 97
#define EROFS_NULL_ADDR UINT32_MAX
#define EROFS_INODE_FLAT_PLAIN 0
#define EROFS_INODE_COMPRESSED_FULL 1
#define EROFS_INODE_FLAT_INLINE 2
#define EROFS_INODE_COMPRESSED_COMPACT 3
#define EROFS_INODE_CHUNK_BASED 4
#define EROFS_MAP_MAPPED 0x0001
#define EROFS_MAP_META 0x0002
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define roundup2(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
typedef uint64_t erofs_off_t;
typedef uint64_t erofs_blk_t;
struct erofs_sb_info {
uint64_t block_size;
unsigned int blkszbits;
};
struct erofs_inode {
uint64_t size;
erofs_off_t inode_off;
erofs_blk_t startblk;
uint8_t datalayout;
uint8_t inode_isize;
uint32_t xattr_isize;
};
struct erofs_map_blocks {
erofs_off_t m_pa, m_la;
uint64_t m_plen, m_llen;
unsigned short m_deviceid;
char m_algorithmformat;
unsigned int m_flags;
};
static bool
erofs_inode_is_data_compressed(unsigned int layout)
{
return (layout == EROFS_INODE_COMPRESSED_FULL ||
layout == EROFS_INODE_COMPRESSED_COMPACT);
}
static int
erofs_map_blocks_chunk(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map)
{
(void)sbi;
(void)vi;
(void)map;
return (EOPNOTSUPP);
}
static int
z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map)
{
(void)sbi;
(void)vi;
(void)map;
return (EOPNOTSUPP);
}
'''
map_program = map_common
map_program += extract_function(data_source, "erofs_inline_tail_start") + "\n"
map_program += flatmode + "\n"
map_program += extract_function(data_source, "erofs_map_blocks") + "\n"
map_program += "\nint\nmain(void)\n{\n\tint error;\n"
for case in map_fixture["map_cases"]:
inode = case["inode"]
map_program += f'''
{{
struct erofs_sb_info sbi = {{ .block_size = 4096, .blkszbits = 12 }};
struct erofs_inode vi = {{
.size = UINT64_C({inode['size']}),
.inode_off = UINT64_C({inode.get('inode_off', 0)}),
.startblk = UINT64_C({inode['startblk']}),
.datalayout = {inode['layout']},
.inode_isize = {inode.get('inode_isize', 0)},
.xattr_isize = {inode.get('xattr_isize', 0)},
}};
struct erofs_map_blocks map = {{ .m_la = UINT64_C({case['request']}) }};
error = erofs_map_blocks(&sbi, &vi, &map);
printf("map\\t{case['id']}\\t%llu\\t%llu\\t%llu\\t%llu\\t%u\\t%u\\t%d\\t%d\\n",
(unsigned long long)map.m_la, (unsigned long long)map.m_pa,
(unsigned long long)map.m_llen, (unsigned long long)map.m_plen,
map.m_deviceid, map.m_flags, (int)map.m_algorithmformat, error);
}}
'''
map_program += "\treturn (0);\n}\n"
map_lines = compile_and_run("B08-map-extractor", map_program)
map_actual = {}
for line in map_lines:
fields = line.split("\t")
if len(fields) != 10 or fields[0] != "map":
raise SystemExit(f"invalid B08 map extractor line: {line!r}")
map_actual[fields[1]] = dict(zip(
(
"m_la", "m_pa", "m_llen", "m_plen", "m_deviceid",
"m_flags", "m_algorithmformat", "errno",
),
[int(value) for value in fields[2:]],
strict=True,
))
if set(map_actual) != {case["id"] for case in map_fixture["map_cases"]}:
raise SystemExit("B08 custom map case set changed")
positive_runs = 0
for case in map_fixture["map_cases"]:
actual = map_actual[case["id"]]
expected = {**case["expected"], "m_algorithmformat": 0}
if actual != expected:
raise SystemExit(
f"B08 custom map mismatch {case['id']}: {actual!r} != {expected!r}"
)
if (
actual["errno"] == 0
and case["request"] < case["inode"]["size"]
):
if actual["m_llen"] == 0:
raise SystemExit(f"B08 non-EOF zero run: {case['id']}")
positive_runs += 1
maxphys = io_fixture["harness_maxphys"]
consumer_common = f'''
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define EIO 5
#define ENOMEM 12
#define EINVAL 22
#define EOPNOTSUPP 45
#define EOVERFLOW 84
#define EINTEGRITY 97
#define PAGE_SIZE 4096
#define MAXPHYS {maxphys}
#define M_EROFS 0
#define M_WAITOK 0
#define EROFS_INODE_FLAT_PLAIN 0
#define EROFS_MAP_MAPPED 0x0001
#define EROFS_MAP_META 0x0002
#define EROFS_BUF_INITIALIZER {{ .data = NULL, .release = NULL }}
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define bzero(ptr, len) memset((ptr), 0, (len))
typedef uint64_t erofs_off_t;
typedef uint64_t erofs_nid_t;
struct erofs_sb_info {{ int unused; }};
struct erofs_inode {{
erofs_nid_t nid;
uint64_t size;
uint8_t datalayout;
}};
struct erofs_map_blocks {{
erofs_off_t m_pa, m_la;
uint64_t m_plen, m_llen;
unsigned short m_deviceid;
char m_algorithmformat;
unsigned int m_flags;
}};
struct erofs_buf {{
void *data;
void (*release)(void *);
}};
struct uio {{
int64_t uio_offset;
size_t uio_resid;
unsigned char *buffer;
size_t moved;
}};
static size_t gate_allocations;
static void *
gate_malloc(size_t size)
{{
void *buffer = malloc(size);
if (buffer != NULL)
++gate_allocations;
return (buffer);
}}
static void
gate_free(void *buffer)
{{
if (buffer != NULL) {{
--gate_allocations;
free(buffer);
}}
}}
#define malloc(size, type, flags) gate_malloc(size)
#define free(buffer, type) gate_free(buffer)
#define GATE_MAX_CALLS 32
#define GATE_PHYSICAL_BASE UINT64_C(1048576)
static bool gate_hole;
static unsigned int gate_map_calls;
static unsigned int gate_physical_calls;
static erofs_off_t gate_physical_offsets[GATE_MAX_CALLS];
static size_t gate_physical_lengths[GATE_MAX_CALLS];
static void
gate_reset(bool hole)
{{
gate_hole = hole;
gate_map_calls = 0;
gate_physical_calls = 0;
memset(gate_physical_offsets, 0, sizeof(gate_physical_offsets));
memset(gate_physical_lengths, 0, sizeof(gate_physical_lengths));
}}
static bool
erofs_inode_is_data_compressed(unsigned int layout)
{{
(void)layout;
return (false);
}}
static int
erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map)
{{
struct erofs_map_blocks next = {{ .m_la = map->m_la }};
(void)sbi;
++gate_map_calls;
if (next.m_la < vi->size) {{
next.m_pa = GATE_PHYSICAL_BASE + next.m_la;
next.m_llen = vi->size - next.m_la;
next.m_plen = next.m_llen;
if (!gate_hole)
next.m_flags = EROFS_MAP_MAPPED;
}}
*map = next;
return (0);
}}
static int
erofs_read_physical(struct erofs_sb_info *sbi, unsigned int device_id,
erofs_off_t off, size_t len, void **bufp)
{{
unsigned char *buffer;
size_t index;
(void)sbi;
(void)device_id;
if (len > MAXPHYS || gate_physical_calls == GATE_MAX_CALLS)
return (EINVAL);
gate_physical_offsets[gate_physical_calls] = off;
gate_physical_lengths[gate_physical_calls] = len;
++gate_physical_calls;
buffer = gate_malloc(len);
if (buffer == NULL)
return (ENOMEM);
for (index = 0; index < len; ++index)
buffer[index] = (unsigned char)(off - GATE_PHYSICAL_BASE + index);
*bufp = buffer;
return (0);
}}
static int
erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid,
erofs_off_t off, size_t len, struct erofs_buf *buf)
{{
(void)sbi;
(void)nid;
(void)off;
(void)len;
(void)buf;
return (EIO);
}}
static void
erofs_put_metabuf(struct erofs_buf *buf)
{{
(void)buf;
}}
static void
erofs_brelse(void *buffer)
{{
gate_free(buffer);
}}
static int
uiomove(const void *source, size_t len, struct uio *uio)
{{
if (len > uio->uio_resid)
return (EINVAL);
memcpy(uio->buffer + uio->moved, source, len);
uio->moved += len;
uio->uio_resid -= len;
uio->uio_offset += (int64_t)len;
return (0);
}}
static int
z_erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi,
erofs_off_t loff, size_t len, void **bufp)
{{
(void)sbi;
(void)vi;
(void)loff;
(void)len;
(void)bufp;
return (EOPNOTSUPP);
}}
static int
z_erofs_read_uio(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct uio *uio)
{{
(void)sbi;
(void)vi;
(void)uio;
return (EOPNOTSUPP);
}}
static int
gate_check_data(const unsigned char *buffer, size_t length,
uint64_t offset, bool hole)
{{
size_t index;
for (index = 0; index < length; ++index) {{
unsigned char expected = hole ? 0 : (unsigned char)(offset + index);
if (buffer[index] != expected)
return (1);
}}
return (0);
}}
static int
gate_fail(const char *id, const char *reason)
{{
fprintf(stderr, "%s: %s\\n", id, reason);
return (1);
}}
'''
consumer_program = consumer_common
consumer_program += read_data + "\n"
consumer_program += read_uio + "\n"
consumer_program += "\nint\nmain(void)\n{\n"
for scenario in io_fixture["scenarios"]:
scenario_id = scenario["id"]
hole = scenario["mapping"] == "hole"
expected_offsets = scenario["expected_physical_offsets"]
expected_lengths = scenario["expected_physical_lengths"]
checks = []
for index, (offset, length) in enumerate(zip(
expected_offsets, expected_lengths, strict=True
)):
checks.append(
f"\t\tif (gate_physical_offsets[{index}] != UINT64_C({offset}) || "
f"gate_physical_lengths[{index}] != {length})\n"
f"\t\t\treturn (gate_fail(\"{scenario_id}\", \"physical call tuple\"));\n"
)
call_checks = "".join(checks)
if scenario["api"] == "read_data":
success_check = ""
if scenario["expected_errno"] == 0:
success_check = f'''
if (buffer == NULL || gate_check_data(buffer, {scenario['length']},
UINT64_C({scenario['offset']}), {'true' if hole else 'false'}) != 0)
return (gate_fail("{scenario_id}", "returned data"));
'''
else:
success_check = f'''
if (buffer != NULL)
return (gate_fail("{scenario_id}", "error buffer ownership"));
'''
consumer_program += f'''
{{
struct erofs_sb_info sbi = {{ 0 }};
struct erofs_inode vi = {{
.nid = 1, .size = UINT64_C({scenario['inode_size']}),
.datalayout = EROFS_INODE_FLAT_PLAIN,
}};
void *buffer = NULL;
int error;
gate_reset({'true' if hole else 'false'});
error = erofs_read_data(&sbi, &vi, UINT64_C({scenario['offset']}),
{scenario['length']}, &buffer);
if (error != {scenario['expected_errno']})
return (gate_fail("{scenario_id}", "errno"));
if (gate_map_calls != {scenario['expected_map_calls']} ||
gate_physical_calls != {len(expected_lengths)})
return (gate_fail("{scenario_id}", "call count"));
{call_checks}{success_check} gate_free(buffer);
if (gate_allocations != 0)
return (gate_fail("{scenario_id}", "allocation balance"));
printf("io\\t{scenario_id}\\tPASS\\t%u\\t%u\\n",
gate_map_calls, gate_physical_calls);
}}
'''
elif scenario["api"] == "read_uio":
consumer_program += f'''
{{
struct erofs_sb_info sbi = {{ 0 }};
struct erofs_inode vi = {{
.nid = 1, .size = UINT64_C({scenario['inode_size']}),
.datalayout = EROFS_INODE_FLAT_PLAIN,
}};
unsigned char *buffer = gate_malloc({scenario['length']});
struct uio uio = {{
.uio_offset = {scenario['offset']}, .uio_resid = {scenario['length']},
.buffer = buffer, .moved = 0,
}};
int error;
if (buffer == NULL)
return (gate_fail("{scenario_id}", "test allocation"));
memset(buffer, 0xa5, {scenario['length']});
gate_reset({'true' if hole else 'false'});
error = erofs_read_uio(&sbi, &vi, &uio);
if (error != {scenario['expected_errno']} ||
uio.uio_offset != {scenario['expected_final_offset']} ||
uio.uio_resid != {scenario['expected_resid']})
return (gate_fail("{scenario_id}", "uio result"));
if (gate_map_calls != {scenario['expected_map_calls']} ||
gate_physical_calls != {len(expected_lengths)})
return (gate_fail("{scenario_id}", "call count"));
{call_checks} if (uio.moved != {scenario['length'] - scenario['expected_resid']} ||
gate_check_data(buffer, uio.moved, UINT64_C({scenario['offset']}),
{'true' if hole else 'false'}) != 0)
return (gate_fail("{scenario_id}", "moved data"));
gate_free(buffer);
if (gate_allocations != 0)
return (gate_fail("{scenario_id}", "allocation balance"));
printf("io\\t{scenario_id}\\tPASS\\t%u\\t%u\\n",
gate_map_calls, gate_physical_calls);
}}
'''
else:
raise SystemExit(f"unknown B08 I/O API: {scenario['api']}")
consumer_program += "\treturn (0);\n}\n"
io_lines = compile_and_run("B08-io-extractor", consumer_program)
expected_io_ids = [scenario["id"] for scenario in io_fixture["scenarios"]]
actual_io_ids = []
for line in io_lines:
fields = line.split("\t")
if len(fields) != 5 or fields[0] != "io" or fields[2] != "PASS":
raise SystemExit(f"invalid B08 I/O extractor line: {line!r}")
actual_io_ids.append(fields[1])
if actual_io_ids != expected_io_ids:
raise SystemExit("B08 I/O scenario order or denominator changed")
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"B08 metadata cleanup became unbalanced: {cleanup!r}")
if any(record["actual"]["errno"] < 0 for record in tuples["records"]):
raise SystemExit("B08 observed a negative errno")
summary = {
"status": "PASS",
"final_id": "P15-074",
"b07_map_cases": 80,
"b07_unchanged_tuples": 80 - len(changed_ids),
"b07_intended_run_tuples": len(changed_ids),
"b07_unexpected_tuple_differences": 0,
"b07_device_cases": 13,
"b07_device_differences": 0,
"b07_model_sha256": result["model_sha256"],
"transformed_tuple_bytes": len(actual_bytes),
"transformed_tuple_sha256": sha256_bytes(bytes(actual_bytes)),
"custom_map_cases": len(map_fixture["map_cases"]),
"custom_positive_runs": positive_runs,
"io_scenarios": len(io_fixture["scenarios"]),
"harness_maxphys": maxphys,
"max_observed_physical_io": max(
length
for scenario in io_fixture["scenarios"]
for length in scenario["expected_physical_lengths"]
),
"cleanup_distribution": {
f"{acquire}:{release}": count
for (acquire, release), count in sorted(cleanup.items())
},
"compressed_scope": "UNCHANGED",
"vnode_backed_scope": "NOT_ADDED",
"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(
"B08 PASS map=80 unchanged=73 intended=7 device=13 "
f"custom={len(map_fixture['map_cases'])} io={len(io_fixture['scenarios'])}"
)
PY
then
:
else
pre15_dut_fail 'B08 plain-run/MAXPHYS 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/B08-result.json" \
"$artifacts/B08-map-extractor" "$artifacts/B08-io-extractor" \
>"$artifacts/SHA256SUMS"
printf 'B08 PASS contiguous plain/pre-inline runs with MAXPHYS-capped I/O\n'