diff --git a/src/devx/make/devx.mak b/src/devx/make/devx.mak index 469a879..8677d78 100644 --- a/src/devx/make/devx.mak +++ b/src/devx/make/devx.mak @@ -385,7 +385,7 @@ devx-check-test-speed: # This is also automatically enforced by the pytest plugin (pytest11 entry point). # Use this target for CI gates or pre-commit hooks. devx-check-test-isolation: - @$(DEVX_PYTHON) -m devx.tools.check_test_isolation --test-path $(DEVX_TEST_PATHS) + @$(DEVX_PYTHON) -m devx.tools.check_test_isolation $(addprefix --test-path ,$(DEVX_TEST_PATHS)) # Check translation files for missing keys, dead keys, and missing languages. # Runs automatically as part of devx-lint to shift-left translation issues diff --git a/src/devx/tools/check_test_isolation.py b/src/devx/tools/check_test_isolation.py index e27ae3c..eea8723 100644 --- a/src/devx/tools/check_test_isolation.py +++ b/src/devx/tools/check_test_isolation.py @@ -449,10 +449,12 @@ def pytest_collection_finish(session): # type: ignore[no-untyped-def] # pragma @click.command() @click.option( "--test-path", + "test_paths", type=click.Path(exists=True, path_type=Path), - default=Path("tests/"), + multiple=True, + default=[Path("tests/")], show_default=True, - help="Path to test directory or file to analyze.", + help="Path to test directory or file to analyze (can be specified multiple times).", ) @click.option( "--max-loop-iterations", @@ -474,31 +476,35 @@ def pytest_collection_finish(session): # type: ignore[no-untyped-def] # pragma help="Comma-separated list of categories to check (default: all). " "Available: unpatched-subprocess, unpatched-sleep, unpatched-helper, excessive-iterations", ) -def cli(test_path: Path, max_loop_iterations: int, strict: bool, categories: str) -> None: +def cli(test_paths: tuple[Path, ...], max_loop_iterations: int, strict: bool, categories: str) -> None: """Check test files for un-hermetic patterns that cause slow or flaky tests.""" allowed: set[str] | None = None if categories: allowed = {c.strip() for c in categories.split(",")} - violations = analyze_test_files(test_path, max_loop_iterations, allowed) + all_violations: list[Violation] = [] + total_files = 0 + for test_path in test_paths: + violations = analyze_test_files(test_path, max_loop_iterations, allowed) + all_violations.extend(violations) + total_files += len(find_test_files(test_path)) - if not violations: - file_count = len(find_test_files(test_path)) + if not all_violations: click.echo( - _("Test isolation check passed: {count} test files analyzed, no violations found.", count=file_count) + _("Test isolation check passed: {count} test files analyzed, no violations found.", count=total_files) ) sys.exit(0) click.echo( _( "Test isolation check FAILED: {count} violation(s) found in {files} test file(s).", - count=len(violations), - files=len({v.file for v in violations}), + count=len(all_violations), + files=len({v.file for v in all_violations}), ), err=True, ) click.echo("") - for v in sorted(violations, key=lambda x: (str(x.file), x.line)): + for v in sorted(all_violations, key=lambda x: (str(x.file), x.line)): click.echo(f" {v.format()}", err=True) click.echo("")