Use a read-only mapping for reading from shared memory
This makes it a lot harder for readers to accidentally write.
Change-Id: I29025f37b0767825e57314cc1221510fd9455b55
diff --git a/aos/events/shm_event_loop_test.cc b/aos/events/shm_event_loop_test.cc
index cbb28f9..d9a8872 100644
--- a/aos/events/shm_event_loop_test.cc
+++ b/aos/events/shm_event_loop_test.cc
@@ -99,12 +99,27 @@
return scheduler == SCHED_FIFO || scheduler == SCHED_RR;
}
+class ShmEventLoopTest : public ::testing::TestWithParam<ReadMethod> {
+ public:
+ ShmEventLoopTest() {
+ if (GetParam() == ReadMethod::PIN) {
+ factory_.PinReads();
+ }
+ }
+
+ ShmEventLoopTestFactory *factory() { return &factory_; }
+
+ private:
+ ShmEventLoopTestFactory factory_;
+};
+
+using ShmEventLoopDeathTest = ShmEventLoopTest;
+
// Tests that every handler type is realtime and runs. There are threads
// involved and it's easy to miss one.
-TEST(ShmEventLoopTest, AllHandlersAreRealtime) {
- ShmEventLoopTestFactory factory;
- auto loop = factory.MakePrimary("primary");
- auto loop2 = factory.Make("loop2");
+TEST_P(ShmEventLoopTest, AllHandlersAreRealtime) {
+ auto loop = factory()->MakePrimary("primary");
+ auto loop2 = factory()->Make("loop2");
loop->SetRuntimeRealtimePriority(1);
@@ -114,10 +129,10 @@
bool did_timer = false;
bool did_watcher = false;
- auto timer = loop->AddTimer([&did_timer, &factory]() {
+ auto timer = loop->AddTimer([this, &did_timer]() {
EXPECT_TRUE(IsRealtime());
did_timer = true;
- factory.Exit();
+ factory()->Exit();
});
loop->MakeWatcher("/test", [&did_watcher](const TestMessage &) {
@@ -136,7 +151,7 @@
msg.Send(builder.Finish());
});
- factory.Run();
+ factory()->Run();
EXPECT_TRUE(did_onrun);
EXPECT_TRUE(did_timer);
@@ -145,16 +160,15 @@
// Tests that missing a deadline inside the function still results in PhasedLoop
// running at the right offset.
-TEST(ShmEventLoopTest, DelayedPhasedLoop) {
- ShmEventLoopTestFactory factory;
- auto loop1 = factory.MakePrimary("primary");
+TEST_P(ShmEventLoopTest, DelayedPhasedLoop) {
+ auto loop1 = factory()->MakePrimary("primary");
::std::vector<::aos::monotonic_clock::time_point> times;
constexpr chrono::milliseconds kOffset = chrono::milliseconds(400);
loop1->AddPhasedLoop(
- [×, &loop1, &kOffset, &factory](int count) {
+ [this, ×, &loop1, &kOffset](int count) {
const ::aos::monotonic_clock::time_point monotonic_now =
loop1->monotonic_now();
@@ -179,7 +193,7 @@
times.push_back(loop1->monotonic_now());
if (times.size() == 2) {
- factory.Exit();
+ factory()->Exit();
}
// Now, add a large delay. This should push us up to 3 cycles.
@@ -187,15 +201,14 @@
},
chrono::seconds(1), kOffset);
- factory.Run();
+ factory()->Run();
EXPECT_EQ(times.size(), 2u);
}
// Test GetWatcherSharedMemory in a few basic scenarios.
-TEST(ShmEventLoopDeathTest, GetWatcherSharedMemory) {
- ShmEventLoopTestFactory factory;
- auto generic_loop1 = factory.MakePrimary("primary");
+TEST_P(ShmEventLoopDeathTest, GetWatcherSharedMemory) {
+ auto generic_loop1 = factory()->MakePrimary("primary");
ShmEventLoop *const loop1 = static_cast<ShmEventLoop *>(generic_loop1.get());
const auto channel = configuration::GetChannel(
loop1->configuration(), "/test", TestMessage::GetFullyQualifiedName(),
@@ -206,31 +219,85 @@
"No watcher found for channel");
// Then, actually create a watcher, and verify it returns something sane.
- loop1->MakeWatcher("/test", [](const TestMessage &) {});
- EXPECT_FALSE(loop1->GetWatcherSharedMemory(channel).empty());
+ absl::Span<const char> shared_memory;
+ bool ran = false;
+ loop1->MakeWatcher("/test", [this, &shared_memory,
+ &ran](const TestMessage &message) {
+ EXPECT_FALSE(ran);
+ ran = true;
+ // If we're using pinning, then we can verify that the message is actually
+ // in the specified region.
+ if (GetParam() == ReadMethod::PIN) {
+ EXPECT_GE(reinterpret_cast<const char *>(&message),
+ shared_memory.begin());
+ EXPECT_LT(reinterpret_cast<const char *>(&message), shared_memory.end());
+ }
+ factory()->Exit();
+ });
+ shared_memory = loop1->GetWatcherSharedMemory(channel);
+ EXPECT_FALSE(shared_memory.empty());
+
+ auto loop2 = factory()->Make("sender");
+ auto sender = loop2->MakeSender<TestMessage>("/test");
+ generic_loop1->OnRun([&sender]() {
+ auto builder = sender.MakeBuilder();
+ TestMessage::Builder test_builder(*builder.fbb());
+ test_builder.add_value(1);
+ CHECK(builder.Send(test_builder.Finish()));
+ });
+ factory()->Run();
+ EXPECT_TRUE(ran);
}
-TEST(ShmEventLoopTest, GetSenderSharedMemory) {
- ShmEventLoopTestFactory factory;
- auto generic_loop1 = factory.MakePrimary("primary");
+TEST_P(ShmEventLoopTest, GetSenderSharedMemory) {
+ auto generic_loop1 = factory()->MakePrimary("primary");
ShmEventLoop *const loop1 = static_cast<ShmEventLoop *>(generic_loop1.get());
- // check that GetSenderSharedMemory returns non-null/non-empty memory span.
+ // Check that GetSenderSharedMemory returns non-null/non-empty memory span.
auto sender = loop1->MakeSender<TestMessage>("/test");
- EXPECT_FALSE(loop1->GetSenderSharedMemory(&sender).empty());
+ const absl::Span<char> shared_memory = loop1->GetSenderSharedMemory(&sender);
+ EXPECT_FALSE(shared_memory.empty());
+
+ auto builder = sender.MakeBuilder();
+ uint8_t *buffer;
+ builder.fbb()->CreateUninitializedVector(5, 1, &buffer);
+ EXPECT_GE(reinterpret_cast<char *>(buffer), shared_memory.begin());
+ EXPECT_LT(reinterpret_cast<char *>(buffer), shared_memory.end());
}
-TEST(ShmEventLoopTest, GetFetcherPrivateMemory) {
- ShmEventLoopTestFactory factory;
- auto generic_loop1 = factory.MakePrimary("primary");
+TEST_P(ShmEventLoopTest, GetFetcherPrivateMemory) {
+ auto generic_loop1 = factory()->MakePrimary("primary");
ShmEventLoop *const loop1 = static_cast<ShmEventLoop *>(generic_loop1.get());
- // check that GetFetcherPrivateMemory returns non-null/non-empty memory span.
+ // Check that GetFetcherPrivateMemory returns non-null/non-empty memory span.
auto fetcher = loop1->MakeFetcher<TestMessage>("/test");
- EXPECT_FALSE(loop1->GetFetcherPrivateMemory(&fetcher).empty());
+ const auto private_memory = loop1->GetFetcherPrivateMemory(&fetcher);
+ EXPECT_FALSE(private_memory.empty());
+
+ auto loop2 = factory()->Make("sender");
+ auto sender = loop2->MakeSender<TestMessage>("/test");
+ {
+ auto builder = sender.MakeBuilder();
+ TestMessage::Builder test_builder(*builder.fbb());
+ test_builder.add_value(1);
+ CHECK(builder.Send(test_builder.Finish()));
+ }
+
+ ASSERT_TRUE(fetcher.Fetch());
+ EXPECT_GE(fetcher.context().data, private_memory.begin());
+ EXPECT_LT(fetcher.context().data, private_memory.end());
}
// TODO(austin): Test that missing a deadline with a timer recovers as expected.
+INSTANTIATE_TEST_CASE_P(ShmEventLoopCopyTest, ShmEventLoopTest,
+ ::testing::Values(ReadMethod::COPY));
+INSTANTIATE_TEST_CASE_P(ShmEventLoopPinTest, ShmEventLoopTest,
+ ::testing::Values(ReadMethod::PIN));
+INSTANTIATE_TEST_CASE_P(ShmEventLoopCopyDeathTest, ShmEventLoopDeathTest,
+ ::testing::Values(ReadMethod::COPY));
+INSTANTIATE_TEST_CASE_P(ShmEventLoopPinDeathTest, ShmEventLoopDeathTest,
+ ::testing::Values(ReadMethod::PIN));
+
} // namespace testing
} // namespace aos