| // Copyright 2015 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/test/test_mock_time_task_runner.h" |
| |
| #include <utility> |
| |
| #include "base/containers/circular_deque.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| |
| namespace base { |
| namespace { |
| |
| // LegacyMockTickClock and LegacyMockClock are used by deprecated APIs of |
| // TestMockTimeTaskRunner. They will be removed after updating callers of |
| // GetMockClock() and GetMockTickClock() to GetMockClockPtr() and |
| // GetMockTickClockPtr(). |
| class LegacyMockTickClock : public TickClock { |
| public: |
| explicit LegacyMockTickClock( |
| scoped_refptr<const TestMockTimeTaskRunner> task_runner) |
| : task_runner_(std::move(task_runner)) {} |
| |
| // TickClock: |
| TimeTicks NowTicks() const override { return task_runner_->NowTicks(); } |
| |
| private: |
| scoped_refptr<const TestMockTimeTaskRunner> task_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LegacyMockTickClock); |
| }; |
| |
| class LegacyMockClock : public Clock { |
| public: |
| explicit LegacyMockClock( |
| scoped_refptr<const TestMockTimeTaskRunner> task_runner) |
| : task_runner_(std::move(task_runner)) {} |
| |
| // Clock: |
| Time Now() const override { return task_runner_->Now(); } |
| |
| private: |
| scoped_refptr<const TestMockTimeTaskRunner> task_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LegacyMockClock); |
| }; |
| |
| // A SingleThreadTaskRunner which forwards everything to its |target_|. This is |
| // useful to break ownership chains when it is known that |target_| will outlive |
| // the NonOwningProxyTaskRunner it's injected into. In particular, |
| // TestMockTimeTaskRunner is forced to be ref-counted by virtue of being a |
| // SingleThreadTaskRunner. As such it is impossible for it to have a |
| // ThreadTaskRunnerHandle member that points back to itself as the |
| // ThreadTaskRunnerHandle which it owns would hold a ref back to it. To break |
| // this dependency cycle, the ThreadTaskRunnerHandle is instead handed a |
| // NonOwningProxyTaskRunner which allows the TestMockTimeTaskRunner to not hand |
| // a ref to its ThreadTaskRunnerHandle while promising in return that it will |
| // outlive that ThreadTaskRunnerHandle instance. |
| class NonOwningProxyTaskRunner : public SingleThreadTaskRunner { |
| public: |
| explicit NonOwningProxyTaskRunner(SingleThreadTaskRunner* target) |
| : target_(target) { |
| DCHECK(target_); |
| } |
| |
| // SingleThreadTaskRunner: |
| bool RunsTasksInCurrentSequence() const override { |
| return target_->RunsTasksInCurrentSequence(); |
| } |
| bool PostDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) override { |
| return target_->PostDelayedTask(from_here, std::move(task), delay); |
| } |
| bool PostNonNestableDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) override { |
| return target_->PostNonNestableDelayedTask(from_here, std::move(task), |
| delay); |
| } |
| |
| private: |
| friend class RefCountedThreadSafe<NonOwningProxyTaskRunner>; |
| ~NonOwningProxyTaskRunner() override = default; |
| |
| SingleThreadTaskRunner* const target_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NonOwningProxyTaskRunner); |
| }; |
| |
| } // namespace |
| |
| // TestMockTimeTaskRunner::TestOrderedPendingTask ----------------------------- |
| |
| // Subclass of TestPendingTask which has a strictly monotonically increasing ID |
| // for every task, so that tasks posted with the same 'time to run' can be run |
| // in the order of being posted. |
| struct TestMockTimeTaskRunner::TestOrderedPendingTask |
| : public base::TestPendingTask { |
| TestOrderedPendingTask(); |
| TestOrderedPendingTask(const Location& location, |
| OnceClosure task, |
| TimeTicks post_time, |
| TimeDelta delay, |
| size_t ordinal, |
| TestNestability nestability); |
| TestOrderedPendingTask(TestOrderedPendingTask&&); |
| ~TestOrderedPendingTask(); |
| |
| TestOrderedPendingTask& operator=(TestOrderedPendingTask&&); |
| |
| size_t ordinal; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestOrderedPendingTask); |
| }; |
| |
| TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask() |
| : ordinal(0) { |
| } |
| |
| TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask( |
| TestOrderedPendingTask&&) = default; |
| |
| TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask( |
| const Location& location, |
| OnceClosure task, |
| TimeTicks post_time, |
| TimeDelta delay, |
| size_t ordinal, |
| TestNestability nestability) |
| : base::TestPendingTask(location, |
| std::move(task), |
| post_time, |
| delay, |
| nestability), |
| ordinal(ordinal) {} |
| |
| TestMockTimeTaskRunner::TestOrderedPendingTask::~TestOrderedPendingTask() = |
| default; |
| |
| TestMockTimeTaskRunner::TestOrderedPendingTask& |
| TestMockTimeTaskRunner::TestOrderedPendingTask::operator=( |
| TestOrderedPendingTask&&) = default; |
| |
| // TestMockTimeTaskRunner ----------------------------------------------------- |
| |
| // TODO(gab): This should also set the SequenceToken for the current thread. |
| // Ref. TestMockTimeTaskRunner::RunsTasksInCurrentSequence(). |
| TestMockTimeTaskRunner::ScopedContext::ScopedContext( |
| scoped_refptr<TestMockTimeTaskRunner> scope) |
| : on_destroy_(ThreadTaskRunnerHandle::OverrideForTesting(scope)) { |
| scope->RunUntilIdle(); |
| } |
| |
| TestMockTimeTaskRunner::ScopedContext::~ScopedContext() = default; |
| |
| bool TestMockTimeTaskRunner::TemporalOrder::operator()( |
| const TestOrderedPendingTask& first_task, |
| const TestOrderedPendingTask& second_task) const { |
| if (first_task.GetTimeToRun() == second_task.GetTimeToRun()) |
| return first_task.ordinal > second_task.ordinal; |
| return first_task.GetTimeToRun() > second_task.GetTimeToRun(); |
| } |
| |
| TestMockTimeTaskRunner::TestMockTimeTaskRunner(Type type) |
| : TestMockTimeTaskRunner(Time::UnixEpoch(), TimeTicks(), type) {} |
| |
| TestMockTimeTaskRunner::TestMockTimeTaskRunner(Time start_time, |
| TimeTicks start_ticks, |
| Type type) |
| : now_(start_time), |
| now_ticks_(start_ticks), |
| tasks_lock_cv_(&tasks_lock_), |
| mock_clock_(this) { |
| if (type == Type::kBoundToThread) { |
| RunLoop::RegisterDelegateForCurrentThread(this); |
| thread_task_runner_handle_ = std::make_unique<ThreadTaskRunnerHandle>( |
| MakeRefCounted<NonOwningProxyTaskRunner>(this)); |
| } |
| } |
| |
| TestMockTimeTaskRunner::~TestMockTimeTaskRunner() = default; |
| |
| void TestMockTimeTaskRunner::FastForwardBy(TimeDelta delta) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_GE(delta, TimeDelta()); |
| |
| const TimeTicks original_now_ticks = NowTicks(); |
| ProcessAllTasksNoLaterThan(delta); |
| ForwardClocksUntilTickTime(original_now_ticks + delta); |
| } |
| |
| void TestMockTimeTaskRunner::AdvanceMockTickClock(TimeDelta delta) { |
| ForwardClocksUntilTickTime(NowTicks() + delta); |
| } |
| |
| void TestMockTimeTaskRunner::RunUntilIdle() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| ProcessAllTasksNoLaterThan(TimeDelta()); |
| } |
| |
| void TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| ProcessAllTasksNoLaterThan(TimeDelta::Max()); |
| } |
| |
| void TestMockTimeTaskRunner::ClearPendingTasks() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| AutoLock scoped_lock(tasks_lock_); |
| while (!tasks_.empty()) |
| tasks_.pop(); |
| } |
| |
| Time TestMockTimeTaskRunner::Now() const { |
| AutoLock scoped_lock(tasks_lock_); |
| return now_; |
| } |
| |
| TimeTicks TestMockTimeTaskRunner::NowTicks() const { |
| AutoLock scoped_lock(tasks_lock_); |
| return now_ticks_; |
| } |
| |
| std::unique_ptr<Clock> TestMockTimeTaskRunner::DeprecatedGetMockClock() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return std::make_unique<LegacyMockClock>(this); |
| } |
| |
| Clock* TestMockTimeTaskRunner::GetMockClock() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return &mock_clock_; |
| } |
| |
| std::unique_ptr<TickClock> TestMockTimeTaskRunner::DeprecatedGetMockTickClock() |
| const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return std::make_unique<LegacyMockTickClock>(this); |
| } |
| |
| const TickClock* TestMockTimeTaskRunner::GetMockTickClock() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return &mock_clock_; |
| } |
| |
| base::circular_deque<TestPendingTask> |
| TestMockTimeTaskRunner::TakePendingTasks() { |
| AutoLock scoped_lock(tasks_lock_); |
| base::circular_deque<TestPendingTask> tasks; |
| while (!tasks_.empty()) { |
| // It's safe to remove const and consume |task| here, since |task| is not |
| // used for ordering the item. |
| if (!tasks_.top().task.IsCancelled()) { |
| tasks.push_back( |
| std::move(const_cast<TestOrderedPendingTask&>(tasks_.top()))); |
| } |
| tasks_.pop(); |
| } |
| return tasks; |
| } |
| |
| bool TestMockTimeTaskRunner::HasPendingTask() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| AutoLock scoped_lock(tasks_lock_); |
| while (!tasks_.empty() && tasks_.top().task.IsCancelled()) |
| tasks_.pop(); |
| return !tasks_.empty(); |
| } |
| |
| size_t TestMockTimeTaskRunner::GetPendingTaskCount() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| AutoLock scoped_lock(tasks_lock_); |
| TaskPriorityQueue preserved_tasks; |
| while (!tasks_.empty()) { |
| if (!tasks_.top().task.IsCancelled()) { |
| preserved_tasks.push( |
| std::move(const_cast<TestOrderedPendingTask&>(tasks_.top()))); |
| } |
| tasks_.pop(); |
| } |
| tasks_.swap(preserved_tasks); |
| return tasks_.size(); |
| } |
| |
| TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| AutoLock scoped_lock(tasks_lock_); |
| while (!tasks_.empty() && tasks_.top().task.IsCancelled()) |
| tasks_.pop(); |
| return tasks_.empty() ? TimeDelta::Max() |
| : tasks_.top().GetTimeToRun() - now_ticks_; |
| } |
| |
| // TODO(gab): Combine |thread_checker_| with a SequenceToken to differentiate |
| // between tasks running in the scope of this TestMockTimeTaskRunner and other |
| // task runners sharing this thread. http://crbug.com/631186 |
| bool TestMockTimeTaskRunner::RunsTasksInCurrentSequence() const { |
| return thread_checker_.CalledOnValidThread(); |
| } |
| |
| bool TestMockTimeTaskRunner::PostDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) { |
| AutoLock scoped_lock(tasks_lock_); |
| tasks_.push(TestOrderedPendingTask(from_here, std::move(task), now_ticks_, |
| delay, next_task_ordinal_++, |
| TestPendingTask::NESTABLE)); |
| tasks_lock_cv_.Signal(); |
| return true; |
| } |
| |
| bool TestMockTimeTaskRunner::PostNonNestableDelayedTask( |
| const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) { |
| return PostDelayedTask(from_here, std::move(task), delay); |
| } |
| |
| void TestMockTimeTaskRunner::OnBeforeSelectingTask() { |
| // Empty default implementation. |
| } |
| |
| void TestMockTimeTaskRunner::OnAfterTimePassed() { |
| // Empty default implementation. |
| } |
| |
| void TestMockTimeTaskRunner::OnAfterTaskRun() { |
| // Empty default implementation. |
| } |
| |
| void TestMockTimeTaskRunner::ProcessAllTasksNoLaterThan(TimeDelta max_delta) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_GE(max_delta, TimeDelta()); |
| |
| // Multiple test task runners can share the same thread for determinism in |
| // unit tests. Make sure this TestMockTimeTaskRunner's tasks run in its scope. |
| ScopedClosureRunner undo_override; |
| if (!ThreadTaskRunnerHandle::IsSet() || |
| ThreadTaskRunnerHandle::Get() != this) { |
| undo_override = ThreadTaskRunnerHandle::OverrideForTesting(this); |
| } |
| |
| const TimeTicks original_now_ticks = NowTicks(); |
| while (!quit_run_loop_) { |
| OnBeforeSelectingTask(); |
| TestPendingTask task_info; |
| if (!DequeueNextTask(original_now_ticks, max_delta, &task_info)) |
| break; |
| if (task_info.task.IsCancelled()) |
| continue; |
| // If tasks were posted with a negative delay, task_info.GetTimeToRun() will |
| // be less than |now_ticks_|. ForwardClocksUntilTickTime() takes care of not |
| // moving the clock backwards in this case. |
| ForwardClocksUntilTickTime(task_info.GetTimeToRun()); |
| std::move(task_info.task).Run(); |
| OnAfterTaskRun(); |
| } |
| } |
| |
| void TestMockTimeTaskRunner::ForwardClocksUntilTickTime(TimeTicks later_ticks) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| { |
| AutoLock scoped_lock(tasks_lock_); |
| if (later_ticks <= now_ticks_) |
| return; |
| |
| now_ += later_ticks - now_ticks_; |
| now_ticks_ = later_ticks; |
| } |
| OnAfterTimePassed(); |
| } |
| |
| bool TestMockTimeTaskRunner::DequeueNextTask(const TimeTicks& reference, |
| const TimeDelta& max_delta, |
| TestPendingTask* next_task) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| AutoLock scoped_lock(tasks_lock_); |
| if (!tasks_.empty() && |
| (tasks_.top().GetTimeToRun() - reference) <= max_delta) { |
| // It's safe to remove const and consume |task| here, since |task| is not |
| // used for ordering the item. |
| *next_task = std::move(const_cast<TestOrderedPendingTask&>(tasks_.top())); |
| tasks_.pop(); |
| return true; |
| } |
| return false; |
| } |
| |
| void TestMockTimeTaskRunner::Run(bool application_tasks_allowed) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Since TestMockTimeTaskRunner doesn't process system messages: there's no |
| // hope for anything but an application task to call Quit(). If this RunLoop |
| // can't process application tasks (i.e. disallowed by default in nested |
| // RunLoops) it's guaranteed to hang... |
| DCHECK(application_tasks_allowed) |
| << "This is a nested RunLoop instance and needs to be of " |
| "Type::kNestableTasksAllowed."; |
| |
| while (!quit_run_loop_) { |
| RunUntilIdle(); |
| if (quit_run_loop_ || ShouldQuitWhenIdle()) |
| break; |
| |
| // Peek into |tasks_| to perform one of two things: |
| // A) If there are no remaining tasks, wait until one is posted and |
| // restart from the top. |
| // B) If there is a remaining delayed task. Fast-forward to reach the next |
| // round of tasks. |
| TimeDelta auto_fast_forward_by; |
| { |
| AutoLock scoped_lock(tasks_lock_); |
| if (tasks_.empty()) { |
| while (tasks_.empty()) |
| tasks_lock_cv_.Wait(); |
| continue; |
| } |
| auto_fast_forward_by = tasks_.top().GetTimeToRun() - now_ticks_; |
| } |
| FastForwardBy(auto_fast_forward_by); |
| } |
| quit_run_loop_ = false; |
| } |
| |
| void TestMockTimeTaskRunner::Quit() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| quit_run_loop_ = true; |
| } |
| |
| void TestMockTimeTaskRunner::EnsureWorkScheduled() { |
| // Nothing to do: TestMockTimeTaskRunner::Run() will always process tasks and |
| // doesn't need an extra kick on nested runs. |
| } |
| |
| TimeTicks TestMockTimeTaskRunner::MockClock::NowTicks() const { |
| return task_runner_->NowTicks(); |
| } |
| |
| Time TestMockTimeTaskRunner::MockClock::Now() const { |
| return task_runner_->Now(); |
| } |
| |
| } // namespace base |