/* * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE file. */ #include "Clock.hpp" #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "ClockTestSupport.hpp" #include "ScopedThread.hpp" #include "TestSupport.hpp" using namespace kotlin; TEST(ClockInternalTest, WaitUntilViaFor_Int_ImmediateOK) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; constexpr int okValue = 13; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(okValue)); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, okValue); } TEST(ClockInternalTest, WaitUntilViaFor_Int_EventualOK) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; constexpr int okValue = 13; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(okValue)); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, okValue); } TEST(ClockInternalTest, WaitUntilViaFor_Int_LastChanceOK) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; constexpr int okValue = 13; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step)); EXPECT_CALL(waitForF, Call(rest)).WillOnce(testing::Return(okValue)); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, okValue); } TEST(ClockInternalTest, WaitUntilViaFor_Int_Timeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step)); EXPECT_CALL(waitForF, Call(rest)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + rest)); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, timeoutValue); } TEST(ClockInternalTest, WaitUntilViaFor_Int_ImmediateTimeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + step)); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, timeoutValue); } TEST(ClockInternalTest, WaitUntilViaFor_Int_ClockJumpTimeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); // Instead of incrementing by `step`, the clock jumped straight to `until`. EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(until)); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, timeoutValue); } TEST(ClockInternalTest, WaitUntilViaFor_Int_NonconformantWaitTimeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); // waitFor non-conformingly waits less than specified. constexpr auto actualStep = std::chrono::seconds(7); constexpr auto rest = std::chrono::seconds(3); constexpr auto until = TimePoint() + step + step + rest; constexpr int timeoutValue = 42; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep)); EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep)); // Waited 2 * 7 out of 2 * 10 + 3 seconds. 9 seconds left. Will wait only 6 seconds. EXPECT_CALL(waitForF, Call(std::chrono::seconds(9))).WillOnce(testing::Return(timeoutValue)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(6))); EXPECT_CALL(waitForF, Call(std::chrono::seconds(3))).WillOnce(testing::Return(timeoutValue)); // Finally waited enough. EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(9))); } auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); EXPECT_THAT(result, timeoutValue); } TEST(ClockInternalTest, WaitUntilViaFor_Void_Timeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); EXPECT_CALL(waitForF, Call(step)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step)); EXPECT_CALL(waitForF, Call(rest)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + rest)); } internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); } TEST(ClockInternalTest, WaitUntilViaFor_Void_ImmediateTimeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + step)); } internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); } TEST(ClockInternalTest, WaitUntilViaFor_Void_ClockJumpTimeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); constexpr auto rest = std::chrono::seconds(1); constexpr auto until = TimePoint() + step + step + rest; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)); // Instead of incrementing by `step`, the clock jumped straight to `until`. EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(until)); } internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); } TEST(ClockInternalTest, WaitUntilViaFor_Void_NonconformantWaitTimeout) { using TimePoint = std::chrono::time_point; testing::StrictMock> nowF; constexpr auto step = std::chrono::seconds(10); // waitFor non-conformingly waits less than specified. constexpr auto actualStep = std::chrono::seconds(7); constexpr auto rest = std::chrono::seconds(3); constexpr auto until = TimePoint() + step + step + rest; testing::StrictMock> waitForF; { testing::InSequence s; EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); EXPECT_CALL(waitForF, Call(step)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep)); EXPECT_CALL(waitForF, Call(step)); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep)); // Waited 2 * 7 out of 2 * 10 + 3 seconds. 9 seconds left. Will wait only 6 seconds. EXPECT_CALL(waitForF, Call(std::chrono::seconds(9))); EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(6))); EXPECT_CALL(waitForF, Call(std::chrono::seconds(3))); // Finally waited enough. EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(9))); } internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { return waitForF.Call(std::chrono::duration_cast(interval)); }); } namespace { class ClockTestNames { public: template static std::string GetName(int) { if constexpr (std::is_same_v) { return "steady_clock"; } else if constexpr (std::is_same_v) { return "manual_clock"; } else { return "unknown"; } } }; template struct WaitForPendingImpl { void operator()() {} }; template <> struct WaitForPendingImpl { void operator()() { while (!test_support::manual_clock::pending()) { } } }; template void waitForPending() { WaitForPendingImpl()(); } } // namespace template class ClockTest : public testing::Test { public: ClockTest() noexcept { test_support::manual_clock::reset(); } }; using ClockTestTypes = testing::Types; TYPED_TEST_SUITE(ClockTest, ClockTestTypes, ClockTestNames); TYPED_TEST(ClockTest, SleepFor) { constexpr auto interval = milliseconds(1); auto before = TypeParam::now(); TypeParam::sleep_for(interval); auto after = TypeParam::now(); EXPECT_THAT(after - before, testing::Ge(interval)); } TYPED_TEST(ClockTest, SleepUntil) { auto until = TypeParam::now() + milliseconds(1); TypeParam::sleep_until(until); auto after = TypeParam::now(); EXPECT_THAT(after, testing::Ge(until)); } TYPED_TEST(ClockTest, CVWaitFor_OK) { constexpr auto interval = hours(10); std::condition_variable cv; std::mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(cv, guard, interval, [&] { return ok; }); auto after = TypeParam::now(); EXPECT_TRUE(result); EXPECT_THAT(after - before, testing::Lt(interval)); } TYPED_TEST(ClockTest, CVWaitFor_Timeout) { constexpr auto interval = microseconds(10); std::condition_variable cv; std::mutex m; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); std::unique_lock guard(m); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(cv, guard, interval, [] { return false; }); auto after = TypeParam::now(); EXPECT_FALSE(result); EXPECT_THAT(after - before, testing::Ge(interval)); } TYPED_TEST(ClockTest, CVWaitFor_InfiniteTimeout) { std::condition_variable cv; std::mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_for` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); go = true; auto result = TypeParam::wait_for(cv, guard, microseconds::max(), [&] { return ok; }); EXPECT_TRUE(result); } TYPED_TEST(ClockTest, CVWaitUntil_OK) { std::condition_variable cv; std::mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); auto until = TypeParam::now() + hours(10); go = true; auto result = TypeParam::wait_until(cv, guard, until, [&] { return ok; }); auto after = TypeParam::now(); EXPECT_TRUE(result); EXPECT_THAT(after, testing::Lt(until)); } TYPED_TEST(ClockTest, CVWaitUntil_Timeout) { constexpr auto interval = microseconds(10); std::condition_variable cv; std::mutex m; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); std::unique_lock guard(m); auto until = TypeParam::now() + interval; go = true; auto result = TypeParam::wait_until(cv, guard, until, [] { return false; }); auto after = TypeParam::now(); EXPECT_FALSE(result); EXPECT_THAT(after, testing::Ge(until)); } TYPED_TEST(ClockTest, CVWaitUntil_InfiniteTimeout) { std::condition_variable cv; std::mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_until` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); go = true; auto result = TypeParam::wait_until(cv, guard, TypeParam::time_point::max(), [&] { return ok; }); EXPECT_TRUE(result); } TYPED_TEST(ClockTest, CVAnyWaitFor_OK) { constexpr auto interval = hours(10); std::condition_variable_any cv; std::shared_mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(cv, guard, interval, [&] { return ok; }); auto after = TypeParam::now(); EXPECT_TRUE(result); EXPECT_THAT(after - before, testing::Lt(interval)); } TYPED_TEST(ClockTest, CVAnyWaitFor_Timeout) { constexpr auto interval = microseconds(10); std::condition_variable_any cv; std::shared_mutex m; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); std::unique_lock guard(m); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(cv, guard, interval, [] { return false; }); auto after = TypeParam::now(); EXPECT_FALSE(result); EXPECT_THAT(after - before, testing::Ge(interval)); } TYPED_TEST(ClockTest, CVAnyWaitFor_InfiniteTimeout) { std::condition_variable_any cv; std::shared_mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_for` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); go = true; auto result = TypeParam::wait_for(cv, guard, microseconds::max(), [&] { return ok; }); EXPECT_TRUE(result); } TYPED_TEST(ClockTest, CVAnyWaitUntil_OK) { std::condition_variable_any cv; std::shared_mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); auto until = TypeParam::now() + hours(10); go = true; auto result = TypeParam::wait_until(cv, guard, until, [&] { return ok; }); auto after = TypeParam::now(); EXPECT_TRUE(result); EXPECT_THAT(after, testing::Lt(until)); } TYPED_TEST(ClockTest, CVAnyWaitUntil_Timeout) { constexpr auto interval = microseconds(10); std::condition_variable_any cv; std::shared_mutex m; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); std::unique_lock guard(m); auto until = TypeParam::now() + interval; go = true; auto result = TypeParam::wait_until(cv, guard, until, [] { return false; }); auto after = TypeParam::now(); EXPECT_FALSE(result); EXPECT_THAT(after, testing::Ge(until)); } TYPED_TEST(ClockTest, CVAnyWaitUntil_InfiniteTimeout) { std::condition_variable_any cv; std::shared_mutex m; bool ok = false; std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_until` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); { std::unique_lock guard(m); ok = true; } cv.notify_all(); }); std::unique_lock guard(m); go = true; auto result = TypeParam::wait_until(cv, guard, TypeParam::time_point::max(), [&] { return ok; }); EXPECT_TRUE(result); } TYPED_TEST(ClockTest, FutureWaitFor_OK) { constexpr auto interval = hours(10); std::promise promise; std::future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); promise.set_value(42); }); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(future, interval); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::ready); EXPECT_THAT(after - before, testing::Lt(interval)); } TYPED_TEST(ClockTest, FutureWaitFor_Deferred) { constexpr auto interval = hours(10); std::future future = std::async(std::launch::deferred, [] { return 42; }); auto before = TypeParam::now(); auto result = TypeParam::wait_for(future, interval); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::deferred); EXPECT_THAT(after - before, testing::Lt(interval)); } TYPED_TEST(ClockTest, FutureWaitFor_Timeout) { constexpr auto interval = microseconds(10); std::promise promise; std::future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(future, interval); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::timeout); EXPECT_THAT(after - before, testing::Ge(interval)); } TYPED_TEST(ClockTest, FutureWaitFor_InfiniteTimeout) { std::promise promise; std::future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_for` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); promise.set_value(42); }); go = true; auto result = TypeParam::wait_for(future, microseconds::max()); EXPECT_THAT(result, std::future_status::ready); } TYPED_TEST(ClockTest, FutureWaitUntil_OK) { std::promise promise; std::future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); promise.set_value(42); }); auto until = TypeParam::now() + hours(10); go = true; auto result = TypeParam::wait_until(future, until); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::ready); EXPECT_THAT(after, testing::Lt(until)); } TYPED_TEST(ClockTest, FutureWaitUntil_Deferred) { std::future future = std::async(std::launch::deferred, [] { return 42; }); auto until = TypeParam::now() + hours(10); auto result = TypeParam::wait_until(future, until); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::deferred); EXPECT_THAT(after, testing::Lt(until)); } TYPED_TEST(ClockTest, FutureWaitUntil_Timeout) { constexpr auto interval = microseconds(10); std::promise promise; std::future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); auto until = TypeParam::now() + interval; go = true; auto result = TypeParam::wait_until(future, until); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::timeout); EXPECT_THAT(after, testing::Ge(until)); } TYPED_TEST(ClockTest, FutureWaitUntil_InfiniteTimeout) { std::promise promise; std::future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_until` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); promise.set_value(42); }); go = true; auto result = TypeParam::wait_until(future, TypeParam::time_point::max()); EXPECT_THAT(result, std::future_status::ready); } TYPED_TEST(ClockTest, SharedFutureWaitFor_OK) { constexpr auto interval = hours(10); std::promise promise; std::shared_future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); promise.set_value(42); }); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(future, interval); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::ready); EXPECT_THAT(after - before, testing::Lt(interval)); } TYPED_TEST(ClockTest, SharedFutureWaitFor_Deferred) { constexpr auto interval = hours(10); std::shared_future future = std::async(std::launch::deferred, [] { return 42; }); auto before = TypeParam::now(); auto result = TypeParam::wait_for(future, interval); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::deferred); EXPECT_THAT(after - before, testing::Lt(interval)); } TYPED_TEST(ClockTest, SharedFutureWaitFor_Timeout) { constexpr auto interval = microseconds(10); std::promise promise; std::shared_future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); auto before = TypeParam::now(); go = true; auto result = TypeParam::wait_for(future, interval); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::timeout); EXPECT_THAT(after - before, testing::Ge(interval)); } TYPED_TEST(ClockTest, SharedFutureWaitFor_InfiniteTimeout) { std::promise promise; std::shared_future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_for` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); promise.set_value(42); }); go = true; auto result = TypeParam::wait_for(future, microseconds::max()); EXPECT_THAT(result, std::future_status::ready); } TYPED_TEST(ClockTest, SharedFutureWaitUntil_OK) { std::promise promise; std::shared_future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); promise.set_value(42); }); auto until = TypeParam::now() + hours(10); go = true; auto result = TypeParam::wait_until(future, until); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::ready); EXPECT_THAT(after, testing::Lt(until)); } TYPED_TEST(ClockTest, SharedFutureWaitUntil_Deferred) { std::shared_future future = std::async(std::launch::deferred, [] { return 42; }); auto until = TypeParam::now() + hours(10); auto result = TypeParam::wait_until(future, until); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::deferred); EXPECT_THAT(after, testing::Lt(until)); } TYPED_TEST(ClockTest, SharedFutureWaitUntil_Timeout) { constexpr auto interval = microseconds(10); std::promise promise; std::shared_future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); TypeParam::sleep_for(interval); }); auto until = TypeParam::now() + interval; go = true; auto result = TypeParam::wait_until(future, until); auto after = TypeParam::now(); EXPECT_THAT(result, std::future_status::timeout); EXPECT_THAT(after, testing::Ge(until)); } TYPED_TEST(ClockTest, SharedFutureWaitUntil_InfiniteTimeout) { std::promise promise; std::shared_future future = promise.get_future(); std::atomic go = false; ScopedThread thread([&] { while (!go.load()) { } waitForPending(); // Wait to see if `TypeParam::wait_until` wakes up from timeout. TypeParam::sleep_for(milliseconds(1)); promise.set_value(42); }); go = true; auto result = TypeParam::wait_until(future, TypeParam::time_point::max()); EXPECT_THAT(result, std::future_status::ready); } namespace { class ClockTypesTestNames { public: template static std::string GetName(int) { std::string result; result += clockName(); result += "_"; result += durationName(); return result; } private: template static const char* clockName() { using Clock = typename std::tuple_element<0, T>::type; if constexpr (std::is_same_v) { return "steady_clock"; } else if constexpr (std::is_same_v) { return "manual_clock"; } else { return "unknown"; } } template static const char* durationName() { using Duration = typename std::tuple_element<1, T>::type; if constexpr (std::is_same_v) { return "ns"; } else if constexpr (std::is_same_v) { return "us"; } else if constexpr (std::is_same_v) { return "ms"; } else if constexpr (std::is_same_v) { return "s"; } else if constexpr (std::is_same_v) { return "m"; } else if constexpr (std::is_same_v) { return "h"; } else if constexpr (std::is_same_v) { return "sat_ns"; } else if constexpr (std::is_same_v) { return "sat_us"; } else if constexpr (std::is_same_v) { return "sat_ms"; } else if constexpr (std::is_same_v) { return "sat_s"; } else if constexpr (std::is_same_v) { return "sat_m"; } else if constexpr (std::is_same_v) { return "sat_h"; } else { return "unknown"; } } }; } // namespace template class ClockTypesTest : public testing::Test { public: using Clock = typename std::tuple_element<0, T>::type; using Duration = typename std::tuple_element<1, T>::type; }; using ClockTypesTestTypes = testing::Types< std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple, std::tuple>; TYPED_TEST_SUITE(ClockTypesTest, ClockTypesTestTypes, ClockTypesTestNames); TYPED_TEST(ClockTypesTest, SleepFor) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v()))>); } TYPED_TEST(ClockTypesTest, SleepUntil) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v>()))>); } TYPED_TEST(ClockTypesTest, CVWaitFor) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< bool, decltype(Clock::wait_for( std::declval(), std::declval&>(), std::declval(), std::declval>()))>); } TYPED_TEST(ClockTypesTest, CVWaitUntil) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< bool, decltype(Clock::wait_until( std::declval(), std::declval&>(), std::declval>(), std::declval>()))>); } TYPED_TEST(ClockTypesTest, CVAnyWaitFor) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< bool, decltype(Clock::wait_for( std::declval(), std::declval&>(), std::declval(), std::declval>()))>); } TYPED_TEST(ClockTypesTest, CVAnyWaitUntil) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< bool, decltype(Clock::wait_until( std::declval(), std::declval&>(), std::declval>(), std::declval>()))>); } TYPED_TEST(ClockTypesTest, FutureWaitFor) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< std::future_status, decltype(Clock::wait_for(std::declval&>(), std::declval()))>); } TYPED_TEST(ClockTypesTest, FutureWaitUntil) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< std::future_status, decltype(Clock::wait_until( std::declval&>(), std::declval>()))>); } TYPED_TEST(ClockTypesTest, SharedFutureWaitFor) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< std::future_status, decltype(Clock::wait_for(std::declval&>(), std::declval()))>); } TYPED_TEST(ClockTypesTest, SharedFutureWaitUntil) { using Clock = typename ClockTypesTest::Clock; using Duration = typename ClockTypesTest::Duration; static_assert(std::is_same_v< std::future_status, decltype(Clock::wait_until( std::declval&>(), std::declval>()))>); } TEST(ManualClockTest, SleepUntil) { test_support::manual_clock::reset(); auto before = test_support::manual_clock::now(); test_support::manual_clock::sleep_until(before + seconds(2)); EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2)); // Sleep until current time. test_support::manual_clock::sleep_until(before + seconds(2)); EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2)); // Sleep until moment in the past. test_support::manual_clock::sleep_until(before); EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2)); } TEST(ManualClockTest, Pending) { test_support::manual_clock::reset(); // Nothing pending at start. EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt); std::promise promise; std::future future = promise.get_future(); ScopedThread thread([&] { test_support::manual_clock::wait_for(future, seconds(1)); }); test_support::manual_clock::waitForPending(test_support::manual_clock::now() + seconds(1)); // Unblocks the thread. test_support::manual_clock::sleep_for(seconds(1)); thread.join(); // Nothing pending anymore. EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt); } TEST(ManualClockTest, ConcurrentSleepUntil) { test_support::manual_clock::reset(); constexpr auto threadCount = kDefaultThreadCount; std::vector threads; std::atomic run = false; std::atomic ready = 0; for (int i = 0; i < threadCount; ++i) { threads.emplace_back([&, i] { auto now = test_support::manual_clock::now(); ++ready; while (!run.load()) { } test_support::manual_clock::sleep_until(now + seconds(i)); }); } auto before = test_support::manual_clock::now(); while (ready.load() < threadCount) { } run = true; threads.clear(); auto after = test_support::manual_clock::now(); EXPECT_THAT(after - before, seconds(threadCount - 1)); } TEST(ManualClockTest, ConcurrentWaits) { test_support::manual_clock::reset(); constexpr auto threadCount = kDefaultThreadCount; std::vector threads; std::mutex mutex; std::condition_variable cv; std::condition_variable_any cvAny; std::promise promise1; std::promise promise2; std::future future = promise1.get_future(); std::shared_future shared_future = promise2.get_future().share(); std::atomic run = false; std::atomic ready = 0; for (int i = 0; i < threadCount; ++i) { threads.emplace_back([&, i] { auto now = test_support::manual_clock::now(); ++ready; while (!run.load()) { } switch (i % 4) { case 0: { std::unique_lock guard(mutex); test_support::manual_clock::wait_until(cv, guard, now + seconds(i / 3 + 1), [] { return false; }); } case 1: { std::unique_lock guard(mutex); test_support::manual_clock::wait_until(cvAny, guard, now + seconds(i / 3 + 1), [] { return false; }); } case 2: { test_support::manual_clock::wait_until(future, now + seconds(i / 3 + 1)); } case 3: { test_support::manual_clock::wait_until(shared_future, now + seconds(i / 3 + 1)); } } }); } auto before = test_support::manual_clock::now(); while (ready.load() < threadCount) { } run = true; test_support::manual_clock::sleep_until(before + seconds(1)); // Now the first 3 threads will be unblocked. threads[0].join(); threads[1].join(); threads[2].join(); // Make sure at least one other thread is waiting. while (!test_support::manual_clock::pending()) { } auto pendingAfterSecond = *test_support::manual_clock::pending(); EXPECT_THAT(pendingAfterSecond, testing::Ge(before + seconds(2))); // Unblock all the threads. test_support::manual_clock::sleep_until(before + seconds((threadCount - 1) / 3 + 1)); threads.clear(); // All threads are gone, nothing can possibly be pending. EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt); }