summaryrefslogtreecommitdiff
path: root/tools/tests/test_lint_project.py
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-18 20:28:09 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-18 20:28:09 +0200
commit2071e2beda763137416ac386d4bb953711ec995d (patch)
treebdcf840cb457f8286c1d967410f98972eb99b50a /tools/tests/test_lint_project.py
parenta9b1dc8bd9354973eabd72f56d82b2eab6f48d56 (diff)
test(tools): add test-log judge, docs and policy checks with tests
- ciq_test_report.py: decides pass/fail from Run No Evil output instead of monkeydo's unreliable exit code; writes JUnit XML. - check_docs.py: offline check of relative links and GitHub anchors. - lint_project.py: project rules (headers, no logging outside Diag, no secret-like literals, no network APIs or permissions). - check_budgets.py with budgets.json: perf and memory gates. - make_icon.py: reproducible launcher icon. - 31 pytest tests, including Hypothesis fuzzing of the log parser and integration checks against the real docs and sources; ruff and mypy (strict) clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/tests/test_lint_project.py')
-rw-r--r--tools/tests/test_lint_project.py81
1 files changed, 81 insertions, 0 deletions
diff --git a/tools/tests/test_lint_project.py b/tools/tests/test_lint_project.py
new file mode 100644
index 0000000..be0151e
--- /dev/null
+++ b/tools/tests/test_lint_project.py
@@ -0,0 +1,81 @@
+"""Tests for tools/lint_project.py, the project policy linter."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+import lint_project as lp
+from conftest import PROJECT_ROOT
+
+GOOD_MANIFEST = "<iq:manifest><iq:permissions/></iq:manifest>\n"
+
+
+def make_project(tmp_path: Path, files: dict[str, str]) -> Path:
+ for name, text in {"manifest.xml": GOOD_MANIFEST, **files}.items():
+ path = tmp_path / name
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(text, encoding="utf-8")
+ return tmp_path
+
+
+def codes(root: Path) -> list[tuple[str, str, int]]:
+ return [(str(p.path), p.code, p.line) for p in lp.lint(root)]
+
+
+def test_clean_project_passes(tmp_path: Path) -> None:
+ root = make_project(
+ tmp_path,
+ {
+ "source/util/Diag.mc": '// Diag\nSystem.println("DIAG {}");\n',
+ "source/otp/Base32.mc": (
+ '// Base32\nconst ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; // lint: allow-literal\n'
+ ),
+ "test/T.mc": (
+ "// T\n"
+ 'System.println("debug output in tests is allowed");\n'
+ 'const V = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ";\n'
+ ),
+ },
+ )
+ assert codes(root) == []
+
+
+def test_each_rule_fires(tmp_path: Path) -> None:
+ root = make_project(
+ tmp_path,
+ {
+ "source/app/NoHeader.mc": "import Toybox.Lang;\n",
+ "source/app/Log.mc": '// Log\nSystem.println("secret?");\n',
+ "source/app/Todo.mc": "// Todo\nvar x = 1; // TODO finish\n",
+ "source/app/Secret.mc": (
+ '// Secret\nconst S = "JBSWY3DPEHPK3PXPJBSWY3DP";\nconst H = "0123456789abcdef0123456789abcdef";\n'
+ ),
+ "source/app/Net.mc": "// Net\nimport Toybox.Communications;\n",
+ },
+ )
+ assert codes(root) == [
+ ("source/app/Log.mc", "MC002", 2),
+ ("source/app/Net.mc", "MC005", 2),
+ ("source/app/NoHeader.mc", "MC001", 1),
+ ("source/app/Secret.mc", "MC004", 2),
+ ("source/app/Secret.mc", "MC004", 3),
+ ("source/app/Todo.mc", "MC003", 2),
+ ]
+
+
+def test_println_in_comment_is_ignored(tmp_path: Path) -> None:
+ root = make_project(tmp_path, {"source/app/A.mc": "// A\nvar a = 1; // System.println(a)\n"})
+ assert codes(root) == []
+
+
+def test_manifest_permissions_are_rejected(tmp_path: Path) -> None:
+ root = make_project(
+ tmp_path,
+ {"manifest.xml": '<iq:permissions>\n<iq:uses-permission id="Communications"/>\n</iq:permissions>\n'},
+ )
+ assert codes(root) == [("manifest.xml", "MC005", 2)]
+
+
+def test_project_passes_its_own_rules() -> None:
+ problems = lp.lint(PROJECT_ROOT)
+ assert problems == [], "\n".join(map(str, problems))