From f6671ff9a25a75edb6c88f7983fc9dd2eec0cc46 Mon Sep 17 00:00:00 2001 From: papertager <2567587994@qq.com> Date: Tue, 9 Jun 2026 22:11:21 +0800 Subject: [PATCH 1/2] Add mcoplib unit test inventory tool --- tools/unit_test_inventory.py | 56 +++++++++++++++++++++++++++ unit_test/test_unit_test_inventory.py | 23 +++++++++++ 2 files changed, 79 insertions(+) create mode 100644 tools/unit_test_inventory.py create mode 100644 unit_test/test_unit_test_inventory.py diff --git a/tools/unit_test_inventory.py b/tools/unit_test_inventory.py new file mode 100644 index 0000000..74a8946 --- /dev/null +++ b/tools/unit_test_inventory.py @@ -0,0 +1,56 @@ +#!/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) 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..1a15929 --- /dev/null +++ b/unit_test/test_unit_test_inventory.py @@ -0,0 +1,23 @@ +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\ndef helper(): pass\n", encoding="utf-8") + + report = inventory(root) + + self.assertEqual(report["file_count"], 1) + self.assertEqual(report["test_function_count"], 1) + + +if __name__ == "__main__": + unittest.main() From c7c71d730e336f4a98189ef0bc17cab84f0f5962 Mon Sep 17 00:00:00 2001 From: papertager <2567587994@qq.com> Date: Thu, 11 Jun 2026 00:29:20 +0800 Subject: [PATCH 2/2] Include async tests in unit test inventory --- tools/unit_test_inventory.py | 6 +++++- unit_test/test_unit_test_inventory.py | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/unit_test_inventory.py b/tools/unit_test_inventory.py index 74a8946..0da1c80 100644 --- a/tools/unit_test_inventory.py +++ b/tools/unit_test_inventory.py @@ -14,7 +14,11 @@ def _test_functions(path: Path) -> tuple[list[str], str]: 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) and node.name.startswith("test_")) + tests = sorted( + node.name + for node in ast.walk(tree) + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith("test_") + ) return tests, "" diff --git a/unit_test/test_unit_test_inventory.py b/unit_test/test_unit_test_inventory.py index 1a15929..4d60edc 100644 --- a/unit_test/test_unit_test_inventory.py +++ b/unit_test/test_unit_test_inventory.py @@ -11,12 +11,17 @@ def test_counts_test_functions(self): root = Path(tmpdir) unit = root / "unit_test" unit.mkdir() - (unit / "test_demo.py").write_text("def test_a(): pass\ndef helper(): pass\n", encoding="utf-8") + (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"], 1) + self.assertEqual(report["test_function_count"], 2) if __name__ == "__main__":