272 lines
11 KiB
Python
272 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Behavioral tests for bounded preflight and BSP-to-PSP handoff recording."""
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import stat
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import bsp_preflight
|
|
import project_guard
|
|
import record_build_handoff
|
|
|
|
|
|
def make_project(root: Path, name: str) -> Path:
|
|
project = root / name
|
|
config = project / "configs" / "cv1841cp_wevb_0015a_spinand"
|
|
config.mkdir(parents=True)
|
|
(project / "buildEnvConfig.sh").write_text(
|
|
"EP_CHIP=CV1841CP\nEP_SDK_VER=cv184x_v6.3.3\n"
|
|
"EP_ARCH=MUSL\nEP_FLASH_TYPE=nand\nNAND_FLASH_ID=0xa1d4\n",
|
|
encoding="utf-8",
|
|
)
|
|
(project / "configs" / "EPSYNCSDKConfig.sh").write_text("#!/bin/bash\n", encoding="utf-8")
|
|
(config / "cv1841cp_wevb_0015a_spinand_defconfig").write_text(
|
|
"CONFIG_DUAL_OS=y\n", encoding="utf-8"
|
|
)
|
|
partition = config / "partition" / "partition_spinand.xml"
|
|
partition.parent.mkdir()
|
|
partition.write_text(
|
|
"""<physical_partition type="spinand">
|
|
<partition label="BOOT" alias="kernel.A" size_in_kb="1024" file="kernel.bin"/>
|
|
<partition label="BOOT2" alias="kernel.B" size_in_kb="1536" file="kernel.bin"/>
|
|
<partition label="ROOTFS" alias="rootfs.A" size_in_kb="1024" file="rootfs.bin"/>
|
|
</physical_partition>
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
(project / "imgs").symlink_to(project_guard.expected_imgs_target(name))
|
|
return project
|
|
|
|
|
|
def main() -> int:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
base = Path(tmp)
|
|
root = base / "EmxRes"
|
|
root.mkdir()
|
|
tools = root / "tools"
|
|
tools.mkdir()
|
|
(tools / "script_completion.sh").write_text("# test\n", encoding="utf-8")
|
|
(tools / "BuildCvitek184XSDKTool.sh").write_text(
|
|
"#!/bin/sh\n./cvitek_cv184x_patch/scripts/repos --check-env || exit 1\n",
|
|
encoding="utf-8",
|
|
)
|
|
reference = make_project(root, "CV1841CP-REF_dualos")
|
|
|
|
project_report = bsp_preflight.project_preflight(
|
|
reference.name, "CV1841CP-NEW_dualos", root
|
|
)
|
|
assert project_report["passed"] is True, project_report
|
|
assert project_report["facts"]["board_build_type"] == "cv1841cp_wevb_0015a_spinand"
|
|
assert project_report["facts"]["system_form"] == "dual-os"
|
|
|
|
output_root = base / "epbuild"
|
|
build_report = bsp_preflight.build_preflight(
|
|
reference.name, "20260902_1", root, output_root
|
|
)
|
|
assert build_report["passed"] is True, build_report
|
|
assert build_report["working_directory"] == str(root.resolve())
|
|
assert build_report["command"].startswith(f"cd {root.resolve()} &&")
|
|
|
|
output = output_root / reference.name / "imgs" / "20260902_1"
|
|
output.mkdir(parents=True)
|
|
boot = output / "boot.bin"
|
|
boot.write_bytes(b"verified BSP artifact")
|
|
kernel = output / "kernel.bin"
|
|
kernel.write_bytes(b"k" * (512 * 1024))
|
|
rootfs = output / "rootfs.bin"
|
|
rootfs.write_bytes(b"r" * (256 * 1024))
|
|
helper_dir = output / "tools"
|
|
helper_dir.mkdir()
|
|
helper = helper_dir / "inspect.sh"
|
|
helper.write_text("#!/bin/sh\n", encoding="utf-8")
|
|
os.chmod(output_root, 0o700)
|
|
os.chmod(output_root / reference.name, 0o700)
|
|
os.chmod(output_root / reference.name / "imgs", 0o700)
|
|
os.chmod(output, 0o700)
|
|
os.chmod(helper_dir, 0o700)
|
|
os.chmod(boot, 0o600)
|
|
os.chmod(kernel, 0o640)
|
|
os.chmod(rootfs, 0o600)
|
|
os.chmod(helper, 0o700)
|
|
conflict = bsp_preflight.build_preflight(
|
|
reference.name, "20260902_1", root, output_root
|
|
)
|
|
assert conflict["passed"] is False
|
|
|
|
clone = reference / "doc" / "bsp_adaptation" / "validation" / "clone_manifest.json"
|
|
clone.parent.mkdir(parents=True)
|
|
clone.write_text(
|
|
json.dumps({"reference_project": "CV1841CP-BASE_dualos"}), encoding="utf-8"
|
|
)
|
|
recorded = record_build_handoff.record_handoff(
|
|
reference.name,
|
|
"20260902_1",
|
|
0,
|
|
True,
|
|
root,
|
|
output_root,
|
|
None,
|
|
["NAND part number pending"],
|
|
)
|
|
assert recorded["passed"] is True, recorded
|
|
assert recorded["imgs_version_path"] == str(output.resolve())
|
|
assert recorded["image_partition_margin"]["passed"] is True
|
|
assert recorded["output_permissions"]["passed"] is True
|
|
assert recorded["output_permissions"]["policy"] == "a+rX"
|
|
assert recorded["output_permissions"]["changed_count"] >= 1
|
|
for directory in (
|
|
output_root,
|
|
output_root / reference.name,
|
|
output_root / reference.name / "imgs",
|
|
output,
|
|
helper_dir,
|
|
):
|
|
assert stat.S_IMODE(directory.stat().st_mode) & 0o555 == 0o555
|
|
for artifact in (boot, kernel, rootfs, helper):
|
|
assert stat.S_IMODE(artifact.stat().st_mode) & 0o444 == 0o444
|
|
assert stat.S_IMODE(boot.stat().st_mode) & 0o111 == 0
|
|
assert stat.S_IMODE(helper.stat().st_mode) & 0o111 == 0o111
|
|
handoff_path = Path(recorded["psp_handoff_path"])
|
|
handoff = json.loads(handoff_path.read_text(encoding="utf-8"))
|
|
assert handoff["handoff_type"] == "bsp-to-psp"
|
|
assert handoff["producer"]["git"]["tracked"] is False
|
|
assert handoff["build"]["imgs_version_path"] == str(output.resolve())
|
|
assert handoff["build"]["status"] == "build_passed"
|
|
margin = handoff["validation"]["image_partition_margin"]
|
|
assert margin["passed"] is True
|
|
assert margin["required_margin_bytes"] == 512 * 1024
|
|
assert margin["partition_table_sha256"]
|
|
kernel_check = next(item for item in margin["checks"] if item["image"] == "kernel.bin")
|
|
assert kernel_check["actual_margin_bytes"] == 512 * 1024
|
|
assert kernel_check["effective_partition_size_bytes"] == 1024 * 1024
|
|
assert Path(recorded["image_partition_margin_path"]).is_file()
|
|
assert Path(recorded["output_permissions_path"]).is_file()
|
|
assert handoff["validation"]["output_permissions"]["passed"] is True
|
|
assert handoff["build"]["output_permissions_path"] == recorded["output_permissions_path"]
|
|
assert handoff["consumer_contract"]["select_latest_directory"] is False
|
|
assert handoff["artifacts"][0]["sha256"] == hashlib.sha256(boot.read_bytes()).hexdigest()
|
|
assert handoff["validation"]["hardware"] == "pending"
|
|
|
|
linked_output_root = Path(tmp) / "linked-output"
|
|
linked_output_root.symlink_to(output_root, target_is_directory=True)
|
|
try:
|
|
record_build_handoff.record_handoff(
|
|
reference.name, "20260902_1", 0, True, root, linked_output_root, None, []
|
|
)
|
|
except project_guard.GuardError as exc:
|
|
assert "输出根目录是软链接" in str(exc)
|
|
else:
|
|
raise AssertionError("linked output root must not be granted permissions")
|
|
|
|
overflow_output = output_root / reference.name / "imgs" / "20260902_2"
|
|
overflow_output.mkdir(parents=True)
|
|
(overflow_output / "boot.bin").write_bytes(b"boot")
|
|
(overflow_output / "kernel.bin").write_bytes(b"k" * (512 * 1024))
|
|
(overflow_output / "rootfs.bin").write_bytes(b"r" * (512 * 1024 + 1))
|
|
try:
|
|
record_build_handoff.record_handoff(
|
|
reference.name,
|
|
"20260902_2",
|
|
0,
|
|
True,
|
|
root,
|
|
output_root,
|
|
None,
|
|
[],
|
|
)
|
|
except project_guard.GuardError as exc:
|
|
assert "rootfs.bin 分区余量不足" in str(exc)
|
|
else:
|
|
raise AssertionError("image with less than 512 KiB margin must fail")
|
|
assert not (
|
|
reference
|
|
/ "doc"
|
|
/ "bsp_adaptation"
|
|
/ "validation"
|
|
/ "image_partition_margin_20260902_2.json"
|
|
).exists()
|
|
|
|
denied_output = output_root / reference.name / "imgs" / "20260902_3"
|
|
denied_output.mkdir(parents=True)
|
|
denied_boot = denied_output / "boot.bin"
|
|
denied_boot.write_bytes(b"boot")
|
|
(denied_output / "kernel.bin").write_bytes(b"k" * (512 * 1024))
|
|
(denied_output / "rootfs.bin").write_bytes(b"r" * (256 * 1024))
|
|
os.chmod(denied_boot, 0o600)
|
|
with patch.object(
|
|
record_build_handoff.os,
|
|
"chmod",
|
|
side_effect=PermissionError(13, "permission denied"),
|
|
):
|
|
try:
|
|
record_build_handoff.record_handoff(
|
|
reference.name,
|
|
"20260902_3",
|
|
0,
|
|
True,
|
|
root,
|
|
output_root,
|
|
None,
|
|
[],
|
|
)
|
|
except project_guard.GuardError as exc:
|
|
assert "无法设置构建输出权限" in str(exc)
|
|
else:
|
|
raise AssertionError("permission failure must block BSP handoff")
|
|
assert not (
|
|
reference
|
|
/ "doc"
|
|
/ "bsp_adaptation"
|
|
/ "validation"
|
|
/ "output_permissions_20260902_3.json"
|
|
).exists()
|
|
|
|
original_partition = (
|
|
reference
|
|
/ "configs"
|
|
/ "cv1841cp_wevb_0015a_spinand"
|
|
/ "partition"
|
|
/ "partition_spinand.xml"
|
|
)
|
|
duplicate_partition = original_partition.with_name("partition_backup.xml")
|
|
duplicate_partition.write_text(original_partition.read_text(encoding="utf-8"), encoding="utf-8")
|
|
try:
|
|
record_build_handoff.resolve_partition_table(reference, None)
|
|
except project_guard.GuardError as exc:
|
|
assert "无法唯一定位" in str(exc)
|
|
else:
|
|
raise AssertionError("ambiguous partition tables must fail")
|
|
assert (
|
|
record_build_handoff.resolve_partition_table(
|
|
reference,
|
|
original_partition.relative_to(reference),
|
|
)
|
|
== original_partition.resolve()
|
|
)
|
|
|
|
try:
|
|
record_build_handoff.record_handoff(
|
|
reference.name,
|
|
"20260902_1",
|
|
1,
|
|
True,
|
|
root,
|
|
output_root,
|
|
None,
|
|
[],
|
|
)
|
|
except project_guard.GuardError as exc:
|
|
assert "退出码" in str(exc)
|
|
else:
|
|
raise AssertionError("failed build must not produce a PSP handoff")
|
|
print("BSP preflight and handoff behavioral tests passed")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|