# Author: cirakg & claude ai # Summary: Tests for tools/lint_project.py, the project policy linter. """Tests for tools/lint_project.py, the project policy linter.""" from __future__ import annotations import json from pathlib import Path import lint_project as lp from conftest import PROJECT_ROOT from PIL import Image from PIL.PngImagePlugin import PngInfo GOOD_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]]: """Findings of the code rules (MC001-MC005) only.""" return [(str(p.path), p.code, p.line) for p in sorted(lp.lint_code(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": '\n\n\n'}, ) assert codes(root) == [("manifest.xml", "MC005", 2)] HEADER = "# Author: cirakg & claude ai\n# Summary: A file with a proper header.\n" def header_findings(root: Path) -> list[str]: return sorted(str(p.path) for p in lp.lint_headers(root)) def save_png(path: Path, author: str | None) -> None: info = PngInfo() if author is not None: info.add_text("Author", author) info.add_text("Description", "test image") path.parent.mkdir(parents=True, exist_ok=True) Image.new("RGB", (4, 4)).save(path, pnginfo=info) def test_headers_are_required_in_every_file_type(tmp_path: Path) -> None: files = { "ok.sh": "#!/bin/sh\n" + HEADER, "late.txt": "\n" * 20 + HEADER, # too far down "none.md": "# Title\n", "data/ok.json": json.dumps({"_author": lp.AUTHOR, "_summary": "x"}), "data/bad.json": json.dumps({"author": "someone else", "description": "x"}), "docker/docs/package-lock.json": "{}", # generated, exempt "build/out.txt": "ignored folder", } for name, text in files.items(): path = tmp_path / name path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text, encoding="utf-8") save_png(tmp_path / "img/ok.png", lp.AUTHOR) save_png(tmp_path / "img/anon.png", None) assert header_findings(tmp_path) == ["data/bad.json", "img/anon.png", "late.txt", "none.md"] def test_project_passes_its_own_rules() -> None: problems = lp.lint(PROJECT_ROOT) assert problems == [], "\n".join(map(str, problems))