#!/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( """ """, 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") 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())