blob: 0b5dbec7f2941db64e956b5f0c2c2c7ebc66edb0 [file] [log] [blame]
#include "aos/events/simulated_event_loop.h"
#include <string_view>
#include "aos/events/event_loop_param_test.h"
#include "aos/events/ping_lib.h"
#include "aos/events/pong_lib.h"
#include "aos/events/test_message_generated.h"
#include "gtest/gtest.h"
namespace aos {
namespace testing {
namespace chrono = ::std::chrono;
class SimulatedEventLoopTestFactory : public EventLoopTestFactory {
public:
::std::unique_ptr<EventLoop> Make(std::string_view name) override {
MaybeMake();
return event_loop_factory_->MakeEventLoop(name, my_node());
}
::std::unique_ptr<EventLoop> MakePrimary(std::string_view name) override {
MaybeMake();
return event_loop_factory_->MakeEventLoop(name, my_node());
}
void Run() override { event_loop_factory_->Run(); }
void Exit() override { event_loop_factory_->Exit(); }
// TODO(austin): Implement this. It's used currently for a phased loop test.
// I'm not sure how much that matters.
void SleepFor(::std::chrono::nanoseconds /*duration*/) override {}
void set_send_delay(std::chrono::nanoseconds send_delay) {
MaybeMake();
event_loop_factory_->set_send_delay(send_delay);
}
private:
void MaybeMake() {
if (!event_loop_factory_) {
if (configuration()->has_nodes()) {
event_loop_factory_ =
std::make_unique<SimulatedEventLoopFactory>(configuration());
} else {
event_loop_factory_ =
std::make_unique<SimulatedEventLoopFactory>(configuration());
}
}
}
std::unique_ptr<SimulatedEventLoopFactory> event_loop_factory_;
};
INSTANTIATE_TEST_CASE_P(SimulatedEventLoopDeathTest, AbstractEventLoopDeathTest,
::testing::Values([]() {
return new SimulatedEventLoopTestFactory();
}));
INSTANTIATE_TEST_CASE_P(SimulatedEventLoopTest, AbstractEventLoopTest,
::testing::Values([]() {
return new SimulatedEventLoopTestFactory();
}));
// Test that creating an event and running the scheduler runs the event.
TEST(EventSchedulerTest, ScheduleEvent) {
int counter = 0;
EventSchedulerScheduler scheduler_scheduler;
EventScheduler scheduler;
scheduler_scheduler.AddEventScheduler(&scheduler);
scheduler.Schedule(monotonic_clock::epoch() + chrono::seconds(1),
[&counter]() { counter += 1; });
scheduler_scheduler.Run();
EXPECT_EQ(counter, 1);
auto token =
scheduler.Schedule(monotonic_clock::epoch() + chrono::seconds(2),
[&counter]() { counter += 1; });
scheduler.Deschedule(token);
scheduler_scheduler.Run();
EXPECT_EQ(counter, 1);
}
// Test that descheduling an already scheduled event doesn't run the event.
TEST(EventSchedulerTest, DescheduleEvent) {
int counter = 0;
EventSchedulerScheduler scheduler_scheduler;
EventScheduler scheduler;
scheduler_scheduler.AddEventScheduler(&scheduler);
auto token = scheduler.Schedule(monotonic_clock::epoch() + chrono::seconds(1),
[&counter]() { counter += 1; });
scheduler.Deschedule(token);
scheduler_scheduler.Run();
EXPECT_EQ(counter, 0);
}
// Test that running for a time period with no handlers causes time to progress
// correctly.
TEST(SimulatedEventLoopTest, RunForNoHandlers) {
SimulatedEventLoopTestFactory factory;
SimulatedEventLoopFactory simulated_event_loop_factory(
factory.configuration());
::std::unique_ptr<EventLoop> event_loop =
simulated_event_loop_factory.MakeEventLoop("loop");
simulated_event_loop_factory.RunFor(chrono::seconds(1));
EXPECT_EQ(::aos::monotonic_clock::epoch() + chrono::seconds(1),
event_loop->monotonic_now());
}
// Test that running for a time with a periodic handler causes time to end
// correctly.
TEST(SimulatedEventLoopTest, RunForTimerHandler) {
SimulatedEventLoopTestFactory factory;
SimulatedEventLoopFactory simulated_event_loop_factory(
factory.configuration());
::std::unique_ptr<EventLoop> event_loop =
simulated_event_loop_factory.MakeEventLoop("loop");
int counter = 0;
auto timer = event_loop->AddTimer([&counter]() { ++counter; });
event_loop->OnRun([&event_loop, &timer] {
timer->Setup(event_loop->monotonic_now() + chrono::milliseconds(50),
chrono::milliseconds(100));
});
simulated_event_loop_factory.RunFor(chrono::seconds(1));
EXPECT_EQ(::aos::monotonic_clock::epoch() + chrono::seconds(1),
event_loop->monotonic_now());
EXPECT_EQ(counter, 10);
}
// Tests that watchers have latency in simulation.
TEST(SimulatedEventLoopTest, WatcherTimingReport) {
SimulatedEventLoopTestFactory factory;
factory.set_send_delay(std::chrono::microseconds(50));
FLAGS_timing_report_ms = 1000;
auto loop1 = factory.MakePrimary("primary");
loop1->MakeWatcher("/test", [](const TestMessage &) {});
auto loop2 = factory.Make("sender_loop");
auto loop3 = factory.Make("report_fetcher");
Fetcher<timing::Report> report_fetcher =
loop3->MakeFetcher<timing::Report>("/aos");
EXPECT_FALSE(report_fetcher.Fetch());
auto sender = loop2->MakeSender<TestMessage>("/test");
// Send 10 messages in the middle of a timing report period so we get
// something interesting back.
auto test_timer = loop2->AddTimer([&sender]() {
for (int i = 0; i < 10; ++i) {
aos::Sender<TestMessage>::Builder msg = sender.MakeBuilder();
TestMessage::Builder builder = msg.MakeBuilder<TestMessage>();
builder.add_value(200 + i);
ASSERT_TRUE(msg.Send(builder.Finish()));
}
});
// Quit after 1 timing report, mid way through the next cycle.
{
auto end_timer = loop1->AddTimer([&factory]() { factory.Exit(); });
end_timer->Setup(loop1->monotonic_now() + chrono::milliseconds(2500));
end_timer->set_name("end");
}
loop1->OnRun([&test_timer, &loop1]() {
test_timer->Setup(loop1->monotonic_now() + chrono::milliseconds(1500));
});
factory.Run();
// And, since we are here, check that the timing report makes sense.
// Start by looking for our event loop's timing.
FlatbufferDetachedBuffer<timing::Report> primary_report =
FlatbufferDetachedBuffer<timing::Report>::Empty();
while (report_fetcher.FetchNext()) {
LOG(INFO) << "Report " << FlatbufferToJson(report_fetcher.get());
if (report_fetcher->name()->string_view() == "primary") {
primary_report = CopyFlatBuffer(report_fetcher.get());
}
}
// Check the watcher report.
VLOG(1) << FlatbufferToJson(primary_report, true);
EXPECT_EQ(primary_report.message().name()->string_view(), "primary");
// Just the timing report timer.
ASSERT_NE(primary_report.message().timers(), nullptr);
EXPECT_EQ(primary_report.message().timers()->size(), 2);
// No phased loops
ASSERT_EQ(primary_report.message().phased_loops(), nullptr);
// And now confirm that the watcher received all 10 messages, and has latency.
ASSERT_NE(primary_report.message().watchers(), nullptr);
ASSERT_EQ(primary_report.message().watchers()->size(), 1);
EXPECT_EQ(primary_report.message().watchers()->Get(0)->count(), 10);
EXPECT_NEAR(
primary_report.message().watchers()->Get(0)->wakeup_latency()->average(),
0.00005, 1e-9);
EXPECT_NEAR(
primary_report.message().watchers()->Get(0)->wakeup_latency()->min(),
0.00005, 1e-9);
EXPECT_NEAR(
primary_report.message().watchers()->Get(0)->wakeup_latency()->max(),
0.00005, 1e-9);
EXPECT_EQ(primary_report.message()
.watchers()
->Get(0)
->wakeup_latency()
->standard_deviation(),
0.0);
EXPECT_EQ(
primary_report.message().watchers()->Get(0)->handler_time()->average(),
0.0);
EXPECT_EQ(primary_report.message().watchers()->Get(0)->handler_time()->min(),
0.0);
EXPECT_EQ(primary_report.message().watchers()->Get(0)->handler_time()->max(),
0.0);
EXPECT_EQ(primary_report.message()
.watchers()
->Get(0)
->handler_time()
->standard_deviation(),
0.0);
}
// Tests that ping and pong work when on 2 different nodes.
TEST(SimulatedEventLoopTest, MultinodePingPong) {
aos::FlatbufferDetachedBuffer<aos::Configuration> config =
aos::configuration::ReadConfig(
"aos/events/multinode_pingpong_config.json");
const Node *pi1 = configuration::GetNode(&config.message(), "pi1");
const Node *pi2 = configuration::GetNode(&config.message(), "pi2");
SimulatedEventLoopFactory simulated_event_loop_factory(&config.message());
std::unique_ptr<EventLoop> ping_event_loop =
simulated_event_loop_factory.MakeEventLoop("ping", pi1);
Ping ping(ping_event_loop.get());
std::unique_ptr<EventLoop> pong_event_loop =
simulated_event_loop_factory.MakeEventLoop("pong", pi2);
Pong pong(pong_event_loop.get());
std::unique_ptr<EventLoop> pi2_pong_counter_event_loop =
simulated_event_loop_factory.MakeEventLoop("pi2_pong_counter", pi2);
int pi2_pong_count = 0;
pi2_pong_counter_event_loop->MakeWatcher(
"/test",
[&pi2_pong_count](const examples::Pong & /*pong*/) { ++pi2_pong_count; });
std::unique_ptr<EventLoop> pi1_pong_counter_event_loop =
simulated_event_loop_factory.MakeEventLoop("pi1_pong_counter", pi1);
int pi1_pong_count = 0;
pi1_pong_counter_event_loop->MakeWatcher(
"/test",
[&pi1_pong_count](const examples::Pong & /*pong*/) { ++pi1_pong_count; });
simulated_event_loop_factory.RunFor(chrono::seconds(10) +
chrono::milliseconds(5));
EXPECT_EQ(pi1_pong_count, 1001);
EXPECT_EQ(pi2_pong_count, 1001);
}
} // namespace testing
} // namespace aos