mirror of
https://github.com/esphome/esphome.git
synced 2025-08-07 19:07:45 +00:00
stress
This commit is contained in:
parent
0665fcea9e
commit
f7ca26eef8
@ -1,65 +1,36 @@
|
|||||||
esphome:
|
esphome:
|
||||||
name: defer-stress-test
|
name: defer-stress-test
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: EXTERNAL_COMPONENT_PATH
|
||||||
|
components: [defer_stress_component]
|
||||||
|
|
||||||
host:
|
host:
|
||||||
|
|
||||||
logger:
|
logger:
|
||||||
level: DEBUG
|
level: DEBUG
|
||||||
|
|
||||||
|
defer_stress_component:
|
||||||
|
id: defer_stress
|
||||||
|
|
||||||
api:
|
api:
|
||||||
services:
|
services:
|
||||||
- service: run_stress_test
|
- service: run_stress_test
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
static int total_defers = 0;
|
id(defer_stress)->run_multi_thread_test();
|
||||||
static int executed_defers = 0;
|
- wait_until:
|
||||||
|
lambda: |-
|
||||||
ESP_LOGI("stress", "Starting defer stress test - rapid sequential defers");
|
return id(defer_stress)->is_test_complete();
|
||||||
|
- lambda: |-
|
||||||
// Reset counters
|
if (id(defer_stress)->is_test_passed()) {
|
||||||
total_defers = 0;
|
id(test_result)->trigger("passed");
|
||||||
executed_defers = 0;
|
} else {
|
||||||
|
id(test_result)->trigger("failed");
|
||||||
// Create a temporary component to access defer()
|
}
|
||||||
class TestComponent : public Component {
|
id(test_complete)->trigger("test_finished");
|
||||||
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");
|
|
||||||
});
|
|
||||||
|
|
||||||
event:
|
event:
|
||||||
- platform: template
|
- platform: template
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Stress test for defer() thread safety with multiple threads."""
|
"""Stress test for defer() thread safety with multiple threads."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from aioesphomeapi import EntityState, Event, EventInfo, UserService
|
from aioesphomeapi import EntityState, Event, EventInfo, UserService
|
||||||
import pytest
|
import pytest
|
||||||
@ -16,6 +17,16 @@ async def test_defer_stress(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test that defer() doesn't crash when called rapidly from multiple threads."""
|
"""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:
|
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||||
# Verify we can connect
|
# Verify we can connect
|
||||||
device_info = await client.device_info()
|
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)
|
# Wait for test completion with a longer timeout (threads run for 100ms + processing time)
|
||||||
try:
|
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)
|
test_passed = await asyncio.wait_for(test_result_future, timeout=1.0)
|
||||||
except asyncio.TimeoutError:
|
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
|
# Verify the test passed
|
||||||
assert test_passed is True, (
|
assert test_passed is True, (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user