mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	revert
This commit is contained in:
		| @@ -877,6 +877,11 @@ async def to_code(config): | ||||
|     for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"): | ||||
|         os.environ.pop(clean_var, None) | ||||
|  | ||||
|     # Set the location of the IDF component manager cache | ||||
|     os.environ["IDF_COMPONENT_CACHE_PATH"] = str( | ||||
|         CORE.relative_internal_path(".espressif") | ||||
|     ) | ||||
|  | ||||
|     add_extra_script( | ||||
|         "post", | ||||
|         "post_build.py", | ||||
|   | ||||
| @@ -61,6 +61,10 @@ void AddressableLightTransformer::start() { | ||||
|   this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state()); | ||||
| } | ||||
|  | ||||
| inline constexpr uint8_t subtract_scaled_difference(uint8_t a, uint8_t b, int32_t scale) { | ||||
|   return uint8_t(int32_t(a) - (((int32_t(a) - int32_t(b)) * scale) / 256)); | ||||
| } | ||||
|  | ||||
| optional<LightColorValues> AddressableLightTransformer::apply() { | ||||
|   float smoothed_progress = LightTransformer::smoothed_progress(this->get_progress_()); | ||||
|  | ||||
| @@ -74,38 +78,37 @@ optional<LightColorValues> AddressableLightTransformer::apply() { | ||||
|   // all LEDs, we use the current state of each LED as the start. | ||||
|  | ||||
|   // We can't use a direct lerp smoothing here though - that would require creating a copy of the original | ||||
|   // state of each LED at the start of the transition. | ||||
|   // Instead, we "fake" the look of the LERP by using an exponential average over time and using | ||||
|   // dynamically-calculated alpha values to match the look. | ||||
|   // state of each LED at the start of the transition. Instead, we "fake" the look of lerp by calculating | ||||
|   // the delta between the current state and the target state, assuming that the delta represents the rest | ||||
|   // of the transition that was to be applied as of the previous transition step, and scaling the delta for | ||||
|   // what should be left after the current transition step. In this manner, the delta decays to zero as the | ||||
|   // transition progresses. | ||||
|   // | ||||
|   // Here's an example of how the algorithm progresses in discrete steps: | ||||
|   // | ||||
|   // At time = 0.00, 0% complete, 100% remaining, 100% will remain after this step, so the scale is 100% / 100% = 100%. | ||||
|   // At time = 0.10, 0% complete, 100% remaining, 90% will remain after this step, so the scale is 90% / 100% = 90%. | ||||
|   // At time = 0.20, 10% complete, 90% remaining, 80% will remain after this step, so the scale is 80% / 90% = 88.9%. | ||||
|   // At time = 0.50, 20% complete, 80% remaining, 50% will remain after this step, so the scale is 50% / 80% = 62.5%. | ||||
|   // At time = 0.90, 50% complete, 50% remaining, 10% will remain after this step, so the scale is 10% / 50% = 20%. | ||||
|   // At time = 0.91, 90% complete, 10% remaining, 9% will remain after this step, so the scale is 9% / 10% = 90%. | ||||
|   // At time = 1.00, 91% complete, 9% remaining, 0% will remain after this step, so the scale is 0% / 9% = 0%. | ||||
|   // | ||||
|   // Because the color values are quantized to 8 bit resolution after each step, the transition may appear | ||||
|   // non-linear when applying small deltas. | ||||
|  | ||||
|   float denom = (1.0f - smoothed_progress); | ||||
|   float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom; | ||||
|  | ||||
|   // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length | ||||
|   // We solve this by accumulating the fractional part of the alpha over time. | ||||
|   float alpha255 = alpha * 255.0f; | ||||
|   float alpha255int = floorf(alpha255); | ||||
|   float alpha255remainder = alpha255 - alpha255int; | ||||
|  | ||||
|   this->accumulated_alpha_ += alpha255remainder; | ||||
|   float alpha_add = floorf(this->accumulated_alpha_); | ||||
|   this->accumulated_alpha_ -= alpha_add; | ||||
|  | ||||
|   alpha255 += alpha_add; | ||||
|   alpha255 = clamp(alpha255, 0.0f, 255.0f); | ||||
|   auto alpha8 = static_cast<uint8_t>(alpha255); | ||||
|  | ||||
|   if (alpha8 != 0) { | ||||
|     uint8_t inv_alpha8 = 255 - alpha8; | ||||
|     Color add = this->target_color_ * alpha8; | ||||
|  | ||||
|     for (auto led : this->light_) | ||||
|       led.set(add + led.get() * inv_alpha8); | ||||
|   if (smoothed_progress > this->last_transition_progress_ && this->last_transition_progress_ < 1.f) { | ||||
|     int32_t scale = int32_t(256.f * std::max((1.f - smoothed_progress) / (1.f - this->last_transition_progress_), 0.f)); | ||||
|     for (auto led : this->light_) { | ||||
|       led.set_rgbw(subtract_scaled_difference(this->target_color_.red, led.get_red(), scale), | ||||
|                    subtract_scaled_difference(this->target_color_.green, led.get_green(), scale), | ||||
|                    subtract_scaled_difference(this->target_color_.blue, led.get_blue(), scale), | ||||
|                    subtract_scaled_difference(this->target_color_.white, led.get_white(), scale)); | ||||
|     } | ||||
|     this->last_transition_progress_ = smoothed_progress; | ||||
|     this->light_.schedule_show(); | ||||
|   } | ||||
|  | ||||
|   this->last_transition_progress_ = smoothed_progress; | ||||
|   this->light_.schedule_show(); | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -113,7 +113,6 @@ class AddressableLightTransformer : public LightTransformer { | ||||
|  protected: | ||||
|   AddressableLight &light_; | ||||
|   float last_transition_progress_{0.0f}; | ||||
|   float accumulated_alpha_{0.0f}; | ||||
|   Color target_color_{}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -8,34 +8,35 @@ | ||||
| 
 | ||||
| namespace esphome { | ||||
| 
 | ||||
| /// Generic bitmask for storing a set of enum values efficiently.
 | ||||
| /// Replaces std::set<EnumType> to eliminate red-black tree overhead (~586 bytes per instantiation).
 | ||||
| /// Generic bitmask for storing a finite set of discrete values efficiently.
 | ||||
| /// Replaces std::set<ValueType> to eliminate red-black tree overhead (~586 bytes per instantiation).
 | ||||
| ///
 | ||||
| /// Template parameters:
 | ||||
| ///   EnumType: The enum type to store (must be uint8_t-based)
 | ||||
| ///   ValueType: The type to store (typically enum, but can be any discrete bounded type)
 | ||||
| ///   MaxBits: Maximum number of bits needed (auto-selects uint8_t/uint16_t/uint32_t)
 | ||||
| ///
 | ||||
| /// Requirements:
 | ||||
| ///   - EnumType must be an enum with sequential values starting from 0
 | ||||
| ///   - Specialization must provide enum_to_bit() and bit_to_enum() static methods
 | ||||
| ///   - MaxBits must be sufficient to hold all enum values
 | ||||
| ///   - ValueType must have a bounded discrete range that maps to bit positions
 | ||||
| ///   - Specialization must provide value_to_bit() and bit_to_value() static methods
 | ||||
| ///   - MaxBits must be sufficient to hold all possible values
 | ||||
| ///
 | ||||
| /// Example usage:
 | ||||
| ///   using ClimateModeMask = EnumBitmask<ClimateMode, 8>;
 | ||||
| ///   using ClimateModeMask = FiniteSetMask<ClimateMode, 8>;
 | ||||
| ///   ClimateModeMask modes({CLIMATE_MODE_HEAT, CLIMATE_MODE_COOL});
 | ||||
| ///   if (modes.count(CLIMATE_MODE_HEAT)) { ... }
 | ||||
| ///   for (auto mode : modes) { ... }  // Iterate over set bits
 | ||||
| ///
 | ||||
| /// For complete usage examples with template specializations, see:
 | ||||
| ///   - esphome/components/light/color_mode.h (ColorMode example)
 | ||||
| ///   - esphome/components/light/color_mode.h (ColorMode enum example)
 | ||||
| ///
 | ||||
| /// Design notes:
 | ||||
| ///   - Uses compile-time type selection for optimal size (uint8_t/uint16_t/uint32_t)
 | ||||
| ///   - Iterator converts bit positions to actual enum values during traversal
 | ||||
| ///   - Iterator converts bit positions to actual values during traversal
 | ||||
| ///   - All operations are constexpr-compatible for compile-time initialization
 | ||||
| ///   - Drop-in replacement for std::set<EnumType> with simpler API
 | ||||
| ///   - Drop-in replacement for std::set<ValueType> with simpler API
 | ||||
| ///   - Despite the name, works with any discrete bounded type, not just enums
 | ||||
| ///
 | ||||
| template<typename EnumType, int MaxBits = 16> class EnumBitmask { | ||||
| template<typename ValueType, int MaxBits = 16> class FiniteSetMask { | ||||
|  public: | ||||
|   // Automatic bitmask type selection based on MaxBits
 | ||||
|   // ≤8 bits: uint8_t, ≤16 bits: uint16_t, otherwise: uint32_t
 | ||||
| @@ -43,38 +44,38 @@ template<typename EnumType, int MaxBits = 16> class EnumBitmask { | ||||
|       typename std::conditional<(MaxBits <= 8), uint8_t, | ||||
|                                 typename std::conditional<(MaxBits <= 16), uint16_t, uint32_t>::type>::type; | ||||
| 
 | ||||
|   constexpr EnumBitmask() = default; | ||||
|   constexpr FiniteSetMask() = default; | ||||
| 
 | ||||
|   /// Construct from initializer list: {VALUE1, VALUE2, ...}
 | ||||
|   constexpr EnumBitmask(std::initializer_list<EnumType> values) { | ||||
|   constexpr FiniteSetMask(std::initializer_list<ValueType> values) { | ||||
|     for (auto value : values) { | ||||
|       this->insert(value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Add a single enum value to the set (std::set compatibility)
 | ||||
|   constexpr void insert(EnumType value) { this->mask_ |= (static_cast<bitmask_t>(1) << enum_to_bit(value)); } | ||||
|   /// Add a single value to the set (std::set compatibility)
 | ||||
|   constexpr void insert(ValueType value) { this->mask_ |= (static_cast<bitmask_t>(1) << value_to_bit(value)); } | ||||
| 
 | ||||
|   /// Add multiple enum values from initializer list
 | ||||
|   constexpr void insert(std::initializer_list<EnumType> values) { | ||||
|   /// Add multiple values from initializer list
 | ||||
|   constexpr void insert(std::initializer_list<ValueType> values) { | ||||
|     for (auto value : values) { | ||||
|       this->insert(value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Remove an enum value from the set (std::set compatibility)
 | ||||
|   constexpr void erase(EnumType value) { this->mask_ &= ~(static_cast<bitmask_t>(1) << enum_to_bit(value)); } | ||||
|   /// Remove a value from the set (std::set compatibility)
 | ||||
|   constexpr void erase(ValueType value) { this->mask_ &= ~(static_cast<bitmask_t>(1) << value_to_bit(value)); } | ||||
| 
 | ||||
|   /// Clear all values from the set
 | ||||
|   constexpr void clear() { this->mask_ = 0; } | ||||
| 
 | ||||
|   /// Check if the set contains a specific enum value (std::set compatibility)
 | ||||
|   /// Check if the set contains a specific value (std::set compatibility)
 | ||||
|   /// Returns 1 if present, 0 if not (same as std::set for unique elements)
 | ||||
|   constexpr size_t count(EnumType value) const { | ||||
|     return (this->mask_ & (static_cast<bitmask_t>(1) << enum_to_bit(value))) != 0 ? 1 : 0; | ||||
|   constexpr size_t count(ValueType value) const { | ||||
|     return (this->mask_ & (static_cast<bitmask_t>(1) << value_to_bit(value))) != 0 ? 1 : 0; | ||||
|   } | ||||
| 
 | ||||
|   /// Count the number of enum values in the set
 | ||||
|   /// Count the number of values in the set
 | ||||
|   constexpr size_t size() const { | ||||
|     // Brian Kernighan's algorithm - efficient for sparse bitmasks
 | ||||
|     // Typical case: 2-4 modes out of 10 possible
 | ||||
| @@ -91,51 +92,52 @@ template<typename EnumType, int MaxBits = 16> class EnumBitmask { | ||||
|   constexpr bool empty() const { return this->mask_ == 0; } | ||||
| 
 | ||||
|   /// Iterator support for range-based for loops and API encoding
 | ||||
|   /// Iterates over set bits and converts bit positions to enum values
 | ||||
|   /// Iterates over set bits and converts bit positions to values
 | ||||
|   /// Optimization: removes bits from mask as we iterate
 | ||||
|   class Iterator { | ||||
|    public: | ||||
|     using iterator_category = std::forward_iterator_tag; | ||||
|     using value_type = EnumType; | ||||
|     using value_type = ValueType; | ||||
|     using difference_type = std::ptrdiff_t; | ||||
|     using pointer = const EnumType *; | ||||
|     using reference = EnumType; | ||||
|     using pointer = const ValueType *; | ||||
|     using reference = ValueType; | ||||
| 
 | ||||
|     constexpr Iterator(bitmask_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); } | ||||
|     constexpr explicit Iterator(bitmask_t mask) : mask_(mask) {} | ||||
| 
 | ||||
|     constexpr EnumType operator*() const { return bit_to_enum(bit_); } | ||||
|     constexpr ValueType operator*() const { | ||||
|       // Return value for the first set bit
 | ||||
|       return bit_to_value(find_next_set_bit(mask_, 0)); | ||||
|     } | ||||
| 
 | ||||
|     constexpr Iterator &operator++() { | ||||
|       ++bit_; | ||||
|       advance_to_next_set_bit_(); | ||||
|       // Clear the lowest set bit (Brian Kernighan's algorithm)
 | ||||
|       mask_ &= mask_ - 1; | ||||
|       return *this; | ||||
|     } | ||||
| 
 | ||||
|     constexpr bool operator==(const Iterator &other) const { return bit_ == other.bit_; } | ||||
|     constexpr bool operator==(const Iterator &other) const { return mask_ == other.mask_; } | ||||
| 
 | ||||
|     constexpr bool operator!=(const Iterator &other) const { return !(*this == other); } | ||||
| 
 | ||||
|    private: | ||||
|     constexpr void advance_to_next_set_bit_() { bit_ = find_next_set_bit(mask_, bit_); } | ||||
| 
 | ||||
|     bitmask_t mask_; | ||||
|     int bit_; | ||||
|   }; | ||||
| 
 | ||||
|   constexpr Iterator begin() const { return Iterator(mask_, 0); } | ||||
|   constexpr Iterator end() const { return Iterator(mask_, MaxBits); } | ||||
|   constexpr Iterator begin() const { return Iterator(mask_); } | ||||
|   constexpr Iterator end() const { return Iterator(0); } | ||||
| 
 | ||||
|   /// Get the raw bitmask value for optimized operations
 | ||||
|   constexpr bitmask_t get_mask() const { return this->mask_; } | ||||
| 
 | ||||
|   /// Check if a specific enum value is present in a raw bitmask
 | ||||
|   /// Check if a specific value is present in a raw bitmask
 | ||||
|   /// Useful for checking intersection results without creating temporary objects
 | ||||
|   static constexpr bool mask_contains(bitmask_t mask, EnumType value) { | ||||
|     return (mask & (static_cast<bitmask_t>(1) << enum_to_bit(value))) != 0; | ||||
|   static constexpr bool mask_contains(bitmask_t mask, ValueType value) { | ||||
|     return (mask & (static_cast<bitmask_t>(1) << value_to_bit(value))) != 0; | ||||
|   } | ||||
| 
 | ||||
|   /// Get the first enum value from a raw bitmask
 | ||||
|   /// Get the first value from a raw bitmask
 | ||||
|   /// Used for optimizing intersection logic (e.g., "pick first suitable mode")
 | ||||
|   static constexpr EnumType first_value_from_mask(bitmask_t mask) { return bit_to_enum(find_next_set_bit(mask, 0)); } | ||||
|   static constexpr ValueType first_value_from_mask(bitmask_t mask) { return bit_to_value(find_next_set_bit(mask, 0)); } | ||||
| 
 | ||||
|   /// Find the next set bit in a bitmask starting from a given position
 | ||||
|   /// Returns the bit position, or MaxBits if no more bits are set
 | ||||
| @@ -149,9 +151,9 @@ template<typename EnumType, int MaxBits = 16> class EnumBitmask { | ||||
| 
 | ||||
|  protected: | ||||
|   // Must be provided by template specialization
 | ||||
|   // These convert between enum values and bit positions (0, 1, 2, ...)
 | ||||
|   static constexpr int enum_to_bit(EnumType value); | ||||
|   static EnumType bit_to_enum(int bit);  // Not constexpr due to static array limitation in C++20
 | ||||
|   // These convert between values and bit positions (0, 1, 2, ...)
 | ||||
|   static constexpr int value_to_bit(ValueType value); | ||||
|   static ValueType bit_to_value(int bit);  // Not constexpr: array indexing with runtime bounds checking
 | ||||
| 
 | ||||
|   bitmask_t mask_{0}; | ||||
| }; | ||||
| @@ -336,7 +336,7 @@ def _component_has_tests(component: str) -> bool: | ||||
|     Returns: | ||||
|         True if the component has test YAML files | ||||
|     """ | ||||
|     return bool(get_component_test_files(component)) | ||||
|     return bool(get_component_test_files(component, all_variants=True)) | ||||
|  | ||||
|  | ||||
| def _select_platform_by_preference( | ||||
| @@ -496,7 +496,7 @@ def detect_memory_impact_config( | ||||
|  | ||||
|     for component in sorted(changed_component_set): | ||||
|         # Look for test files on preferred platforms | ||||
|         test_files = get_component_test_files(component) | ||||
|         test_files = get_component_test_files(component, all_variants=True) | ||||
|         if not test_files: | ||||
|             continue | ||||
|  | ||||
|   | ||||
| @@ -49,9 +49,9 @@ def has_test_files(component_name: str, tests_dir: Path) -> bool: | ||||
|         tests_dir: Path to tests/components directory (unused, kept for compatibility) | ||||
|  | ||||
|     Returns: | ||||
|         True if the component has test.*.yaml files | ||||
|         True if the component has test.*.yaml or test-*.yaml files | ||||
|     """ | ||||
|     return bool(get_component_test_files(component_name)) | ||||
|     return bool(get_component_test_files(component_name, all_variants=True)) | ||||
|  | ||||
|  | ||||
| def create_intelligent_batches( | ||||
|   | ||||
| @@ -574,6 +574,105 @@ def test_main_filters_components_without_tests( | ||||
|     assert output["memory_impact"]["should_run"] == "false" | ||||
|  | ||||
|  | ||||
| def test_main_detects_components_with_variant_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_changed_files: Mock, | ||||
|     capsys: pytest.CaptureFixture[str], | ||||
|     tmp_path: Path, | ||||
|     monkeypatch: pytest.MonkeyPatch, | ||||
| ) -> None: | ||||
|     """Test that components with only variant test files (test-*.yaml) are detected. | ||||
|  | ||||
|     This test verifies the fix for components like improv_serial, ethernet, mdns, | ||||
|     improv_base, and safe_mode which only have variant test files (test-*.yaml) | ||||
|     instead of base test files (test.*.yaml). | ||||
|     """ | ||||
|     # Ensure we're not in GITHUB_ACTIONS mode for this test | ||||
|     monkeypatch.delenv("GITHUB_ACTIONS", raising=False) | ||||
|  | ||||
|     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 changed_files to return component files | ||||
|     mock_changed_files.return_value = [ | ||||
|         "esphome/components/improv_serial/improv_serial.cpp", | ||||
|         "esphome/components/ethernet/ethernet.cpp", | ||||
|         "esphome/components/no_tests/component.cpp", | ||||
|     ] | ||||
|  | ||||
|     # Create test directory structure | ||||
|     tests_dir = tmp_path / "tests" / "components" | ||||
|  | ||||
|     # improv_serial has only variant tests (like the real component) | ||||
|     improv_serial_dir = tests_dir / "improv_serial" | ||||
|     improv_serial_dir.mkdir(parents=True) | ||||
|     (improv_serial_dir / "test-uart0.esp32-idf.yaml").write_text("test: config") | ||||
|     (improv_serial_dir / "test-uart0.esp8266-ard.yaml").write_text("test: config") | ||||
|     (improv_serial_dir / "test-usb_cdc.esp32-s2-idf.yaml").write_text("test: config") | ||||
|  | ||||
|     # ethernet also has only variant tests | ||||
|     ethernet_dir = tests_dir / "ethernet" | ||||
|     ethernet_dir.mkdir(parents=True) | ||||
|     (ethernet_dir / "test-manual_ip.esp32-idf.yaml").write_text("test: config") | ||||
|     (ethernet_dir / "test-dhcp.esp32-idf.yaml").write_text("test: config") | ||||
|  | ||||
|     # no_tests component has no test files at all | ||||
|     no_tests_dir = tests_dir / "no_tests" | ||||
|     no_tests_dir.mkdir(parents=True) | ||||
|  | ||||
|     # Mock root_path to use tmp_path (need to patch both determine_jobs and helpers) | ||||
|     with ( | ||||
|         patch.object(determine_jobs, "root_path", str(tmp_path)), | ||||
|         patch.object(helpers, "root_path", str(tmp_path)), | ||||
|         patch("sys.argv", ["determine-jobs.py"]), | ||||
|         patch.object( | ||||
|             determine_jobs, | ||||
|             "get_changed_components", | ||||
|             return_value=["improv_serial", "ethernet", "no_tests"], | ||||
|         ), | ||||
|         patch.object( | ||||
|             determine_jobs, | ||||
|             "filter_component_and_test_files", | ||||
|             side_effect=lambda f: f.startswith("esphome/components/"), | ||||
|         ), | ||||
|         patch.object( | ||||
|             determine_jobs, | ||||
|             "get_components_with_dependencies", | ||||
|             side_effect=lambda files, deps: ( | ||||
|                 ["improv_serial", "ethernet"] | ||||
|                 if not deps | ||||
|                 else ["improv_serial", "ethernet", "no_tests"] | ||||
|             ), | ||||
|         ), | ||||
|         patch.object(determine_jobs, "changed_files", return_value=[]), | ||||
|     ): | ||||
|         # Clear the cache since we're mocking root_path | ||||
|         determine_jobs._component_has_tests.cache_clear() | ||||
|         determine_jobs.main() | ||||
|  | ||||
|     # Check output | ||||
|     captured = capsys.readouterr() | ||||
|     output = json.loads(captured.out) | ||||
|  | ||||
|     # changed_components should have all components | ||||
|     assert set(output["changed_components"]) == { | ||||
|         "improv_serial", | ||||
|         "ethernet", | ||||
|         "no_tests", | ||||
|     } | ||||
|     # changed_components_with_tests should include components with variant tests | ||||
|     assert set(output["changed_components_with_tests"]) == {"improv_serial", "ethernet"} | ||||
|     # component_test_count should be 2 (improv_serial and ethernet) | ||||
|     assert output["component_test_count"] == 2 | ||||
|     # no_tests should be excluded since it has no test files | ||||
|     assert "no_tests" not in output["changed_components_with_tests"] | ||||
|  | ||||
|  | ||||
| # Tests for detect_memory_impact_config function | ||||
|  | ||||
|  | ||||
| @@ -785,6 +884,51 @@ def test_detect_memory_impact_config_skips_base_bus_components(tmp_path: Path) - | ||||
|     assert "i2c" not in result["components"] | ||||
|  | ||||
|  | ||||
| def test_detect_memory_impact_config_with_variant_tests(tmp_path: Path) -> None: | ||||
|     """Test memory impact detection for components with only variant test files. | ||||
|  | ||||
|     This verifies that memory impact analysis works correctly for components like | ||||
|     improv_serial, ethernet, mdns, etc. which only have variant test files | ||||
|     (test-*.yaml) instead of base test files (test.*.yaml). | ||||
|     """ | ||||
|     # Create test directory structure | ||||
|     tests_dir = tmp_path / "tests" / "components" | ||||
|  | ||||
|     # improv_serial with only variant tests | ||||
|     improv_serial_dir = tests_dir / "improv_serial" | ||||
|     improv_serial_dir.mkdir(parents=True) | ||||
|     (improv_serial_dir / "test-uart0.esp32-idf.yaml").write_text("test: improv") | ||||
|     (improv_serial_dir / "test-uart0.esp8266-ard.yaml").write_text("test: improv") | ||||
|     (improv_serial_dir / "test-usb_cdc.esp32-s2-idf.yaml").write_text("test: improv") | ||||
|  | ||||
|     # ethernet with only variant tests | ||||
|     ethernet_dir = tests_dir / "ethernet" | ||||
|     ethernet_dir.mkdir(parents=True) | ||||
|     (ethernet_dir / "test-manual_ip.esp32-idf.yaml").write_text("test: ethernet") | ||||
|     (ethernet_dir / "test-dhcp.esp32-c3-idf.yaml").write_text("test: ethernet") | ||||
|  | ||||
|     # Mock changed_files to return both components | ||||
|     with ( | ||||
|         patch.object(determine_jobs, "root_path", str(tmp_path)), | ||||
|         patch.object(helpers, "root_path", str(tmp_path)), | ||||
|         patch.object(determine_jobs, "changed_files") as mock_changed_files, | ||||
|     ): | ||||
|         mock_changed_files.return_value = [ | ||||
|             "esphome/components/improv_serial/improv_serial.cpp", | ||||
|             "esphome/components/ethernet/ethernet.cpp", | ||||
|         ] | ||||
|         determine_jobs._component_has_tests.cache_clear() | ||||
|  | ||||
|         result = determine_jobs.detect_memory_impact_config() | ||||
|  | ||||
|     # Should detect both components even though they only have variant tests | ||||
|     assert result["should_run"] == "true" | ||||
|     assert set(result["components"]) == {"improv_serial", "ethernet"} | ||||
|     # Both components support esp32-idf | ||||
|     assert result["platform"] == "esp32-idf" | ||||
|     assert result["use_merged_config"] == "true" | ||||
|  | ||||
|  | ||||
| # Tests for clang-tidy split mode logic | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user