Files

304 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""Behavioral tests for project_guard.py using isolated temporary projects."""
import json
import os
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import project_guard # noqa: E402
def make_reference(root: Path, name: str = "CV1841CP-REF_dualos") -> Path:
reference = root / name
uboot = reference / "configs" / "uboot"
uboot.mkdir(parents=True)
(reference / "buildEnvConfig.sh").write_text(
"EP_CHIP=CV1841CP\nEP_SDK_VER=v6.3.2\nEP_FLASH_TYPE=nand\n",
encoding="utf-8",
)
(uboot / "cvi_board_init.c").write_text("int cvi_board_init(void) { return 0; }\n", encoding="utf-8")
generated = reference / "build"
generated.mkdir()
(generated / "artifact.bin").write_bytes(b"generated")
(generated / "external-cache-link").symlink_to("/outside/ignored-build-cache")
(reference / "debug.log").write_text("ignored\n", encoding="utf-8")
(reference / "imgs").symlink_to(project_guard.expected_imgs_target(name))
return reference
def test_prepare_verify_finalize(root: Path) -> None:
reference_name = "CV1841CP-REF_dualos"
target_name = "CV1841CP-NEW_dualos"
reference = make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
assert staging.is_dir()
assert not (staging / "build").exists()
assert not (staging / "debug.log").exists()
assert (staging / "imgs").is_symlink()
assert os.readlink(staging / "imgs") == project_guard.expected_imgs_target(target_name)
manifest_path = staging / "doc" / "bsp_adaptation" / "validation" / "clone_manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
assert manifest["reference_project"] == reference_name
assert manifest["imgs_retargeted"] is True
relative_config = "configs/uboot/cvi_board_init.c"
(staging / relative_config).write_text("int cvi_board_init(void) { return 1; }\n", encoding="utf-8")
report = staging / "doc" / "bsp_adaptation" / "adaptation_report.md"
report.write_text("# BSP adaptation\n\nStatic validation passed.\n", encoding="utf-8")
verified = project_guard.verify_project(
reference_name,
staging.name,
target_name,
[relative_config],
root=root,
)
assert verified["passed"] is True, verified
assert relative_config in verified["changes"]["modified"]
assert "imgs" in verified["changes"]["modified"]
finalized = project_guard.finalize_project(
reference_name,
staging.name,
target_name,
[relative_config],
root=root,
)
target = root / target_name
assert finalized["target_path"] == str(target)
assert target.is_dir() and not staging.exists()
assert reference.is_dir()
final_manifest = json.loads(
(target / "doc" / "bsp_adaptation" / "validation" / "clone_manifest.json").read_text(
encoding="utf-8"
)
)
assert final_manifest["final_path"] == str(target)
handoff = target / "doc" / "bsp_adaptation" / "handoff" / "psp_handoff.json"
handoff.parent.mkdir(parents=True)
handoff.write_text('{"schema_version": 1}\n', encoding="utf-8")
audited = project_guard.audit_final_project(target_name, root=root)
assert audited["passed"] is True, audited
assert audited["reference_drifted_since_prepare"] is False
(target / "buildEnvConfig.sh").write_text("EP_CHIP=CV1842HP\n", encoding="utf-8")
violated = project_guard.audit_final_project(target_name, root=root)
assert violated["passed"] is False
assert violated["violations"] == ["buildEnvConfig.sh"]
def test_unapproved_delta_is_rejected(root: Path) -> None:
reference_name = "CV1841CP-REF2_dualos"
target_name = "CV1841CP-NEW2_dualos"
make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
(staging / "buildEnvConfig.sh").write_text("EP_CHIP=CV1842HP\n", encoding="utf-8")
(staging / "doc" / "bsp_adaptation" / "adaptation_report.md").write_text(
"not final\n", encoding="utf-8"
)
verified = project_guard.verify_project(
reference_name,
staging.name,
target_name,
["configs/uboot/cvi_board_init.c"],
root=root,
)
assert verified["passed"] is False
assert verified["violations"] == ["buildEnvConfig.sh"]
try:
project_guard.finalize_project(
reference_name,
staging.name,
target_name,
["configs/uboot/cvi_board_init.c"],
root=root,
)
except project_guard.GuardError as exc:
assert "未通过" in str(exc)
else:
raise AssertionError("finalize should reject a failed delta report")
def test_external_link_and_credentials_are_rejected(root: Path) -> None:
external = make_reference(root, "CV1815JA-EXTERNAL")
(external / "imgs").unlink()
(external / "other-link").symlink_to("/outside/emxres/shared")
try:
project_guard.prepare_project(external.name, "CV1815JA-NEW", root=root)
except project_guard.GuardError as exc:
assert "外部绝对软链接" in str(exc)
else:
raise AssertionError("unexpected external symlink should be rejected")
credential = make_reference(root, "CV1815JA-CREDENTIAL")
(credential / ".env").write_text("TOKEN=secret\n", encoding="utf-8")
try:
project_guard.prepare_project(credential.name, "CV1815JA-CLEAN", root=root)
except project_guard.GuardError as exc:
assert "疑似凭据" in str(exc)
else:
raise AssertionError("credential-like files should be rejected")
def test_changes_after_verify_are_rejected(root: Path) -> None:
reference_name = "CV1841CP-REF3_dualos"
target_name = "CV1841CP-NEW3_dualos"
make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
report = staging / "doc" / "bsp_adaptation" / "adaptation_report.md"
report.write_text("# verified\n", encoding="utf-8")
verified = project_guard.verify_project(
reference_name,
staging.name,
target_name,
[],
root=root,
)
assert verified["passed"] is True
(staging / "buildEnvConfig.sh").write_text("EP_CHIP=CV1842HP\n", encoding="utf-8")
try:
project_guard.finalize_project(reference_name, staging.name, target_name, [], root=root)
except project_guard.GuardError as exc:
assert "未通过" in str(exc)
else:
raise AssertionError("finalize should reject post-verification changes")
def test_reference_drift_is_rejected(root: Path) -> None:
reference_name = "CV1841CP-REF4_dualos"
target_name = "CV1841CP-NEW4_dualos"
reference = make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
(reference / "buildEnvConfig.sh").write_text("EP_CHIP=CV1842HP\n", encoding="utf-8")
try:
project_guard.verify_project(reference_name, staging.name, target_name, [], root=root)
except project_guard.GuardError as exc:
assert "参考项目发生变化" in str(exc)
else:
raise AssertionError("verify should reject reference drift after prepare")
def test_staging_credentials_and_excluded_outputs_are_rejected(root: Path) -> None:
reference_name = "CV1815JA-REF5"
target_name = "CV1815JA-NEW5"
make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
(staging / ".env").write_text("TOKEN=secret\n", encoding="utf-8")
try:
project_guard.verify_project(reference_name, staging.name, target_name, [], root=root)
except project_guard.GuardError as exc:
assert "疑似凭据" in str(exc)
else:
raise AssertionError("verify should reject credentials added during editing")
(staging / ".env").unlink()
(staging / "logs").mkdir()
(staging / "logs" / "build.txt").write_text("generated\n", encoding="utf-8")
try:
project_guard.verify_project(reference_name, staging.name, target_name, [], root=root)
except project_guard.GuardError as exc:
assert "被排除" in str(exc)
else:
raise AssertionError("verify should reject excluded outputs added during editing")
def test_excluded_output_added_after_verify_is_rejected(root: Path) -> None:
reference_name = "CV1815JA-REF6"
target_name = "CV1815JA-NEW6"
make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
(staging / "doc" / "bsp_adaptation" / "adaptation_report.md").write_text(
"# verified\n", encoding="utf-8"
)
verified = project_guard.verify_project(
reference_name, staging.name, target_name, [], root=root
)
assert verified["passed"] is True
(staging / "logs").mkdir()
(staging / "logs" / "late.txt").write_text("late\n", encoding="utf-8")
try:
project_guard.finalize_project(reference_name, staging.name, target_name, [], root=root)
except project_guard.GuardError as exc:
assert "被排除" in str(exc)
else:
raise AssertionError("finalize should reject excluded output added after verify")
def test_tampered_delta_report_is_not_trusted(root: Path) -> None:
reference_name = "CV1815JA-REF7"
target_name = "CV1815JA-NEW7"
make_reference(root, reference_name)
prepared = project_guard.prepare_project(reference_name, target_name, root=root)
staging = root / prepared["staging_directory"]
(staging / "doc" / "bsp_adaptation" / "adaptation_report.md").write_text(
"# report\n", encoding="utf-8"
)
(staging / "buildEnvConfig.sh").write_text("EP_CHIP=CV1842HP\n", encoding="utf-8")
verified = project_guard.verify_project(
reference_name, staging.name, target_name, [], root=root
)
assert verified["passed"] is False
delta_path = staging / "doc" / "bsp_adaptation" / "validation" / "project_delta.json"
tampered = json.loads(delta_path.read_text(encoding="utf-8"))
tampered["passed"] = True
tampered["violations"] = []
project_guard.write_json(delta_path, tampered)
try:
project_guard.finalize_project(reference_name, staging.name, target_name, [], root=root)
except project_guard.GuardError as exc:
assert "未通过" in str(exc)
else:
raise AssertionError("finalize should recompute instead of trusting a tampered delta report")
def test_allow_paths_must_be_exact() -> None:
try:
project_guard.normalize_allow_paths(["configs/**"])
except project_guard.GuardError as exc:
assert "不允许通配符" in str(exc)
else:
raise AssertionError("wildcard allow paths should be rejected")
def test_audit_requires_frozen_baseline(root: Path) -> None:
target = root / "CV1841CP-UNMANAGED_dualos"
target.mkdir()
try:
project_guard.audit_final_project(target.name, root=root)
except project_guard.GuardError as exc:
assert "clone_manifest" in str(exc)
else:
raise AssertionError("audit should reject an unmanaged project")
def main() -> int:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp) / "EmxRes"
root.mkdir()
test_prepare_verify_finalize(root)
test_unapproved_delta_is_rejected(root)
test_external_link_and_credentials_are_rejected(root)
test_changes_after_verify_are_rejected(root)
test_reference_drift_is_rejected(root)
test_staging_credentials_and_excluded_outputs_are_rejected(root)
test_excluded_output_added_after_verify_is_rejected(root)
test_tampered_delta_report_is_not_trusted(root)
test_audit_requires_frozen_baseline(root)
test_allow_paths_must_be_exact()
print("Project guard behavioral tests passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())