304 lines
12 KiB
Python
Executable File
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())
|