Skip to content

Commit

Permalink
pw_async2: Add more SimulatedTimeProvider utilities
Browse files Browse the repository at this point in the history
These are useful for advancing test time without random
periods or endless iterations (e.g. advancing by 10ns
many many times).

Change-Id: Idf2b0ba13162ab9d5463b120e69e00e133b39a6e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/234929
Docs-Not-Needed: Taylor Cramer <[email protected]>
Commit-Queue: Taylor Cramer <[email protected]>
Lint: Lint 🤖 <[email protected]>
Reviewed-by: Aaron Green <[email protected]>
  • Loading branch information
cramertj authored and CQ Bot Account committed Sep 12, 2024
1 parent 4cf921e commit cfcbaf5
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 17 deletions.
74 changes: 57 additions & 17 deletions pw_async2/public/pw_async2/simulated_time_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,32 @@ class SimulatedTimeProvider final : public TimeProvider<Clock> {

/// Advances the simulated time and runs any newly-expired timers.
void AdvanceTime(typename Clock::duration duration) {
typename Clock::time_point new_now;
{
std::lock_guard lock(lock_);
now_ += duration;
new_now = now_;
lock_.lock();
SetTimeUnlockAndRun(now_ + duration);
}

/// Advances the simulated time until the next point at which a timer
/// would fire.
///
/// Returns whether any timers were waiting to be run.
bool AdvanceUntilNextExpiration() {
lock_.lock();
if (next_wake_time_ == std::nullopt) {
lock_.unlock();
return false;
}
TimeProvider<Clock>::RunExpired(new_now);
SetTimeUnlockAndRun(*next_wake_time_);
return true;
}

/// Modifies the simulated time and runs any newly-expired timers.
///
/// WARNING: Use of this function with a timestamp older than the current
/// `now()` will violate the is_monotonic clock attribute. We don't like it
/// when time goes backwards!
void SetTime(typename Clock::time_point timestamp) {
{
std::lock_guard lock(lock_);
now_ = timestamp;
}
TimeProvider<Clock>::RunExpired(timestamp);
void SetTime(typename Clock::time_point new_now) {
lock_.lock();
SetTimeUnlockAndRun(new_now);
}

/// Explicitly run expired timers.
Expand All @@ -66,13 +72,39 @@ class SimulatedTimeProvider final : public TimeProvider<Clock> {
return now_;
}

std::optional<typename Clock::time_point> NextExpiration() {
std::lock_guard lock(lock_);
return next_wake_time_;
}

std::optional<typename Clock::duration> TimeUntilNextExpiration() {
std::lock_guard lock(lock_);
if (next_wake_time_ == std::nullopt) {
return std::nullopt;
}
return *next_wake_time_ - now_;
}

private:
mutable sync::InterruptSpinLock lock_;
typename Clock::time_point now_ PW_GUARDED_BY(lock_);
void SetTimeUnlockAndRun(typename Clock::time_point new_now)
PW_UNLOCK_FUNCTION(lock_) {
now_ = new_now;
if (new_now >= next_wake_time_) {
next_wake_time_ = std::nullopt;
lock_.unlock();
TimeProvider<Clock>::RunExpired(new_now);
} else {
lock_.unlock();
}
}

void DoInvokeAt(typename Clock::time_point wake_time) final {
std::lock_guard lock(lock_);
next_wake_time_ = wake_time;

void DoInvokeAt(typename Clock::time_point) final {
// We don't need to do anything here since calls to `RunExpired` are
// triggered directly by user calls to `AdvanceTime`/`SetTime`.
// We don't need to actually schedule anything here since calls to
// `RunExpired` are triggered directly by user calls to
// `AdvanceTime`/`SetTime`.
//
// Note: we cannot run the timer here even if it was in the past because
// `DoInvokeAt` is called under `TimeProvider`'s lock. Furthermore, we
Expand All @@ -81,9 +113,17 @@ class SimulatedTimeProvider final : public TimeProvider<Clock> {
}

void DoCancel() final {
std::lock_guard lock(lock_);
next_wake_time_ = std::nullopt;

// We don't need to do anything here since `RunExpired` itself is safe to
// call redundantly-- it will filter out extra invocations.
}

mutable sync::InterruptSpinLock lock_;
typename Clock::time_point now_ PW_GUARDED_BY(lock_);
std::optional<typename Clock::time_point> next_wake_time_
PW_GUARDED_BY(lock_);
};

} // namespace pw::async2
33 changes: 33 additions & 0 deletions pw_async2/simulated_time_provider_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,37 @@ TEST(SimulatedTimeProvider, AdvanceTimeRunsPastTimers) {
EXPECT_EQ(dispatcher.RunUntilStalled(), Ready());
}

TEST(SimulatedTimeProvider, AdvanceUntilNextExpirationRunsPastTimers) {
SimulatedTimeProvider<SystemClock> tp;
WaitTask task(tp.WaitFor(1h));
Dispatcher dispatcher;
dispatcher.Post(task);
EXPECT_TRUE(tp.AdvanceUntilNextExpiration());
EXPECT_EQ(dispatcher.RunUntilStalled(), Ready());
EXPECT_FALSE(tp.AdvanceUntilNextExpiration());
}

TEST(SimulatedTimeProvider, TimeUntilNextExpirationGetsTimerDelay) {
SimulatedTimeProvider<SystemClock> tp;
auto timer = tp.WaitFor(1h);
ASSERT_TRUE(tp.TimeUntilNextExpiration().has_value());
EXPECT_GE(*tp.TimeUntilNextExpiration(), 1h);
}

TEST(SimulatedTimeProvider,
TimeUntilNextExpirationAfterCompletionReturnsNullopt) {
SimulatedTimeProvider<SystemClock> tp;
auto timer = tp.WaitFor(1h);
tp.AdvanceTime(90min);
EXPECT_FALSE(tp.TimeUntilNextExpiration().has_value());
}

TEST(SimulatedTimeProvider, TimeUntilNextExpirationAfterDestroyReturnsNullopt) {
SimulatedTimeProvider<SystemClock> tp;
{
auto timer = tp.WaitFor(1h);
}
EXPECT_FALSE(tp.TimeUntilNextExpiration().has_value());
}

} // namespace

0 comments on commit cfcbaf5

Please sign in to comment.