mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	cover
This commit is contained in:
		
							
								
								
									
										156
									
								
								tests/integration/fixtures/scheduler_string_test.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								tests/integration/fixtures/scheduler_string_test.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| esphome: | ||||
|   name: scheduler-string-test | ||||
|   on_boot: | ||||
|     priority: -100 | ||||
|     then: | ||||
|       - logger.log: "Starting scheduler string tests" | ||||
|   platformio_options: | ||||
|     build_flags: | ||||
|       - "-DESPHOME_DEBUG_SCHEDULER"  # Enable scheduler debug logging | ||||
|  | ||||
| host: | ||||
| api: | ||||
| logger: | ||||
|   level: VERBOSE | ||||
|  | ||||
| globals: | ||||
|   - id: timeout_counter | ||||
|     type: int | ||||
|     initial_value: '0' | ||||
|   - id: interval_counter | ||||
|     type: int | ||||
|     initial_value: '0' | ||||
|   - id: dynamic_counter | ||||
|     type: int | ||||
|     initial_value: '0' | ||||
|   - id: static_tests_done | ||||
|     type: bool | ||||
|     initial_value: 'false' | ||||
|   - id: dynamic_tests_done | ||||
|     type: bool | ||||
|     initial_value: 'false' | ||||
|   - id: results_reported | ||||
|     type: bool | ||||
|     initial_value: 'false' | ||||
|  | ||||
| script: | ||||
|   - id: test_static_strings | ||||
|     then: | ||||
|       - logger.log: "Testing static string timeouts and intervals" | ||||
|       - lambda: |- | ||||
|           auto *component1 = id(test_sensor1); | ||||
|           // Test 1: Static string literals with set_timeout | ||||
|           App.scheduler.set_timeout(component1, "static_timeout_1", 100, []() { | ||||
|             ESP_LOGI("test", "Static timeout 1 fired"); | ||||
|             id(timeout_counter) += 1; | ||||
|           }); | ||||
|  | ||||
|           // Test 2: Static const char* with set_timeout | ||||
|           static const char* TIMEOUT_NAME = "static_timeout_2"; | ||||
|           App.scheduler.set_timeout(component1, TIMEOUT_NAME, 200, []() { | ||||
|             ESP_LOGI("test", "Static timeout 2 fired"); | ||||
|             id(timeout_counter) += 1; | ||||
|           }); | ||||
|  | ||||
|           // Test 3: Static string literal with set_interval | ||||
|           App.scheduler.set_interval(component1, "static_interval_1", 500, []() { | ||||
|             ESP_LOGI("test", "Static interval 1 fired, count: %d", id(interval_counter)); | ||||
|             id(interval_counter) += 1; | ||||
|             if (id(interval_counter) >= 3) { | ||||
|               App.scheduler.cancel_interval(id(test_sensor1), "static_interval_1"); | ||||
|               ESP_LOGI("test", "Cancelled static interval 1"); | ||||
|             } | ||||
|           }); | ||||
|  | ||||
|           // Test 4: Empty string (should be handled safely) | ||||
|           App.scheduler.set_timeout(component1, "", 300, []() { | ||||
|             ESP_LOGI("test", "Empty string timeout fired"); | ||||
|           }); | ||||
|  | ||||
|   - id: test_dynamic_strings | ||||
|     then: | ||||
|       - logger.log: "Testing dynamic string timeouts and intervals" | ||||
|       - lambda: |- | ||||
|           auto *component2 = id(test_sensor2); | ||||
|  | ||||
|           // Test 5: Dynamic string with set_timeout (std::string) | ||||
|           std::string dynamic_name = "dynamic_timeout_" + std::to_string(id(dynamic_counter)++); | ||||
|           App.scheduler.set_timeout(component2, dynamic_name, 150, []() { | ||||
|             ESP_LOGI("test", "Dynamic timeout fired"); | ||||
|             id(timeout_counter) += 1; | ||||
|           }); | ||||
|  | ||||
|           // Test 6: Dynamic string with set_interval | ||||
|           std::string interval_name = "dynamic_interval_" + std::to_string(id(dynamic_counter)++); | ||||
|           App.scheduler.set_interval(component2, interval_name, 600, [interval_name]() { | ||||
|             ESP_LOGI("test", "Dynamic interval fired: %s", interval_name.c_str()); | ||||
|             id(interval_counter) += 1; | ||||
|             if (id(interval_counter) >= 6) { | ||||
|               App.scheduler.cancel_interval(id(test_sensor2), interval_name); | ||||
|               ESP_LOGI("test", "Cancelled dynamic interval"); | ||||
|             } | ||||
|           }); | ||||
|  | ||||
|           // Test 7: Cancel with different string object but same content | ||||
|           std::string cancel_name = "cancel_test"; | ||||
|           App.scheduler.set_timeout(component2, cancel_name, 5000, []() { | ||||
|             ESP_LOGI("test", "This should be cancelled"); | ||||
|           }); | ||||
|  | ||||
|           // Cancel using a different string object | ||||
|           std::string cancel_name_2 = "cancel_test"; | ||||
|           App.scheduler.cancel_timeout(component2, cancel_name_2); | ||||
|           ESP_LOGI("test", "Cancelled timeout using different string object"); | ||||
|  | ||||
|   - id: report_results | ||||
|     then: | ||||
|       - lambda: |- | ||||
|           ESP_LOGI("test", "Final results - Timeouts: %d, Intervals: %d", | ||||
|                    id(timeout_counter), id(interval_counter)); | ||||
|  | ||||
| sensor: | ||||
|   - platform: template | ||||
|     name: Test Sensor 1 | ||||
|     id: test_sensor1 | ||||
|     lambda: return 1.0; | ||||
|     update_interval: never | ||||
|  | ||||
|   - platform: template | ||||
|     name: Test Sensor 2 | ||||
|     id: test_sensor2 | ||||
|     lambda: return 2.0; | ||||
|     update_interval: never | ||||
|  | ||||
| interval: | ||||
|   # Run static string tests after boot - using script to run once | ||||
|   - interval: 0.5s | ||||
|     then: | ||||
|       - if: | ||||
|           condition: | ||||
|             lambda: 'return id(static_tests_done) == false;' | ||||
|           then: | ||||
|             - lambda: 'id(static_tests_done) = true;' | ||||
|             - script.execute: test_static_strings | ||||
|             - logger.log: "Started static string tests" | ||||
|  | ||||
|   # Run dynamic string tests after static tests | ||||
|   - interval: 1s | ||||
|     then: | ||||
|       - if: | ||||
|           condition: | ||||
|             lambda: 'return id(static_tests_done) && !id(dynamic_tests_done);' | ||||
|           then: | ||||
|             - lambda: 'id(dynamic_tests_done) = true;' | ||||
|             - delay: 1s | ||||
|             - script.execute: test_dynamic_strings | ||||
|  | ||||
|   # Report results after all tests | ||||
|   - interval: 1s | ||||
|     then: | ||||
|       - if: | ||||
|           condition: | ||||
|             lambda: 'return id(dynamic_tests_done) && !id(results_reported);' | ||||
|           then: | ||||
|             - lambda: 'id(results_reported) = true;' | ||||
|             - delay: 3s | ||||
|             - script.execute: report_results | ||||
							
								
								
									
										163
									
								
								tests/integration/test_scheduler_string_test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								tests/integration/test_scheduler_string_test.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| """Test scheduler string optimization with static and dynamic strings.""" | ||||
