diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9507c5938..a7ed035063 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1225,7 +1225,14 @@ jobs: if [ -n "$compile_csv" ]; then # Run compilation with grouping and isolation - python3 script/test_build_components.py -e compile -c "$compile_csv" -f --isolate "$directly_changed_csv" + # The bootloader has no ESPHome code and these builds never + # flash; the check_idf_py batch keeps the full build so the + # native sub-build and the equivalence check stay covered. + skip_flag="--skip-bootloader" + if [[ "${{ matrix.batch.check_idf_py }}" == "true" ]]; then + skip_flag="" + fi + python3 script/test_build_components.py -e compile -c "$compile_csv" -f --isolate "$directly_changed_csv" $skip_flag if [[ "${{ matrix.batch.check_idf_py }}" == "true" ]]; then # The real idf.py must find nothing to configure or build in a # tree built above; catches drift on ESP-IDF bumps. diff --git a/script/test_build_components.py b/script/test_build_components.py index d3dfd36076..c955391a61 100755 --- a/script/test_build_components.py +++ b/script/test_build_components.py @@ -333,6 +333,12 @@ def extract_platform_with_version(base_file: Path) -> str: return base_file.stem.replace("build_components_base.", "") +def _wants_skip_bootloader(skip: bool, command: str, platform: str) -> bool: + """Every esp32 target: idf variants, and esp32-ard, whose Arduino + core builds as an ESP-IDF component under the native toolchain.""" + return skip and command == "compile" and platform.startswith("esp32") + + def run_esphome_test( component: str, test_file: Path, @@ -344,6 +350,7 @@ def run_esphome_test( continue_on_fail: bool, use_testing_mode: bool = False, toolchain: str | None = None, + skip_bootloader: bool = False, ) -> TestResult: """Run esphome test for a single component. @@ -408,6 +415,8 @@ def run_esphome_test( # Add command cmd.append(esphome_command) + if _wants_skip_bootloader(skip_bootloader, esphome_command, platform): + cmd.append("--skip-bootloader") # Add config file cmd.append(str(output_file)) @@ -469,6 +478,7 @@ def run_grouped_test( esphome_command: str, continue_on_fail: bool, toolchain: str | None = None, + skip_bootloader: bool = False, ) -> TestResult: """Run esphome test for a group of components with shared bus configs. @@ -554,6 +564,8 @@ def run_grouped_test( # Add command cmd.append(esphome_command) + if _wants_skip_bootloader(skip_bootloader, esphome_command, platform): + cmd.append("--skip-bootloader") cmd.append(str(output_file)) @@ -614,6 +626,7 @@ def run_grouped_component_tests( continue_on_fail: bool, additional_isolated: set[str] | None = None, toolchain: str | None = None, + skip_bootloader: bool = False, ) -> tuple[set[tuple[str, str]], list[TestResult]]: """Run grouped component tests. @@ -956,6 +969,7 @@ def run_grouped_component_tests( esphome_command=esphome_command, continue_on_fail=continue_on_fail, toolchain=toolchain, + skip_bootloader=skip_bootloader, ) # Mark all components as tested @@ -980,6 +994,7 @@ def run_individual_component_test( tested_components: set[tuple[str, str]], test_results: list[TestResult], toolchain: str | None = None, + skip_bootloader: bool = False, ) -> None: """Run an individual component test if not already tested in a group. @@ -1014,6 +1029,7 @@ def run_individual_component_test( esphome_command=esphome_command, continue_on_fail=continue_on_fail, toolchain=toolchain, + skip_bootloader=skip_bootloader, ) test_results.append(test_result) @@ -1027,6 +1043,7 @@ def test_components( isolated_components: set[str] | None = None, base_only: bool = False, toolchain: str | None = None, + skip_bootloader: bool = False, fail_on_no_tests: bool = False, ) -> int: """Test components with optional intelligent grouping. @@ -1127,6 +1144,7 @@ def test_components( continue_on_fail=continue_on_fail, additional_isolated=isolated_components, toolchain=toolchain, + skip_bootloader=skip_bootloader, ) test_results.extend(grouped_results) @@ -1156,6 +1174,7 @@ def test_components( tested_components=tested_components, test_results=test_results, toolchain=toolchain, + skip_bootloader=skip_bootloader, ) else: # Platform-specific test @@ -1189,6 +1208,7 @@ def test_components( tested_components=tested_components, test_results=test_results, toolchain=toolchain, + skip_bootloader=skip_bootloader, ) silent: list[str] = [] @@ -1294,6 +1314,12 @@ def main() -> int: "--toolchain", help="Select toolchain for compiling.", ) + parser.add_argument( + "--skip-bootloader", + action="store_true", + help="Pass --skip-bootloader to esphome compile; component builds " + "never flash, and the bootloader is covered by the toolchain jobs", + ) parser.add_argument( "--fail-on-no-tests", action="store_true", @@ -1321,6 +1347,7 @@ def main() -> int: fail_on_no_tests=args.fail_on_no_tests, base_only=args.base_only, toolchain=args.toolchain, + skip_bootloader=args.skip_bootloader, ) diff --git a/tests/script/test_test_build_components.py b/tests/script/test_test_build_components.py index 1d21e5d943..cf15472888 100644 --- a/tests/script/test_test_build_components.py +++ b/tests/script/test_test_build_components.py @@ -94,6 +94,87 @@ def test_run_esphome_test_wraps_output_in_group( assert out.index("::group::") < out.index("> [foo]") < out.index("::endgroup::") +@pytest.mark.parametrize( + ("platform", "command", "skip", "expects_flag"), + [ + ("esp32-idf", "compile", True, True), + ("esp32-s3-idf", "compile", True, True), + ("esp32-ard", "compile", True, True), + ("esp32-idf", "compile", False, False), + ("esp32-idf", "config", True, False), + ("esp8266-ard", "compile", True, False), + ], + ids=[ + "idf-skip", + "idf-variant", + "arduino-as-idf-component", + "idf-off", + "config", + "esp8266", + ], +) +def test_run_esphome_test_skip_bootloader_argv( + _ci: None, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + platform: str, + command: str, + skip: bool, + expects_flag: bool, +) -> None: + """The flag lands on esp32 family compiles only.""" + seen: list[list[str]] = [] + + def fake_run(cmd: list[str], **kwargs: object) -> _FakeCompleted: + seen.append(cmd) + return _FakeCompleted(0) + + monkeypatch.setattr(tbc.subprocess, "run", fake_run) + repo_root = Path(tbc.__file__).parent.parent + test_file = repo_root / "tests" / "components" / "foo" / f"test.{platform}.yaml" + tbc.run_esphome_test( + component="foo", + test_file=test_file, + platform=platform, + platform_with_version=platform, + base_file=_make_base_file(tmp_path), + build_dir=tmp_path, + esphome_command=command, + continue_on_fail=True, + skip_bootloader=skip, + ) + assert ("--skip-bootloader" in seen[0]) is expects_flag + + +def test_run_grouped_test_skip_bootloader_argv( + _ci: None, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + """The grouped command site adds the flag the same way.""" + seen: list[list[str]] = [] + + def fake_run(cmd: list[str], **kwargs: object) -> _FakeCompleted: + seen.append(cmd) + return _FakeCompleted(0) + + monkeypatch.setattr(tbc.subprocess, "run", fake_run) + repo_root = Path(tbc.__file__).parent.parent + tests_dir = repo_root / "tests" / "components" + tbc.run_grouped_test( + components=["gpio"], + platform="esp32-idf", + platform_with_version="esp32-idf", + base_file=_make_base_file(tmp_path), + build_dir=tmp_path, + tests_dir=tests_dir, + esphome_command="compile", + continue_on_fail=True, + skip_bootloader=True, + ) + assert "--skip-bootloader" in seen[0] + + def test_run_esphome_test_closes_group_before_failure_report( _ci: None, monkeypatch: pytest.MonkeyPatch,