Discover all test files in the tests directory. Recursively scans the entire tests/ tree for *.cpp and *.ino files, excluding infrastructure files and directories. No hardcoded subdirectory list needed. Args: tests_dir: Root directory containing test files excl
(
tests_dir: Path, excluded_files: set[Path], excluded_dirs: set[Path]
)
| 21 | |
| 22 | |
| 23 | def discover_test_files( |
| 24 | tests_dir: Path, excluded_files: set[Path], excluded_dirs: set[Path] |
| 25 | ) -> list[str]: |
| 26 | """ |
| 27 | Discover all test files in the tests directory. |
| 28 | |
| 29 | Recursively scans the entire tests/ tree for *.cpp and *.ino files, |
| 30 | excluding infrastructure files and directories. No hardcoded subdirectory |
| 31 | list needed. |
| 32 | |
| 33 | Args: |
| 34 | tests_dir: Root directory containing test files |
| 35 | excluded_files: Set of full paths to exclude (e.g., {Path(".../tests/doctest_main.cpp")}) |
| 36 | excluded_dirs: Set of full directory paths to exclude (e.g., {Path(".../tests/shared")}) |
| 37 | |
| 38 | Returns: |
| 39 | List of relative paths (POSIX format) to test files, sorted |
| 40 | """ |
| 41 | test_files: list[str] = [] |
| 42 | |
| 43 | for f in chain.from_iterable(tests_dir.rglob(g) for g in TEST_SOURCE_GLOBS): |
| 44 | resolved = f.resolve() |
| 45 | |
| 46 | # Skip excluded files |
| 47 | if resolved in excluded_files: |
| 48 | continue |
| 49 | |
| 50 | # Skip files inside excluded directories |
| 51 | if any(resolved.is_relative_to(d) for d in excluded_dirs): |
| 52 | continue |
| 53 | |
| 54 | # Skip hidden directories and .dir directories |
| 55 | rel = f.relative_to(tests_dir) |
| 56 | parent_parts = rel.parts[:-1] # directory components only |
| 57 | if any(part.startswith(".") or part.endswith(".dir") for part in parent_parts): |
| 58 | continue |
| 59 | |
| 60 | test_files.append(rel.as_posix()) |
| 61 | |
| 62 | # Sort and deduplicate |
| 63 | return sorted(set(test_files)) |
| 64 | |
| 65 | |
| 66 | def main() -> None: |