|  | ||||
| import asyncio | ||||
| import re | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_scheduler_string_test( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test that scheduler handles both static and dynamic strings correctly.""" | ||||
|     # Track counts | ||||
|     timeout_count = 0 | ||||
|     interval_count = 0 | ||||
|  | ||||
|     # Events for each test completion | ||||
|     static_timeout_1_fired = asyncio.Event() | ||||
|     static_timeout_2_fired = asyncio.Event() | ||||
|     static_interval_fired = asyncio.Event() | ||||
|     static_interval_cancelled = asyncio.Event() | ||||
|     empty_string_timeout_fired = asyncio.Event() | ||||
|     dynamic_timeout_fired = asyncio.Event() | ||||
|     dynamic_interval_fired = asyncio.Event() | ||||
|     cancel_test_done = asyncio.Event() | ||||
|     final_results_logged = asyncio.Event() | ||||
|  | ||||
|     # Track interval counts | ||||
|     static_interval_count = 0 | ||||
|     dynamic_interval_count = 0 | ||||
|  | ||||
|     def on_log_line(line: str) -> None: | ||||
|         nonlocal \ | ||||
|             timeout_count, \ | ||||
|             interval_count, \ | ||||
|             static_interval_count, \ | ||||
|             dynamic_interval_count | ||||
|  | ||||
|         # Strip ANSI color codes | ||||
|         clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) | ||||
|  | ||||
|         # Check for static timeout completions | ||||
|         if "Static timeout 1 fired" in clean_line: | ||||
|             static_timeout_1_fired.set() | ||||
|             timeout_count += 1 | ||||
|  | ||||
|         elif "Static timeout 2 fired" in clean_line: | ||||
|             static_timeout_2_fired.set() | ||||
|             timeout_count += 1 | ||||
|  | ||||
|         # Check for static interval | ||||
|         elif "Static interval 1 fired" in clean_line: | ||||
|             match = re.search(r"count: (\d+)", clean_line) | ||||
|             if match: | ||||
|                 static_interval_count = int(match.group(1)) | ||||
|                 static_interval_fired.set() | ||||
|  | ||||
|         elif "Cancelled static interval 1" in clean_line: | ||||
|             static_interval_cancelled.set() | ||||
|  | ||||
|         # Check for empty string timeout | ||||
|         elif "Empty string timeout fired" in clean_line: | ||||
|             empty_string_timeout_fired.set() | ||||
|  | ||||
|         # Check for dynamic string tests | ||||
|         elif "Dynamic timeout fired" in clean_line: | ||||
|             dynamic_timeout_fired.set() | ||||
|             timeout_count += 1 | ||||
|  | ||||
|         elif "Dynamic interval fired" in clean_line: | ||||
|             dynamic_interval_count += 1 | ||||
|             dynamic_interval_fired.set() | ||||
|  | ||||
|         # Check for cancel test | ||||
|         elif "Cancelled timeout using different string object" in clean_line: | ||||
|             cancel_test_done.set() | ||||
|  | ||||
|         # Check for final results | ||||
|         elif "Final results" in clean_line: | ||||
|             match = re.search(r"Timeouts: (\d+), Intervals: (\d+)", clean_line) | ||||
|             if match: | ||||
|                 timeout_count = int(match.group(1)) | ||||
|                 interval_count = int(match.group(2)) | ||||
|                 final_results_logged.set() | ||||
|  | ||||
|     async with ( | ||||
|         run_compiled(yaml_config, line_callback=on_log_line), | ||||
|         api_client_connected() as client, | ||||
|     ): | ||||
|         # Verify we can connect | ||||
|         device_info = await client.device_info() | ||||
|         assert device_info is not None | ||||
|         assert device_info.name == "scheduler-string-test" | ||||
|  | ||||
|         # Wait for static string tests | ||||
|         try: | ||||
|             await asyncio.wait_for(static_timeout_1_fired.wait(), timeout=3.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Static timeout 1 did not fire within 3 seconds") | ||||
|  | ||||
|         try: | ||||
|             await asyncio.wait_for(static_timeout_2_fired.wait(), timeout=3.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Static timeout 2 did not fire within 3 seconds") | ||||
|  | ||||
|         try: | ||||
|             await asyncio.wait_for(static_interval_fired.wait(), timeout=3.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Static interval did not fire within 3 seconds") | ||||
|  | ||||
|         try: | ||||
|             await asyncio.wait_for(static_interval_cancelled.wait(), timeout=3.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Static interval was not cancelled within 3 seconds") | ||||
|  | ||||
|         # Verify static interval ran at least 3 times | ||||
|         assert static_interval_count >= 2, ( | ||||
|             f"Expected static interval to run at least 3 times, got {static_interval_count + 1}" | ||||
|         ) | ||||
|  | ||||
|         # Wait for dynamic string tests | ||||
|         try: | ||||
|             await asyncio.wait_for(dynamic_timeout_fired.wait(), timeout=5.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Dynamic timeout did not fire within 5 seconds") | ||||
|  | ||||
|         try: | ||||
|             await asyncio.wait_for(dynamic_interval_fired.wait(), timeout=5.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Dynamic interval did not fire within 5 seconds") | ||||
|  | ||||
|         # Wait for cancel test | ||||
|         try: | ||||
|             await asyncio.wait_for(cancel_test_done.wait(), timeout=5.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Cancel test did not complete within 5 seconds") | ||||
|  | ||||
|         # Wait for final results | ||||
|         try: | ||||
|             await asyncio.wait_for(final_results_logged.wait(), timeout=10.0) | ||||
|         except asyncio.TimeoutError: | ||||
|             pytest.fail("Final results were not logged within 10 seconds") | ||||
|  | ||||
|         # Verify results | ||||
|         assert timeout_count >= 3, f"Expected at least 3 timeouts, got {timeout_count}" | ||||
|         assert interval_count >= 3, ( | ||||
|             f"Expected at least 3 interval fires, got {interval_count}" | ||||
|         ) | ||||
|  | ||||
|         # Empty string timeout DOES fire (scheduler accepts empty names) | ||||
|         assert empty_string_timeout_fired.is_set(), "Empty string timeout should fire" | ||||
|  | ||||
|         # Log final status | ||||
|         print("\nScheduler string test completed successfully:") | ||||
|         print(f"  Timeouts fired: {timeout_count}") | ||||
|         print(f"  Intervals fired: {interval_count}") | ||||
|         print(f"  Static interval count: {static_interval_count + 1}") | ||||
|         print(f"  Dynamic interval count: {dynamic_interval_count}") | ||||
		Reference in New Issue
	
	Block a user