// Copyright 2026 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "absl/time/simulated_clock.h" #include #include #include #include // NOLINT(build/c++11) #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/thread_annotations.h" #include "absl/random/random.h" #include "absl/synchronization/blocking_counter.h" #include "absl/synchronization/mutex.h" #include "absl/synchronization/notification.h" #include "absl/time/clock.h" #include "absl/time/clock_interface.h" #include "absl/time/time.h" namespace { constexpr absl::Duration kShortPause = absl::Milliseconds(20); constexpr absl::Duration kLongPause = absl::Milliseconds(1000); #ifdef _MSC_VER // As of 2026-01-29, multithreaded tests on MSVC are too flaky. const char* kSkipFlakyReason = "Skipping this timing test because it is too flaky"; #else const char* kSkipFlakyReason = nullptr; #endif TEST(SimulatedClock, TimeInitializedToZero) { absl::SimulatedClock simclock; EXPECT_EQ(absl::UnixEpoch(), simclock.TimeNow()); } TEST(SimulatedClock, NowSetTime) { absl::SimulatedClock simclock; absl::Time now = simclock.TimeNow(); now += absl::Seconds(123); simclock.SetTime(now); EXPECT_EQ(now, simclock.TimeNow()); now += absl::Seconds(123); simclock.SetTime(now); EXPECT_EQ(now, simclock.TimeNow()); now += absl::ZeroDuration(); simclock.SetTime(now); EXPECT_EQ(now, simclock.TimeNow()); } TEST(SimulatedClock, NowAdvanceTime) { absl::SimulatedClock simclock; absl::Time now = simclock.TimeNow(); simclock.AdvanceTime(absl::Seconds(123)); now += absl::Seconds(123); EXPECT_EQ(now, simclock.TimeNow()); simclock.AdvanceTime(absl::Seconds(123)); now += absl::Seconds(123); EXPECT_EQ(now, simclock.TimeNow()); simclock.AdvanceTime(absl::ZeroDuration()); now += absl::ZeroDuration(); EXPECT_EQ(now, simclock.TimeNow()); } void SleepAndNotify(absl::Clock* clock, absl::Duration sleep_secs, absl::Notification* note) { clock->Sleep(sleep_secs); note->Notify(); } TEST(SimulatedClock, Sleep_SetToSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); // wait for SleepAndNotify() to block absl::SleepFor(kLongPause); simclock.SetTime(absl::FromUnixSeconds(122)); // give Sleep() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.SetTime(absl::FromUnixSeconds(122 + 1)); // wait for Sleep() to return absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, SleepAdvanceToSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); // wait for SleepAndNotify() to block absl::SleepFor(kLongPause); simclock.AdvanceTime(absl::Seconds(122)); // give Sleep() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.AdvanceTime(absl::Seconds(1)); // wait for Sleep() to return absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, SleepSetPastSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); // wait for SleepAndNotify() to block absl::SleepFor(kLongPause); simclock.SetTime(absl::FromUnixSeconds(122)); // give Sleep() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.SetTime(absl::FromUnixSeconds(122 + 2)); // wait for Sleep() to return absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, SleepAdvancePastSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); // wait for SleepAndNotify() to block absl::SleepFor(kLongPause); simclock.AdvanceTime(absl::Seconds(122)); // give Sleep() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.AdvanceTime(absl::Seconds(2)); // wait for Sleep() to return absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, SleepZeroSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; std::thread tr(SleepAndNotify, &simclock, absl::ZeroDuration(), ¬e); // wait for SleepAndNotify() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } void SleepUntilAndNotify(absl::Clock* clock, absl::Time wakeup_time, absl::Notification* note) { clock->SleepUntil(wakeup_time); note->Notify(); } TEST(SimulatedClock, SleepUntilSetToSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; simclock.SetTime(absl::FromUnixSeconds(123)); std::thread tr(SleepUntilAndNotify, &simclock, absl::UnixEpoch() + absl::Seconds(246), ¬e); // wait for SleepUntilAndNotify() to block absl::SleepFor(kLongPause); simclock.SetTime(absl::FromUnixSeconds(123 + 122)); // give SleepUntil() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.SetTime(absl::FromUnixSeconds(123 + 122 + 1)); absl::Time start = absl::Now(); note.WaitForNotification(); // SleepUntilAndNotify() should ping note EXPECT_GE(absl::Milliseconds(50), absl::Now() - start); tr.join(); } TEST(SimulatedClock, SleepUntilAdvanceToSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; simclock.AdvanceTime(absl::Seconds(123)); std::thread tr(SleepUntilAndNotify, &simclock, absl::UnixEpoch() + absl::Seconds(246), ¬e); // wait for SleepUntilAndNotify() to block absl::SleepFor(kLongPause); simclock.AdvanceTime(absl::Seconds(122)); // give SleepUntil() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.AdvanceTime(absl::Seconds(1)); absl::Time start = absl::Now(); note.WaitForNotification(); // SleepUntilAndNotify() should ping note EXPECT_GE(absl::Milliseconds(70), absl::Now() - start); tr.join(); } TEST(SimulatedClock, SleepUntilSetPastSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; simclock.SetTime(absl::FromUnixSeconds(123)); std::thread tr(SleepUntilAndNotify, &simclock, absl::UnixEpoch() + absl::Seconds(246), ¬e); // wait for SleepUntilAndNotify() to block absl::SleepFor(kLongPause); simclock.SetTime(absl::FromUnixSeconds(123 + 122)); // give SleepUntil() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.SetTime(absl::FromUnixSeconds(123 + 122 + 2)); // wait for SleepUntilAndNotify() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, SleepUntilAdvancePastSleepTime) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; simclock.AdvanceTime(absl::Seconds(123)); std::thread tr(SleepUntilAndNotify, &simclock, absl::UnixEpoch() + absl::Seconds(246), ¬e); // wait for SleepUntilAndNotify() to block absl::SleepFor(kLongPause); simclock.AdvanceTime(absl::Seconds(122)); // give SleepUntil() the opportunity to fail absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.AdvanceTime(absl::Seconds(2)); // wait for SleepUntilAndNotify() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, SleepUntilTimeAlreadyPassed) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Notification note; simclock.AdvanceTime(absl::Seconds(123)); std::thread tr(SleepUntilAndNotify, &simclock, absl::UnixEpoch() + absl::Seconds(123), ¬e); // wait for SleepUntilAndNotify() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); note.WaitForNotification(); // in case the expectation fails tr.join(); } void AwaitWithDeadlineAndNotify(absl::Clock* clock, absl::Mutex* mu, absl::Condition* cond, absl::Time wakeup_time, absl::Notification* note, bool* return_val) { mu->lock_shared(); *return_val = clock->AwaitWithDeadline(mu, *cond, wakeup_time); mu->unlock_shared(); note->Notify(); } TEST(SimulatedClock, AwaitWithDeadlineConditionInitiallyTrue) { absl::SimulatedClock simclock; absl::Mutex mu; bool f = true; absl::Condition cond(&f); absl::MutexLock lock(mu); ASSERT_TRUE(simclock.AwaitWithDeadline(&mu, cond, absl::InfiniteFuture())); } TEST(SimulatedClock, AwaitWithDeadlineConditionInitiallyFalse) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Mutex mu; bool f = false; absl::Condition cond(&f); absl::Notification note; bool return_val; std::thread tr(AwaitWithDeadlineAndNotify, &simclock, &mu, &cond, absl::UnixEpoch() + absl::Seconds(123), ¬e, &return_val); // wait for AwaitWithDeadline...() to block absl::SleepFor(kShortPause); EXPECT_FALSE(note.HasBeenNotified()); mu.lock(); f = true; mu.unlock(); // wait for AwaitWithDeadline...() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); EXPECT_TRUE(return_val); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, AwaitWithDeadlineDeadlinePassed) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Mutex mu; bool f = false; absl::Condition cond(&f); absl::Notification note; bool return_val; std::thread tr(AwaitWithDeadlineAndNotify, &simclock, &mu, &cond, absl::UnixEpoch() + absl::Seconds(123), ¬e, &return_val); // wait for AwaitWithDeadline...() to block absl::SleepFor(kLongPause); EXPECT_FALSE(note.HasBeenNotified()); simclock.AdvanceTime(absl::Seconds(124)); // wait for AwaitWithDeadline...() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); EXPECT_FALSE(return_val); note.WaitForNotification(); // in case the expectation fails tr.join(); } TEST(SimulatedClock, AwaitWithDeadlineDeadlineAlreadyPassed) { if (kSkipFlakyReason != nullptr) { GTEST_SKIP() << kSkipFlakyReason; } absl::SimulatedClock simclock; absl::Mutex mu; bool f = false; absl::Condition cond(&f); absl::Notification note; bool return_val; std::thread tr(AwaitWithDeadlineAndNotify, &simclock, &mu, &cond, absl::UnixEpoch(), ¬e, &return_val); // wait for AwaitWithDeadline...() to ping note absl::SleepFor(kLongPause); EXPECT_TRUE(note.HasBeenNotified()); EXPECT_FALSE(return_val); note.WaitForNotification(); // in case the expectation fails tr.join(); } void RacerMakesConditionTrue(absl::Notification* start_note, absl::Mutex* mu, bool* f, absl::BlockingCounter* threads_done) { start_note->WaitForNotification(); absl::SleepFor(absl::Milliseconds(1)); mu->lock(); *f = true; mu->unlock(); threads_done->DecrementCount(); } void RacerAdvancesTime(absl::Notification* start_note, absl::SimulatedClock* simclock, absl::Duration d, absl::BlockingCounter* threads_done) { start_note->WaitForNotification(); absl::SleepFor(absl::Milliseconds(1)); simclock->AdvanceTime(d); threads_done->DecrementCount(); } TEST(SimulatedClock, SimultaneousConditionTrueAndDeadline) { absl::SimulatedClock simclock; for (int iteration = 0; iteration < 100; ++iteration) { auto mu = std::make_unique(); bool f = false; absl::Condition cond(&f); absl::Notification note_start; absl::BlockingCounter threads_done(2); std::thread tr1(RacerMakesConditionTrue, ¬e_start, mu.get(), &f, &threads_done); std::thread tr2(RacerAdvancesTime, ¬e_start, &simclock, absl::Seconds(20), &threads_done); note_start.Notify(); mu->lock(); absl::Time deadline = simclock.TimeNow() + absl::Seconds(10); simclock.AwaitWithDeadline(mu.get(), cond, deadline); EXPECT_TRUE(f || (simclock.TimeNow() >= deadline)); if (f) { // RacerMakesConditionTrue has unlocked mu and AwaitWithDeadline has // returned, so it is safe to destruct mu. Do so while RacerAdvancesTime // is possibly still running in an attempt to catch simclock holding on // to a reference to mu and using it after AwaitWithDeadline returns. mu->unlock(); mu = nullptr; } else { mu->unlock(); } threads_done.Wait(); tr1.join(); tr2.join(); } } void RacerDeletesClock(absl::Mutex* mu, absl::Notification* start_note, absl::Clock* clock, absl::BlockingCounter* threads_done) { start_note->WaitForNotification(); // mu is acquired temporarily to make sure that AwaitWithDeadline() in // SimultaneousConditionTrueAndDestruction has blocked. mu->lock(); mu->unlock(); absl::SleepFor(absl::Milliseconds(1)); delete clock; threads_done->DecrementCount(); } TEST(SimulatedClock, SimultaneousConditionTrueAndDestruction) { for (int iteration = 0; iteration < 100; ++iteration) { absl::Clock* clock = new absl::SimulatedClock(); absl::Mutex mu; bool f = false; absl::Condition cond(&f); absl::Notification note_start; absl::BlockingCounter threads_done(2); std::thread tr1([¬e_start, &mu, &f, &threads_done] { RacerMakesConditionTrue(¬e_start, &mu, &f, &threads_done); }); std::thread tr2([&mu, ¬e_start, clock, &threads_done] { RacerDeletesClock(&mu, ¬e_start, clock, &threads_done); }); mu.lock(); note_start.Notify(); absl::Time deadline = absl::UnixEpoch() + absl::Seconds(100000); clock->AwaitWithDeadline(&mu, cond, deadline); mu.unlock(); threads_done.Wait(); tr1.join(); tr2.join(); } } class SimulatedClockTorturer { public: SimulatedClockTorturer(absl::SimulatedClock* simclock, int num_threads, int num_iterations) : simclock_(simclock), num_threads_(num_threads), num_iterations_(num_iterations), num_flags_(2 * num_threads), mutex_and_flag_(static_cast(num_flags_)) {} // Implements a torture test. // // This method uses several groups of: // SimulatedClock // Mutex protected flag // It starts several threads that call AwaitWithDeadline() and several // threads that call AdvanceTime() or toggle flag values. void DoTorture() { // The threads calling AwaitWithDeadline() have a separate BlockingCounter // than the threads calling AdvanceTime()/toggling flags, since the former // would be deadlocked if all the threads that might unblock them had // already finished. absl::Notification go; absl::BlockingCounter await_threads_done(num_threads_); absl::Notification signal_threads_should_exit; absl::BlockingCounter signal_threads_done(num_threads_); std::vector trs; for (int i = 0; i < num_threads_; ++i) { trs.emplace_back(&SimulatedClockTorturer::AwaitRandomly, this, &go, &await_threads_done); } for (int i = 0; i < num_threads_; ++i) { trs.emplace_back(&SimulatedClockTorturer::SignalRandomly, this, &go, &signal_threads_should_exit, &signal_threads_done); } go.Notify(); await_threads_done.Wait(); signal_threads_should_exit.Notify(); signal_threads_done.Wait(); for (auto& thread : trs) { thread.join(); } } private: // Randomly call AwaitWithDeadline() for num_iterations_ times. void AwaitRandomly(absl::Notification* go, absl::BlockingCounter* threads_done) { go->WaitForNotification(); absl::BitGen gen; for (int i = 0; i < num_iterations_; ++i) { auto& [mu, f] = mutex_and_flag_[absl::Uniform(gen, size_t{0}, static_cast(num_flags_))]; absl::MutexLock lock(mu); absl::Time deadline = simclock_->TimeNow() + absl::Seconds(1); simclock_->AwaitWithDeadline(&mu, absl::Condition(&f), deadline); ABSL_RAW_CHECK(f || simclock_->TimeNow() >= deadline, ""); } threads_done->DecrementCount(); } // Randomly call AdvanceTime() or toggle a flag value until notified to // stop. void SignalRandomly(absl::Notification* go, absl::Notification* should_exit, absl::BlockingCounter* threads_done) { go->WaitForNotification(); absl::BitGen gen; while (!should_exit->HasBeenNotified()) { int action = absl::Uniform(gen, 0, num_flags_ + 1); if (action < num_flags_) { // Change a flag value. auto& [mutex, flag] = mutex_and_flag_[static_cast(action)]; absl::MutexLock lock(mutex); flag = !flag; } else { // Advance time. simclock_->AdvanceTime(absl::Seconds(1)); } } threads_done->DecrementCount(); } absl::SimulatedClock* simclock_; int num_threads_; int num_iterations_; int num_flags_; struct MutexAndFlag { absl::Mutex mutex; bool flag ABSL_GUARDED_BY(mutex) = false; }; std::vector mutex_and_flag_; }; TEST(SimulatedClock, Torture) { absl::SimulatedClock simclock; constexpr int kNumThreads = 10; constexpr int kNumIterations = 1000; SimulatedClockTorturer torturer(&simclock, kNumThreads, kNumIterations); torturer.DoTorture(); } } // namespace