Mock MessagePump that advances time as needed.
This pump will be useful for writing SequenceManager tests.
Change-Id: I02446cfb00dc46b205f36cb6abb002db6b0df6af
Reviewed-on: https://chromium-review.googlesource.com/c/1445877
Commit-Queue: Carlos Caballero <carlscab@google.com>
Reviewed-by: Gabriel Charette <gab@chromium.org>
Reviewed-by: Alexander Timin <altimin@chromium.org>
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Reviewed-by: Alex Clarke <alexclarke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#629533}
diff --git a/base/BUILD.gn b/base/BUILD.gn
index d84c3353..9c48507 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2500,6 +2500,7 @@
"task/sequence_manager/lazily_deallocated_deque_unittest.cc",
"task/sequence_manager/sequence_manager_impl_unittest.cc",
"task/sequence_manager/task_queue_selector_unittest.cc",
+ "task/sequence_manager/test/mock_time_message_pump_unittest.cc",
"task/sequence_manager/thread_controller_with_message_pump_impl_unittest.cc",
"task/sequence_manager/time_domain_unittest.cc",
"task/sequence_manager/work_queue_sets_unittest.cc",
diff --git a/base/task/sequence_manager/test/mock_time_message_pump.cc b/base/task/sequence_manager/test/mock_time_message_pump.cc
new file mode 100644
index 0000000..66b6f2c
--- /dev/null
+++ b/base/task/sequence_manager/test/mock_time_message_pump.cc
@@ -0,0 +1,88 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task/sequence_manager/test/mock_time_message_pump.h"
+
+#include <algorithm>
+
+#include "base/auto_reset.h"
+#include "base/test/simple_test_tick_clock.h"
+
+namespace base {
+namespace sequence_manager {
+
+MockTimeMessagePump::MockTimeMessagePump(SimpleTestTickClock* clock)
+ : clock_(clock) {}
+
+MockTimeMessagePump::~MockTimeMessagePump() {}
+
+bool MockTimeMessagePump::MaybeAdvanceTime(TimeTicks target_time) {
+ auto now = clock_->NowTicks();
+
+ if (target_time <= now)
+ return true;
+
+ TimeTicks next_now;
+
+ if (!target_time.is_max()) {
+ next_now = std::min(allow_advance_until_, target_time);
+ } else if (allow_advance_until_ == TimeTicks::Max()) {
+ next_now = now;
+ } else {
+ next_now = allow_advance_until_;
+ }
+
+ if (now < next_now) {
+ clock_->SetNowTicks(next_now);
+ return true;
+ }
+ return false;
+}
+
+void MockTimeMessagePump::Run(Delegate* delegate) {
+ AutoReset<bool> auto_reset_keep_running(&keep_running_, true);
+
+ for (;;) {
+ Delegate::NextWorkInfo info = delegate->DoSomeWork();
+
+ if (!keep_running_ || quit_after_do_some_work_)
+ break;
+
+ if (info.is_immediate())
+ continue;
+
+ bool have_immediate_work = delegate->DoIdleWork();
+
+ if (!keep_running_)
+ break;
+
+ if (have_immediate_work)
+ continue;
+
+ if (MaybeAdvanceTime(info.delayed_run_time))
+ continue;
+
+ next_wake_up_time_ = info.delayed_run_time;
+
+ if (stop_when_message_pump_is_idle_)
+ return;
+
+ NOTREACHED() << "Pump would go to sleep. Probably not what you wanted, "
+ "consider rewriting your test.";
+ }
+}
+
+void MockTimeMessagePump::Quit() {
+ keep_running_ = false;
+}
+
+void MockTimeMessagePump::ScheduleWork() {}
+
+void MockTimeMessagePump::ScheduleDelayedWork(
+ const TimeTicks& delayed_work_time) {
+ next_wake_up_time_ = delayed_work_time;
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/test/mock_time_message_pump.h b/base/task/sequence_manager/test/mock_time_message_pump.h
new file mode 100644
index 0000000..223dfa5
--- /dev/null
+++ b/base/task/sequence_manager/test/mock_time_message_pump.h
@@ -0,0 +1,86 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_MESSAGE_PUMP_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_MESSAGE_PUMP_H_
+
+#include "base/callback.h"
+#include "base/message_loop/message_pump.h"
+#include "base/optional.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/time/time.h"
+
+namespace base {
+
+class SimpleTestTickClock;
+
+namespace sequence_manager {
+
+// MessagePump implementation that uses a SimpleTestTickClock to keep track of
+// time and will advance it as needed to keep running tasks.
+//
+// This pump will actually check fail if it ever has to go to sleep as this
+// would indicate that the unit test might block indefinitely.
+// TODO(carlscab): In the future we could consider sleeping if there is no
+// outstanding |delayed_work_time_|, because we could be woken up by concurrent
+// ScheduleWork() calls.
+class MockTimeMessagePump : public MessagePump {
+ public:
+ explicit MockTimeMessagePump(SimpleTestTickClock* clock);
+ ~MockTimeMessagePump() override;
+
+ // MessagePump implementation
+ void Run(Delegate* delegate) override;
+ void Quit() override;
+ void ScheduleWork() override;
+ void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override;
+
+ // Returns the time at which the pump would have to wake up to be perform
+ // work.
+ TimeTicks next_wake_up_time() const { return next_wake_up_time_; }
+
+ // Quits after the first call to Delegate::DoSomeWork(). Useful
+ // for tests that want to make sure certain things happen during a DoSomeWork
+ // call.
+ void SetQuitAfterDoSomeWork(bool quit_after_do_some_work) {
+ quit_after_do_some_work_ = quit_after_do_some_work;
+ }
+
+ // Allows this instance to advance the SimpleTestTickClock up to but not over
+ // |advance_until| when idle (i.e. when a regular pump would go to sleep).
+ // The clock will allways be advanced to |advance_until|, even if there are no
+ // tasks requiring it (i.e. delayed tasks to be run after
+ // |advance_until|) except for a value of TimeTicks::Max() which will advance
+ // the clock as long as there is pending delayed work.
+ void SetAllowTimeToAutoAdvanceUntil(TimeTicks advance_until) {
+ allow_advance_until_ = advance_until;
+ }
+
+ // Quit when this pump's Delegate is out of work (i.e. when a regular pump
+ // would go to sleep) and we are not allowed to advance the clock anymore.
+ void SetStopWhenMessagePumpIsIdle(bool stop_when_message_pump_is_idle) {
+ stop_when_message_pump_is_idle_ = stop_when_message_pump_is_idle;
+ }
+
+ private:
+ // Returns true if the clock was indeed advanced and thus we should attempt
+ // another iteration of the DoSomeWork-DoIdleWork-loop.
+ bool MaybeAdvanceTime(TimeTicks target_time);
+
+ SimpleTestTickClock* const clock_;
+ // This flag is set to false when Run should return.
+ bool keep_running_ = true;
+
+ bool stop_when_message_pump_is_idle_ = false;
+ bool quit_after_do_some_work_ = false;
+
+ TimeTicks next_wake_up_time_{TimeTicks::Max()};
+
+ TimeTicks allow_advance_until_ = TimeTicks::Min();
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_MESSAGE_PUMP_H_
diff --git a/base/task/sequence_manager/test/mock_time_message_pump_unittest.cc b/base/task/sequence_manager/test/mock_time_message_pump_unittest.cc
new file mode 100644
index 0000000..dfa6e408
--- /dev/null
+++ b/base/task/sequence_manager/test/mock_time_message_pump_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/task/sequence_manager/test/mock_time_message_pump.h"
+
+#include "base/message_loop/message_pump.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace sequence_manager {
+namespace {
+
+using ::testing::DoAll;
+using ::testing::Eq;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrictMock;
+
+class MockMessagePumpDelegate : public MessagePump::Delegate {
+ public:
+ MOCK_METHOD0(BeforeDoInternalWork, void());
+ MOCK_METHOD0(DoWork, bool());
+ MOCK_METHOD1(DoDelayedWork, bool(TimeTicks*));
+ MOCK_METHOD0(DoSomeWork, NextWorkInfo());
+ MOCK_METHOD0(DoIdleWork, bool());
+};
+
+MessagePump::Delegate::NextWorkInfo NextWorkInfo(TimeTicks delayed_run_time) {
+ MessagePump::Delegate::NextWorkInfo info;
+ info.delayed_run_time = delayed_run_time;
+ return info;
+}
+
+TEST(MockMessagePumpTest, KeepsRunningIfNotAllowedToAdvanceTime) {
+ SimpleTestTickClock mock_clock;
+ mock_clock.Advance(TimeDelta::FromHours(42));
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kStartTime = mock_clock.NowTicks();
+ const auto kFutureTime = kStartTime + TimeDelta::FromSeconds(42);
+
+ EXPECT_CALL(delegate, DoSomeWork)
+ .WillOnce(Return(NextWorkInfo(TimeTicks())))
+ .WillOnce(Return(NextWorkInfo(TimeTicks())))
+ .WillOnce(Return(NextWorkInfo(kFutureTime)));
+ EXPECT_CALL(delegate, DoIdleWork).WillOnce(Invoke([&] {
+ pump.Quit();
+ return false;
+ }));
+
+ pump.Run(&delegate);
+
+ EXPECT_THAT(mock_clock.NowTicks(), Eq(kStartTime));
+}
+
+TEST(MockMessagePumpTest, AdvancesTimeAsAllowed) {
+ SimpleTestTickClock mock_clock;
+ mock_clock.Advance(TimeDelta::FromHours(42));
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kStartTime = mock_clock.NowTicks();
+ const auto kEndTime = kStartTime + TimeDelta::FromSeconds(2);
+
+ pump.SetAllowTimeToAutoAdvanceUntil(kEndTime);
+ pump.SetStopWhenMessagePumpIsIdle(true);
+ EXPECT_CALL(delegate, DoSomeWork).Times(3).WillRepeatedly(Invoke([&]() {
+ return NextWorkInfo(mock_clock.NowTicks() + TimeDelta::FromSeconds(1));
+ }));
+ EXPECT_CALL(delegate, DoIdleWork).Times(3).WillRepeatedly(Return(false));
+
+ pump.Run(&delegate);
+
+ EXPECT_THAT(mock_clock.NowTicks(), Eq(kEndTime));
+}
+
+TEST(MockMessagePumpTest, CanQuitAfterMaybeDoWork) {
+ SimpleTestTickClock mock_clock;
+ mock_clock.Advance(TimeDelta::FromHours(42));
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+
+ pump.SetQuitAfterDoSomeWork(true);
+ EXPECT_CALL(delegate, DoSomeWork).WillOnce(Return(NextWorkInfo(TimeTicks())));
+
+ pump.Run(&delegate);
+}
+
+TEST(MockMessagePumpTest, AdvancesUntilAllowedTime) {
+ SimpleTestTickClock mock_clock;
+ mock_clock.Advance(TimeDelta::FromHours(42));
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kStartTime = mock_clock.NowTicks();
+ const auto kEndTime = kStartTime + TimeDelta::FromSeconds(2);
+ const auto kNextDelayedWorkTime = kEndTime + TimeDelta::FromSeconds(2);
+
+ pump.SetAllowTimeToAutoAdvanceUntil(kEndTime);
+ pump.SetStopWhenMessagePumpIsIdle(true);
+ EXPECT_CALL(delegate, DoSomeWork)
+ .Times(2)
+ .WillRepeatedly(Return(NextWorkInfo(kNextDelayedWorkTime)));
+ EXPECT_CALL(delegate, DoIdleWork).Times(2).WillRepeatedly(Return(false));
+
+ pump.Run(&delegate);
+
+ EXPECT_THAT(mock_clock.NowTicks(), Eq(kEndTime));
+}
+
+TEST(MockMessagePumpTest, StoresNextWakeUpTime) {
+ SimpleTestTickClock mock_clock;
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kStartTime = mock_clock.NowTicks();
+ const auto kEndTime = kStartTime;
+ const auto kNextDelayedWorkTime = kEndTime + TimeDelta::FromSeconds(2);
+
+ pump.SetAllowTimeToAutoAdvanceUntil(kEndTime);
+ pump.SetStopWhenMessagePumpIsIdle(true);
+ EXPECT_CALL(delegate, DoSomeWork)
+ .WillOnce(Return(NextWorkInfo(kNextDelayedWorkTime)));
+ EXPECT_CALL(delegate, DoIdleWork).WillOnce(Return(false));
+
+ pump.Run(&delegate);
+
+ EXPECT_THAT(pump.next_wake_up_time(), Eq(kNextDelayedWorkTime));
+}
+
+TEST(MockMessagePumpTest, StoresNextWakeUpTimeInScheduleDelayedWork) {
+ SimpleTestTickClock mock_clock;
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kNextDelayedWorkTime =
+ mock_clock.NowTicks() + TimeDelta::FromSeconds(2);
+
+ pump.ScheduleDelayedWork(kNextDelayedWorkTime);
+
+ EXPECT_THAT(pump.next_wake_up_time(), Eq(kNextDelayedWorkTime));
+}
+
+TEST(MockMessagePumpTest, NextDelayedWorkTimeInThePastKeepsRunning) {
+ SimpleTestTickClock mock_clock;
+ mock_clock.Advance(TimeDelta::FromHours(42));
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kNextDelayedWorkTime = mock_clock.NowTicks();
+ mock_clock.Advance(TimeDelta::FromHours(2));
+
+ pump.SetStopWhenMessagePumpIsIdle(true);
+
+ EXPECT_CALL(delegate, DoSomeWork)
+ .WillOnce(Return(NextWorkInfo(kNextDelayedWorkTime)))
+ .WillOnce(Return(NextWorkInfo(kNextDelayedWorkTime)))
+ .WillOnce(Return(NextWorkInfo(TimeTicks::Max())));
+ EXPECT_CALL(delegate, DoIdleWork).WillRepeatedly(Return(false));
+
+ pump.Run(&delegate);
+}
+
+TEST(MockMessagePumpTest,
+ AdvancesUntilAllowedTimeWhenNextDelayedWorkTimeIsMax) {
+ SimpleTestTickClock mock_clock;
+ mock_clock.Advance(TimeDelta::FromHours(42));
+ StrictMock<MockMessagePumpDelegate> delegate;
+ MockTimeMessagePump pump(&mock_clock);
+ const auto kAdvanceUntil =
+ mock_clock.NowTicks() + TimeDelta::FromSeconds(123);
+
+ pump.SetStopWhenMessagePumpIsIdle(true);
+ pump.SetAllowTimeToAutoAdvanceUntil(kAdvanceUntil);
+ EXPECT_CALL(delegate, DoSomeWork)
+ .WillRepeatedly(Return(NextWorkInfo(TimeTicks::Max())));
+ EXPECT_CALL(delegate, DoIdleWork).WillRepeatedly(Return(false));
+
+ pump.Run(&delegate);
+
+ EXPECT_THAT(mock_clock.NowTicks(), Eq(kAdvanceUntil));
+}
+
+} // namespace
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index 513b172..7c3632a 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -32,6 +32,8 @@
"../task/sequence_manager/test/fake_task.h",
"../task/sequence_manager/test/mock_time_domain.cc",
"../task/sequence_manager/test/mock_time_domain.h",
+ "../task/sequence_manager/test/mock_time_message_pump.cc",
+ "../task/sequence_manager/test/mock_time_message_pump.h",
"../task/sequence_manager/test/sequence_manager_for_test.cc",
"../task/sequence_manager/test/sequence_manager_for_test.h",
"../task/sequence_manager/test/test_task_queue.cc",