From ca70f17b3b282ee505da73e9e3ebc383ed31b6ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 4 Jul 2025 08:33:24 -0500 Subject: [PATCH] make test race safe --- .../fixtures/defer_fifo_simple.yaml | 171 ++++++++++-------- tests/integration/test_defer_fifo_simple.py | 70 ++++--- 2 files changed, 132 insertions(+), 109 deletions(-) diff --git a/tests/integration/fixtures/defer_fifo_simple.yaml b/tests/integration/fixtures/defer_fifo_simple.yaml index 29c1a2bf38..aede9a3cd0 100644 --- a/tests/integration/fixtures/defer_fifo_simple.yaml +++ b/tests/integration/fixtures/defer_fifo_simple.yaml @@ -1,81 +1,5 @@ esphome: name: defer-fifo-simple - on_boot: - - lambda: |- - // Test 1: Test set_timeout with 0 delay (direct scheduler call) - static int set_timeout_order = 0; - static bool set_timeout_passed = true; - - ESP_LOGD("defer_test", "Test 1: Testing set_timeout(0) for FIFO order..."); - for (int i = 0; i < 10; i++) { - int expected = i; - App.scheduler.set_timeout((Component*)nullptr, nullptr, 0, [expected]() { - ESP_LOGD("defer_test", "set_timeout(0) item %d executed, order %d", expected, set_timeout_order); - if (set_timeout_order != expected) { - ESP_LOGE("defer_test", "FIFO violation in set_timeout: expected %d but got execution order %d", expected, set_timeout_order); - set_timeout_passed = false; - } - set_timeout_order++; - - if (set_timeout_order == 10) { - if (set_timeout_passed) { - ESP_LOGI("defer_test", "✓ Test 1 PASSED - set_timeout(0) maintains FIFO order"); - } else { - ESP_LOGE("defer_test", "✗ Test 1 FAILED - set_timeout(0) executed out of order"); - } - - // Start Test 2 after Test 1 completes - App.scheduler.set_timeout((Component*)nullptr, nullptr, 100, []() { - // Test 2: Test defer() method (component method) - static int defer_order = 0; - static bool defer_passed = true; - - ESP_LOGD("defer_test", "Test 2: Testing defer() for FIFO order..."); - - // Create a test component class that exposes defer() - class TestComponent : public Component { - public: - void test_defer() { - for (int i = 0; i < 10; i++) { - int expected = i; - this->defer([expected]() { - ESP_LOGD("defer_test", "defer() item %d executed, order %d", expected, defer_order); - if (defer_order != expected) { - ESP_LOGE("defer_test", "FIFO violation in defer: expected %d but got execution order %d", expected, defer_order); - defer_passed = false; - } - defer_order++; - - if (defer_order == 10) { - bool all_passed = set_timeout_passed && defer_passed; - if (defer_passed) { - ESP_LOGI("defer_test", "✓ Test 2 PASSED - defer() maintains FIFO order"); - if (all_passed) { - ESP_LOGI("defer_test", "✓ ALL TESTS PASSED - Both set_timeout(0) and defer() maintain FIFO order"); - } - } else { - ESP_LOGE("defer_test", "✗ Test 2 FAILED - defer() executed out of order"); - } - - // Publish test results - id(test_complete)->publish_state(true); - id(test_passed)->publish_state(all_passed); - } - }); - } - } - }; - - TestComponent test_component; - test_component.test_defer(); - - ESP_LOGD("defer_test", "Deferred 10 items using defer(), waiting for execution..."); - }); - } - }); - } - - ESP_LOGD("defer_test", "Deferred 10 items using set_timeout(0), waiting for execution..."); host: @@ -83,11 +7,100 @@ logger: level: DEBUG api: + services: + - service: run_defer_test + then: + - lambda: |- + // Test 1: Test set_timeout with 0 delay (direct scheduler call) + static int set_timeout_order = 0; + static bool set_timeout_passed = true; -binary_sensor: + ESP_LOGD("defer_test", "Test 1: Testing set_timeout(0) for FIFO order..."); + for (int i = 0; i < 10; i++) { + int expected = i; + App.scheduler.set_timeout((Component*)nullptr, nullptr, 0, [expected]() { + ESP_LOGD("defer_test", "set_timeout(0) item %d executed, order %d", expected, set_timeout_order); + if (set_timeout_order != expected) { + ESP_LOGE("defer_test", "FIFO violation in set_timeout: expected %d but got execution order %d", expected, set_timeout_order); + set_timeout_passed = false; + } + set_timeout_order++; + + if (set_timeout_order == 10) { + if (set_timeout_passed) { + ESP_LOGI("defer_test", "✓ Test 1 PASSED - set_timeout(0) maintains FIFO order"); + } else { + ESP_LOGE("defer_test", "✗ Test 1 FAILED - set_timeout(0) executed out of order"); + } + + // Start Test 2 after Test 1 completes + App.scheduler.set_timeout((Component*)nullptr, nullptr, 100, []() { + // Test 2: Test defer() method (component method) + static int defer_order = 0; + static bool defer_passed = true; + + ESP_LOGD("defer_test", "Test 2: Testing defer() for FIFO order..."); + + // Create a test component class that exposes defer() + class TestComponent : public Component { + public: + void test_defer() { + for (int i = 0; i < 10; i++) { + int expected = i; + this->defer([expected]() { + ESP_LOGD("defer_test", "defer() item %d executed, order %d", expected, defer_order); + if (defer_order != expected) { + ESP_LOGE("defer_test", "FIFO violation in defer: expected %d but got execution order %d", expected, defer_order); + defer_passed = false; + } + defer_order++; + + if (defer_order == 10) { + bool all_passed = set_timeout_passed && defer_passed; + if (defer_passed) { + ESP_LOGI("defer_test", "✓ Test 2 PASSED - defer() maintains FIFO order"); + if (all_passed) { + ESP_LOGI("defer_test", "✓ ALL TESTS PASSED - Both set_timeout(0) and defer() maintain FIFO order"); + } + } else { + ESP_LOGE("defer_test", "✗ Test 2 FAILED - defer() executed out of order"); + } + + // Fire test result events + if (all_passed) { + id(test_result)->trigger("passed"); + } else { + id(test_result)->trigger("failed"); + } + id(test_complete)->trigger("test_finished"); + } + }); + } + } + }; + + TestComponent test_component; + test_component.test_defer(); + + ESP_LOGD("defer_test", "Deferred 10 items using defer(), waiting for execution..."); + }); + } + }); + } + + ESP_LOGD("defer_test", "Deferred 10 items using set_timeout(0), waiting for execution..."); + +event: - platform: template name: "Test Complete" id: test_complete + device_class: button + event_types: + - "test_finished" - platform: template - name: "Test Passed" - id: test_passed + name: "Test Result" + id: test_result + device_class: button + event_types: + - "passed" + - "failed" diff --git a/tests/integration/test_defer_fifo_simple.py b/tests/integration/test_defer_fifo_simple.py index 95a14e64b7..46d68db171 100644 --- a/tests/integration/test_defer_fifo_simple.py +++ b/tests/integration/test_defer_fifo_simple.py @@ -2,7 +2,7 @@ import asyncio -from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityState +from aioesphomeapi import EntityState, Event, EventInfo, UserService import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -22,57 +22,67 @@ async def test_defer_fifo_simple( assert device_info is not None assert device_info.name == "defer-fifo-simple" - # List entities to get the keys - entity_info, _ = await asyncio.wait_for( + # List entities and services + entity_info, services = await asyncio.wait_for( client.list_entities_services(), timeout=5.0 ) # Find our test entities - test_complete_entity: BinarySensorInfo | None = None - test_passed_entity: BinarySensorInfo | None = None + test_complete_entity: EventInfo | None = None + test_result_entity: EventInfo | None = None for entity in entity_info: - if isinstance(entity, BinarySensorInfo): + if isinstance(entity, EventInfo): if entity.object_id == "test_complete": test_complete_entity = entity - elif entity.object_id == "test_passed": - test_passed_entity = entity + elif entity.object_id == "test_result": + test_result_entity = entity - assert test_complete_entity is not None, "test_complete sensor not found" - assert test_passed_entity is not None, "test_passed sensor not found" + assert test_complete_entity is not None, "test_complete event not found" + assert test_result_entity is not None, "test_result event not found" + + # Find our test service + run_defer_test_service: UserService | None = None + for service in services: + if service.name == "run_defer_test": + run_defer_test_service = service + break + + assert run_defer_test_service is not None, "run_defer_test service not found" # Get the event loop loop = asyncio.get_running_loop() - # Subscribe to state changes - states: dict[int, EntityState] = {} - test_complete_future: asyncio.Future[BinarySensorState] = loop.create_future() - test_passed_future: asyncio.Future[BinarySensorState] = loop.create_future() + # Subscribe to states (events are delivered as EventStates through subscribe_states) + test_complete_future: asyncio.Future[bool] = loop.create_future() + test_result_future: asyncio.Future[bool] = loop.create_future() def on_state(state: EntityState) -> None: - states[state.key] = state - # Check if this is our test_complete binary sensor - if isinstance(state, BinarySensorState): + if isinstance(state, Event): if state.key == test_complete_entity.key: - if state.state and not test_complete_future.done(): - test_complete_future.set_result(state) - elif state.key == test_passed_entity.key: - if not test_passed_future.done(): - test_passed_future.set_result(state) + if ( + state.event_type == "test_finished" + and not test_complete_future.done() + ): + test_complete_future.set_result(True) + elif state.key == test_result_entity.key: + if not test_result_future.done(): + if state.event_type == "passed": + test_result_future.set_result(True) + elif state.event_type == "failed": + test_result_future.set_result(False) client.subscribe_states(on_state) + # Call the run_defer_test service to start the test + client.execute_service(run_defer_test_service, {}) + # Wait for test completion with timeout try: await asyncio.wait_for(test_complete_future, timeout=10.0) - test_passed_state = await asyncio.wait_for(test_passed_future, timeout=1.0) + test_passed = await asyncio.wait_for(test_result_future, timeout=1.0) except asyncio.TimeoutError: - pytest.fail( - f"Test did not complete within 10 seconds. " - f"Received states: {list(states.values())}" - ) + pytest.fail("Test did not complete within 10 seconds") # Verify the test passed - assert test_passed_state.state is True, ( - "FIFO test failed - items executed out of order" - ) + assert test_passed is True, "FIFO test failed - items executed out of order"