Commit 5e2df665 authored by Gabriel Charette's avatar Gabriel Charette Committed by Commit Bot

Add MOCK_TIME mode to ScopedTaskEnvironment :)

Taking advantage of the new kBoundToThread mode on
TestMockTimeTaskRunner.

This change also required tweaking the
ScopedTaskEnvironment::RunUntilIdle() logic as RunLoop().Run() on
TestMockTimeTaskRunner results in advancing time when there's no
request to quit-when-idle which is undesired here. New logic gets rid
of need for |on_queue_empty_closure_| and I think is simpler overall.
As of patch set 20, this new RunUntilIdle() logic also avoids using
TaskScheduler::FlushForTesting() as that can result in hangs should a
TaskScheduler task synchronously block on the main thread.

R=fdoray@chromium.org
TBR=gab@chromium.org (IWYU fixes)

Bug: 708584
Change-Id: I76ba55ec64d398151420379d3fcdcd5186fbceb8
Reviewed-on: https://chromium-review.googlesource.com/638550
Commit-Queue: Gabriel Charette <gab@chromium.org>
Reviewed-by: default avatarGabriel Charette <gab@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#518433}
parent 78294a3b
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/location.h" #include "base/location.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
......
...@@ -246,7 +246,7 @@ void TaskTracker::Shutdown() { ...@@ -246,7 +246,7 @@ void TaskTracker::Shutdown() {
void TaskTracker::Flush() { void TaskTracker::Flush() {
AutoSchedulerLock auto_lock(flush_lock_); AutoSchedulerLock auto_lock(flush_lock_);
while (subtle::Acquire_Load(&num_pending_undelayed_tasks_) != 0 && while (subtle::Acquire_Load(&num_incomplete_undelayed_tasks_) != 0 &&
!IsShutdownComplete()) { !IsShutdownComplete()) {
flush_cv_->Wait(); flush_cv_->Wait();
} }
...@@ -259,7 +259,7 @@ bool TaskTracker::WillPostTask(const Task* task) { ...@@ -259,7 +259,7 @@ bool TaskTracker::WillPostTask(const Task* task) {
return false; return false;
if (task->delayed_run_time.is_null()) if (task->delayed_run_time.is_null())
subtle::NoBarrier_AtomicIncrement(&num_pending_undelayed_tasks_, 1); subtle::NoBarrier_AtomicIncrement(&num_incomplete_undelayed_tasks_, 1);
debug::TaskAnnotator task_annotator; debug::TaskAnnotator task_annotator;
task_annotator.DidQueueTask(kQueueFunctionName, *task); task_annotator.DidQueueTask(kQueueFunctionName, *task);
...@@ -313,9 +313,7 @@ scoped_refptr<Sequence> TaskTracker::RunNextTask( ...@@ -313,9 +313,7 @@ scoped_refptr<Sequence> TaskTracker::RunNextTask(
AfterRunTask(shutdown_behavior); AfterRunTask(shutdown_behavior);
if (!is_delayed) if (!is_delayed)
DecrementNumPendingUndelayedTasks(); DecrementNumIncompleteUndelayedTasks();
OnRunNextTaskCompleted();
const bool sequence_is_empty_after_pop = sequence->Pop(); const bool sequence_is_empty_after_pop = sequence->Pop();
...@@ -476,8 +474,8 @@ bool TaskTracker::IsPostingBlockShutdownTaskAfterShutdownAllowed() { ...@@ -476,8 +474,8 @@ bool TaskTracker::IsPostingBlockShutdownTaskAfterShutdownAllowed() {
} }
#endif #endif
int TaskTracker::GetNumPendingUndelayedTasksForTesting() const { int TaskTracker::GetNumIncompleteUndelayedTasksForTesting() const {
return subtle::NoBarrier_Load(&num_pending_undelayed_tasks_); return subtle::NoBarrier_Load(&num_incomplete_undelayed_tasks_);
} }
bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) { bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) {
...@@ -595,11 +593,11 @@ void TaskTracker::OnBlockingShutdownTasksComplete() { ...@@ -595,11 +593,11 @@ void TaskTracker::OnBlockingShutdownTasksComplete() {
shutdown_event_->Signal(); shutdown_event_->Signal();
} }
void TaskTracker::DecrementNumPendingUndelayedTasks() { void TaskTracker::DecrementNumIncompleteUndelayedTasks() {
const auto new_num_pending_undelayed_tasks = const auto new_num_incomplete_undelayed_tasks =
subtle::Barrier_AtomicIncrement(&num_pending_undelayed_tasks_, -1); subtle::Barrier_AtomicIncrement(&num_incomplete_undelayed_tasks_, -1);
DCHECK_GE(new_num_pending_undelayed_tasks, 0); DCHECK_GE(new_num_incomplete_undelayed_tasks, 0);
if (new_num_pending_undelayed_tasks == 0) { if (new_num_incomplete_undelayed_tasks == 0) {
AutoSchedulerLock auto_lock(flush_lock_); AutoSchedulerLock auto_lock(flush_lock_);
flush_cv_->Signal(); flush_cv_->Signal();
} }
......
...@@ -99,7 +99,7 @@ class BASE_EXPORT TaskTracker { ...@@ -99,7 +99,7 @@ class BASE_EXPORT TaskTracker {
// This can only be called once. // This can only be called once.
void Shutdown(); void Shutdown();
// Waits until there are no pending undelayed tasks. May be called in tests // Waits until there are no incomplete undelayed tasks. May be called in tests
// to validate that a condition is met after all undelayed tasks have run. // to validate that a condition is met after all undelayed tasks have run.
// //
// Does not wait for delayed tasks. Waits for undelayed tasks posted from // Does not wait for delayed tasks. Waits for undelayed tasks posted from
...@@ -166,13 +166,9 @@ class BASE_EXPORT TaskTracker { ...@@ -166,13 +166,9 @@ class BASE_EXPORT TaskTracker {
virtual bool IsPostingBlockShutdownTaskAfterShutdownAllowed(); virtual bool IsPostingBlockShutdownTaskAfterShutdownAllowed();
#endif #endif
// Called at the very end of RunNextTask() after the completion of all task
// metrics accounting.
virtual void OnRunNextTaskCompleted() {}
// Returns the number of undelayed tasks that haven't completed their // Returns the number of undelayed tasks that haven't completed their
// execution. // execution (still queued or in progress).
int GetNumPendingUndelayedTasksForTesting() const; int GetNumIncompleteUndelayedTasksForTesting() const;
private: private:
class State; class State;
...@@ -199,9 +195,9 @@ class BASE_EXPORT TaskTracker { ...@@ -199,9 +195,9 @@ class BASE_EXPORT TaskTracker {
// shutdown has started. // shutdown has started.
void OnBlockingShutdownTasksComplete(); void OnBlockingShutdownTasksComplete();
// Decrements the number of pending undelayed tasks and signals |flush_cv_| if // Decrements the number of incomplete undelayed tasks and signals |flush_cv_|
// it reaches zero. // if it reaches zero.
void DecrementNumPendingUndelayedTasks(); void DecrementNumIncompleteUndelayedTasks();
// To be called after running a background task from |just_ran_sequence|. // To be called after running a background task from |just_ran_sequence|.
// Performs the following actions: // Performs the following actions:
...@@ -233,15 +229,15 @@ class BASE_EXPORT TaskTracker { ...@@ -233,15 +229,15 @@ class BASE_EXPORT TaskTracker {
// decremented with a memory barrier after a task runs. Is accessed with an // decremented with a memory barrier after a task runs. Is accessed with an
// acquire memory barrier in Flush(). The memory barriers ensure that the // acquire memory barrier in Flush(). The memory barriers ensure that the
// memory written by flushed tasks is visible when Flush() returns. // memory written by flushed tasks is visible when Flush() returns.
subtle::Atomic32 num_pending_undelayed_tasks_ = 0; subtle::Atomic32 num_incomplete_undelayed_tasks_ = 0;
// Lock associated with |flush_cv_|. Partially synchronizes access to // Lock associated with |flush_cv_|. Partially synchronizes access to
// |num_pending_undelayed_tasks_|. Full synchronization isn't needed because // |num_incomplete_undelayed_tasks_|. Full synchronization isn't needed
// it's atomic, but synchronization is needed to coordinate waking and // because it's atomic, but synchronization is needed to coordinate waking and
// sleeping at the right time. // sleeping at the right time.
mutable SchedulerLock flush_lock_; mutable SchedulerLock flush_lock_;
// Signaled when |num_pending_undelayed_tasks_| is zero or when shutdown // Signaled when |num_incomplete_undelayed_tasks_| is zero or when shutdown
// completes. // completes.
const std::unique_ptr<ConditionVariable> flush_cv_; const std::unique_ptr<ConditionVariable> flush_cv_;
......
...@@ -6,52 +6,28 @@ ...@@ -6,52 +6,28 @@
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/synchronization/condition_variable.h" #include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/task_scheduler/post_task.h" #include "base/task_scheduler/post_task.h"
#include "base/task_scheduler/task_scheduler.h" #include "base/task_scheduler/task_scheduler.h"
#include "base/task_scheduler/task_scheduler_impl.h" #include "base/task_scheduler/task_scheduler_impl.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h" #include "base/time/time.h"
namespace base { namespace base {
namespace test { namespace test {
namespace {
class TaskObserver : public MessageLoop::TaskObserver {
public:
TaskObserver() = default;
// MessageLoop::TaskObserver:
void WillProcessTask(const PendingTask& pending_task) override {}
void DidProcessTask(const PendingTask& pending_task) override {
++task_count_;
}
int task_count() const { return task_count_; }
private:
int task_count_ = 0;
DISALLOW_COPY_AND_ASSIGN(TaskObserver);
};
} // namespace
class ScopedTaskEnvironment::TestTaskTracker class ScopedTaskEnvironment::TestTaskTracker
: public internal::TaskSchedulerImpl::TaskTrackerImpl { : public internal::TaskSchedulerImpl::TaskTrackerImpl {
public: public:
TestTaskTracker(); TestTaskTracker();
void RegisterOnQueueEmptyClosure(OnceClosure queue_empty_closure);
// Returns true if closure needed reset.
bool ResetOnQueueEmptyClosureIfNotNull();
// Allow running tasks. // Allow running tasks.
void AllowRunRask(); void AllowRunTasks();
// Disallow running tasks. No-ops and returns false if a task is running. // Disallow running tasks. No-ops and returns false if a task is running.
bool DisallowRunTasks(); bool DisallowRunTasks();
...@@ -63,14 +39,10 @@ class ScopedTaskEnvironment::TestTaskTracker ...@@ -63,14 +39,10 @@ class ScopedTaskEnvironment::TestTaskTracker
void RunOrSkipTask(std::unique_ptr<internal::Task> task, void RunOrSkipTask(std::unique_ptr<internal::Task> task,
internal::Sequence* sequence, internal::Sequence* sequence,
bool can_run_task) override; bool can_run_task) override;
void OnRunNextTaskCompleted() override;
// Synchronizes accesses to members below. // Synchronizes accesses to members below.
Lock lock_; Lock lock_;
// Closure posted to the main thread when the task queue becomes empty.
OnceClosure queue_empty_closure_;
// True if running tasks is allowed. // True if running tasks is allowed.
bool can_run_tasks_ = true; bool can_run_tasks_ = true;
...@@ -87,11 +59,19 @@ ScopedTaskEnvironment::ScopedTaskEnvironment( ...@@ -87,11 +59,19 @@ ScopedTaskEnvironment::ScopedTaskEnvironment(
MainThreadType main_thread_type, MainThreadType main_thread_type,
ExecutionMode execution_control_mode) ExecutionMode execution_control_mode)
: execution_control_mode_(execution_control_mode), : execution_control_mode_(execution_control_mode),
message_loop_(main_thread_type == MainThreadType::DEFAULT message_loop_(main_thread_type == MainThreadType::MOCK_TIME
? MessageLoop::TYPE_DEFAULT ? nullptr
: (main_thread_type == MainThreadType::UI : (std::make_unique<MessageLoop>(
? MessageLoop::TYPE_UI main_thread_type == MainThreadType::DEFAULT
: MessageLoop::TYPE_IO)), ? MessageLoop::TYPE_DEFAULT
: (main_thread_type == MainThreadType::UI
? MessageLoop::TYPE_UI
: MessageLoop::TYPE_IO)))),
mock_time_task_runner_(
main_thread_type == MainThreadType::MOCK_TIME
? MakeRefCounted<TestMockTimeTaskRunner>(
TestMockTimeTaskRunner::Type::kBoundToThread)
: nullptr),
task_tracker_(new TestTaskTracker()) { task_tracker_(new TestTaskTracker()) {
CHECK(!TaskScheduler::GetInstance()); CHECK(!TaskScheduler::GetInstance());
...@@ -125,7 +105,7 @@ ScopedTaskEnvironment::~ScopedTaskEnvironment() { ...@@ -125,7 +105,7 @@ ScopedTaskEnvironment::~ScopedTaskEnvironment() {
CHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_); CHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
// Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
// skipped, resulting in memory leaks. // skipped, resulting in memory leaks.
task_tracker_->AllowRunRask(); task_tracker_->AllowRunTasks();
TaskScheduler::GetInstance()->FlushForTesting(); TaskScheduler::GetInstance()->FlushForTesting();
TaskScheduler::GetInstance()->Shutdown(); TaskScheduler::GetInstance()->Shutdown();
TaskScheduler::GetInstance()->JoinForTesting(); TaskScheduler::GetInstance()->JoinForTesting();
...@@ -134,79 +114,92 @@ ScopedTaskEnvironment::~ScopedTaskEnvironment() { ...@@ -134,79 +114,92 @@ ScopedTaskEnvironment::~ScopedTaskEnvironment() {
scoped_refptr<base::SingleThreadTaskRunner> scoped_refptr<base::SingleThreadTaskRunner>
ScopedTaskEnvironment::GetMainThreadTaskRunner() { ScopedTaskEnvironment::GetMainThreadTaskRunner() {
return message_loop_.task_runner(); if (message_loop_)
return message_loop_->task_runner();
DCHECK(mock_time_task_runner_);
return mock_time_task_runner_;
} }
void ScopedTaskEnvironment::RunUntilIdle() { void ScopedTaskEnvironment::RunUntilIdle() {
for (;;) { for (int i = 0;; ++i) {
RunLoop run_loop; LOG_IF(WARNING, i % 1000 == 999)
<< "ScopedTaskEnvironment::RunUntilIdle() appears to be stuck in an "
// Register a closure to stop running tasks on the main thread when the "infinite loop (e.g. A posts B posts C posts A?).";
// TaskScheduler queue becomes empty.
task_tracker_->RegisterOnQueueEmptyClosure(run_loop.QuitWhenIdleClosure()); task_tracker_->AllowRunTasks();
// The closure registered above may never run if the TaskScheduler queue // Another pass is only ever required when there is work remaining in the
// starts empty. Post a TaskScheduler tasks to make sure that the queue // TaskScheduler (see logic below), yield the main thread to avoid it
// doesn't start empty. // entering a DisallowRunTasks()/AllowRunTasks() busy loop that merely
PostTask(FROM_HERE, BindOnce(&DoNothing)); // confirms TaskScheduler has work to do while preventing its execution.
if (i > 0)
// Run main thread and TaskScheduler tasks. PlatformThread::YieldCurrentThread();
task_tracker_->AllowRunRask();
TaskObserver task_observer; // First run as many tasks as possible on the main thread in parallel with
MessageLoop::current()->AddTaskObserver(&task_observer); // tasks in TaskScheduler. This increases likelihood of TSAN catching
run_loop.Run(); // threading errors and eliminates possibility of hangs should a
MessageLoop::current()->RemoveTaskObserver(&task_observer); // TaskScheduler task synchronously block on a main thread task
// (TaskScheduler::FlushForTesting() can't be used here for that reason).
// If |task_tracker_|'s |queue_empty_closure_| is not null, it means that RunLoop().RunUntilIdle();
// external code exited the RunLoop (through deprecated static methods) and
// that the MessageLoop and TaskScheduler queues might not be empty. Run the // TODO(gab): The complex piece of logic below can be boiled down to "loop
// loop again to make sure that no task remains. // again if >0 incomplete tasks" when TaskTracker is made aware of main
if (task_tracker_->ResetOnQueueEmptyClosureIfNotNull()) // thread tasks. https://crbug.com/660078.
// Then halt TaskScheduler. DisallowRunTasks() failing indicates that there
// are TaskScheduler tasks currently running, yield to them and try again
// (restarting from top as they may have posted main thread tasks).
if (!task_tracker_->DisallowRunTasks())
continue; continue;
// If tasks other than the QuitWhenIdle closure ran on the main thread, they // Once TaskScheduler is halted. Run any remaining main thread tasks (which
// may have posted TaskScheduler tasks that didn't run yet. Another // may have been posted by TaskScheduler tasks that completed between the
// iteration is required to run them. // above main thread RunUntilIdle() and TaskScheduler DisallowRunTasks()).
// // Note: this assumes that no main thread task synchronously blocks on a
// If the ExecutionMode is QUEUED and DisallowRunTasks() fails, // TaskScheduler tasks (it certainly shouldn't); this call could otherwise
// another iteration is required to make sure that RunUntilIdle() doesn't // hang.
// return while TaskScheduler tasks are still allowed to run. RunLoop().RunUntilIdle();
//
// Note: DisallowRunTasks() fails when a TaskScheduler task is running. A // The above RunUntilIdle() guarantees there are no remaining main thread
// TaskScheduler task may be running after the TaskScheduler queue became // tasks (the TaskScheduler being halted during the last RunUntilIdle() is
// empty even if no tasks ran on the main thread in these cases: // key as it prevents a task being posted to it racily with it determining
// - An undelayed task became ripe for execution. // it had no work remaining). Therefore, we're done if there is no more work
// - A task was posted from an external thread. // on TaskScheduler either (there can be TaskScheduler work remaining if
if (task_observer.task_count() == 1 && // DisallowRunTasks() preempted work and/or the last RunUntilIdle() posted
(execution_control_mode_ != ExecutionMode::QUEUED || // more TaskScheduler tasks).
task_tracker_->DisallowRunTasks())) { // Note: this last |if| couldn't be turned into a |do {} while();|
// (regardless of the use of a for-loop for |i|). A conditional loop makes
// it such that |continue;| results in checking the condition (not
// unconditionally loop again) which would be incorrect for the above logic
// as it'd then be possible for a TaskScheduler task to be running during
// the DisallowRunTasks() test, causing it to fail, but then post to the
// main thread and complete before the loop's condition is verified which
// could result in GetNumIncompleteUndelayedTasksForTesting() returning 0
// and the loop erroneously exiting with a pending task on the main thread.
if (task_tracker_->GetNumIncompleteUndelayedTasksForTesting() == 0)
break; break;
}
} }
}
ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker() // The above loop always ends with running tasks being disallowed. Re-enable
: can_run_tasks_cv_(&lock_) {} // parallel execution before returning unless in ExecutionMode::QUEUED.
if (execution_control_mode_ != ExecutionMode::QUEUED)
void ScopedTaskEnvironment::TestTaskTracker::RegisterOnQueueEmptyClosure( task_tracker_->AllowRunTasks();
OnceClosure queue_empty_closure) {
AutoLock auto_lock(lock_);
CHECK(!queue_empty_closure_);
queue_empty_closure_ = std::move(queue_empty_closure);
} }
bool ScopedTaskEnvironment::TestTaskTracker:: void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
ResetOnQueueEmptyClosureIfNotNull() { DCHECK(mock_time_task_runner_);
AutoLock auto_lock(lock_); mock_time_task_runner_->FastForwardBy(delta);
if (queue_empty_closure_) { }
queue_empty_closure_ = Closure();
return true;
}
return false; void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
DCHECK(mock_time_task_runner_);
mock_time_task_runner_->FastForwardUntilNoTasksRemain();
} }
void ScopedTaskEnvironment::TestTaskTracker::AllowRunRask() { ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker()
: can_run_tasks_cv_(&lock_) {}
void ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
AutoLock auto_lock(lock_); AutoLock auto_lock(lock_);
can_run_tasks_ = true; can_run_tasks_ = true;
can_run_tasks_cv_.Broadcast(); can_run_tasks_cv_.Broadcast();
...@@ -249,14 +242,5 @@ void ScopedTaskEnvironment::TestTaskTracker::RunOrSkipTask( ...@@ -249,14 +242,5 @@ void ScopedTaskEnvironment::TestTaskTracker::RunOrSkipTask(
} }
} }
void ScopedTaskEnvironment::TestTaskTracker::OnRunNextTaskCompleted() {
// Notify the main thread when no tasks are running or queued.
AutoLock auto_lock(lock_);
if (num_tasks_running_ == 0 && GetNumPendingUndelayedTasksForTesting() == 0 &&
queue_empty_closure_) {
std::move(queue_empty_closure_).Run();
}
}
} // namespace test } // namespace test
} // namespace base } // namespace base
...@@ -6,13 +6,15 @@ ...@@ -6,13 +6,15 @@
#define BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_ #define BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_
#include "base/macros.h" #include "base/macros.h"
#include "base/message_loop/message_loop.h" #include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/task_scheduler/lazy_task_runner.h" #include "base/task_scheduler/lazy_task_runner.h"
namespace base { namespace base {
class MessageLoop;
class TaskScheduler; class TaskScheduler;
class TestMockTimeTaskRunner;
namespace test { namespace test {
...@@ -58,6 +60,12 @@ class ScopedTaskEnvironment { ...@@ -58,6 +60,12 @@ class ScopedTaskEnvironment {
enum class MainThreadType { enum class MainThreadType {
// The main thread doesn't pump system messages. // The main thread doesn't pump system messages.
DEFAULT, DEFAULT,
// The main thread doesn't pump system messages and uses a mock clock for
// delayed tasks (controllable via FastForward*() methods).
// TODO(gab): Make this the default |main_thread_type|.
// TODO(gab): Also mock the TaskScheduler's clock simultaneously (this
// currently only mocks the main thread's clock).
MOCK_TIME,
// The main thread pumps UI messages. // The main thread pumps UI messages.
UI, UI,
// The main thread pumps asynchronous IO messages. // The main thread pumps asynchronous IO messages.
...@@ -85,18 +93,31 @@ class ScopedTaskEnvironment { ...@@ -85,18 +93,31 @@ class ScopedTaskEnvironment {
scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner(); scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner();
// Runs tasks until both the (Thread|Sequenced)TaskRunnerHandle and the // Runs tasks until both the (Thread|Sequenced)TaskRunnerHandle and the
// TaskScheduler queues are empty. // TaskScheduler's non-delayed queues are empty.
void RunUntilIdle(); void RunUntilIdle();
// Only valid for instances with a MOCK_TIME MainThreadType.
// Fast-forwards virtual time by |delta|, causing all tasks on the main thread
// with a remaining delay less than or equal to |delta| to be executed.
// |delta| must be non-negative.
void FastForwardBy(TimeDelta delta);
// Only valid for instances with a MOCK_TIME MainThreadType.
// Fast-forwards virtual time just until all tasks are executed on the main
// thread.
void FastForwardUntilNoTasksRemain();
private: private:
class TestTaskTracker; class TestTaskTracker;
const ExecutionMode execution_control_mode_; const ExecutionMode execution_control_mode_;
// Note: |message_loop_| is an implementation detail and will be replaced in // Exactly one of these will be non-null to provide the task environment on
// the future, do NOT rely on the presence of a MessageLoop beyond // the main thread. Users of this class should NOT rely on the presence of a
// (Thread|Sequenced)TaskRunnerHandle and RunLoop. // MessageLoop beyond (Thread|Sequenced)TaskRunnerHandle and RunLoop as
MessageLoop message_loop_; // the backing implementation of each MainThreadType may change over time.
const std::unique_ptr<MessageLoop> message_loop_;
const scoped_refptr<TestMockTimeTaskRunner> mock_time_task_runner_;
const TaskScheduler* task_scheduler_ = nullptr; const TaskScheduler* task_scheduler_ = nullptr;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "base/atomicops.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/synchronization/atomic_flag.h" #include "base/synchronization/atomic_flag.h"
#include "base/synchronization/waitable_event.h" #include "base/synchronization/waitable_event.h"
...@@ -18,6 +19,9 @@ namespace test { ...@@ -18,6 +19,9 @@ namespace test {
namespace { namespace {
class ScopedTaskEnvironmentTest
: public testing::TestWithParam<ScopedTaskEnvironment::MainThreadType> {};
void VerifyRunUntilIdleDidNotReturnAndSetFlag( void VerifyRunUntilIdleDidNotReturnAndSetFlag(
AtomicFlag* run_until_idle_returned, AtomicFlag* run_until_idle_returned,
AtomicFlag* task_ran) { AtomicFlag* task_ran) {
...@@ -26,10 +30,11 @@ void VerifyRunUntilIdleDidNotReturnAndSetFlag( ...@@ -26,10 +30,11 @@ void VerifyRunUntilIdleDidNotReturnAndSetFlag(
} }
void RunUntilIdleTest( void RunUntilIdleTest(
ScopedTaskEnvironment::MainThreadType main_thread_type,
ScopedTaskEnvironment::ExecutionMode execution_control_mode) { ScopedTaskEnvironment::ExecutionMode execution_control_mode) {
AtomicFlag run_until_idle_returned; AtomicFlag run_until_idle_returned;
ScopedTaskEnvironment scoped_task_environment( ScopedTaskEnvironment scoped_task_environment(main_thread_type,
ScopedTaskEnvironment::MainThreadType::DEFAULT, execution_control_mode); execution_control_mode);
AtomicFlag first_main_thread_task_ran; AtomicFlag first_main_thread_task_ran;
ThreadTaskRunnerHandle::Get()->PostTask( ThreadTaskRunnerHandle::Get()->PostTask(
...@@ -63,20 +68,19 @@ void RunUntilIdleTest( ...@@ -63,20 +68,19 @@ void RunUntilIdleTest(
} // namespace } // namespace
TEST(ScopedTaskEnvironmentTest, QueuedRunUntilIdle) { TEST_P(ScopedTaskEnvironmentTest, QueuedRunUntilIdle) {
RunUntilIdleTest(ScopedTaskEnvironment::ExecutionMode::QUEUED); RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
} }
TEST(ScopedTaskEnvironmentTest, AsyncRunUntilIdle) { TEST_P(ScopedTaskEnvironmentTest, AsyncRunUntilIdle) {
RunUntilIdleTest(ScopedTaskEnvironment::ExecutionMode::ASYNC); RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
} }
// Verify that tasks posted to an ExecutionMode::QUEUED ScopedTaskEnvironment do // Verify that tasks posted to an ExecutionMode::QUEUED ScopedTaskEnvironment do
// not run outside of RunUntilIdle(). // not run outside of RunUntilIdle().
TEST(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) { TEST_P(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) {
ScopedTaskEnvironment scoped_task_environment( ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::DEFAULT, GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
ScopedTaskEnvironment::ExecutionMode::QUEUED);
AtomicFlag run_until_idle_called; AtomicFlag run_until_idle_called;
PostTask(FROM_HERE, BindOnce( PostTask(FROM_HERE, BindOnce(
...@@ -101,10 +105,9 @@ TEST(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) { ...@@ -101,10 +105,9 @@ TEST(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) {
// Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment // Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment
// can run without a call to RunUntilIdle(). // can run without a call to RunUntilIdle().
TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) { TEST_P(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) {
ScopedTaskEnvironment scoped_task_environment( ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::DEFAULT, GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
ScopedTaskEnvironment::ExecutionMode::ASYNC);
WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL, WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED); WaitableEvent::InitialState::NOT_SIGNALED);
...@@ -117,10 +120,10 @@ TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) { ...@@ -117,10 +120,10 @@ TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) {
// Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment // Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment
// after a call to RunUntilIdle() can run without another call to // after a call to RunUntilIdle() can run without another call to
// RunUntilIdle(). // RunUntilIdle().
TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) { TEST_P(ScopedTaskEnvironmentTest,
AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) {
ScopedTaskEnvironment scoped_task_environment( ScopedTaskEnvironment scoped_task_environment(
ScopedTaskEnvironment::MainThreadType::DEFAULT, GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
ScopedTaskEnvironment::ExecutionMode::ASYNC);
scoped_task_environment.RunUntilIdle(); scoped_task_environment.RunUntilIdle();
...@@ -132,5 +135,102 @@ TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) { ...@@ -132,5 +135,102 @@ TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) {
task_ran.Wait(); task_ran.Wait();
} }
TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
ScopedTaskEnvironment scoped_task_environment(
GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
subtle::Atomic32 counter = 0;
// Should run only in MOCK_TIME environment when time is fast-forwarded.
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
Bind(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 4);
},
Unretained(&counter)),
TimeDelta::FromDays(1));
// TODO(gab): This currently doesn't run because the TaskScheduler's clock
// isn't mocked but it should be.
PostDelayedTask(FROM_HERE,
Bind(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 128);
},
Unretained(&counter)),
TimeDelta::FromDays(1));
// Same as first task, longer delays to exercise
// FastForwardUntilNoTasksRemain().
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
Bind(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 8);
},
Unretained(&counter)),
TimeDelta::FromDays(5));
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
Bind(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 16);
},
Unretained(&counter)),
TimeDelta::FromDays(7));
ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, Bind(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 1);
},
Unretained(&counter)));
PostTask(FROM_HERE, Bind(
[](subtle::Atomic32* counter) {
subtle::NoBarrier_AtomicIncrement(counter, 2);
},
Unretained(&counter)));
int expected_value = 0;
EXPECT_EQ(expected_value, counter);
// RunUntilIdle() should process non-delayed tasks only in all queues.
scoped_task_environment.RunUntilIdle();
expected_value += 1;
expected_value += 2;
EXPECT_EQ(expected_value, counter);
if (GetParam() == ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {
scoped_task_environment.FastForwardBy(TimeDelta::FromSeconds(1));
EXPECT_EQ(expected_value, counter);
scoped_task_environment.FastForwardBy(TimeDelta::FromDays(1));
expected_value += 4;
EXPECT_EQ(expected_value, counter);
scoped_task_environment.FastForwardUntilNoTasksRemain();
expected_value += 8;
expected_value += 16;
EXPECT_EQ(expected_value, counter);
}
}
INSTANTIATE_TEST_CASE_P(
MainThreadDefault,
ScopedTaskEnvironmentTest,
::testing::Values(ScopedTaskEnvironment::MainThreadType::DEFAULT));
INSTANTIATE_TEST_CASE_P(
MainThreadMockTime,
ScopedTaskEnvironmentTest,
::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
INSTANTIATE_TEST_CASE_P(
MainThreadUI,
ScopedTaskEnvironmentTest,
::testing::Values(ScopedTaskEnvironment::MainThreadType::UI));
INSTANTIATE_TEST_CASE_P(
MainThreadIO,
ScopedTaskEnvironmentTest,
::testing::Values(ScopedTaskEnvironment::MainThreadType::IO));
} // namespace test } // namespace test
} // namespace base } // namespace base
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
using ::testing::NiceMock; using ::testing::NiceMock;
namespace base {
class RunLoop;
}
namespace media { namespace media {
class FakeEncryptedMedia; class FakeEncryptedMedia;
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "remoting/host/file_proxy_wrapper.h" #include "remoting/host/file_proxy_wrapper.h"
#include <queue>
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
#include "base/containers/queue.h" #include "base/containers/queue.h"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment