#include #include #include using c10::LeftRight; using std::vector; TEST(LeftRightTest, givenInt_whenWritingAndReading_thenChangesArePresent) { LeftRight obj; obj.write([](int& obj) { obj = 5; }); int read = obj.read([](const int& obj) { return obj; }); EXPECT_EQ(5, read); // check changes are also present in background copy obj.write([](int&) {}); // this switches to the background copy read = obj.read([](const int& obj) { return obj; }); EXPECT_EQ(5, read); } TEST(LeftRightTest, givenVector_whenWritingAndReading_thenChangesArePresent) { LeftRight> obj; obj.write([](vector& obj) { obj.push_back(5); }); vector read = obj.read([](const vector& obj) { return obj; }); EXPECT_EQ((vector{5}), read); obj.write([](vector& obj) { obj.push_back(6); }); read = obj.read([](const vector& obj) { return obj; }); EXPECT_EQ((vector{5, 6}), read); } TEST(LeftRightTest, givenVector_whenWritingReturnsValue_thenValueIsReturned) { LeftRight> obj; auto a = obj.write([](vector&) -> int { return 5; }); static_assert(std::is_same::value); EXPECT_EQ(5, a); } TEST(LeftRightTest, readsCanBeConcurrent) { LeftRight obj; std::atomic num_running_readers{0}; std::thread reader1([&]() { obj.read([&](const int&) { ++num_running_readers; while (num_running_readers.load() < 2) { } }); }); std::thread reader2([&]() { obj.read([&](const int&) { ++num_running_readers; while (num_running_readers.load() < 2) { } }); }); // the threads only finish after both entered the read function. // if LeftRight didn't allow concurrency, this would cause a deadlock. reader1.join(); reader2.join(); } TEST(LeftRightTest, writesCanBeConcurrentWithReads_readThenWrite) { LeftRight obj; std::atomic reader_running{false}; std::atomic writer_running{false}; std::thread reader([&]() { obj.read([&](const int&) { reader_running = true; while (!writer_running.load()) { } }); }); std::thread writer([&]() { // run read first, write second while (!reader_running.load()) { } obj.write([&](int&) { writer_running = true; }); }); // the threads only finish after both entered the read function. // if LeftRight didn't allow concurrency, this would cause a deadlock. reader.join(); writer.join(); } TEST(LeftRightTest, writesCanBeConcurrentWithReads_writeThenRead) { LeftRight obj; std::atomic writer_running{false}; std::atomic reader_running{false}; std::thread writer([&]() { obj.read([&](const int&) { writer_running = true; while (!reader_running.load()) { } }); }); std::thread reader([&]() { // run write first, read second while (!writer_running.load()) { } obj.read([&](const int&) { reader_running = true; }); }); // the threads only finish after both entered the read function. // if LeftRight didn't allow concurrency, this would cause a deadlock. writer.join(); reader.join(); } TEST(LeftRightTest, writesCannotBeConcurrentWithWrites) { LeftRight obj; std::atomic first_writer_started{false}; std::atomic first_writer_finished{false}; std::thread writer1([&]() { obj.write([&](int&) { first_writer_started = true; std::this_thread::sleep_for(std::chrono::milliseconds(50)); first_writer_finished = true; }); }); std::thread writer2([&]() { // make sure the other writer runs first while (!first_writer_started.load()) { } obj.write([&](int&) { // expect the other writer finished before this one starts EXPECT_TRUE(first_writer_finished.load()); }); }); writer1.join(); writer2.join(); } namespace { class MyException : public std::exception {}; } // namespace TEST(LeftRightTest, whenReadThrowsException_thenThrowsThrough) { LeftRight obj; // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto,hicpp-avoid-goto) EXPECT_THROW(obj.read([](const int&) { throw MyException(); }), MyException); } TEST(LeftRightTest, whenWriteThrowsException_thenThrowsThrough) { LeftRight obj; // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto,hicpp-avoid-goto) EXPECT_THROW(obj.write([](int&) { throw MyException(); }), MyException); } TEST( LeftRightTest, givenInt_whenWriteThrowsExceptionOnFirstCall_thenResetsToOldState) { LeftRight obj; obj.write([](int& obj) { obj = 5; }); // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto,hicpp-avoid-goto) EXPECT_THROW( obj.write([](int& obj) { obj = 6; throw MyException(); }), MyException); // check reading it returns old value int read = obj.read([](const int& obj) { return obj; }); EXPECT_EQ(5, read); // check changes are also present in background copy obj.write([](int&) {}); // this switches to the background copy read = obj.read([](const int& obj) { return obj; }); EXPECT_EQ(5, read); } // note: each write is executed twice, on the foreground and background copy. // We need to test a thrown exception in either call is handled correctly. TEST( LeftRightTest, givenInt_whenWriteThrowsExceptionOnSecondCall_thenKeepsNewState) { LeftRight obj; obj.write([](int& obj) { obj = 5; }); bool write_called = false; // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto,hicpp-avoid-goto) EXPECT_THROW( obj.write([&](int& obj) { obj = 6; if (write_called) { // this is the second time the write callback is executed throw MyException(); } else { write_called = true; } }), MyException); // check reading it returns new value int read = obj.read([](const int& obj) { return obj; }); EXPECT_EQ(6, read); // check changes are also present in background copy obj.write([](int&) {}); // this switches to the background copy read = obj.read([](const int& obj) { return obj; }); EXPECT_EQ(6, read); } TEST(LeftRightTest, givenVector_whenWriteThrowsException_thenResetsToOldState) { LeftRight> obj; obj.write([](vector& obj) { obj.push_back(5); }); // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto,hicpp-avoid-goto) EXPECT_THROW( obj.write([](vector& obj) { obj.push_back(6); throw MyException(); }), MyException); // check reading it returns old value vector read = obj.read([](const vector& obj) { return obj; }); EXPECT_EQ((vector{5}), read); // check changes are also present in background copy obj.write([](vector&) {}); // this switches to the background copy read = obj.read([](const vector& obj) { return obj; }); EXPECT_EQ((vector{5}), read); }