mirror of
https://github.com/esphome/esphome.git
synced 2025-10-13 23:33:48 +01:00
Merge branch 'dev' into improv_cap_portal_fix
This commit is contained in:
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -177,6 +177,7 @@ jobs:
|
|||||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||||
|
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
||||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
@@ -204,6 +205,7 @@ jobs:
|
|||||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||||
|
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
@@ -367,7 +369,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
|
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components-with-tests) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Cache apt packages
|
- name: Cache apt packages
|
||||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
|
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
|
||||||
@@ -414,7 +416,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
|
||||||
# Use intelligent splitter that groups components with same bus configs
|
# Use intelligent splitter that groups components with same bus configs
|
||||||
components='${{ needs.determine-jobs.outputs.changed-components }}'
|
components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
|
||||||
|
|
||||||
echo "Splitting components intelligently..."
|
echo "Splitting components intelligently..."
|
||||||
output=$(python3 script/split_components_for_ci.py --components "$components" --batch-size 40 --output github)
|
output=$(python3 script/split_components_for_ci.py --components "$components" --batch-size 40 --output github)
|
||||||
|
@@ -237,6 +237,16 @@ def main() -> None:
|
|||||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
changed_components = parse_list_components_output(result.stdout)
|
changed_components = parse_list_components_output(result.stdout)
|
||||||
|
|
||||||
|
# Filter to only components that have test files
|
||||||
|
# Components without tests shouldn't generate CI test jobs
|
||||||
|
tests_dir = Path(root_path) / "tests" / "components"
|
||||||
|
changed_components_with_tests = [
|
||||||
|
component
|
||||||
|
for component in changed_components
|
||||||
|
if (component_test_dir := tests_dir / component).exists()
|
||||||
|
and any(component_test_dir.glob("test.*.yaml"))
|
||||||
|
]
|
||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
output: dict[str, Any] = {
|
output: dict[str, Any] = {
|
||||||
"integration_tests": run_integration,
|
"integration_tests": run_integration,
|
||||||
@@ -244,7 +254,8 @@ def main() -> None:
|
|||||||
"clang_format": run_clang_format,
|
"clang_format": run_clang_format,
|
||||||
"python_linters": run_python_linters,
|
"python_linters": run_python_linters,
|
||||||
"changed_components": changed_components,
|
"changed_components": changed_components,
|
||||||
"component_test_count": len(changed_components),
|
"changed_components_with_tests": changed_components_with_tests,
|
||||||
|
"component_test_count": len(changed_components_with_tests),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Output as JSON
|
# Output as JSON
|
||||||
|
@@ -4,6 +4,7 @@ from collections.abc import Generator
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from unittest.mock import Mock, call, patch
|
from unittest.mock import Mock, call, patch
|
||||||
@@ -90,7 +91,13 @@ def test_main_all_tests_should_run(
|
|||||||
assert output["clang_format"] is True
|
assert output["clang_format"] is True
|
||||||
assert output["python_linters"] is True
|
assert output["python_linters"] is True
|
||||||
assert output["changed_components"] == ["wifi", "api", "sensor"]
|
assert output["changed_components"] == ["wifi", "api", "sensor"]
|
||||||
assert output["component_test_count"] == 3
|
# changed_components_with_tests will only include components that actually have test files
|
||||||
|
assert "changed_components_with_tests" in output
|
||||||
|
assert isinstance(output["changed_components_with_tests"], list)
|
||||||
|
# component_test_count matches number of components with tests
|
||||||
|
assert output["component_test_count"] == len(
|
||||||
|
output["changed_components_with_tests"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_main_no_tests_should_run(
|
def test_main_no_tests_should_run(
|
||||||
@@ -125,6 +132,7 @@ def test_main_no_tests_should_run(
|
|||||||
assert output["clang_format"] is False
|
assert output["clang_format"] is False
|
||||||
assert output["python_linters"] is False
|
assert output["python_linters"] is False
|
||||||
assert output["changed_components"] == []
|
assert output["changed_components"] == []
|
||||||
|
assert output["changed_components_with_tests"] == []
|
||||||
assert output["component_test_count"] == 0
|
assert output["component_test_count"] == 0
|
||||||
|
|
||||||
|
|
||||||
@@ -197,7 +205,13 @@ def test_main_with_branch_argument(
|
|||||||
assert output["clang_format"] is False
|
assert output["clang_format"] is False
|
||||||
assert output["python_linters"] is True
|
assert output["python_linters"] is True
|
||||||
assert output["changed_components"] == ["mqtt"]
|
assert output["changed_components"] == ["mqtt"]
|
||||||
assert output["component_test_count"] == 1
|
# changed_components_with_tests will only include components that actually have test files
|
||||||
|
assert "changed_components_with_tests" in output
|
||||||
|
assert isinstance(output["changed_components_with_tests"], list)
|
||||||
|
# component_test_count matches number of components with tests
|
||||||
|
assert output["component_test_count"] == len(
|
||||||
|
output["changed_components_with_tests"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_should_run_integration_tests(
|
def test_should_run_integration_tests(
|
||||||
@@ -377,3 +391,60 @@ def test_should_run_clang_format_with_branch() -> None:
|
|||||||
mock_changed.return_value = []
|
mock_changed.return_value = []
|
||||||
determine_jobs.should_run_clang_format("release")
|
determine_jobs.should_run_clang_format("release")
|
||||||
mock_changed.assert_called_once_with("release")
|
mock_changed.assert_called_once_with("release")
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_filters_components_without_tests(
|
||||||
|
mock_should_run_integration_tests: Mock,
|
||||||
|
mock_should_run_clang_tidy: Mock,
|
||||||
|
mock_should_run_clang_format: Mock,
|
||||||
|
mock_should_run_python_linters: Mock,
|
||||||
|
mock_subprocess_run: Mock,
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Test that components without test files are filtered out."""
|
||||||
|
mock_should_run_integration_tests.return_value = False
|
||||||
|
mock_should_run_clang_tidy.return_value = False
|
||||||
|
mock_should_run_clang_format.return_value = False
|
||||||
|
mock_should_run_python_linters.return_value = False
|
||||||
|
|
||||||
|
# Mock list-components.py output with 3 components
|
||||||
|
# wifi: has tests, sensor: has tests, airthings_ble: no tests
|
||||||
|
mock_result = Mock()
|
||||||
|
mock_result.stdout = "wifi\nsensor\nairthings_ble\n"
|
||||||
|
mock_subprocess_run.return_value = mock_result
|
||||||
|
|
||||||
|
# Create test directory structure
|
||||||
|
tests_dir = tmp_path / "tests" / "components"
|
||||||
|
|
||||||
|
# wifi has tests
|
||||||
|
wifi_dir = tests_dir / "wifi"
|
||||||
|
wifi_dir.mkdir(parents=True)
|
||||||
|
(wifi_dir / "test.esp32.yaml").write_text("test: config")
|
||||||
|
|
||||||
|
# sensor has tests
|
||||||
|
sensor_dir = tests_dir / "sensor"
|
||||||
|
sensor_dir.mkdir(parents=True)
|
||||||
|
(sensor_dir / "test.esp8266.yaml").write_text("test: config")
|
||||||
|
|
||||||
|
# airthings_ble exists but has no test files
|
||||||
|
airthings_dir = tests_dir / "airthings_ble"
|
||||||
|
airthings_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
# Mock root_path to use tmp_path
|
||||||
|
with (
|
||||||
|
patch.object(determine_jobs, "root_path", str(tmp_path)),
|
||||||
|
patch("sys.argv", ["determine-jobs.py"]),
|
||||||
|
):
|
||||||
|
determine_jobs.main()
|
||||||
|
|
||||||
|
# Check output
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
output = json.loads(captured.out)
|
||||||
|
|
||||||
|
# changed_components should have all components
|
||||||
|
assert set(output["changed_components"]) == {"wifi", "sensor", "airthings_ble"}
|
||||||
|
# changed_components_with_tests should only have components with test files
|
||||||
|
assert set(output["changed_components_with_tests"]) == {"wifi", "sensor"}
|
||||||
|
# component_test_count should be based on components with tests
|
||||||
|
assert output["component_test_count"] == 2
|
||||||
|
Reference in New Issue
Block a user