| // Copyright 2017 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/scoped_task_environment.h" | 
 |  | 
 | #include <memory> | 
 |  | 
 | #include "base/atomicops.h" | 
 | #include "base/bind.h" | 
 | #include "base/bind_helpers.h" | 
 | #include "base/synchronization/atomic_flag.h" | 
 | #include "base/synchronization/waitable_event.h" | 
 | #include "base/task_scheduler/post_task.h" | 
 | #include "base/test/test_timeouts.h" | 
 | #include "base/threading/platform_thread.h" | 
 | #include "base/threading/sequence_local_storage_slot.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/time/tick_clock.h" | 
 | #include "build_config.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | #if defined(OS_POSIX) | 
 | #include <unistd.h> | 
 | #include "base/files/file_descriptor_watcher_posix.h" | 
 | #endif  // defined(OS_POSIX) | 
 |  | 
 | namespace base { | 
 | namespace test { | 
 |  | 
 | namespace { | 
 |  | 
 | class ScopedTaskEnvironmentTest | 
 |     : public testing::TestWithParam<ScopedTaskEnvironment::MainThreadType> {}; | 
 |  | 
 | void VerifyRunUntilIdleDidNotReturnAndSetFlag( | 
 |     AtomicFlag* run_until_idle_returned, | 
 |     AtomicFlag* task_ran) { | 
 |   EXPECT_FALSE(run_until_idle_returned->IsSet()); | 
 |   task_ran->Set(); | 
 | } | 
 |  | 
 | void RunUntilIdleTest( | 
 |     ScopedTaskEnvironment::MainThreadType main_thread_type, | 
 |     ScopedTaskEnvironment::ExecutionMode execution_control_mode) { | 
 |   AtomicFlag run_until_idle_returned; | 
 |   ScopedTaskEnvironment scoped_task_environment(main_thread_type, | 
 |                                                 execution_control_mode); | 
 |  | 
 |   AtomicFlag first_main_thread_task_ran; | 
 |   ThreadTaskRunnerHandle::Get()->PostTask( | 
 |       FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, | 
 |                           Unretained(&run_until_idle_returned), | 
 |                           Unretained(&first_main_thread_task_ran))); | 
 |  | 
 |   AtomicFlag first_task_scheduler_task_ran; | 
 |   PostTask(FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, | 
 |                                Unretained(&run_until_idle_returned), | 
 |                                Unretained(&first_task_scheduler_task_ran))); | 
 |  | 
 |   AtomicFlag second_task_scheduler_task_ran; | 
 |   AtomicFlag second_main_thread_task_ran; | 
 |   PostTaskAndReply(FROM_HERE, | 
 |                    BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, | 
 |                             Unretained(&run_until_idle_returned), | 
 |                             Unretained(&second_task_scheduler_task_ran)), | 
 |                    BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, | 
 |                             Unretained(&run_until_idle_returned), | 
 |                             Unretained(&second_main_thread_task_ran))); | 
 |  | 
 |   scoped_task_environment.RunUntilIdle(); | 
 |   run_until_idle_returned.Set(); | 
 |  | 
 |   EXPECT_TRUE(first_main_thread_task_ran.IsSet()); | 
 |   EXPECT_TRUE(first_task_scheduler_task_ran.IsSet()); | 
 |   EXPECT_TRUE(second_task_scheduler_task_ran.IsSet()); | 
 |   EXPECT_TRUE(second_main_thread_task_ran.IsSet()); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST_P(ScopedTaskEnvironmentTest, QueuedRunUntilIdle) { | 
 |   RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED); | 
 | } | 
 |  | 
 | TEST_P(ScopedTaskEnvironmentTest, AsyncRunUntilIdle) { | 
 |   RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC); | 
 | } | 
 |  | 
 | // Verify that tasks posted to an ExecutionMode::QUEUED ScopedTaskEnvironment do | 
 | // not run outside of RunUntilIdle(). | 
 | TEST_P(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) { | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED); | 
 |  | 
 |   AtomicFlag run_until_idle_called; | 
 |   PostTask(FROM_HERE, BindOnce( | 
 |                           [](AtomicFlag* run_until_idle_called) { | 
 |                             EXPECT_TRUE(run_until_idle_called->IsSet()); | 
 |                           }, | 
 |                           Unretained(&run_until_idle_called))); | 
 |   PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | 
 |   run_until_idle_called.Set(); | 
 |   scoped_task_environment.RunUntilIdle(); | 
 |  | 
 |   AtomicFlag other_run_until_idle_called; | 
 |   PostTask(FROM_HERE, BindOnce( | 
 |                           [](AtomicFlag* other_run_until_idle_called) { | 
 |                             EXPECT_TRUE(other_run_until_idle_called->IsSet()); | 
 |                           }, | 
 |                           Unretained(&other_run_until_idle_called))); | 
 |   PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | 
 |   other_run_until_idle_called.Set(); | 
 |   scoped_task_environment.RunUntilIdle(); | 
 | } | 
 |  | 
 | // Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment | 
 | // can run without a call to RunUntilIdle(). | 
 | TEST_P(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) { | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC); | 
 |  | 
 |   WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   PostTask(FROM_HERE, | 
 |            BindOnce([](WaitableEvent* task_ran) { task_ran->Signal(); }, | 
 |                     Unretained(&task_ran))); | 
 |   task_ran.Wait(); | 
 | } | 
 |  | 
 | // Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment | 
 | // after a call to RunUntilIdle() can run without another call to | 
 | // RunUntilIdle(). | 
 | TEST_P(ScopedTaskEnvironmentTest, | 
 |        AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) { | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC); | 
 |  | 
 |   scoped_task_environment.RunUntilIdle(); | 
 |  | 
 |   WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   PostTask(FROM_HERE, | 
 |            BindOnce([](WaitableEvent* task_ran) { task_ran->Signal(); }, | 
 |                     Unretained(&task_ran))); | 
 |   task_ran.Wait(); | 
 | } | 
 |  | 
 | TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) { | 
 |   // Use a QUEUED execution-mode environment, so that no tasks are actually | 
 |   // executed until RunUntilIdle()/FastForwardBy() are invoked. | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED); | 
 |  | 
 |   subtle::Atomic32 counter = 0; | 
 |  | 
 |   constexpr base::TimeDelta kShortTaskDelay = TimeDelta::FromDays(1); | 
 |   // 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)), | 
 |       kShortTaskDelay); | 
 |   // 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)), | 
 |                   kShortTaskDelay); | 
 |  | 
 |   constexpr base::TimeDelta kLongTaskDelay = TimeDelta::FromDays(7); | 
 |   // 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)), | 
 |       kLongTaskDelay); | 
 |  | 
 |   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))); | 
 |  | 
 |   // This expectation will fail flakily if the preceding PostTask() is executed | 
 |   // asynchronously, indicating a problem with the QUEUED execution mode. | 
 |   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) { | 
 |     // Delay inferior to the delay of the first posted task. | 
 |     constexpr base::TimeDelta kInferiorTaskDelay = TimeDelta::FromSeconds(1); | 
 |     static_assert(kInferiorTaskDelay < kShortTaskDelay, | 
 |                   "|kInferiorTaskDelay| should be " | 
 |                   "set to a value inferior to the first posted task's delay."); | 
 |     scoped_task_environment.FastForwardBy(kInferiorTaskDelay); | 
 |     EXPECT_EQ(expected_value, counter); | 
 |  | 
 |     scoped_task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay); | 
 |     expected_value += 4; | 
 |     EXPECT_EQ(expected_value, counter); | 
 |  | 
 |     scoped_task_environment.FastForwardUntilNoTasksRemain(); | 
 |     expected_value += 8; | 
 |     expected_value += 16; | 
 |     EXPECT_EQ(expected_value, counter); | 
 |   } | 
 | } | 
 |  | 
 | // Regression test for https://crbug.com/824770. | 
 | TEST_P(ScopedTaskEnvironmentTest, SupportsSequenceLocalStorageOnMainThread) { | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC); | 
 |  | 
 |   SequenceLocalStorageSlot<int> sls_slot; | 
 |   sls_slot.Set(5); | 
 |   EXPECT_EQ(5, sls_slot.Get()); | 
 | } | 
 |  | 
 | #if defined(OS_POSIX) | 
 | TEST_F(ScopedTaskEnvironmentTest, SupportsFileDescriptorWatcherOnIOMainThread) { | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       ScopedTaskEnvironment::MainThreadType::IO, | 
 |       ScopedTaskEnvironment::ExecutionMode::ASYNC); | 
 |  | 
 |   int pipe_fds_[2]; | 
 |   ASSERT_EQ(0, pipe(pipe_fds_)); | 
 |  | 
 |   RunLoop run_loop; | 
 |  | 
 |   // The write end of a newly created pipe is immediately writable. | 
 |   auto controller = FileDescriptorWatcher::WatchWritable( | 
 |       pipe_fds_[1], run_loop.QuitClosure()); | 
 |  | 
 |   // This will hang if the notification doesn't occur as expected. | 
 |   run_loop.Run(); | 
 | } | 
 | #endif  // defined(OS_POSIX) | 
 |  | 
 | // Verify that the TickClock returned by | 
 | // |ScopedTaskEnvironment::GetMockTickClock| gets updated when the | 
 | // FastForward(By|UntilNoTasksRemain) functions are called. | 
 | TEST_F(ScopedTaskEnvironmentTest, FastForwardAdvanceTickClock) { | 
 |   // Use a QUEUED execution-mode environment, so that no tasks are actually | 
 |   // executed until RunUntilIdle()/FastForwardBy() are invoked. | 
 |   ScopedTaskEnvironment scoped_task_environment( | 
 |       ScopedTaskEnvironment::MainThreadType::MOCK_TIME, | 
 |       ScopedTaskEnvironment::ExecutionMode::QUEUED); | 
 |  | 
 |   constexpr base::TimeDelta kShortTaskDelay = TimeDelta::FromDays(1); | 
 |   ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(), | 
 |                                                  kShortTaskDelay); | 
 |  | 
 |   constexpr base::TimeDelta kLongTaskDelay = TimeDelta::FromDays(7); | 
 |   ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(), | 
 |                                                  kLongTaskDelay); | 
 |  | 
 |   const base::TickClock* tick_clock = | 
 |       scoped_task_environment.GetMockTickClock(); | 
 |   base::TimeTicks tick_clock_ref = tick_clock->NowTicks(); | 
 |  | 
 |   // Make sure that |FastForwardBy| advances the clock. | 
 |   scoped_task_environment.FastForwardBy(kShortTaskDelay); | 
 |   EXPECT_EQ(kShortTaskDelay, tick_clock->NowTicks() - tick_clock_ref); | 
 |  | 
 |   // Make sure that |FastForwardUntilNoTasksRemain| advances the clock. | 
 |   scoped_task_environment.FastForwardUntilNoTasksRemain(); | 
 |   EXPECT_EQ(kLongTaskDelay, tick_clock->NowTicks() - tick_clock_ref); | 
 |  | 
 |   // Fast-forwarding to a time at which there's no tasks should also advance the | 
 |   // clock. | 
 |   scoped_task_environment.FastForwardBy(kLongTaskDelay); | 
 |   EXPECT_EQ(kLongTaskDelay * 2, tick_clock->NowTicks() - tick_clock_ref); | 
 | } | 
 |  | 
 | 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 base |