From 11298ca485363148a8639bde72a8c1c38e4cfb8a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 Oct 2025 18:05:49 -1000 Subject: [PATCH] uart grouping --- esphome/components/uart/__init__.py | 2 +- script/analyze_component_buses.py | 20 +++-- script/test_build_components | 120 +--------------------------- script/test_build_components.py | 2 +- 4 files changed, 18 insertions(+), 126 deletions(-) mode change 100755 => 120000 script/test_build_components diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 764576744f..f8f927d469 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -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}." diff --git a/script/analyze_component_buses.py b/script/analyze_component_buses.py index 2818cc41b1..2f77d9b223 100644 --- a/script/analyze_component_buses.py +++ b/script/analyze_component_buses.py @@ -50,16 +50,26 @@ VALID_BUS_CONFIGS = { "uart_9600_even", } -# Bus types that support component grouping +# Bus types that support component grouping for config validation # I2C and SPI are shared buses with addressing/CS pins # BLE sensors scan independently and don't conflict -# UART is point-to-point and components would conflict on the same bus +# UART is point-to-point but can be grouped for testing since --testing-mode +# bypasses the conflict validation (we only care that configs compile) GROUPABLE_BUS_TYPES = { "ble", "i2c", "i2c_low_freq", "qspi", "spi", + "uart", + "uart_115200", + "uart_1200", + "uart_1200_even", + "uart_19200", + "uart_38400", + "uart_4800", + "uart_4800_even", + "uart_9600_even", } @@ -194,15 +204,15 @@ def create_grouping_signature( """Create a signature string for grouping components. Components with the same signature can be grouped together for testing. - Only includes groupable bus types (I2C, SPI, BLE) - excludes point-to-point - protocols like UART that can't be shared. + Includes groupable bus types (I2C, SPI, BLE, UART) that can be validated + together using --testing-mode to bypass runtime conflicts. Args: platform_buses: Mapping of platform to list of buses platform: The specific platform to create signature for Returns: - Signature string (e.g., "i2c" or "spi") or empty if no groupable buses + Signature string (e.g., "i2c" or "uart") or empty if no groupable buses """ buses = platform_buses.get(platform, []) if not buses: diff --git a/script/test_build_components b/script/test_build_components deleted file mode 100755 index 49c66f1407..0000000000 --- a/script/test_build_components +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env bash - -set -e - -help() { - echo "Usage: $0 [-e ] [-c ] [-t ] [-f]" 1>&2 - echo 1>&2 - echo " - e - Parameter for esphome command. Default compile. Common alternative is config." 1>&2 - echo " - c - Component folder name(s) to test. Default *. Supports comma-separated list. E.g. '-c logger' or '-c logger,api,ota'." 1>&2 - echo " - t - Target name to test. Put '-t list' to display all possibilities. E.g. '-t esp32-s2-idf-51'." 1>&2 - echo " - f - Continue on fail. Don't exit on first error." 1>&2 - exit 1 -} - -# Parse parameter: -# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`. -# - `c` - Component folder name to test. Default `*`. -# - `f` - Continue on fail flag. -esphome_command="compile" -target_component="*" -continue_on_fail=false -while getopts e:c:t:f flag -do - case $flag in - e) esphome_command=${OPTARG};; - c) target_component=${OPTARG};; - t) requested_target_platform=${OPTARG};; - f) continue_on_fail=true;; - \?) help;; - esac -done - -cd "$(dirname "$0")/.." - -if ! [ -d "./tests/test_build_components/build" ]; then - mkdir ./tests/test_build_components/build -fi - -start_esphome() { - if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform_with_version" ]; then - echo "Skipping $target_platform_with_version" - return - fi - # create dynamic yaml file in `build` folder. - # `./tests/test_build_components/build/[target_component].[test_name].[target_platform_with_version].yaml` - component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform_with_version.yaml" - - cp $target_platform_file $component_test_file - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS sed is...different - sed -i '' "s!\$component_test_file!../../.$f!g" $component_test_file - else - sed -i "s!\$component_test_file!../../.$f!g" $component_test_file - fi - - # Start esphome process - echo "> [$target_component] [$test_name] [$target_platform_with_version]" - set -x - # TODO: Validate escape of Command line substitution value - if [ "$continue_on_fail" = true ]; then - python3 -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file || true - else - python3 -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file - fi - { set +x; } 2>/dev/null -} - -# Split comma-separated components into array -IFS=',' read -r -a component_list <<< "$target_component" - -# Find all test yaml files. -# - `./tests/components/[target_component]/[test_name].[target_platform].yaml` -# - `./tests/components/[target_component]/[test_name].all.yaml` -for component_pattern in "${component_list[@]}"; do - for f in ./tests/components/$component_pattern/*.*.yaml; do - [ -f "$f" ] || continue - IFS='/' read -r -a folder_name <<< "$f" - target_component="${folder_name[3]}" - - IFS='.' read -r -a file_name <<< "${folder_name[4]}" - test_name="${file_name[0]}" - target_platform="${file_name[1]}" - file_name_parts=${#file_name[@]} - - if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then - # Test has *not* defined a specific target platform. Need to run tests for all possible target platforms. - - for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do - IFS='/' read -r -a folder_name <<< "$target_platform_file" - IFS='.' read -r -a file_name <<< "${folder_name[3]}" - target_platform="${file_name[1]}" - - start_esphome - done - - else - # Test has defined a specific target platform. - - # Validate we have a base test yaml for selected platform. - # The target_platform is sourced from the following location. - # 1. `./tests/test_build_components/build_components_base.[target_platform].yaml` - # 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml` - target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml" - if ! [ -f "$target_platform_file" ]; then - echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml] for component test [$f] found." - exit 1 - fi - - for target_platform_file in ./tests/test_build_components/build_components_base.$target_platform*.yaml; do - # trim off "./tests/test_build_components/build_components_base." prefix - target_platform_with_version=${target_platform_file:52} - # ...now remove suffix starting with "." leaving just the test target hardware and software platform (possibly with version) - # For example: "esp32-s3-idf-50" - target_platform_with_version=${target_platform_with_version%.*} - start_esphome - done - fi - done -done diff --git a/script/test_build_components b/script/test_build_components new file mode 120000 index 0000000000..832a4a72c6 --- /dev/null +++ b/script/test_build_components @@ -0,0 +1 @@ +test_build_components.py \ No newline at end of file diff --git a/script/test_build_components.py b/script/test_build_components.py index f8874266cb..ee8ed193b5 100755 --- a/script/test_build_components.py +++ b/script/test_build_components.py @@ -336,7 +336,7 @@ def run_grouped_component_tests( if buses: signature = create_grouping_signature({platform: buses}, platform) # Only add to grouped_components if signature is non-empty - # (empty means only non-groupable buses like UART) + # (empty means component has no groupable bus configurations) if signature: grouped_components[(platform, signature)].append(component)