mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'integration' into memory_api
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