diff --git a/tools/unit_test_inventory.py b/tools/unit_test_inventory.py new file mode 100644 index 0000000..0da1c80 --- /dev/null +++ b/tools/unit_test_inventory.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Export mcoplib unit test metadata as JSON.""" + +from __future__ import annotations + +import argparse +import ast +import json +from pathlib import Path + + +def _test_functions(path: Path) -> tuple[list[str], str]: + try: + tree = ast.parse(path.read_text(encoding="utf-8", errors="replace")) + except SyntaxError as exc: + return [], f"{exc.__class__.__name__}: {exc}" + tests = sorted( + node.name + for node in ast.walk(tree) + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith("test_") + ) + return tests, "" + + +def inventory(root: Path) -> dict[str, object]: + tests = [] + for path in sorted((root / "unit_test").glob("test_*.py")): + funcs, parse_error = _test_functions(path) + tests.append( + { + "path": path.relative_to(root).as_posix(), + "test_functions": funcs, + "count": len(funcs), + "parse_error": parse_error, + } + ) + return { + "file_count": len(tests), + "test_function_count": sum(item["count"] for item in tests), + "parse_error_count": sum(1 for item in tests if item["parse_error"]), + "tests": tests, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path.cwd()) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + text = json.dumps(inventory(args.root), indent=2, ensure_ascii=False) + if args.output: + args.output.write_text(text + "\n", encoding="utf-8") + else: + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/unit_test/test_unit_test_inventory.py b/unit_test/test_unit_test_inventory.py new file mode 100644 index 0000000..4d60edc --- /dev/null +++ b/unit_test/test_unit_test_inventory.py @@ -0,0 +1,28 @@ +import tempfile +import unittest +from pathlib import Path + +from tools.unit_test_inventory import inventory + + +class UnitTestInventoryTest(unittest.TestCase): + def test_counts_test_functions(self): + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + unit = root / "unit_test" + unit.mkdir() + (unit / "test_demo.py").write_text( + "def test_a(): pass\n" + "async def test_async_case(): pass\n" + "def helper(): pass\n", + encoding="utf-8", + ) + + report = inventory(root) + + self.assertEqual(report["file_count"], 1) + self.assertEqual(report["test_function_count"], 2) + + +if __name__ == "__main__": + unittest.main()