#!/usr/bin/env python3 import json import sys import unittest from pathlib import Path from tempfile import TemporaryDirectory sys.path.insert(0, str(Path(__file__).resolve().parent)) from generate_dependency_closure import generate class DependencyClosureTests(unittest.TestCase): def create_tree(self): directory = TemporaryDirectory() root = Path(directory.name) (root / "coverage").mkdir() (root / "linuxcnc").mkdir() (root / "linuxcnc/src").mkdir() (root / "linuxcnc/configs").mkdir() (root / "linuxcnc/tests/demo").mkdir(parents=True) (root / "linuxcnc/src/a.c").write_text("int a;\n", encoding="utf-8") (root / "linuxcnc/configs/demo.ini").write_text("[EMC]\n", encoding="utf-8") (root / "linuxcnc/tests/demo/test.sh").write_text("#!/bin/sh\n", encoding="utf-8") rows = [ {"source_id": "src:file:linuxcnc/src/a.c", "origin_index": "source", "origin_source_id": "build:a", "path": "linuxcnc/src/a.c", "hash_path": "linuxcnc/src/a.c", "source_sha": "a", "domain": "source", "kind": "source", "line": None, "parse_status": "parsed", "related_source_ids": []}, {"source_id": "src:file:linuxcnc/configs/demo.ini", "origin_index": "source", "origin_source_id": "build:ini", "path": "linuxcnc/configs/demo.ini", "hash_path": "linuxcnc/configs/demo.ini", "source_sha": "b", "domain": "source", "kind": "source", "line": None, "parse_status": "parsed", "related_source_ids": []}, {"source_id": "src:test:linuxcnc/tests/demo/test.sh", "origin_index": "test", "origin_source_id": "test:demo", "path": "linuxcnc/tests/demo/test.sh", "hash_path": "linuxcnc/tests/demo/test.sh", "source_sha": "c", "domain": "test", "kind": "shell_test", "line": None, "parse_status": "parsed", "related_source_ids": [], "command": "bash", "inputs": ["linuxcnc/src/a.c"], "expected": [], "config_refs": ["linuxcnc/configs/missing.ini"], "test_kind": "shell_test"}, ] (root / "coverage/normalized-sources.jsonl").write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8") return directory, root def test_resolved_and_unresolved_edges_are_retained(self): directory, root = self.create_tree() self.addCleanup(directory.cleanup) count, _ = generate(root, root / "out.jsonl", root / "report.json") rows = [json.loads(line) for line in (root / "out.jsonl").read_text(encoding="utf-8").splitlines()] self.assertEqual(count, 2) self.assertTrue(any(row["parse_status"] == "resolved" for row in rows)) self.assertTrue(any(row["to_source_id"].startswith("unresolved:") for row in rows)) def test_deterministic_output(self): directory, root = self.create_tree() self.addCleanup(directory.cleanup) _, first = generate(root, root / "one.jsonl", root / "report.json") _, second = generate(root, root / "two.jsonl", root / "report.json") self.assertEqual(first, second) self.assertEqual((root / "one.jsonl").read_bytes(), (root / "two.jsonl").read_bytes()) def test_duplicate_edges_are_reported(self): directory, root = self.create_tree() self.addCleanup(directory.cleanup) path = root / "coverage/normalized-sources.jsonl" rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()] rows[-1]["inputs"] = ["linuxcnc/src/a.c", "linuxcnc/src/a.c"] path.write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8") generate(root, root / "out.jsonl", root / "report.json") report = json.loads((root / "report.json").read_text(encoding="utf-8")) self.assertTrue(report["duplicate_edges"]) if __name__ == "__main__": unittest.main()