Add const char overload for Component::defer() (#9324)

This commit is contained in:
J. Nick Koston 2025-07-04 21:52:12 -05:00 committed by GitHub
parent 58b4e7dab2
commit 86e7013f40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 95 additions and 4 deletions

View File

@ -248,6 +248,9 @@ bool Component::cancel_defer(const std::string &name) { // NOLINT
void Component::defer(const std::string &name, std::function<void()> &&f) { // NOLINT void Component::defer(const std::string &name, std::function<void()> &&f) { // NOLINT
App.scheduler.set_timeout(this, name, 0, std::move(f)); App.scheduler.set_timeout(this, name, 0, std::move(f));
} }
void Component::defer(const char *name, std::function<void()> &&f) { // NOLINT
App.scheduler.set_timeout(this, name, 0, std::move(f));
}
void Component::set_timeout(uint32_t timeout, std::function<void()> &&f) { // NOLINT void Component::set_timeout(uint32_t timeout, std::function<void()> &&f) { // NOLINT
App.scheduler.set_timeout(this, "", timeout, std::move(f)); App.scheduler.set_timeout(this, "", timeout, std::move(f));
} }

View File

@ -380,6 +380,21 @@ class Component {
*/ */
void defer(const std::string &name, std::function<void()> &&f); // NOLINT void defer(const std::string &name, std::function<void()> &&f); // NOLINT
/** Defer a callback to the next loop() call with a const char* name.
*
* IMPORTANT: The provided name pointer must remain valid for the lifetime of the deferred task.
* This means the name should be:
* - A string literal (e.g., "update")
* - A static const char* variable
* - A pointer with lifetime >= the deferred execution
*
* For dynamic strings, use the std::string overload instead.
*
* @param name The name of the defer function (must have static lifetime)
* @param f The callback
*/
void defer(const char *name, std::function<void()> &&f); // NOLINT
/// Defer a callback to the next loop() call. /// Defer a callback to the next loop() call.
void defer(std::function<void()> &&f); // NOLINT void defer(std::function<void()> &&f); // NOLINT

View File

@ -75,20 +75,42 @@ script:
App.scheduler.cancel_timeout(component1, "cancel_static_timeout"); App.scheduler.cancel_timeout(component1, "cancel_static_timeout");
ESP_LOGI("test", "Cancelled static timeout using const char*"); ESP_LOGI("test", "Cancelled static timeout using const char*");
// Test 6 & 7: Test defer with const char* overload using a test component
class TestDeferComponent : public Component {
public:
void test_static_defer() {
// Test 6: Static string literal with defer (const char* overload)
this->defer("static_defer_1", []() {
ESP_LOGI("test", "Static defer 1 fired");
id(timeout_counter) += 1;
});
// Test 7: Static const char* with defer
static const char* DEFER_NAME = "static_defer_2";
this->defer(DEFER_NAME, []() {
ESP_LOGI("test", "Static defer 2 fired");
id(timeout_counter) += 1;
});
}
};
static TestDeferComponent test_defer_component;
test_defer_component.test_static_defer();
- id: test_dynamic_strings - id: test_dynamic_strings
then: then:
- logger.log: "Testing dynamic string timeouts and intervals" - logger.log: "Testing dynamic string timeouts and intervals"
- lambda: |- - lambda: |-
auto *component2 = id(test_sensor2); auto *component2 = id(test_sensor2);
// Test 6: Dynamic string with set_timeout (std::string) // Test 8: Dynamic string with set_timeout (std::string)
std::string dynamic_name = "dynamic_timeout_" + std::to_string(id(dynamic_counter)++); std::string dynamic_name = "dynamic_timeout_" + std::to_string(id(dynamic_counter)++);
App.scheduler.set_timeout(component2, dynamic_name, 100, []() { App.scheduler.set_timeout(component2, dynamic_name, 100, []() {
ESP_LOGI("test", "Dynamic timeout fired"); ESP_LOGI("test", "Dynamic timeout fired");
id(timeout_counter) += 1; id(timeout_counter) += 1;
}); });
// Test 7: Dynamic string with set_interval // Test 9: Dynamic string with set_interval
std::string interval_name = "dynamic_interval_" + std::to_string(id(dynamic_counter)++); std::string interval_name = "dynamic_interval_" + std::to_string(id(dynamic_counter)++);
App.scheduler.set_interval(component2, interval_name, 250, [interval_name]() { App.scheduler.set_interval(component2, interval_name, 250, [interval_name]() {
ESP_LOGI("test", "Dynamic interval fired: %s", interval_name.c_str()); ESP_LOGI("test", "Dynamic interval fired: %s", interval_name.c_str());
@ -99,7 +121,7 @@ script:
} }
}); });
// Test 8: Cancel with different string object but same content // Test 10: Cancel with different string object but same content
std::string cancel_name = "cancel_test"; std::string cancel_name = "cancel_test";
App.scheduler.set_timeout(component2, cancel_name, 2000, []() { App.scheduler.set_timeout(component2, cancel_name, 2000, []() {
ESP_LOGI("test", "This should be cancelled"); ESP_LOGI("test", "This should be cancelled");
@ -110,6 +132,21 @@ script:
App.scheduler.cancel_timeout(component2, cancel_name_2); App.scheduler.cancel_timeout(component2, cancel_name_2);
ESP_LOGI("test", "Cancelled timeout using different string object"); ESP_LOGI("test", "Cancelled timeout using different string object");
// Test 11: Dynamic string with defer (using std::string overload)
class TestDynamicDeferComponent : public Component {
public:
void test_dynamic_defer() {
std::string defer_name = "dynamic_defer_" + std::to_string(id(dynamic_counter)++);
this->defer(defer_name, [defer_name]() {
ESP_LOGI("test", "Dynamic defer fired: %s", defer_name.c_str());
id(timeout_counter) += 1;
});
}
};
static TestDynamicDeferComponent test_dynamic_defer_component;
test_dynamic_defer_component.test_dynamic_defer();
- id: report_results - id: report_results
then: then:
- lambda: |- - lambda: |-

View File

@ -26,8 +26,11 @@ async def test_scheduler_string_test(
static_interval_cancelled = asyncio.Event() static_interval_cancelled = asyncio.Event()
empty_string_timeout_fired = asyncio.Event() empty_string_timeout_fired = asyncio.Event()
static_timeout_cancelled = asyncio.Event() static_timeout_cancelled = asyncio.Event()
static_defer_1_fired = asyncio.Event()
static_defer_2_fired = asyncio.Event()
dynamic_timeout_fired = asyncio.Event() dynamic_timeout_fired = asyncio.Event()
dynamic_interval_fired = asyncio.Event() dynamic_interval_fired = asyncio.Event()
dynamic_defer_fired = asyncio.Event()
cancel_test_done = asyncio.Event() cancel_test_done = asyncio.Event()
final_results_logged = asyncio.Event() final_results_logged = asyncio.Event()
@ -72,6 +75,15 @@ async def test_scheduler_string_test(
elif "Cancelled static timeout using const char*" in clean_line: elif "Cancelled static timeout using const char*" in clean_line:
static_timeout_cancelled.set() static_timeout_cancelled.set()
# Check for static defer tests
elif "Static defer 1 fired" in clean_line:
static_defer_1_fired.set()
timeout_count += 1
elif "Static defer 2 fired" in clean_line:
static_defer_2_fired.set()
timeout_count += 1
# Check for dynamic string tests # Check for dynamic string tests
elif "Dynamic timeout fired" in clean_line: elif "Dynamic timeout fired" in clean_line:
dynamic_timeout_fired.set() dynamic_timeout_fired.set()
@ -81,6 +93,11 @@ async def test_scheduler_string_test(
dynamic_interval_count += 1 dynamic_interval_count += 1
dynamic_interval_fired.set() dynamic_interval_fired.set()
# Check for dynamic defer test
elif "Dynamic defer fired" in clean_line:
dynamic_defer_fired.set()
timeout_count += 1
# Check for cancel test # Check for cancel test
elif "Cancelled timeout using different string object" in clean_line: elif "Cancelled timeout using different string object" in clean_line:
cancel_test_done.set() cancel_test_done.set()
@ -133,6 +150,17 @@ async def test_scheduler_string_test(
"Static timeout should have been cancelled" "Static timeout should have been cancelled"
) )
# Wait for static defer tests
try:
await asyncio.wait_for(static_defer_1_fired.wait(), timeout=0.5)
except asyncio.TimeoutError:
pytest.fail("Static defer 1 did not fire within 0.5 seconds")
try:
await asyncio.wait_for(static_defer_2_fired.wait(), timeout=0.5)
except asyncio.TimeoutError:
pytest.fail("Static defer 2 did not fire within 0.5 seconds")
# Wait for dynamic string tests # Wait for dynamic string tests
try: try:
await asyncio.wait_for(dynamic_timeout_fired.wait(), timeout=1.0) await asyncio.wait_for(dynamic_timeout_fired.wait(), timeout=1.0)
@ -144,6 +172,12 @@ async def test_scheduler_string_test(
except asyncio.TimeoutError: except asyncio.TimeoutError:
pytest.fail("Dynamic interval did not fire within 1.5 seconds") pytest.fail("Dynamic interval did not fire within 1.5 seconds")
# Wait for dynamic defer test
try:
await asyncio.wait_for(dynamic_defer_fired.wait(), timeout=1.0)
except asyncio.TimeoutError:
pytest.fail("Dynamic defer did not fire within 1 second")
# Wait for cancel test # Wait for cancel test
try: try:
await asyncio.wait_for(cancel_test_done.wait(), timeout=1.0) await asyncio.wait_for(cancel_test_done.wait(), timeout=1.0)
@ -157,7 +191,9 @@ async def test_scheduler_string_test(
pytest.fail("Final results were not logged within 4 seconds") pytest.fail("Final results were not logged within 4 seconds")
# Verify results # Verify results
assert timeout_count >= 3, f"Expected at least 3 timeouts, got {timeout_count}" assert timeout_count >= 6, (
f"Expected at least 6 timeouts (including defers), got {timeout_count}"
)
assert interval_count >= 3, ( assert interval_count >= 3, (
f"Expected at least 3 interval fires, got {interval_count}" f"Expected at least 3 interval fires, got {interval_count}"
) )