This commit is contained in:
J. Nick Koston 2025-07-04 08:59:15 -05:00
parent 0665fcea9e
commit f7ca26eef8
No known key found for this signature in database
2 changed files with 33 additions and 51 deletions

View File

@ -1,65 +1,36 @@
esphome:
name: defer-stress-test
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
components: [defer_stress_component]
host:
logger:
level: DEBUG
defer_stress_component:
id: defer_stress
api:
services:
- service: run_stress_test
then:
- lambda: |-
static int total_defers = 0;
static int executed_defers = 0;
ESP_LOGI("stress", "Starting defer stress test - rapid sequential defers");
// Reset counters
total_defers = 0;
executed_defers = 0;
// Create a temporary component to access defer()
class TestComponent : public Component {
public:
void run_test() {
// Rapidly defer many callbacks to stress the defer mechanism
for (int batch = 0; batch < 10; batch++) {
for (int i = 0; i < 100; i++) {
int expected_id = total_defers;
this->defer([expected_id]() {
executed_defers++;
ESP_LOGV("stress", "Defer %d executed", expected_id);
});
total_defers++;
}
// Brief yield to let other work happen
delay(1);
}
}
};
TestComponent test_comp;
test_comp.run_test();
ESP_LOGI("stress", "Scheduled %d defers", total_defers);
// Give the main loop time to process all defers
App.scheduler.set_timeout((Component*)nullptr, nullptr, 500, []() {
ESP_LOGI("stress", "Test complete. Defers scheduled: %d, executed: %d", total_defers, executed_defers);
// We should have executed all defers without crashing
if (executed_defers == total_defers && total_defers == 1000) {
ESP_LOGI("stress", "✓ Stress test PASSED - All %d defers executed", total_defers);
id(test_result)->trigger("passed");
} else {
ESP_LOGE("stress", "✗ Stress test FAILED - Expected 1000 executed, got %d", executed_defers);
id(test_result)->trigger("failed");
}
id(test_complete)->trigger("test_finished");
});
id(defer_stress)->run_multi_thread_test();
- wait_until:
lambda: |-
return id(defer_stress)->is_test_complete();
- lambda: |-
if (id(defer_stress)->is_test_passed()) {
id(test_result)->trigger("passed");
} else {
id(test_result)->trigger("failed");
}
id(test_complete)->trigger("test_finished");
event:
- platform: template

View File

@ -1,6 +1,7 @@
"""Stress test for defer() thread safety with multiple threads."""
import asyncio
from pathlib import Path
from aioesphomeapi import EntityState, Event, EventInfo, UserService
import pytest
@ -16,6 +17,16 @@ async def test_defer_stress(
) -> None:
"""Test that defer() doesn't crash when called rapidly from multiple threads."""
# Get the absolute path to the external components directory
external_components_path = str(
Path(__file__).parent / "fixtures" / "external_components"
)
# Replace the placeholder in the YAML config with the actual path
yaml_config = yaml_config.replace(
"EXTERNAL_COMPONENT_PATH", external_components_path
)
async with run_compiled(yaml_config), api_client_connected() as client:
# Verify we can connect
device_info = await client.device_info()
@ -79,10 +90,10 @@ async def test_defer_stress(
# Wait for test completion with a longer timeout (threads run for 100ms + processing time)
try:
await asyncio.wait_for(test_complete_future, timeout=10.0)
await asyncio.wait_for(test_complete_future, timeout=15.0)
test_passed = await asyncio.wait_for(test_result_future, timeout=1.0)
except asyncio.TimeoutError:
pytest.fail("Stress test did not complete within 10 seconds")
pytest.fail("Stress test did not complete within 15 seconds")
# Verify the test passed
assert test_passed is True, (