mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
		
							
								
								
									
										48
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -180,6 +180,7 @@ jobs: | |||||||
|       memory_impact: ${{ steps.determine.outputs.memory-impact }} |       memory_impact: ${{ steps.determine.outputs.memory-impact }} | ||||||
|       cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }} |       cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }} | ||||||
|       cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }} |       cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }} | ||||||
|  |       component-test-batches: ${{ steps.determine.outputs.component-test-batches }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out code from GitHub |       - name: Check out code from GitHub | ||||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 |         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||||
| @@ -214,6 +215,7 @@ jobs: | |||||||
|           echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT |           echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT | ||||||
|           echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT |           echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT | ||||||
|           echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT |           echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT | ||||||
|  |           echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT | ||||||
|  |  | ||||||
|   integration-tests: |   integration-tests: | ||||||
|     name: Run integration tests |     name: Run integration tests | ||||||
| @@ -458,7 +460,7 @@ jobs: | |||||||
|       GH_TOKEN: ${{ github.token }} |       GH_TOKEN: ${{ github.token }} | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       max-parallel: 1 |       max-parallel: 2 | ||||||
|       matrix: |       matrix: | ||||||
|         include: |         include: | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
| @@ -536,59 +538,18 @@ jobs: | |||||||
|         run: script/ci-suggest-changes |         run: script/ci-suggest-changes | ||||||
|         if: always() |         if: always() | ||||||
|  |  | ||||||
|   test-build-components-splitter: |  | ||||||
|     name: Split components for intelligent grouping (40 weighted per batch) |  | ||||||
|     runs-on: ubuntu-24.04 |  | ||||||
|     needs: |  | ||||||
|       - common |  | ||||||
|       - determine-jobs |  | ||||||
|     if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 |  | ||||||
|     outputs: |  | ||||||
|       matrix: ${{ steps.split.outputs.components }} |  | ||||||
|     steps: |  | ||||||
|       - name: Check out code from GitHub |  | ||||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 |  | ||||||
|       - name: Restore Python |  | ||||||
|         uses: ./.github/actions/restore-python |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ env.DEFAULT_PYTHON }} |  | ||||||
|           cache-key: ${{ needs.common.outputs.cache-key }} |  | ||||||
|       - name: Split components intelligently based on bus configurations |  | ||||||
|         id: split |  | ||||||
|         run: | |  | ||||||
|           . venv/bin/activate |  | ||||||
|  |  | ||||||
|           # Use intelligent splitter that groups components with same bus configs |  | ||||||
|           components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}' |  | ||||||
|  |  | ||||||
|           # Only isolate directly changed components when targeting dev branch |  | ||||||
|           # For beta/release branches, group everything for faster CI |  | ||||||
|           if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then |  | ||||||
|             directly_changed='[]' |  | ||||||
|             echo "Target branch: ${{ github.base_ref }} - grouping all components" |  | ||||||
|           else |  | ||||||
|             directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}' |  | ||||||
|             echo "Target branch: ${{ github.base_ref }} - isolating directly changed components" |  | ||||||
|           fi |  | ||||||
|  |  | ||||||
|           echo "Splitting components intelligently..." |  | ||||||
|           output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github) |  | ||||||
|  |  | ||||||
|           echo "$output" >> $GITHUB_OUTPUT |  | ||||||
|  |  | ||||||
|   test-build-components-split: |   test-build-components-split: | ||||||
|     name: Test components batch (${{ matrix.components }}) |     name: Test components batch (${{ matrix.components }}) | ||||||
|     runs-on: ubuntu-24.04 |     runs-on: ubuntu-24.04 | ||||||
|     needs: |     needs: | ||||||
|       - common |       - common | ||||||
|       - determine-jobs |       - determine-jobs | ||||||
|       - test-build-components-splitter |  | ||||||
|     if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 |     if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }} |       max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }} | ||||||
|       matrix: |       matrix: | ||||||
|         components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }} |         components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Show disk space |       - name: Show disk space | ||||||
|         run: | |         run: | | ||||||
| @@ -980,7 +941,6 @@ jobs: | |||||||
|       - clang-tidy-nosplit |       - clang-tidy-nosplit | ||||||
|       - clang-tidy-split |       - clang-tidy-split | ||||||
|       - determine-jobs |       - determine-jobs | ||||||
|       - test-build-components-splitter |  | ||||||
|       - test-build-components-split |       - test-build-components-split | ||||||
|       - pre-commit-ci-lite |       - pre-commit-ci-lite | ||||||
|       - memory-impact-target-branch |       - memory-impact-target-branch | ||||||
|   | |||||||
| @@ -125,7 +125,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() { | |||||||
|  |  | ||||||
|       case IMAGE_TYPE_RGB: |       case IMAGE_TYPE_RGB: | ||||||
| #if LV_COLOR_DEPTH == 32 | #if LV_COLOR_DEPTH == 32 | ||||||
|         switch (this->transparent_) { |         switch (this->transparency_) { | ||||||
|           case TRANSPARENCY_ALPHA_CHANNEL: |           case TRANSPARENCY_ALPHA_CHANNEL: | ||||||
|             this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; |             this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; | ||||||
|             break; |             break; | ||||||
| @@ -156,7 +156,8 @@ lv_img_dsc_t *Image::get_lv_img_dsc() { | |||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| #else | #else | ||||||
|         this->dsc_.header.cf = this->transparent_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565; |         this->dsc_.header.cf = | ||||||
|  |             this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565; | ||||||
| #endif | #endif | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ from ..lv_validation import ( | |||||||
|     pixels, |     pixels, | ||||||
|     size, |     size, | ||||||
| ) | ) | ||||||
| from ..lvcode import LocalVariable, lv, lv_assign | from ..lvcode import LocalVariable, lv, lv_assign, lv_expr | ||||||
| from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema | from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema | ||||||
| from ..types import LvType, ObjUpdateAction, WidgetType | from ..types import LvType, ObjUpdateAction, WidgetType | ||||||
| from . import Widget, get_widgets | from . import Widget, get_widgets | ||||||
| @@ -70,15 +70,18 @@ class CanvasType(WidgetType): | |||||||
|         width = config[CONF_WIDTH] |         width = config[CONF_WIDTH] | ||||||
|         height = config[CONF_HEIGHT] |         height = config[CONF_HEIGHT] | ||||||
|         use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else "" |         use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else "" | ||||||
|         lv.canvas_set_buffer( |         buf_size = literal( | ||||||
|             w.obj, |             f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})" | ||||||
|             lv.custom_mem_alloc( |  | ||||||
|                 literal(f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})") |  | ||||||
|             ), |  | ||||||
|             width, |  | ||||||
|             height, |  | ||||||
|             literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"), |  | ||||||
|         ) |         ) | ||||||
|  |         with LocalVariable("buf", cg.void, lv_expr.custom_mem_alloc(buf_size)) as buf: | ||||||
|  |             cg.add(cg.RawExpression(f"memset({buf}, 0, {buf_size});")) | ||||||
|  |             lv.canvas_set_buffer( | ||||||
|  |                 w.obj, | ||||||
|  |                 buf, | ||||||
|  |                 width, | ||||||
|  |                 height, | ||||||
|  |                 literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"), | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |  | ||||||
| canvas_spec = CanvasType() | canvas_spec = CanvasType() | ||||||
|   | |||||||
| @@ -43,12 +43,14 @@ from enum import StrEnum | |||||||
| from functools import cache | from functools import cache | ||||||
| import json | import json | ||||||
| import os | import os | ||||||
|  | from pathlib import Path | ||||||
| import subprocess | import subprocess | ||||||
| import sys | import sys | ||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| from helpers import ( | from helpers import ( | ||||||
|     CPP_FILE_EXTENSIONS, |     CPP_FILE_EXTENSIONS, | ||||||
|  |     ESPHOME_TESTS_COMPONENTS_PATH, | ||||||
|     PYTHON_FILE_EXTENSIONS, |     PYTHON_FILE_EXTENSIONS, | ||||||
|     changed_files, |     changed_files, | ||||||
|     core_changed, |     core_changed, | ||||||
| @@ -65,12 +67,17 @@ from helpers import ( | |||||||
|     parse_test_filename, |     parse_test_filename, | ||||||
|     root_path, |     root_path, | ||||||
| ) | ) | ||||||
|  | from split_components_for_ci import create_intelligent_batches | ||||||
|  |  | ||||||
| # Threshold for splitting clang-tidy jobs | # Threshold for splitting clang-tidy jobs | ||||||
| # For small PRs (< 65 files), use nosplit for faster CI | # For small PRs (< 65 files), use nosplit for faster CI | ||||||
| # For large PRs (>= 65 files), use split for better parallelization | # For large PRs (>= 65 files), use split for better parallelization | ||||||
| CLANG_TIDY_SPLIT_THRESHOLD = 65 | CLANG_TIDY_SPLIT_THRESHOLD = 65 | ||||||
|  |  | ||||||
|  | # Component test batch size (weighted) | ||||||
|  | # Isolated components count as 10x, groupable components count as 1x | ||||||
|  | COMPONENT_TEST_BATCH_SIZE = 40 | ||||||
|  |  | ||||||
|  |  | ||||||
| class Platform(StrEnum): | class Platform(StrEnum): | ||||||
|     """Platform identifiers for memory impact analysis.""" |     """Platform identifiers for memory impact analysis.""" | ||||||
| @@ -686,6 +693,22 @@ def main() -> None: | |||||||
|     # Determine which C++ unit tests to run |     # Determine which C++ unit tests to run | ||||||
|     cpp_run_all, cpp_components = determine_cpp_unit_tests(args.branch) |     cpp_run_all, cpp_components = determine_cpp_unit_tests(args.branch) | ||||||
|  |  | ||||||
|  |     # Split components into batches for CI testing | ||||||
|  |     # This intelligently groups components with similar bus configurations | ||||||
|  |     component_test_batches: list[str] | ||||||
|  |     if changed_components_with_tests: | ||||||
|  |         tests_dir = Path(root_path) / ESPHOME_TESTS_COMPONENTS_PATH | ||||||
|  |         batches, _ = create_intelligent_batches( | ||||||
|  |             components=changed_components_with_tests, | ||||||
|  |             tests_dir=tests_dir, | ||||||
|  |             batch_size=COMPONENT_TEST_BATCH_SIZE, | ||||||
|  |             directly_changed=directly_changed_with_tests, | ||||||
|  |         ) | ||||||
|  |         # Convert batches to space-separated strings for CI matrix | ||||||
|  |         component_test_batches = [" ".join(batch) for batch in batches] | ||||||
|  |     else: | ||||||
|  |         component_test_batches = [] | ||||||
|  |  | ||||||
|     output: dict[str, Any] = { |     output: dict[str, Any] = { | ||||||
|         "integration_tests": run_integration, |         "integration_tests": run_integration, | ||||||
|         "clang_tidy": run_clang_tidy, |         "clang_tidy": run_clang_tidy, | ||||||
| @@ -703,6 +726,7 @@ def main() -> None: | |||||||
|         "memory_impact": memory_impact, |         "memory_impact": memory_impact, | ||||||
|         "cpp_unit_tests_run_all": cpp_run_all, |         "cpp_unit_tests_run_all": cpp_run_all, | ||||||
|         "cpp_unit_tests_components": cpp_components, |         "cpp_unit_tests_components": cpp_components, | ||||||
|  |         "component_test_batches": component_test_batches, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     # Output as JSON |     # Output as JSON | ||||||
|   | |||||||
| @@ -62,6 +62,10 @@ def create_intelligent_batches( | |||||||
| ) -> tuple[list[list[str]], dict[tuple[str, str], list[str]]]: | ) -> tuple[list[list[str]], dict[tuple[str, str], list[str]]]: | ||||||
|     """Create batches optimized for component grouping. |     """Create batches optimized for component grouping. | ||||||
|  |  | ||||||
|  |     IMPORTANT: This function is called from both split_components_for_ci.py (standalone script) | ||||||
|  |     and determine-jobs.py (integrated into job determination). Be careful when refactoring | ||||||
|  |     to ensure changes work in both contexts. | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         components: List of component names to batch |         components: List of component names to batch | ||||||
|         tests_dir: Path to tests/components directory |         tests_dir: Path to tests/components directory | ||||||
|   | |||||||
| @@ -18,7 +18,8 @@ def test_gpio_binary_sensor_basic_setup( | |||||||
|  |  | ||||||
|     assert "new gpio::GPIOBinarySensor();" in main_cpp |     assert "new gpio::GPIOBinarySensor();" in main_cpp | ||||||
|     assert "App.register_binary_sensor" in main_cpp |     assert "App.register_binary_sensor" in main_cpp | ||||||
|     assert "bs_gpio->set_use_interrupt(true);" in main_cpp |     # set_use_interrupt(true) should NOT be generated (uses C++ default) | ||||||
|  |     assert "bs_gpio->set_use_interrupt(true);" not in main_cpp | ||||||
|     assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp |     assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -51,8 +52,8 @@ def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt( | |||||||
|         "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" |         "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     # GPIO5 should still use interrupts |     # GPIO5 should still use interrupts (default, so no setter call) | ||||||
|     assert "bs_gpio5->set_use_interrupt(true);" in main_cpp |     assert "bs_gpio5->set_use_interrupt(true);" not in main_cpp | ||||||
|     assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp |     assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -152,6 +152,14 @@ def test_main_all_tests_should_run( | |||||||
|     assert output["memory_impact"]["should_run"] == "false" |     assert output["memory_impact"]["should_run"] == "false" | ||||||
|     assert output["cpp_unit_tests_run_all"] is False |     assert output["cpp_unit_tests_run_all"] is False | ||||||
|     assert output["cpp_unit_tests_components"] == ["wifi", "api", "sensor"] |     assert output["cpp_unit_tests_components"] == ["wifi", "api", "sensor"] | ||||||
|  |     # component_test_batches should be present and be a list of space-separated strings | ||||||
|  |     assert "component_test_batches" in output | ||||||
|  |     assert isinstance(output["component_test_batches"], list) | ||||||
|  |     # Each batch should be a space-separated string of component names | ||||||
|  |     for batch in output["component_test_batches"]: | ||||||
|  |         assert isinstance(batch, str) | ||||||
|  |         # Should contain at least one component (no empty batches) | ||||||
|  |         assert len(batch) > 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_main_no_tests_should_run( | def test_main_no_tests_should_run( | ||||||
| @@ -209,6 +217,9 @@ def test_main_no_tests_should_run( | |||||||
|     assert output["memory_impact"]["should_run"] == "false" |     assert output["memory_impact"]["should_run"] == "false" | ||||||
|     assert output["cpp_unit_tests_run_all"] is False |     assert output["cpp_unit_tests_run_all"] is False | ||||||
|     assert output["cpp_unit_tests_components"] == [] |     assert output["cpp_unit_tests_components"] == [] | ||||||
|  |     # component_test_batches should be empty list | ||||||
|  |     assert "component_test_batches" in output | ||||||
|  |     assert output["component_test_batches"] == [] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_main_with_branch_argument( | def test_main_with_branch_argument( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user