mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 00:10:22 +00:00
[ci] Add the native ESP8266 compile smoke test (#19714)
This commit is contained in:
@@ -78,6 +78,17 @@ def mock_esp32_platformio_components_to_test() -> Generator[Mock, None, None]:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_esp8266_native_components_to_test() -> Generator[Mock, None, None]:
|
||||
"""Mock esp8266_native_components_to_test from determine_jobs.
|
||||
|
||||
main() drives both the ``esp8266_native`` boolean output and the
|
||||
``esp8266_native_components`` CSV from this one function.
|
||||
"""
|
||||
with patch.object(determine_jobs, "esp8266_native_components_to_test") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_determine_cpp_unit_tests() -> Generator[Mock, None, None]:
|
||||
"""Mock determine_cpp_unit_tests from helpers."""
|
||||
@@ -106,6 +117,7 @@ def clear_determine_jobs_caches() -> None:
|
||||
"""Clear all cached functions before each test."""
|
||||
determine_jobs._is_clang_tidy_full_scan.cache_clear()
|
||||
determine_jobs._component_has_tests.cache_clear()
|
||||
determine_jobs._cached_components_closure.cache_clear()
|
||||
|
||||
|
||||
def test_main_all_tests_should_run(
|
||||
@@ -116,6 +128,7 @@ def test_main_all_tests_should_run(
|
||||
mock_should_run_import_time: Mock,
|
||||
mock_should_run_device_builder: Mock,
|
||||
mock_esp32_platformio_components_to_test: Mock,
|
||||
mock_esp8266_native_components_to_test: Mock,
|
||||
mock_changed_files: Mock,
|
||||
mock_determine_cpp_unit_tests: Mock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
@@ -132,6 +145,7 @@ def test_main_all_tests_should_run(
|
||||
mock_should_run_import_time.return_value = True
|
||||
mock_should_run_device_builder.return_value = True
|
||||
mock_esp32_platformio_components_to_test.return_value = ["api", "esp32"]
|
||||
mock_esp8266_native_components_to_test.return_value = ["api", "logger"]
|
||||
mock_determine_cpp_unit_tests.return_value = (False, ["wifi", "api", "sensor"])
|
||||
|
||||
# Mock changed_files to return non-component files (to avoid memory impact)
|
||||
@@ -208,6 +222,8 @@ def test_main_all_tests_should_run(
|
||||
assert output["device_builder"] is True
|
||||
assert output["esp32_platformio"] is True
|
||||
assert output["esp32_platformio_components"] == "api,esp32"
|
||||
assert output["esp8266_native"] is True
|
||||
assert output["esp8266_native_components"] == "api,logger"
|
||||
assert output["changed_components"] == ["wifi", "api", "sensor"]
|
||||
# changed_components_with_tests will only include components that actually have test files
|
||||
assert "changed_components_with_tests" in output
|
||||
@@ -244,6 +260,7 @@ def test_main_no_tests_should_run(
|
||||
mock_should_run_import_time: Mock,
|
||||
mock_should_run_device_builder: Mock,
|
||||
mock_esp32_platformio_components_to_test: Mock,
|
||||
mock_esp8266_native_components_to_test: Mock,
|
||||
mock_changed_files: Mock,
|
||||
mock_determine_cpp_unit_tests: Mock,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
@@ -260,6 +277,7 @@ def test_main_no_tests_should_run(
|
||||
mock_should_run_import_time.return_value = False
|
||||
mock_should_run_device_builder.return_value = False
|
||||
mock_esp32_platformio_components_to_test.return_value = []
|
||||
mock_esp8266_native_components_to_test.return_value = []
|
||||
mock_determine_cpp_unit_tests.return_value = (False, [])
|
||||
|
||||
# Mock changed_files to return no component files
|
||||
@@ -302,6 +320,8 @@ def test_main_no_tests_should_run(
|
||||
assert output["device_builder"] is False
|
||||
assert output["esp32_platformio"] is False
|
||||
assert output["esp32_platformio_components"] == ""
|
||||
assert output["esp8266_native"] is False
|
||||
assert output["esp8266_native_components"] == ""
|
||||
assert output["changed_components"] == []
|
||||
assert output["changed_components_with_tests"] == []
|
||||
assert output["component_test_count"] == 0
|
||||
@@ -997,6 +1017,9 @@ _ESP32_PLATFORMIO_FULL_LIST_FILES = [
|
||||
# Workflow / harness files
|
||||
["script/test_build_components.py"],
|
||||
[".github/workflows/ci.yml"],
|
||||
# The base config and bus packages every generated build includes
|
||||
["tests/test_build_components/build_components_base.esp32-idf.yaml"],
|
||||
["tests/test_build_components/common/uart/esp32-idf.yaml"],
|
||||
]
|
||||
|
||||
|
||||
@@ -3151,6 +3174,81 @@ def test_memory_impact_elf_layouts_are_found(tmp_path: Path) -> None:
|
||||
assert find_elf_path(build_path) == elf, f"{platform} ELF not found"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"changed",
|
||||
[
|
||||
"esphome/arduino8266/framework.py",
|
||||
"esphome/build_gen/arduino8266.py",
|
||||
"esphome/components/esp8266/build_surgery.py",
|
||||
# Shared modules the native build depends on
|
||||
"esphome/build_helpers/idedata.py",
|
||||
"esphome/platformio/library.py",
|
||||
# Top-level esphome/*.py modules the backend imports directly
|
||||
"esphome/framework_helpers.py",
|
||||
"esphome/writer.py",
|
||||
# esp8266/__init__.py imports copy_ccache_script from it
|
||||
"esphome/platformio/toolchain.py",
|
||||
# The composite cache action must not ship unexercised
|
||||
".github/actions/cache-arduino8266/action.yml",
|
||||
# The base config and bus packages every generated build includes
|
||||
"tests/test_build_components/build_components_base.esp8266-ard.yaml",
|
||||
"tests/test_build_components/common/uart/esp8266-ard.yaml",
|
||||
],
|
||||
)
|
||||
def test_esp8266_native_components_full_list_on_infra_change(changed: str) -> None:
|
||||
"""Native-ESP8266 infrastructure changes run the full test list."""
|
||||
with (
|
||||
patch.object(determine_jobs, "changed_files", return_value=[changed]),
|
||||
patch.object(
|
||||
determine_jobs,
|
||||
"get_components_with_dependencies",
|
||||
return_value=["wifi"],
|
||||
),
|
||||
):
|
||||
result = determine_jobs.esp8266_native_components_to_test()
|
||||
assert result == sorted(determine_jobs.ESP8266_NATIVE_TEST_COMPONENTS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("changed_files", "dependency_closure", "expected"),
|
||||
[
|
||||
# Tested component changed -- narrow to the intersection.
|
||||
(
|
||||
["esphome/components/mqtt/mqtt_client.cpp"],
|
||||
["mqtt", "json"],
|
||||
["mqtt"],
|
||||
),
|
||||
# Components outside the test set return an empty list (job skipped).
|
||||
(
|
||||
["esphome/components/wifi/wifi_component.cpp"],
|
||||
["wifi", "network"],
|
||||
[],
|
||||
),
|
||||
# espidf infrastructure is not an esp8266-native trigger; the
|
||||
# native backend depends on esphome/build_helpers/ instead.
|
||||
(["esphome/build_gen/espidf.py"], [], []),
|
||||
(["esphome/espidf/toolchain.py"], [], []),
|
||||
(["README.md"], [], []),
|
||||
],
|
||||
)
|
||||
def test_esp8266_native_components_to_test_narrowing(
|
||||
changed_files: list[str],
|
||||
dependency_closure: list[str],
|
||||
expected: list[str],
|
||||
) -> None:
|
||||
"""Component changes narrow the native-ESP8266 test list."""
|
||||
with (
|
||||
patch.object(determine_jobs, "changed_files", return_value=changed_files),
|
||||
patch.object(
|
||||
determine_jobs,
|
||||
"get_components_with_dependencies",
|
||||
return_value=dependency_closure,
|
||||
),
|
||||
):
|
||||
result = determine_jobs.esp8266_native_components_to_test()
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_compute_integration_test_buckets_no_durations_full_fanout() -> None:
|
||||
"""Without recorded durations the fan-out stays at the maximum."""
|
||||
files = [f"tests/integration/test_{i:03d}.py" for i in range(15)]
|
||||
|
||||
@@ -236,3 +236,98 @@ def test_run_grouped_test_closes_group_when_subprocess_raises(
|
||||
)
|
||||
|
||||
assert "::endgroup::" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_components_empty_match_fails_with_flag(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""Under --fail-on-no-tests, a real component filtered to a platform
|
||||
with no matching test file must not pass CI as a green zero-component
|
||||
compile."""
|
||||
rc = tbc.test_components(
|
||||
["logger"],
|
||||
"zz-none",
|
||||
"compile",
|
||||
False,
|
||||
enable_grouping=False,
|
||||
fail_on_no_tests=True,
|
||||
)
|
||||
assert rc == 1
|
||||
assert "No tests ran for requested pattern(s): logger" in (capsys.readouterr().out)
|
||||
|
||||
|
||||
def test_components_component_with_no_base_file_fails_with_flag(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""A component whose fixture matches the platform but whose platform has
|
||||
no base file builds nothing; under the flag that silent zero fails by
|
||||
component name instead of hiding behind other components."""
|
||||
monkeypatch.setattr(tbc, "get_platform_base_files", lambda base_dir: {})
|
||||
rc = tbc.test_components(
|
||||
["logger"],
|
||||
"esp8266-ard",
|
||||
"compile",
|
||||
False,
|
||||
enable_grouping=False,
|
||||
fail_on_no_tests=True,
|
||||
)
|
||||
assert rc == 1
|
||||
assert "No tests ran for requested pattern(s): logger" in (capsys.readouterr().out)
|
||||
|
||||
|
||||
def test_components_blank_list_fails_with_flag(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""A fully blank component list must not slide into the baseline
|
||||
fallback and exit green under the flag."""
|
||||
rc = tbc.test_components(
|
||||
[""], "esp8266-ard", "compile", False, fail_on_no_tests=True
|
||||
)
|
||||
assert rc == 1
|
||||
assert "blank component list" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_components_wildcard_no_match_fails_with_flag(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""A wildcard matching nothing must not degrade to the synthetic
|
||||
baseline build and exit green under the flag."""
|
||||
rc = tbc.test_components(
|
||||
["zz_no_such*"],
|
||||
"esp8266-ard",
|
||||
"compile",
|
||||
False,
|
||||
enable_grouping=False,
|
||||
fail_on_no_tests=True,
|
||||
)
|
||||
assert rc == 1
|
||||
assert "No components found matching" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_components_empty_match_tolerated_without_flag() -> None:
|
||||
"""The esp32-ard smoke leg deliberately builds only the subset with a
|
||||
matching fixture; without the flag an empty match stays green."""
|
||||
assert (
|
||||
tbc.test_components(
|
||||
["logger"], "zz-none", "compile", False, enable_grouping=False
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_components_unknown_component_fails_with_flag(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""A renamed smoke-test component must shrink coverage loudly, not fall
|
||||
into the reference-baseline build."""
|
||||
rc = tbc.test_components(
|
||||
["no_such_component_xyz"],
|
||||
"esp8266-ard",
|
||||
"compile",
|
||||
False,
|
||||
enable_grouping=False,
|
||||
fail_on_no_tests=True,
|
||||
)
|
||||
assert rc == 1
|
||||
assert "No components found matching" in capsys.readouterr().out
|
||||
|
||||
Reference in New Issue
Block a user