mirror of
https://github.com/esphome/esphome.git
synced 2026-10-13 09:42:50 +00:00
[core] Copy trigger arguments in wait_until and script.wait (#20536)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5d75828ceb
commit
7c0f63ea20
@@ -0,0 +1,109 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/base_automation.h"
|
||||
|
||||
namespace esphome::testing {
|
||||
|
||||
class FlagCondition : public Condition<const std::string &> {
|
||||
public:
|
||||
bool check(const std::string & /*value*/) override { return this->ready; }
|
||||
bool ready{false};
|
||||
};
|
||||
|
||||
class RecordAction : public Action<const std::string &> {
|
||||
public:
|
||||
void play(const std::string &value) override { this->seen = value; }
|
||||
std::string seen;
|
||||
};
|
||||
|
||||
// A trigger argument passed as `const T &` must be copied while waiting: the
|
||||
// referenced object is gone once the trigger has returned.
|
||||
TEST(WaitUntilActionTest, KeepsCopyOfReferenceArguments) {
|
||||
FlagCondition condition;
|
||||
WaitUntilAction<const std::string &> wait(&condition);
|
||||
RecordAction record;
|
||||
ActionList<const std::string &> actions;
|
||||
actions.add_actions({&wait, &record});
|
||||
|
||||
{
|
||||
auto value = std::make_unique<std::string>("argument longer than the small string buffer");
|
||||
actions.play(*value);
|
||||
}
|
||||
EXPECT_TRUE(record.seen.empty());
|
||||
|
||||
condition.ready = true;
|
||||
wait.loop();
|
||||
EXPECT_EQ(record.seen, "argument longer than the small string buffer");
|
||||
}
|
||||
|
||||
class Counter {
|
||||
public:
|
||||
int value{0};
|
||||
};
|
||||
|
||||
class CounterCondition : public Condition<Counter &> {
|
||||
public:
|
||||
bool check(Counter & /*counter*/) override { return this->ready; }
|
||||
bool ready{false};
|
||||
};
|
||||
|
||||
class IncrementAction : public Action<Counter &> {
|
||||
public:
|
||||
void play(Counter &counter) override { this->seen = ++counter.value; }
|
||||
int seen{0};
|
||||
};
|
||||
|
||||
// A non-const reference argument used to fail to compile
|
||||
TEST(WaitUntilActionTest, AcceptsMutableReferenceArguments) {
|
||||
CounterCondition condition;
|
||||
WaitUntilAction<Counter &> wait(&condition);
|
||||
IncrementAction increment;
|
||||
ActionList<Counter &> actions;
|
||||
actions.add_actions({&wait, &increment});
|
||||
|
||||
Counter counter;
|
||||
actions.play(counter);
|
||||
condition.ready = true;
|
||||
wait.loop();
|
||||
EXPECT_EQ(increment.seen, 1);
|
||||
// Like delay, the actions after the wait work on a copy
|
||||
EXPECT_EQ(counter.value, 0);
|
||||
}
|
||||
|
||||
class Entity {
|
||||
public:
|
||||
Entity() = default;
|
||||
Entity(const Entity &) = delete;
|
||||
int value{0};
|
||||
};
|
||||
|
||||
class EntityCondition : public Condition<Entity &> {
|
||||
public:
|
||||
bool check(Entity & /*entity*/) override { return this->ready; }
|
||||
bool ready{false};
|
||||
};
|
||||
|
||||
class EntityIncrementAction : public Action<Entity &> {
|
||||
public:
|
||||
void play(Entity &entity) override { entity.value++; }
|
||||
};
|
||||
|
||||
// A type that cannot be copied, such as an entity, is kept by reference
|
||||
TEST(WaitUntilActionTest, KeepsReferenceToNonCopyableArguments) {
|
||||
EntityCondition condition;
|
||||
WaitUntilAction<Entity &> wait(&condition);
|
||||
EntityIncrementAction increment;
|
||||
ActionList<Entity &> actions;
|
||||
actions.add_actions({&wait, &increment});
|
||||
|
||||
Entity entity;
|
||||
actions.play(entity);
|
||||
condition.ready = true;
|
||||
wait.loop();
|
||||
EXPECT_EQ(entity.value, 1);
|
||||
}
|
||||
|
||||
} // namespace esphome::testing
|
||||
@@ -0,0 +1,96 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/components/script/script.h"
|
||||
|
||||
namespace esphome::script::testing {
|
||||
|
||||
class FakeScript {
|
||||
public:
|
||||
bool is_running() const { return this->running; }
|
||||
bool running{true};
|
||||
};
|
||||
|
||||
class RecordAction : public Action<const std::string &> {
|
||||
public:
|
||||
void play(const std::string &value) override { this->seen = value; }
|
||||
std::string seen;
|
||||
};
|
||||
|
||||
// A trigger argument passed as `const T &` must be copied while waiting: the
|
||||
// referenced object is gone once the trigger has returned.
|
||||
TEST(ScriptWaitActionTest, KeepsCopyOfReferenceArguments) {
|
||||
FakeScript script;
|
||||
ScriptWaitAction<FakeScript, const std::string &> wait(&script);
|
||||
RecordAction record;
|
||||
ActionList<const std::string &> actions;
|
||||
actions.add_actions({&wait, &record});
|
||||
|
||||
{
|
||||
auto value = std::make_unique<std::string>("argument longer than the small string buffer");
|
||||
actions.play(*value);
|
||||
}
|
||||
EXPECT_TRUE(record.seen.empty());
|
||||
|
||||
script.running = false;
|
||||
wait.loop();
|
||||
EXPECT_EQ(record.seen, "argument longer than the small string buffer");
|
||||
}
|
||||
|
||||
class Counter {
|
||||
public:
|
||||
int value{0};
|
||||
};
|
||||
|
||||
class IncrementAction : public Action<Counter &> {
|
||||
public:
|
||||
void play(Counter &counter) override { this->seen = ++counter.value; }
|
||||
int seen{0};
|
||||
};
|
||||
|
||||
TEST(ScriptWaitActionTest, KeepsCopyOfMutableReferenceArguments) {
|
||||
FakeScript script;
|
||||
ScriptWaitAction<FakeScript, Counter &> wait(&script);
|
||||
IncrementAction increment;
|
||||
ActionList<Counter &> actions;
|
||||
actions.add_actions({&wait, &increment});
|
||||
|
||||
Counter counter;
|
||||
actions.play(counter);
|
||||
script.running = false;
|
||||
wait.loop();
|
||||
EXPECT_EQ(increment.seen, 1);
|
||||
// Like delay, the actions after the wait work on a copy
|
||||
EXPECT_EQ(counter.value, 0);
|
||||
}
|
||||
|
||||
class Entity {
|
||||
public:
|
||||
Entity() = default;
|
||||
Entity(const Entity &) = delete;
|
||||
int value{0};
|
||||
};
|
||||
|
||||
class EntityIncrementAction : public Action<Entity &> {
|
||||
public:
|
||||
void play(Entity &entity) override { entity.value++; }
|
||||
};
|
||||
|
||||
// A type that cannot be copied, such as an entity, is kept by reference
|
||||
TEST(ScriptWaitActionTest, KeepsReferenceToNonCopyableArguments) {
|
||||
FakeScript script;
|
||||
ScriptWaitAction<FakeScript, Entity &> wait(&script);
|
||||
EntityIncrementAction increment;
|
||||
ActionList<Entity &> actions;
|
||||
actions.add_actions({&wait, &increment});
|
||||
|
||||
Entity entity;
|
||||
actions.play(entity);
|
||||
script.running = false;
|
||||
wait.loop();
|
||||
EXPECT_EQ(entity.value, 1);
|
||||
}
|
||||
|
||||
} // namespace esphome::script::testing
|
||||
Reference in New Issue
Block a user