Skip to content

Commit

Permalink
Add base::TaskEnvironment::AdvanceClock() which advances the mock tim…
Browse files Browse the repository at this point in the history
…e without running tasks.

Change-Id: Ide200b9cd0cf7f483381eeb63a8fafbee14df34e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1814640
Commit-Queue: Matt Mueller <mattm@chromium.org>
Reviewed-by: Gabriel Charette <gab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#698600}
  • Loading branch information
matt-mueller authored and Commit Bot committed Sep 20, 2019
1 parent 87f9155 commit aec1fa6
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 3 deletions.
13 changes: 13 additions & 0 deletions base/test/task_environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ class TaskEnvironment::MockTimeDomain : public sequence_manager::TimeDomain,
return TimeDomain::NextScheduledRunTime();
}

void AdvanceClock(TimeDelta delta) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AutoLock lock(now_ticks_lock_);
now_ticks_ += delta;
}

static std::unique_ptr<TaskEnvironment::MockTimeDomain> CreateAndRegister(
sequence_manager::SequenceManager* sequence_manager) {
auto mock_time_domain =
Expand Down Expand Up @@ -646,6 +652,13 @@ void TaskEnvironment::FastForwardUntilNoTasksRemain() {
FastForwardBy(TimeDelta::Max());
}

void TaskEnvironment::AdvanceClock(TimeDelta delta) {
DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
DCHECK(mock_time_domain_);
DCHECK_GE(delta, TimeDelta());
mock_time_domain_->AdvanceClock(delta);
}

const TickClock* TaskEnvironment::GetMockTickClock() const {
DCHECK(mock_time_domain_);
return mock_time_domain_.get();
Expand Down
6 changes: 6 additions & 0 deletions base/test/task_environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ class TaskEnvironment {
// to spin forever (any RepeatingTimer will cause this).
void FastForwardUntilNoTasksRemain();

// Only valid for instances using TimeSource::MOCK_TIME. Advances virtual time
// by |delta|. Unlike FastForwardBy, this does not run tasks. Prefer
// FastForwardBy() when possible but this can be useful when testing blocked
// pending tasks where being idle (required to fast-forward) is not possible.
void AdvanceClock(TimeDelta delta);

// Only valid for instances using TimeSource::MOCK_TIME. Returns a
// TickClock whose time is updated by FastForward(By|UntilNoTasksRemain).
const TickClock* GetMockTickClock() const;
Expand Down
57 changes: 57 additions & 0 deletions base/test/task_environment_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,63 @@ TEST_F(TaskEnvironmentTest, FastForwardAdvanceTimeTicks) {
EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now());
}

TEST_F(TaskEnvironmentTest, AdvanceClockAdvanceTickClock) {
constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42);
TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);

const base::TickClock* tick_clock = task_environment.GetMockTickClock();
const base::TimeTicks start_time = tick_clock->NowTicks();
task_environment.AdvanceClock(kDelay);

EXPECT_EQ(start_time + kDelay, tick_clock->NowTicks());
}

TEST_F(TaskEnvironmentTest, AdvanceClockAdvanceMockClock) {
constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42);
TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);

const Clock* clock = task_environment.GetMockClock();
const Time start_time = clock->Now();
task_environment.AdvanceClock(kDelay);

EXPECT_EQ(start_time + kDelay, clock->Now());
}

TEST_F(TaskEnvironmentTest, AdvanceClockAdvanceTime) {
constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42);
TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);

const Time start_time = base::Time::Now();
task_environment.AdvanceClock(kDelay);
EXPECT_EQ(start_time + kDelay, base::Time::Now());
}

TEST_F(TaskEnvironmentTest, AdvanceClockAdvanceTimeTicks) {
constexpr base::TimeDelta kDelay = TimeDelta::FromSeconds(42);
TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);

const TimeTicks start_time = base::TimeTicks::Now();
task_environment.AdvanceClock(kDelay);
EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now());
}

TEST_F(TaskEnvironmentTest, AdvanceClockDoesNotRunTasks) {
TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME);

constexpr base::TimeDelta kTaskDelay = TimeDelta::FromDays(1);
ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(),
kTaskDelay);

EXPECT_EQ(1U, task_environment.GetPendingMainThreadTaskCount());
EXPECT_TRUE(task_environment.NextTaskIsDelayed());

task_environment.AdvanceClock(kTaskDelay);

// The task is still pending, but is now runnable.
EXPECT_EQ(1U, task_environment.GetPendingMainThreadTaskCount());
EXPECT_FALSE(task_environment.NextTaskIsDelayed());
}

// Verify that FastForwardBy() runs existing immediate tasks before advancing,
// then advances to the next delayed task, runs it, then advances the remainder
// of time when out of tasks.
Expand Down
49 changes: 46 additions & 3 deletions docs/threading_and_tasks_testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,15 @@ trait. This makes it such that delayed tasks and `base::Time::Now()` +

Under this mode, the mock clock will start at the current system time but will
then only advance when explicitly requested by `TaskEnvironment::FastForward*()`
methods *or* when `RunLoop::Run()` is running and all managed threads become
idle (auto-advances to the soonest delayed task, if any, amongst all managed
threads).
and `TaskEnvironment::AdvanceClock()` methods *or* when `RunLoop::Run()` is
running and all managed threads become idle (auto-advances to the soonest
delayed task, if any, amongst all managed threads).

`TaskEnvironment::FastForwardBy()` repeatedly runs existing immediately
executable tasks until idle and then advances the mock clock incrementally to
run the next delayed task within the time delta. It may advance time by more
than the requested amount if running the tasks causes nested
time-advancing-method calls.

This makes it possible to test code with flush intervals, repeating timers,
timeouts, etc. without any test-specific seams in the product code, e.g.:
Expand Down Expand Up @@ -231,6 +237,43 @@ TEST_F(FooStorageTest, Set) {
}
```

In contrast, `TaskEnvironment::AdvanceClock()` simply advances the mock time by
the requested amount, and does not run tasks. This may be useful in
cases where `TaskEnvironment::FastForwardBy()` would result in a livelock. For
example, if one task is blocked on a `WaitableEvent` and there is a delayed
task that would signal the event (e.g., a timeout), then
`TaskEnvironment::FastForwardBy()` will never complete. In this case, you could
advance the clock enough that the delayed task becomes runnable, and then
`TaskEnvironment::RunUntilIdle()` would run the delayed task, signalling the
event.

```
TEST(FooTest, TimeoutExceeded)
{
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
base::WaitableEvent event;
base::RunLoop run_loop;
base::PostTaskAndReply(
FROM_HERE, {base::ThreadPool(), base::MayBlock()},
base::BindOnce(&BlocksOnEvent, base::Unretained(&event)),
run_loop.QuitClosure());
base::PostDelayedTask(
FROM_HERE, {base::ThreadPool()},
base::BindOnce(&WaitableEvent::Signal, base::Unretained(&event)),
kTimeout);
// Can't use task_environment.FastForwardBy() since BlocksOnEvent blocks
// and the task pool will not become idle.
// Instead, advance time until the timeout task becomes runnable.
task_environment.AdvanceClock(kTimeout);
// Now the timeout task is runable.
task_environment.RunUntilIdle();
// The reply task should already have been executed, but run the run_loop to
// verify.
run_loop.Run();
}
```

### MainThreadType trait

The average component only cares about running its tasks and
Expand Down

0 comments on commit aec1fa6

Please sign in to comment.