mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00: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 }}
 | 
			
		||||
      python-linters: ${{ steps.determine.outputs.python-linters }}
 | 
			
		||||
      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 }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
@@ -204,6 +205,7 @@ jobs:
 | 
			
		||||
          echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $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-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
 | 
			
		||||
 | 
			
		||||
  integration-tests:
 | 
			
		||||
@@ -367,7 +369,7 @@ jobs:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      max-parallel: 2
 | 
			
		||||
      matrix:
 | 
			
		||||
        file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
 | 
			
		||||
        file: ${{ fromJson(needs.determine-jobs.outputs.changed-components-with-tests) }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Cache apt packages
 | 
			
		||||
        uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
 | 
			
		||||
@@ -414,7 +416,7 @@ jobs:
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
 | 
			
		||||
          # 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..."
 | 
			
		||||
          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)
 | 
			
		||||
    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
 | 
			
		||||
    output: dict[str, Any] = {
 | 
			
		||||
        "integration_tests": run_integration,
 | 
			
		||||
@@ -244,7 +254,8 @@ def main() -> None:
 | 
			
		||||
        "clang_format": run_clang_format,
 | 
			
		||||
        "python_linters": run_python_linters,
 | 
			
		||||
        "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
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from collections.abc import Generator
 | 
			
		||||
import importlib.util
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
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["python_linters"] is True
 | 
			
		||||
    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(
 | 
			
		||||
@@ -125,6 +132,7 @@ def test_main_no_tests_should_run(
 | 
			
		||||
    assert output["clang_format"] is False
 | 
			
		||||
    assert output["python_linters"] is False
 | 
			
		||||
    assert output["changed_components"] == []
 | 
			
		||||
    assert output["changed_components_with_tests"] == []
 | 
			
		||||
    assert output["component_test_count"] == 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -197,7 +205,13 @@ def test_main_with_branch_argument(
 | 
			
		||||
    assert output["clang_format"] is False
 | 
			
		||||
    assert output["python_linters"] is True
 | 
			
		||||
    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(
 | 
			
		||||
@@ -377,3 +391,60 @@ def test_should_run_clang_format_with_branch() -> None:
 | 
			
		||||
        mock_changed.return_value = []
 | 
			
		||||
        determine_jobs.should_run_clang_format("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