mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 14:43:51 +00:00
Add heap scheduler tests
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
scheduler_string_lifetime_component_ns = cg.esphome_ns.namespace(
|
||||
"scheduler_string_lifetime_component"
|
||||
)
|
||||
SchedulerStringLifetimeComponent = scheduler_string_lifetime_component_ns.class_(
|
||||
"SchedulerStringLifetimeComponent", cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SchedulerStringLifetimeComponent),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
@@ -0,0 +1,233 @@
|
||||
#include "string_lifetime_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
namespace esphome {
|
||||
namespace scheduler_string_lifetime_component {
|
||||
|
||||
static const char *const TAG = "scheduler_string_lifetime";
|
||||
|
||||
void SchedulerStringLifetimeComponent::setup() { ESP_LOGCONFIG(TAG, "SchedulerStringLifetimeComponent setup"); }
|
||||
|
||||
void SchedulerStringLifetimeComponent::run_string_lifetime_test() {
|
||||
ESP_LOGI(TAG, "Starting string lifetime tests");
|
||||
|
||||
this->tests_passed_ = 0;
|
||||
this->tests_failed_ = 0;
|
||||
|
||||
// Run each test
|
||||
test_temporary_string_lifetime();
|
||||
test_scope_exit_string();
|
||||
test_vector_reallocation();
|
||||
test_string_move_semantics();
|
||||
test_lambda_capture_lifetime();
|
||||
|
||||
// Schedule final check
|
||||
this->set_timeout("final_check", 200, [this]() {
|
||||
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() {
|
||||
ESP_LOGI(TAG, "Test 1: Temporary string lifetime for timeout names");
|
||||
|
||||
// Test with a temporary string that goes out of scope immediately
|
||||
{
|
||||
std::string temp_name = "temp_callback_" + std::to_string(12345);
|
||||
|
||||
// Schedule with temporary string name - scheduler must copy/store this
|
||||
this->set_timeout(temp_name, 1, [this]() {
|
||||
ESP_LOGD(TAG, "Callback for temp string name executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
|
||||
// String goes out of scope here, but scheduler should have made a copy
|
||||
}
|
||||
|
||||
// Test with rvalue string as name
|
||||
this->set_timeout(std::string("rvalue_test"), 2, [this]() {
|
||||
ESP_LOGD(TAG, "Rvalue string name callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
|
||||
// Test cancelling with reconstructed string
|
||||
{
|
||||
std::string cancel_name = "cancel_test_" + std::to_string(999);
|
||||
this->set_timeout(cancel_name, 100, [this]() {
|
||||
ESP_LOGE(TAG, "This should have been cancelled!");
|
||||
this->tests_failed_++;
|
||||
});
|
||||
} // cancel_name goes out of scope
|
||||
|
||||
// Reconstruct the same string to cancel
|
||||
std::string cancel_name_2 = "cancel_test_" + std::to_string(999);
|
||||
bool cancelled = this->cancel_timeout(cancel_name_2);
|
||||
if (cancelled) {
|
||||
ESP_LOGD(TAG, "Successfully cancelled with reconstructed string");
|
||||
this->tests_passed_++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to cancel with reconstructed string");
|
||||
this->tests_failed_++;
|
||||
}
|
||||
}
|
||||
|
||||
void SchedulerStringLifetimeComponent::test_scope_exit_string() {
|
||||
ESP_LOGI(TAG, "Test 2: Scope exit string names");
|
||||
|
||||
// Create string names in a limited scope
|
||||
{
|
||||
std::string scoped_name = "scoped_timeout_" + std::to_string(555);
|
||||
|
||||
// Schedule with scoped string name
|
||||
this->set_timeout(scoped_name, 3, [this]() {
|
||||
ESP_LOGD(TAG, "Scoped name callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
|
||||
// scoped_name goes out of scope here
|
||||
}
|
||||
|
||||
// Test with dynamically allocated string name
|
||||
{
|
||||
auto *dynamic_name = new std::string("dynamic_timeout_" + std::to_string(777));
|
||||
|
||||
this->set_timeout(*dynamic_name, 4, [this, dynamic_name]() {
|
||||
ESP_LOGD(TAG, "Dynamic string name callback executed");
|
||||
this->tests_passed_++;
|
||||
delete dynamic_name; // Clean up in callback
|
||||
});
|
||||
|
||||
// Pointer goes out of scope but string object remains until callback
|
||||
}
|
||||
|
||||
// Test multiple timeouts with same dynamically created name
|
||||
for (int i = 0; i < 3; i++) {
|
||||
std::string loop_name = "loop_timeout_" + std::to_string(i);
|
||||
this->set_timeout(loop_name, 5 + i * 1, [this, i]() {
|
||||
ESP_LOGD(TAG, "Loop timeout %d executed", i);
|
||||
this->tests_passed_++;
|
||||
});
|
||||
// loop_name destroyed and recreated each iteration
|
||||
}
|
||||
}
|
||||
|
||||
void SchedulerStringLifetimeComponent::test_vector_reallocation() {
|
||||
ESP_LOGI(TAG, "Test 3: Vector reallocation stress on timeout names");
|
||||
|
||||
// Create a vector that will reallocate
|
||||
std::vector<std::string> names;
|
||||
names.reserve(2); // Small initial capacity to force reallocation
|
||||
|
||||
// Schedule callbacks with string names from vector
|
||||
for (int i = 0; i < 10; i++) {
|
||||
names.push_back("vector_cb_" + std::to_string(i));
|
||||
// Use the string from vector as timeout name
|
||||
this->set_timeout(names.back(), 8 + i * 1, [this, i]() {
|
||||
ESP_LOGV(TAG, "Vector name callback %d executed", i);
|
||||
this->tests_passed_++;
|
||||
});
|
||||
}
|
||||
|
||||
// Force reallocation by adding more elements
|
||||
// This will move all strings to new memory locations
|
||||
for (int i = 10; i < 50; i++) {
|
||||
names.push_back("realloc_trigger_" + std::to_string(i));
|
||||
}
|
||||
|
||||
// Add more timeouts after reallocation to ensure old names still work
|
||||
for (int i = 50; i < 55; i++) {
|
||||
names.push_back("post_realloc_" + std::to_string(i));
|
||||
this->set_timeout(names.back(), 20 + (i - 50), [this]() {
|
||||
ESP_LOGV(TAG, "Post-reallocation callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
}
|
||||
|
||||
// Clear the vector while timeouts are still pending
|
||||
names.clear();
|
||||
ESP_LOGD(TAG, "Vector cleared - all string names destroyed");
|
||||
}
|
||||
|
||||
void SchedulerStringLifetimeComponent::test_string_move_semantics() {
|
||||
ESP_LOGI(TAG, "Test 4: String move semantics for timeout names");
|
||||
|
||||
// Test moving string names
|
||||
std::string original = "move_test_original";
|
||||
std::string moved = std::move(original);
|
||||
|
||||
// Schedule with moved string as name
|
||||
this->set_timeout(moved, 30, [this]() {
|
||||
ESP_LOGD(TAG, "Moved string name callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
|
||||
// original is now empty, try to use it as a different timeout name
|
||||
original = "reused_after_move";
|
||||
this->set_timeout(original, 32, [this]() {
|
||||
ESP_LOGD(TAG, "Reused string name callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
}
|
||||
|
||||
void SchedulerStringLifetimeComponent::test_lambda_capture_lifetime() {
|
||||
ESP_LOGI(TAG, "Test 5: Complex timeout name scenarios");
|
||||
|
||||
// Test scheduling with name built in lambda
|
||||
[this]() {
|
||||
std::string lambda_name = "lambda_built_name_" + std::to_string(888);
|
||||
this->set_timeout(lambda_name, 38, [this]() {
|
||||
ESP_LOGD(TAG, "Lambda-built name callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
}(); // Lambda executes and lambda_name is destroyed
|
||||
|
||||
// Test with shared_ptr name
|
||||
auto shared_name = std::make_shared<std::string>("shared_ptr_timeout");
|
||||
this->set_timeout(*shared_name, 40, [this, shared_name]() {
|
||||
ESP_LOGD(TAG, "Shared_ptr name callback executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
shared_name.reset(); // Release the shared_ptr
|
||||
|
||||
// Test overwriting timeout with same name
|
||||
std::string overwrite_name = "overwrite_test";
|
||||
this->set_timeout(overwrite_name, 1000, [this]() {
|
||||
ESP_LOGE(TAG, "This should have been overwritten!");
|
||||
this->tests_failed_++;
|
||||
});
|
||||
|
||||
// Overwrite with shorter timeout
|
||||
this->set_timeout(overwrite_name, 42, [this]() {
|
||||
ESP_LOGD(TAG, "Overwritten timeout executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
|
||||
// Test very long string name
|
||||
std::string long_name;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
long_name += "very_long_timeout_name_segment_" + std::to_string(i) + "_";
|
||||
}
|
||||
this->set_timeout(long_name, 44, [this]() {
|
||||
ESP_LOGD(TAG, "Very long name timeout executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
|
||||
// Test empty string as name
|
||||
this->set_timeout("", 46, [this]() {
|
||||
ESP_LOGD(TAG, "Empty string name timeout executed");
|
||||
this->tests_passed_++;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace scheduler_string_lifetime_component
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace scheduler_string_lifetime_component {
|
||||
|
||||
class SchedulerStringLifetimeComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return setup_priority::LATE; }
|
||||
|
||||
void run_string_lifetime_test();
|
||||
|
||||
private:
|
||||
void test_temporary_string_lifetime();
|
||||
void test_scope_exit_string();
|
||||
void test_vector_reallocation();
|
||||
void test_string_move_semantics();
|
||||
void test_lambda_capture_lifetime();
|
||||
|
||||
int tests_passed_{0};
|
||||
int tests_failed_{0};
|
||||
};
|
||||
|
||||
} // namespace scheduler_string_lifetime_component
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user