#!/usr/bin/env python3 import argparse import os import sys from pathlib import Path from typing import Optional from typeguard import typechecked from ci.util.test_types import TestArgs def _python_test_exists(test_name: str) -> bool: """Check if a Python test file exists for the given test name""" # Check for the test file in ci/tests directory tests_dir = Path("ci/tests") # Try various naming patterns for Python tests possible_names = [ f"test_{test_name}.py", f"{test_name}.py", ] for name in possible_names: test_file = tests_dir / name if test_file.exists(): return True return False def parse_args(args: Optional[list[str]] = None) -> TestArgs: """Parse command line arguments""" parser = argparse.ArgumentParser(description="Run FastLED tests") parser.add_argument( "--cpp", action="store_true", help="Run C++ tests only (equivalent to --unit --examples, suppresses Python tests)", ) parser.add_argument("--unit", action="store_true", help="Run C++ unit tests only") parser.add_argument("--py", action="store_true", help="Run Python tests only") parser.add_argument( "test", type=str, nargs="?", default=None, help="Specific test to run (Python or C++)", ) # Create mutually exclusive group for compiler selection compiler_group = parser.add_mutually_exclusive_group() compiler_group.add_argument( "--clang", action="store_true", help="Use Clang compiler" ) compiler_group.add_argument( "--gcc", action="store_true", help="Use GCC compiler (default on non-Windows)" ) parser.add_argument( "--clean", action="store_true", help="Clean build before compiling" ) parser.add_argument( "--no-interactive", action="store_true", help="Force non-interactive mode (no confirmation prompts)", ) parser.add_argument( "--interactive", action="store_true", help="Enable interactive mode (allows confirmation prompts)", ) parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose output showing all test details", ) parser.add_argument( "--show-compile", action="store_true", help="Show compilation commands and output", ) parser.add_argument( "--show-link", action="store_true", help="Show linking commands and output", ) parser.add_argument( "--quick", action="store_true", help="Enable quick mode with FASTLED_ALL_SRC=1" ) parser.add_argument( "--no-stack-trace", action="store_true", help="Disable stack trace dumping on timeout", ) parser.add_argument( "--check", action="store_true", help="Enable static analysis (IWYU, clang-tidy) - auto-enables --cpp and --clang", ) parser.add_argument( "--examples", nargs="*", help="Run example compilation tests only (optionally specify example names). Use with --full for complete compilation + linking + execution", ) parser.add_argument( "--no-pch", action="store_true", help="Disable precompiled headers (PCH) when running example compilation tests", ) parser.add_argument( "--unity", action="store_true", help="Enable UNITY build mode for examples - compile all source files as a single unit for improved performance", ) parser.add_argument( "--no-unity", action="store_true", help="Disable unity builds for cpp tests and examples", ) parser.add_argument( "--full", action="store_true", help="Run full integration tests including compilation + linking + program execution", ) parser.add_argument( "--no-parallel", action="store_true", help="Force sequential test execution", ) parser.add_argument( "--unity-chunks", type=int, default=1, help="Number of unity chunks when building libfastled.a (default: 1)", ) parser.add_argument( "--debug", action="store_true", help="Enable debug mode for C++ unit tests (e.g., full debug symbols)", ) parser.add_argument( "--qemu", nargs="+", help="Run examples in QEMU emulation. Usage: --qemu esp32s3 [example_names...]", ) parsed_args = parser.parse_args(args) # Convert argparse.Namespace to TestArgs dataclass test_args = TestArgs( cpp=parsed_args.cpp, unit=parsed_args.unit, py=parsed_args.py, test=parsed_args.test, clang=parsed_args.clang, gcc=parsed_args.gcc, clean=parsed_args.clean, no_interactive=parsed_args.no_interactive, interactive=parsed_args.interactive, verbose=parsed_args.verbose, show_compile=parsed_args.show_compile, show_link=parsed_args.show_link, quick=parsed_args.quick, no_stack_trace=parsed_args.no_stack_trace, check=parsed_args.check, examples=parsed_args.examples, no_pch=parsed_args.no_pch, unity=parsed_args.unity, no_unity=parsed_args.no_unity, full=parsed_args.full, no_parallel=parsed_args.no_parallel, unity_chunks=parsed_args.unity_chunks, debug=parsed_args.debug, qemu=parsed_args.qemu, ) # Auto-enable --py or --cpp mode when a specific test is provided if test_args.test: # Check if this is a Python test first if _python_test_exists(test_args.test): # This is a Python test - enable Python mode if not test_args.py and not test_args.cpp: test_args.py = True print( f"Auto-enabled --py mode for specific Python test: {test_args.test}" ) else: # This is not a Python test - assume it's a C++ test if not test_args.cpp and not test_args.py: test_args.cpp = True print(f"Auto-enabled --cpp mode for specific test: {test_args.test}") # Also enable --unit when a specific C++ test is provided without any other flags if ( not test_args.unit and not test_args.examples and not test_args.py and not test_args.full ): test_args.unit = True print(f"Auto-enabled --unit mode for specific test: {test_args.test}") # Auto-enable --verbose when running unit tests (disabled) # if test_args.unit and not test_args.verbose: # test_args.verbose = True # print("Auto-enabled --verbose mode for unit tests") # Auto-enable --cpp and --clang when --check is provided if test_args.check: if not test_args.cpp: test_args.cpp = True print("Auto-enabled --cpp mode for static analysis (--check)") if not test_args.clang and not test_args.gcc: test_args.clang = True print("Auto-enabled --clang compiler for static analysis (--check)") # Auto-enable --cpp and --quick when --examples is provided if test_args.examples is not None: if not test_args.cpp: test_args.cpp = True print("Auto-enabled --cpp mode for example compilation (--examples)") if not test_args.quick: test_args.quick = True print( "Auto-enabled --quick mode for faster example compilation (--examples)" ) # Handle --full flag behavior if test_args.full: if test_args.examples is not None: # --examples --full: Run examples with full compilation+linking+execution print("Full examples mode: compilation + linking + program execution") else: # --full alone: Run integration tests if not test_args.cpp: test_args.cpp = True print("Auto-enabled --cpp mode for full integration tests (--full)") print("Full integration tests: compilation + linking + program execution") # Default to Clang on Windows unless --gcc is explicitly passed if sys.platform == "win32" and not test_args.gcc and not test_args.clang: test_args.clang = True print("Windows detected: defaulting to Clang compiler (use --gcc to override)") elif test_args.gcc: print("Using GCC compiler") elif test_args.clang: print("Using Clang compiler") # Validate conflicting arguments if test_args.no_interactive and test_args.interactive: print( "Error: --interactive and --no-interactive cannot be used together", file=sys.stderr, ) sys.exit(1) # Set NO_PARALLEL environment variable if --no-parallel is used if test_args.no_parallel: os.environ["NO_PARALLEL"] = "1" print("Forcing sequential execution (NO_PARALLEL=1)") return test_args