mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	no flakey
This commit is contained in:
		| @@ -38,6 +38,48 @@ void SchedulerStringLifetimeComponent::run_string_lifetime_test() { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void SchedulerStringLifetimeComponent::run_test1() { | ||||||
|  |   test_temporary_string_lifetime(); | ||||||
|  |   // Wait for all callbacks to execute | ||||||
|  |   this->set_timeout("test1_complete", 10, [this]() { ESP_LOGI(TAG, "Test 1 complete"); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SchedulerStringLifetimeComponent::run_test2() { | ||||||
|  |   test_scope_exit_string(); | ||||||
|  |   // Wait for all callbacks to execute | ||||||
|  |   this->set_timeout("test2_complete", 20, [this]() { ESP_LOGI(TAG, "Test 2 complete"); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SchedulerStringLifetimeComponent::run_test3() { | ||||||
|  |   test_vector_reallocation(); | ||||||
|  |   // Wait for all callbacks to execute | ||||||
|  |   this->set_timeout("test3_complete", 60, [this]() { ESP_LOGI(TAG, "Test 3 complete"); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SchedulerStringLifetimeComponent::run_test4() { | ||||||
|  |   test_string_move_semantics(); | ||||||
|  |   // Wait for all callbacks to execute | ||||||
|  |   this->set_timeout("test4_complete", 35, [this]() { ESP_LOGI(TAG, "Test 4 complete"); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SchedulerStringLifetimeComponent::run_test5() { | ||||||
|  |   test_lambda_capture_lifetime(); | ||||||
|  |   // Wait for all callbacks to execute | ||||||
|  |   this->set_timeout("test5_complete", 50, [this]() { ESP_LOGI(TAG, "Test 5 complete"); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SchedulerStringLifetimeComponent::run_final_check() { | ||||||
|  |   ESP_LOGI(TAG, "String lifetime tests complete"); | ||||||
|  |   ESP_LOGI(TAG, "Tests passed: %d", this->tests_passed_); | ||||||
|  |   ESP_LOGI(TAG, "Tests failed: %d", this->tests_failed_); | ||||||
|  |  | ||||||
|  |   if (this->tests_failed_ == 0) { | ||||||
|  |     ESP_LOGI(TAG, "SUCCESS: All string lifetime tests passed!"); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "FAILURE: %d string lifetime tests failed!", this->tests_failed_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void SchedulerStringLifetimeComponent::test_temporary_string_lifetime() { | void SchedulerStringLifetimeComponent::test_temporary_string_lifetime() { | ||||||
|   ESP_LOGI(TAG, "Test 1: Temporary string lifetime for timeout names"); |   ESP_LOGI(TAG, "Test 1: Temporary string lifetime for timeout names"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,14 @@ class SchedulerStringLifetimeComponent : public Component { | |||||||
|  |  | ||||||
|   void run_string_lifetime_test(); |   void run_string_lifetime_test(); | ||||||
|  |  | ||||||
|  |   // Individual test methods exposed as services | ||||||
|  |   void run_test1(); | ||||||
|  |   void run_test2(); | ||||||
|  |   void run_test3(); | ||||||
|  |   void run_test4(); | ||||||
|  |   void run_test5(); | ||||||
|  |   void run_final_check(); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   void test_temporary_string_lifetime(); |   void test_temporary_string_lifetime(); | ||||||
|   void test_scope_exit_string(); |   void test_scope_exit_string(); | ||||||
|   | |||||||
| @@ -21,3 +21,27 @@ api: | |||||||
|       then: |       then: | ||||||
|         - lambda: |- |         - lambda: |- | ||||||
|             id(string_lifetime)->run_string_lifetime_test(); |             id(string_lifetime)->run_string_lifetime_test(); | ||||||
|  |     - service: run_test1 | ||||||
|  |       then: | ||||||
|  |         - lambda: |- | ||||||
|  |             id(string_lifetime)->run_test1(); | ||||||
|  |     - service: run_test2 | ||||||
|  |       then: | ||||||
|  |         - lambda: |- | ||||||
|  |             id(string_lifetime)->run_test2(); | ||||||
|  |     - service: run_test3 | ||||||
|  |       then: | ||||||
|  |         - lambda: |- | ||||||
|  |             id(string_lifetime)->run_test3(); | ||||||
|  |     - service: run_test4 | ||||||
|  |       then: | ||||||
|  |         - lambda: |- | ||||||
|  |             id(string_lifetime)->run_test4(); | ||||||
|  |     - service: run_test5 | ||||||
|  |       then: | ||||||
|  |         - lambda: |- | ||||||
|  |             id(string_lifetime)->run_test5(); | ||||||
|  |     - service: run_final_check | ||||||
|  |       then: | ||||||
|  |         - lambda: |- | ||||||
|  |             id(string_lifetime)->run_final_check(); | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import asyncio | |||||||
| from pathlib import Path | from pathlib import Path | ||||||
| import re | import re | ||||||
|  |  | ||||||
| from aioesphomeapi import UserService |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
| @@ -28,19 +27,42 @@ async def test_scheduler_string_lifetime( | |||||||
|         "EXTERNAL_COMPONENT_PATH", external_components_path |         "EXTERNAL_COMPONENT_PATH", external_components_path | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     # Create a future to signal test completion |     # Create events for synchronization | ||||||
|     loop = asyncio.get_running_loop() |     test1_complete = asyncio.Event() | ||||||
|     test_complete_future: asyncio.Future[None] = loop.create_future() |     test2_complete = asyncio.Event() | ||||||
|  |     test3_complete = asyncio.Event() | ||||||
|  |     test4_complete = asyncio.Event() | ||||||
|  |     test5_complete = asyncio.Event() | ||||||
|  |     all_tests_complete = asyncio.Event() | ||||||
|  |  | ||||||
|     # Track test progress |     # Track test progress | ||||||
|     test_stats = { |     test_stats = { | ||||||
|         "tests_passed": 0, |         "tests_passed": 0, | ||||||
|         "tests_failed": 0, |         "tests_failed": 0, | ||||||
|         "errors": [], |         "errors": [], | ||||||
|         "use_after_free_detected": False, |         "current_test": None, | ||||||
|  |         "test_callbacks_executed": {}, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     def on_log_line(line: str) -> None: |     def on_log_line(line: str) -> None: | ||||||
|  |         # Track test-specific events | ||||||
|  |         if "Test 1 complete" in line: | ||||||
|  |             test1_complete.set() | ||||||
|  |         elif "Test 2 complete" in line: | ||||||
|  |             test2_complete.set() | ||||||
|  |         elif "Test 3 complete" in line: | ||||||
|  |             test3_complete.set() | ||||||
|  |         elif "Test 4 complete" in line: | ||||||
|  |             test4_complete.set() | ||||||
|  |         elif "Test 5 complete" in line: | ||||||
|  |             test5_complete.set() | ||||||
|  |  | ||||||
|  |         # Track individual callback executions | ||||||
|  |         callback_match = re.search(r"Callback '(.+?)' executed", line) | ||||||
|  |         if callback_match: | ||||||
|  |             callback_name = callback_match.group(1) | ||||||
|  |             test_stats["test_callbacks_executed"][callback_name] = True | ||||||
|  |  | ||||||
|         # Track test results from the C++ test output |         # Track test results from the C++ test output | ||||||
|         if "Tests passed:" in line and "string_lifetime" in line: |         if "Tests passed:" in line and "string_lifetime" in line: | ||||||
|             # Extract the number from "Tests passed: 32" |             # Extract the number from "Tests passed: 32" | ||||||
| @@ -68,16 +90,11 @@ async def test_scheduler_string_lifetime( | |||||||
|                 "invalid pointer", |                 "invalid pointer", | ||||||
|             ] |             ] | ||||||
|         ): |         ): | ||||||
|             test_stats["use_after_free_detected"] = True |             pytest.fail(f"Memory corruption detected: {line}") | ||||||
|             if not test_complete_future.done(): |  | ||||||
|                 test_complete_future.set_exception( |  | ||||||
|                     Exception(f"Memory corruption detected: {line}") |  | ||||||
|                 ) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         # Check for completion |         # Check for completion | ||||||
|         if "String lifetime tests complete" in line and not test_complete_future.done(): |         if "String lifetime tests complete" in line: | ||||||
|             test_complete_future.set_result(None) |             all_tests_complete.set() | ||||||
|  |  | ||||||
|     async with ( |     async with ( | ||||||
|         run_compiled(yaml_config, line_callback=on_log_line), |         run_compiled(yaml_config, line_callback=on_log_line), | ||||||
| @@ -93,29 +110,56 @@ async def test_scheduler_string_lifetime( | |||||||
|             client.list_entities_services(), timeout=5.0 |             client.list_entities_services(), timeout=5.0 | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         # Find our test service |         # Find our test services | ||||||
|         run_test_service: UserService | None = None |         test_services = {} | ||||||
|         for service in services: |         for service in services: | ||||||
|             if service.name == "run_string_lifetime_test": |             if service.name == "run_test1": | ||||||
|                 run_test_service = service |                 test_services["test1"] = service | ||||||
|                 break |             elif service.name == "run_test2": | ||||||
|  |                 test_services["test2"] = service | ||||||
|  |             elif service.name == "run_test3": | ||||||
|  |                 test_services["test3"] = service | ||||||
|  |             elif service.name == "run_test4": | ||||||
|  |                 test_services["test4"] = service | ||||||
|  |             elif service.name == "run_test5": | ||||||
|  |                 test_services["test5"] = service | ||||||
|  |             elif service.name == "run_final_check": | ||||||
|  |                 test_services["final"] = service | ||||||
|  |  | ||||||
|         assert run_test_service is not None, ( |         # Ensure all services are found | ||||||
|             "run_string_lifetime_test service not found" |         required_services = ["test1", "test2", "test3", "test4", "test5", "final"] | ||||||
|         ) |         for service_name in required_services: | ||||||
|  |             assert service_name in test_services, f"{service_name} service not found" | ||||||
|  |  | ||||||
|         # Call the service to start the test |         # Run tests sequentially, waiting for each to complete | ||||||
|         client.execute_service(run_test_service, {}) |  | ||||||
|  |  | ||||||
|         # Wait for test to complete |  | ||||||
|         try: |         try: | ||||||
|             await asyncio.wait_for(test_complete_future, timeout=30.0) |             # Test 1 | ||||||
|  |             client.execute_service(test_services["test1"], {}) | ||||||
|  |             await asyncio.wait_for(test1_complete.wait(), timeout=5.0) | ||||||
|  |  | ||||||
|  |             # Test 2 | ||||||
|  |             client.execute_service(test_services["test2"], {}) | ||||||
|  |             await asyncio.wait_for(test2_complete.wait(), timeout=5.0) | ||||||
|  |  | ||||||
|  |             # Test 3 | ||||||
|  |             client.execute_service(test_services["test3"], {}) | ||||||
|  |             await asyncio.wait_for(test3_complete.wait(), timeout=5.0) | ||||||
|  |  | ||||||
|  |             # Test 4 | ||||||
|  |             client.execute_service(test_services["test4"], {}) | ||||||
|  |             await asyncio.wait_for(test4_complete.wait(), timeout=5.0) | ||||||
|  |  | ||||||
|  |             # Test 5 | ||||||
|  |             client.execute_service(test_services["test5"], {}) | ||||||
|  |             await asyncio.wait_for(test5_complete.wait(), timeout=5.0) | ||||||
|  |  | ||||||
|  |             # Final check | ||||||
|  |             client.execute_service(test_services["final"], {}) | ||||||
|  |             await asyncio.wait_for(all_tests_complete.wait(), timeout=5.0) | ||||||
|  |  | ||||||
|         except asyncio.TimeoutError: |         except asyncio.TimeoutError: | ||||||
|             pytest.fail(f"String lifetime test timed out. Stats: {test_stats}") |             pytest.fail(f"String lifetime test timed out. Stats: {test_stats}") | ||||||
|  |  | ||||||
|         # Check for use-after-free |  | ||||||
|         assert not test_stats["use_after_free_detected"], "Use-after-free detected!" |  | ||||||
|  |  | ||||||
|         # Check for any errors |         # Check for any errors | ||||||
|         assert test_stats["tests_failed"] == 0, f"Tests failed: {test_stats['errors']}" |         assert test_stats["tests_failed"] == 0, f"Tests failed: {test_stats['errors']}" | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user