mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-29 22:24:26 +00:00 
			
		
		
		
	Group component tests to reduce CI time (#11134)
This commit is contained in:
		| @@ -1002,6 +1002,12 @@ def parse_args(argv): | ||||
|         action="append", | ||||
|         default=[], | ||||
|     ) | ||||
|     options_parser.add_argument( | ||||
|         "--testing-mode", | ||||
|         help="Enable testing mode (disables validation checks for grouped component testing)", | ||||
|         action="store_true", | ||||
|         default=False, | ||||
|     ) | ||||
|  | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description=f"ESPHome {const.__version__}", parents=[options_parser] | ||||
| @@ -1260,6 +1266,7 @@ def run_esphome(argv): | ||||
|  | ||||
|     args = parse_args(argv) | ||||
|     CORE.dashboard = args.dashboard | ||||
|     CORE.testing_mode = args.testing_mode | ||||
|  | ||||
|     # Create address cache from command-line arguments | ||||
|     CORE.address_cache = AddressCache.from_cli_args( | ||||
|   | ||||
| @@ -285,6 +285,10 @@ def consume_connection_slots( | ||||
|  | ||||
| def validate_connection_slots(max_connections: int) -> None: | ||||
|     """Validate that BLE connection slots don't exceed the configured maximum.""" | ||||
|     # Skip validation in testing mode to allow component grouping | ||||
|     if CORE.testing_mode: | ||||
|         return | ||||
|  | ||||
|     ble_data = CORE.data.get(KEY_ESP32_BLE, {}) | ||||
|     used_slots = ble_data.get(KEY_USED_CONNECTION_SLOTS, []) | ||||
|     num_used = len(used_slots) | ||||
|   | ||||
| @@ -347,7 +347,7 @@ def final_validate_device_schema( | ||||
|  | ||||
|     def validate_pin(opt, device): | ||||
|         def validator(value): | ||||
|             if opt in device: | ||||
|             if opt in device and not CORE.testing_mode: | ||||
|                 raise cv.Invalid( | ||||
|                     f"The uart {opt} is used both by {name} and {device[opt]}, " | ||||
|                     f"but can only be used by one. Please create a new uart bus for {name}." | ||||
|   | ||||
| @@ -529,6 +529,8 @@ class EsphomeCore: | ||||
|         self.dashboard = False | ||||
|         # True if command is run from vscode api | ||||
|         self.vscode = False | ||||
|         # True if running in testing mode (disables validation checks for grouped testing) | ||||
|         self.testing_mode = False | ||||
|         # The name of the node | ||||
|         self.name: str | None = None | ||||
|         # The friendly name of the node | ||||
|   | ||||
| @@ -246,12 +246,15 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy | ||||
|                     "\n          to distinguish them" | ||||
|                 ) | ||||
|  | ||||
|             raise cv.Invalid( | ||||
|                 f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. " | ||||
|                 f"{conflict_msg}. " | ||||
|                 "Each entity on a device must have a unique name within its platform." | ||||
|                 f"{sanitized_msg}" | ||||
|             ) | ||||
|             # Skip duplicate entity name validation when testing_mode is enabled | ||||
|             # This flag is used for grouped component testing | ||||
|             if not CORE.testing_mode: | ||||
|                 raise cv.Invalid( | ||||
|                     f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. " | ||||
|                     f"{conflict_msg}. " | ||||
|                     "Each entity on a device must have a unique name within its platform." | ||||
|                     f"{sanitized_msg}" | ||||
|                 ) | ||||
|  | ||||
|         # Store metadata about this entity | ||||
|         entity_metadata: EntityMetadata = { | ||||
|   | ||||
| @@ -118,11 +118,11 @@ class PinRegistry(dict): | ||||
|                         parent_config = fconf.get_config_for_path(parent_path) | ||||
|                         final_val_fun(pin_config, parent_config) | ||||
|                     allow_others = pin_config.get(CONF_ALLOW_OTHER_USES, False) | ||||
|                     if count != 1 and not allow_others: | ||||
|                     if count != 1 and not allow_others and not CORE.testing_mode: | ||||
|                         raise cv.Invalid( | ||||
|                             f"Pin {pin_config[CONF_NUMBER]} is used in multiple places" | ||||
|                         ) | ||||
|                     if count == 1 and allow_others: | ||||
|                     if count == 1 and allow_others and not CORE.testing_mode: | ||||
|                         raise cv.Invalid( | ||||
|                             f"Pin {pin_config[CONF_NUMBER]} incorrectly sets {CONF_ALLOW_OTHER_USES}: true" | ||||
|                         ) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import os | ||||
| from pathlib import Path | ||||
| import re | ||||
| import subprocess | ||||
| from typing import Any | ||||
|  | ||||
| from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE | ||||
| from esphome.core import CORE, EsphomeError | ||||
| @@ -42,6 +43,35 @@ def patch_structhash(): | ||||
|     cli.clean_build_dir = patched_clean_build_dir | ||||
|  | ||||
|  | ||||
| def patch_file_downloader(): | ||||
|     """Patch PlatformIO's FileDownloader to retry on PackageException errors.""" | ||||
|     from platformio.package.download import FileDownloader | ||||
|     from platformio.package.exception import PackageException | ||||
|  | ||||
|     original_init = FileDownloader.__init__ | ||||
|  | ||||
|     def patched_init(self, *args: Any, **kwargs: Any) -> None: | ||||
|         max_retries = 3 | ||||
|  | ||||
|         for attempt in range(max_retries): | ||||
|             try: | ||||
|                 return original_init(self, *args, **kwargs) | ||||
|             except PackageException as e: | ||||
|                 if attempt < max_retries - 1: | ||||
|                     _LOGGER.warning( | ||||
|                         "Package download failed: %s. Retrying... (attempt %d/%d)", | ||||
|                         str(e), | ||||
|                         attempt + 1, | ||||
|                         max_retries, | ||||
|                     ) | ||||
|                 else: | ||||
|                     # Final attempt - re-raise | ||||
|                     raise | ||||
|         return None | ||||
|  | ||||
|     FileDownloader.__init__ = patched_init | ||||
|  | ||||
|  | ||||
| IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})" | ||||
| FILTER_PLATFORMIO_LINES = [ | ||||
|     r"Verbose mode can be enabled via `-v, --verbose` option.*", | ||||
| @@ -99,6 +129,7 @@ def run_platformio_cli(*args, **kwargs) -> str | int: | ||||
|     import platformio.__main__ | ||||
|  | ||||
|     patch_structhash() | ||||
|     patch_file_downloader() | ||||
|     return run_external_command(platformio.__main__.main, *cmd, **kwargs) | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user