feat: 发布固件底层开发 Skill 套件
This commit is contained in:
commit
c7ae3b4ce1
72 files changed
+10212
No files matched your search
+303
@@ -0,0 +1,303 @@
|
||||
#!/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())
|
||||
Reference in new issue
Block a user