1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-14 17:22:20 +01:00

Merge branch 'loop_done' into integration

This commit is contained in:
J. Nick Koston
2025-06-15 20:34:22 -05:00
3 changed files with 4 additions and 380 deletions

View File

@@ -1,378 +0,0 @@
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
#include <string>
// Forward declare tests vector
struct Test {
std::string name;
void (*func)();
};
std::vector<Test> tests;
// Minimal test framework
#define TEST(name) \
void test_##name(); \
struct test_##name##_registrar { \
test_##name##_registrar() { tests.push_back({#name, test_##name}); } \
} test_##name##_instance; \
void test_##name()
#define ASSERT(cond) \
do { \
if (!(cond)) { \
std::cerr << "FAILED: " #cond " at " << __FILE__ << ":" << __LINE__ << std::endl; \
exit(1); \
} \
} while (0)
#define ASSERT_EQ(a, b) ASSERT((a) == (b))
// Mock classes matching ESPHome structure
const uint8_t COMPONENT_STATE_MASK = 0x07;
const uint8_t COMPONENT_STATE_LOOP = 0x02;
const uint8_t COMPONENT_STATE_LOOP_DONE = 0x04;
const uint8_t COMPONENT_STATE_FAILED = 0x03;
class Component {
protected:
uint8_t component_state_ = COMPONENT_STATE_LOOP;
int id_;
int loop_count_ = 0;
public:
Component(int id) : id_(id) {}
virtual ~Component() = default;
virtual void call() { loop_count_++; }
int get_id() const { return id_; }
int get_loop_count() const { return loop_count_; }
uint8_t get_state() const { return component_state_ & COMPONENT_STATE_MASK; }
void set_state(uint8_t state) { component_state_ = (component_state_ & ~COMPONENT_STATE_MASK) | state; }
};
class Application {
public:
std::vector<Component *> looping_components_;
uint16_t looping_components_active_end_ = 0;
uint16_t current_loop_index_ = 0;
bool in_loop_ = false;
void add_component(Component *c) {
looping_components_.push_back(c);
looping_components_active_end_ = looping_components_.size();
}
void loop() {
in_loop_ = true;
for (current_loop_index_ = 0; current_loop_index_ < looping_components_active_end_; current_loop_index_++) {
looping_components_[current_loop_index_]->call();
}
in_loop_ = false;
}
void disable_component_loop(Component *component) {
for (uint16_t i = 0; i < looping_components_active_end_; i++) {
if (looping_components_[i] == component) {
looping_components_active_end_--;
if (i != looping_components_active_end_) {
std::swap(looping_components_[i], looping_components_[looping_components_active_end_]);
if (in_loop_ && i == current_loop_index_) {
current_loop_index_--;
}
}
return;
}
}
}
void enable_component_loop(Component *component) {
const uint16_t size = looping_components_.size();
for (uint16_t i = 0; i < size; i++) {
if (looping_components_[i] == component) {
if (i < looping_components_active_end_) {
return; // Already active
}
if (i != looping_components_active_end_) {
std::swap(looping_components_[i], looping_components_[looping_components_active_end_]);
}
looping_components_active_end_++;
return;
}
}
}
// Helper methods for testing
std::vector<int> get_active_ids() const {
std::vector<int> ids;
for (uint16_t i = 0; i < looping_components_active_end_; i++) {
ids.push_back(looping_components_[i]->get_id());
}
return ids;
}
bool is_component_active(Component *c) const {
for (uint16_t i = 0; i < looping_components_active_end_; i++) {
if (looping_components_[i] == c)
return true;
}
return false;
}
};
// Test basic functionality
TEST(basic_loop) {
Application app;
std::vector<std::unique_ptr<Component>> components;
for (int i = 0; i < 5; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
app.loop();
for (const auto &c : components) {
ASSERT_EQ(c->get_loop_count(), 1);
}
}
TEST(disable_component) {
Application app;
std::vector<std::unique_ptr<Component>> components;
for (int i = 0; i < 5; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
// Disable component 2
app.disable_component_loop(components[2].get());
app.loop();
// Components 0,1,3,4 should have been called
ASSERT_EQ(components[0]->get_loop_count(), 1);
ASSERT_EQ(components[1]->get_loop_count(), 1);
ASSERT_EQ(components[2]->get_loop_count(), 0); // Disabled
ASSERT_EQ(components[3]->get_loop_count(), 1);
ASSERT_EQ(components[4]->get_loop_count(), 1);
// Verify partitioning
ASSERT_EQ(app.looping_components_active_end_, 4);
ASSERT(!app.is_component_active(components[2].get()));
}
TEST(enable_component) {
Application app;
std::vector<std::unique_ptr<Component>> components;
for (int i = 0; i < 5; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
// Disable then re-enable
app.disable_component_loop(components[2].get());
app.enable_component_loop(components[2].get());
app.loop();
// All should have been called
for (const auto &c : components) {
ASSERT_EQ(c->get_loop_count(), 1);
}
ASSERT_EQ(app.looping_components_active_end_, 5);
}
TEST(multiple_disable_enable) {
Application app;
std::vector<std::unique_ptr<Component>> components;
for (int i = 0; i < 10; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
// Disable multiple
app.disable_component_loop(components[1].get());
app.disable_component_loop(components[5].get());
app.disable_component_loop(components[7].get());
ASSERT_EQ(app.looping_components_active_end_, 7);
app.loop();
// Check counts
int active_count = 0;
for (const auto &c : components) {
if (c->get_loop_count() == 1)
active_count++;
}
ASSERT_EQ(active_count, 7);
// Re-enable one
app.enable_component_loop(components[5].get());
ASSERT_EQ(app.looping_components_active_end_, 8);
app.loop();
ASSERT_EQ(components[5]->get_loop_count(), 1);
}
// Test reentrant behavior
class SelfDisablingComponent : public Component {
Application *app_;
public:
SelfDisablingComponent(int id, Application *app) : Component(id), app_(app) {}
void call() override {
Component::call();
if (loop_count_ == 2) {
app_->disable_component_loop(this);
}
}
};
TEST(reentrant_disable) {
Application app;
std::vector<std::unique_ptr<Component>> components;
// Add regular components
for (int i = 0; i < 3; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
// Add self-disabling component
auto self_disable = std::make_unique<SelfDisablingComponent>(3, &app);
app.add_component(self_disable.get());
// Add more regular components
for (int i = 4; i < 6; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
// First loop - all active
app.loop();
ASSERT_EQ(app.looping_components_active_end_, 6);
// Second loop - self-disabling component disables itself
app.loop();
ASSERT_EQ(app.looping_components_active_end_, 5);
ASSERT_EQ(self_disable->get_loop_count(), 2);
// Third loop - self-disabling component should not be called
app.loop();
ASSERT_EQ(self_disable->get_loop_count(), 2); // Still 2
}
// Test edge cases
TEST(disable_already_disabled) {
Application app;
auto comp = std::make_unique<Component>(0);
app.add_component(comp.get());
app.disable_component_loop(comp.get());
ASSERT_EQ(app.looping_components_active_end_, 0);
// Disable again - should be no-op
app.disable_component_loop(comp.get());
ASSERT_EQ(app.looping_components_active_end_, 0);
}
TEST(enable_already_enabled) {
Application app;
auto comp = std::make_unique<Component>(0);
app.add_component(comp.get());
ASSERT_EQ(app.looping_components_active_end_, 1);
// Enable again - should be no-op
app.enable_component_loop(comp.get());
ASSERT_EQ(app.looping_components_active_end_, 1);
}
TEST(disable_last_component) {
Application app;
auto comp = std::make_unique<Component>(0);
app.add_component(comp.get());
app.disable_component_loop(comp.get());
ASSERT_EQ(app.looping_components_active_end_, 0);
app.loop(); // Should not crash with empty active set
}
// Test that mimics real ESPHome component behavior
class MockSNTPComponent : public Component {
Application *app_;
bool time_synced_ = false;
public:
MockSNTPComponent(int id, Application *app) : Component(id), app_(app) {}
void call() override {
Component::call();
// Simulate time sync after 3 calls
if (loop_count_ >= 3 && !time_synced_) {
time_synced_ = true;
std::cout << " SNTP: Time synced, disabling loop" << std::endl;
set_state(COMPONENT_STATE_LOOP_DONE);
app_->disable_component_loop(this);
}
}
bool is_synced() const { return time_synced_; }
};
TEST(real_world_sntp) {
Application app;
// Regular components
std::vector<std::unique_ptr<Component>> components;
for (int i = 0; i < 5; i++) {
components.push_back(std::make_unique<Component>(i));
app.add_component(components.back().get());
}
// SNTP component
auto sntp = std::make_unique<MockSNTPComponent>(5, &app);
app.add_component(sntp.get());
// Run 5 iterations
for (int i = 0; i < 5; i++) {
app.loop();
}
// SNTP should have disabled itself after 3 calls
ASSERT_EQ(sntp->get_loop_count(), 3);
ASSERT(sntp->is_synced());
ASSERT_EQ(app.looping_components_active_end_, 5); // SNTP removed
// Regular components should have 5 calls each
for (const auto &c : components) {
ASSERT_EQ(c->get_loop_count(), 5);
}
}
int main() {
std::cout << "Running partitioned vector tests...\n" << std::endl;
for (const auto &test : tests) {
std::cout << "Running test: " << test.name << std::endl;
test.func();
std::cout << " ✓ PASSED" << std::endl;
}
std::cout << "\nAll " << tests.size() << " tests passed!" << std::endl;
return 0;
}

View File

@@ -9,7 +9,6 @@ import logging
import os import os
from pathlib import Path from pathlib import Path
import platform import platform
import pty
import signal import signal
import socket import socket
import sys import sys
@@ -48,6 +47,9 @@ if platform.system() == "Windows":
) )
import pty # not available on Windows
@pytest.fixture(scope="module", autouse=True) @pytest.fixture(scope="module", autouse=True)
def enable_aioesphomeapi_debug_logging(): def enable_aioesphomeapi_debug_logging():
"""Enable debug logging for aioesphomeapi to help diagnose connection issues.""" """Enable debug logging for aioesphomeapi to help diagnose connection issues."""

View File

@@ -37,7 +37,7 @@ loop_test_component:
# Interval to re-enable the self_disable_10 component after some time # Interval to re-enable the self_disable_10 component after some time
interval: interval:
- interval: 2s - interval: 0.5s
then: then:
- if: - if:
condition: condition: