| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/test/task_environment.h" |
| |
| #include <atomic> |
| #include <memory> |
| |
| #include "base/atomicops.h" |
| #include "base/cancelable_callback.h" |
| #include "base/check.h" |
| #include "base/debug/debugger.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/synchronization/atomic_flag.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/sequence_manager/time_domain.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/mock_log.h" |
| #include "base/test/scoped_run_loop_timeout.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/test/test_waitable_event.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/threading/sequence_local_storage_slot.h" |
| #include "base/threading/thread.h" |
| #include "base/time/clock.h" |
| #include "base/time/default_clock.h" |
| #include "base/time/tick_clock.h" |
| #include "base/time/time.h" |
| #include "base/win/com_init_util.h" |
| #include "build/build_config.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest-spi.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(IS_POSIX) |
| #include <unistd.h> |
| |
| #include "base/files/file_descriptor_watcher_posix.h" |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/win/scoped_com_initializer.h" |
| #endif |
| |
| namespace base { |
| namespace test { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::HasSubstr; |
| using ::testing::IsNull; |
| using ::testing::Not; |
| using ::testing::Return; |
| |
| class TaskEnvironmentTest : public testing::Test {}; |
| |
| void VerifyRunUntilIdleDidNotReturnAndSetFlag( |
| AtomicFlag* run_until_idle_returned, |
| AtomicFlag* task_ran) { |
| EXPECT_FALSE(run_until_idle_returned->IsSet()); |
| task_ran->Set(); |
| } |
| |
| void RunUntilIdleTest( |
| TaskEnvironment::ThreadPoolExecutionMode thread_pool_execution_mode) { |
| AtomicFlag run_until_idle_returned; |
| TaskEnvironment task_environment(thread_pool_execution_mode); |
| |
| AtomicFlag first_main_thread_task_ran; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, |
| Unretained(&run_until_idle_returned), |
| Unretained(&first_main_thread_task_ran))); |
| |
| AtomicFlag first_thread_pool_task_ran; |
| ThreadPool::PostTask(FROM_HERE, |
| BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, |
| Unretained(&run_until_idle_returned), |
| Unretained(&first_thread_pool_task_ran))); |
| |
| AtomicFlag second_thread_pool_task_ran; |
| AtomicFlag second_main_thread_task_ran; |
| ThreadPool::PostTaskAndReply( |
| FROM_HERE, |
| BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, |
| Unretained(&run_until_idle_returned), |
| Unretained(&second_thread_pool_task_ran)), |
| BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag, |
| Unretained(&run_until_idle_returned), |
| Unretained(&second_main_thread_task_ran))); |
| |
| task_environment.RunUntilIdle(); |
| run_until_idle_returned.Set(); |
| |
| EXPECT_TRUE(first_main_thread_task_ran.IsSet()); |
| EXPECT_TRUE(first_thread_pool_task_ran.IsSet()); |
| EXPECT_TRUE(second_thread_pool_task_ran.IsSet()); |
| EXPECT_TRUE(second_main_thread_task_ran.IsSet()); |
| } |
| |
| } // namespace |
| |
| TEST_F(TaskEnvironmentTest, QueuedRunUntilIdle) { |
| RunUntilIdleTest(TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| } |
| |
| TEST_F(TaskEnvironmentTest, AsyncRunUntilIdle) { |
| RunUntilIdleTest(TaskEnvironment::ThreadPoolExecutionMode::ASYNC); |
| } |
| |
| // Verify that tasks posted to an ThreadPoolExecutionMode::QUEUED |
| // TaskEnvironment do not run outside of RunUntilIdle(). |
| TEST_F(TaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| AtomicFlag run_until_idle_called; |
| ThreadPool::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(); |
| task_environment.RunUntilIdle(); |
| |
| AtomicFlag other_run_until_idle_called; |
| ThreadPool::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(); |
| task_environment.RunUntilIdle(); |
| } |
| |
| // Verify that a task posted to an ThreadPoolExecutionMode::ASYNC |
| // TaskEnvironment can run without a call to RunUntilIdle(). |
| TEST_F(TaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::ThreadPoolExecutionMode::ASYNC); |
| |
| WaitableEvent task_ran; |
| ThreadPool::PostTask(FROM_HERE, |
| BindOnce(&WaitableEvent::Signal, Unretained(&task_ran))); |
| task_ran.Wait(); |
| } |
| |
| // Verify that a task posted to an ThreadPoolExecutionMode::ASYNC |
| // TaskEnvironment after a call to RunUntilIdle() can run without another |
| // call to RunUntilIdle(). |
| TEST_F(TaskEnvironmentTest, AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::ThreadPoolExecutionMode::ASYNC); |
| |
| task_environment.RunUntilIdle(); |
| |
| WaitableEvent task_ran; |
| ThreadPool::PostTask(FROM_HERE, |
| BindOnce(&WaitableEvent::Signal, Unretained(&task_ran))); |
| task_ran.Wait(); |
| } |
| |
| void DelayedTasksTest(TaskEnvironment::TimeSource time_source) { |
| // Use a QUEUED execution-mode environment, so that no tasks are actually |
| // executed until RunUntilIdle()/FastForwardBy() are invoked. |
| TaskEnvironment task_environment( |
| time_source, TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| subtle::Atomic32 counter = 0; |
| |
| constexpr base::TimeDelta kShortTaskDelay = Days(1); |
| // Should run only in MOCK_TIME environment when time is fast-forwarded. |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, 4); |
| }, |
| Unretained(&counter)), |
| kShortTaskDelay); |
| ThreadPool::PostDelayedTask(FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, |
| 128); |
| }, |
| Unretained(&counter)), |
| kShortTaskDelay); |
| |
| constexpr base::TimeDelta kLongTaskDelay = Days(7); |
| // Same as first task, longer delays to exercise |
| // FastForwardUntilNoTasksRemain(). |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, 8); |
| }, |
| Unretained(&counter)), |
| Days(5)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, 16); |
| }, |
| Unretained(&counter)), |
| kLongTaskDelay); |
| ThreadPool::PostDelayedTask(FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, |
| 256); |
| }, |
| Unretained(&counter)), |
| kLongTaskDelay * 2); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, 512); |
| }, |
| Unretained(&counter)), |
| kLongTaskDelay * 3); |
| ThreadPool::PostDelayedTask(FROM_HERE, |
| BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, |
| 1024); |
| }, |
| Unretained(&counter)), |
| kLongTaskDelay * 4); |
| |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindOnce( |
| [](subtle::Atomic32* counter) { |
| subtle::NoBarrier_AtomicIncrement(counter, 1); |
| }, |
| Unretained(&counter))); |
| ThreadPool::PostTask(FROM_HERE, BindOnce( |
| [](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. |
| task_environment.RunUntilIdle(); |
| expected_value += 1; |
| expected_value += 2; |
| EXPECT_EQ(expected_value, counter); |
| |
| if (time_source == TaskEnvironment::TimeSource::MOCK_TIME) { |
| const TimeTicks start_time = task_environment.NowTicks(); |
| |
| // Delay inferior to the delay of the first posted task. |
| constexpr base::TimeDelta kInferiorTaskDelay = Seconds(1); |
| static_assert(kInferiorTaskDelay < kShortTaskDelay, |
| "|kInferiorTaskDelay| should be " |
| "set to a value inferior to the first posted task's delay."); |
| task_environment.FastForwardBy(kInferiorTaskDelay); |
| EXPECT_EQ(expected_value, counter); |
| // Time advances to cap even if there was no task at cap. |
| EXPECT_EQ(task_environment.NowTicks() - start_time, kInferiorTaskDelay); |
| |
| task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay); |
| expected_value += 4; |
| expected_value += 128; |
| EXPECT_EQ(expected_value, counter); |
| EXPECT_EQ(task_environment.NowTicks() - start_time, kShortTaskDelay); |
| |
| task_environment.FastForwardUntilNoTasksRemain(); |
| expected_value += 8; |
| expected_value += 16; |
| expected_value += 256; |
| expected_value += 512; |
| expected_value += 1024; |
| EXPECT_EQ(expected_value, counter); |
| EXPECT_EQ(task_environment.NowTicks() - start_time, kLongTaskDelay * 4); |
| } |
| } |
| |
| TEST_F(TaskEnvironmentTest, DelayedTasksUnderSystemTime) { |
| DelayedTasksTest(TaskEnvironment::TimeSource::SYSTEM_TIME); |
| } |
| |
| TEST_F(TaskEnvironmentTest, DelayedTasksUnderMockTime) { |
| DelayedTasksTest(TaskEnvironment::TimeSource::MOCK_TIME); |
| } |
| |
| // Regression test for https://crbug.com/824770. |
| void SupportsSequenceLocalStorageOnMainThreadTest( |
| TaskEnvironment::TimeSource time_source) { |
| TaskEnvironment task_environment( |
| time_source, TaskEnvironment::ThreadPoolExecutionMode::ASYNC); |
| |
| SequenceLocalStorageSlot<int> sls_slot; |
| sls_slot.emplace(5); |
| EXPECT_EQ(5, *sls_slot); |
| } |
| |
| TEST_F(TaskEnvironmentTest, SupportsSequenceLocalStorageOnMainThread) { |
| SupportsSequenceLocalStorageOnMainThreadTest( |
| TaskEnvironment::TimeSource::SYSTEM_TIME); |
| } |
| |
| TEST_F(TaskEnvironmentTest, |
| SupportsSequenceLocalStorageOnMainThreadWithMockTime) { |
| SupportsSequenceLocalStorageOnMainThreadTest( |
| TaskEnvironment::TimeSource::MOCK_TIME); |
| } |
| |
| // Verify that the right MessagePump is instantiated under each MainThreadType. |
| // This avoids having to run all other TaskEnvironmentTests in every |
| // MainThreadType which is redundant (message loop and message pump tests |
| // otherwise cover the advanced functionality provided by UI/IO pumps). |
| TEST_F(TaskEnvironmentTest, MainThreadType) { |
| // Uses CurrentThread as a convenience accessor but could be replaced by |
| // different accessors when we get rid of CurrentThread. |
| EXPECT_FALSE(CurrentThread::IsSet()); |
| EXPECT_FALSE(CurrentUIThread::IsSet()); |
| EXPECT_FALSE(CurrentIOThread::IsSet()); |
| { |
| TaskEnvironment task_environment; |
| EXPECT_TRUE(CurrentThread::IsSet()); |
| EXPECT_FALSE(CurrentUIThread::IsSet()); |
| EXPECT_FALSE(CurrentIOThread::IsSet()); |
| } |
| { |
| TaskEnvironment task_environment(TaskEnvironment::MainThreadType::UI); |
| EXPECT_TRUE(CurrentThread::IsSet()); |
| EXPECT_TRUE(CurrentUIThread::IsSet()); |
| EXPECT_FALSE(CurrentIOThread::IsSet()); |
| } |
| { |
| TaskEnvironment task_environment(TaskEnvironment::MainThreadType::IO); |
| EXPECT_TRUE(CurrentThread::IsSet()); |
| EXPECT_FALSE(CurrentUIThread::IsSet()); |
| EXPECT_TRUE(CurrentIOThread::IsSet()); |
| } |
| EXPECT_FALSE(CurrentThread::IsSet()); |
| EXPECT_FALSE(CurrentUIThread::IsSet()); |
| EXPECT_FALSE(CurrentIOThread::IsSet()); |
| } |
| |
| #if !defined(STARBOARD) |
| #if BUILDFLAG(IS_POSIX) |
| TEST_F(TaskEnvironmentTest, SupportsFileDescriptorWatcherOnIOMainThread) { |
| TaskEnvironment task_environment(TaskEnvironment::MainThreadType::IO); |
| |
| 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(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, |
| SupportsFileDescriptorWatcherOnIOMockTimeMainThread) { |
| TaskEnvironment task_environment(TaskEnvironment::MainThreadType::IO, |
| TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| int pipe_fds_[2]; |
| ASSERT_EQ(0, pipe(pipe_fds_)); |
| |
| RunLoop run_loop; |
| |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| int64_t x = 1; |
| auto ret = write(pipe_fds_[1], &x, sizeof(x)); |
| ASSERT_EQ(static_cast<size_t>(ret), sizeof(x)); |
| }), |
| Hours(1)); |
| |
| auto controller = FileDescriptorWatcher::WatchReadable( |
| pipe_fds_[0], run_loop.QuitClosure()); |
| |
| // This will hang if the notification doesn't occur as expected (Run() should |
| // fast-forward-time when idle). |
| run_loop.Run(); |
| } |
| #endif // BUILDFLAG(IS_POSIX) |
| #endif // !defined(STARBOARD) |
| |
| TEST_F(TaskEnvironmentTest, MockTimeStartsWithWholeMilliseconds) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| const TickClock* mock_tick_clock = task_environment.GetMockTickClock(); |
| const Clock* mock_clock = task_environment.GetMockClock(); |
| EXPECT_TRUE( |
| (mock_tick_clock->NowTicks().since_origin() % Milliseconds(1)).is_zero()); |
| // The Windows epoch has no submillisecond components, so any submillisecond |
| // components in `Time::Now()` will appear in their difference. |
| EXPECT_TRUE((mock_clock->Now().since_origin() % Milliseconds(1)).is_zero()); |
| EXPECT_TRUE((Time::Now().since_origin() % Milliseconds(1)).is_zero()); |
| EXPECT_TRUE((TimeTicks::Now().since_origin() % Milliseconds(1)).is_zero()); |
| } |
| |
| // Verify that the TickClock returned by |
| // |TaskEnvironment::GetMockTickClock| gets updated when the |
| // FastForward(By|UntilNoTasksRemain) functions are called. |
| TEST_F(TaskEnvironmentTest, FastForwardAdvancesTickClock) { |
| // Use a QUEUED execution-mode environment, so that no tasks are actually |
| // executed until RunUntilIdle()/FastForwardBy() are invoked. |
| TaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME, |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| constexpr base::TimeDelta kShortTaskDelay = Days(1); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, base::DoNothing(), kShortTaskDelay); |
| |
| constexpr base::TimeDelta kLongTaskDelay = Days(7); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, base::DoNothing(), kLongTaskDelay); |
| |
| const base::TickClock* tick_clock = task_environment.GetMockTickClock(); |
| base::TimeTicks tick_clock_ref = tick_clock->NowTicks(); |
| |
| // Make sure that |FastForwardBy| advances the clock. |
| task_environment.FastForwardBy(kShortTaskDelay); |
| EXPECT_EQ(kShortTaskDelay, tick_clock->NowTicks() - tick_clock_ref); |
| |
| // Make sure that |FastForwardUntilNoTasksRemain| advances the clock. |
| 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. |
| task_environment.FastForwardBy(kLongTaskDelay); |
| EXPECT_EQ(kLongTaskDelay * 2, tick_clock->NowTicks() - tick_clock_ref); |
| } |
| |
| TEST_F(TaskEnvironmentTest, FastForwardAdvancesMockClock) { |
| constexpr base::TimeDelta kDelay = Seconds(42); |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const Clock* clock = task_environment.GetMockClock(); |
| const Time start_time = clock->Now(); |
| task_environment.FastForwardBy(kDelay); |
| |
| EXPECT_EQ(start_time + kDelay, clock->Now()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, FastForwardAdvancesTime) { |
| constexpr base::TimeDelta kDelay = Seconds(42); |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const Time start_time = base::Time::Now(); |
| task_environment.FastForwardBy(kDelay); |
| EXPECT_EQ(start_time + kDelay, base::Time::Now()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, FastForwardAdvancesTimeTicks) { |
| constexpr base::TimeDelta kDelay = Seconds(42); |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const TimeTicks start_time = base::TimeTicks::Now(); |
| task_environment.FastForwardBy(kDelay); |
| EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, AdvanceClockAdvancesTickClock) { |
| constexpr base::TimeDelta kDelay = Seconds(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, AdvanceClockAdvancesMockClock) { |
| constexpr base::TimeDelta kDelay = Seconds(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, AdvanceClockAdvancesTime) { |
| constexpr base::TimeDelta kDelay = Seconds(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, AdvanceClockAdvancesTimeTicks) { |
| constexpr base::TimeDelta kDelay = Seconds(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 = Days(1); |
| SingleThreadTaskRunner::GetCurrentDefault()->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()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, AdvanceClockSchedulesRipeDelayedTasks) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| bool ran = false; |
| |
| constexpr base::TimeDelta kTaskDelay = Days(1); |
| ThreadPool::PostDelayedTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { ran = true; }), kTaskDelay); |
| |
| task_environment.AdvanceClock(kTaskDelay); |
| EXPECT_FALSE(ran); |
| task_environment.RunUntilIdle(); |
| EXPECT_TRUE(ran); |
| } |
| |
| // 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. |
| TEST_F(TaskEnvironmentTest, FastForwardOnlyAdvancesWhenIdle) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const TimeTicks start_time = base::TimeTicks::Now(); |
| |
| constexpr base::TimeDelta kDelay = Seconds(42); |
| constexpr base::TimeDelta kFastForwardUntil = Seconds(100); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting( |
| [&]() { EXPECT_EQ(start_time, base::TimeTicks::Now()); })); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| EXPECT_EQ(start_time + kDelay, base::TimeTicks::Now()); |
| }), |
| kDelay); |
| task_environment.FastForwardBy(kFastForwardUntil); |
| EXPECT_EQ(start_time + kFastForwardUntil, base::TimeTicks::Now()); |
| } |
| |
| // FastForwardBy(0) should be equivalent of RunUntilIdle(). |
| TEST_F(TaskEnvironmentTest, FastForwardZero) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| std::atomic_int run_count{0}; |
| |
| for (int i = 0; i < 1000; ++i) { |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| run_count.fetch_add(1, std::memory_order_relaxed); |
| })); |
| ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&]() { |
| run_count.fetch_add(1, std::memory_order_relaxed); |
| })); |
| } |
| |
| task_environment.FastForwardBy(base::TimeDelta()); |
| |
| EXPECT_EQ(2000, run_count.load(std::memory_order_relaxed)); |
| } |
| |
| TEST_F(TaskEnvironmentTest, NestedFastForwardBy) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| constexpr TimeDelta kDelayPerTask = Milliseconds(1); |
| const TimeTicks start_time = task_environment.NowTicks(); |
| |
| int max_nesting_level = 0; |
| |
| RepeatingClosure post_fast_forwarding_task; |
| post_fast_forwarding_task = BindLambdaForTesting([&]() { |
| if (max_nesting_level < 5) { |
| ++max_nesting_level; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_fast_forwarding_task, kDelayPerTask); |
| task_environment.FastForwardBy(kDelayPerTask); |
| } |
| }); |
| post_fast_forwarding_task.Run(); |
| |
| EXPECT_EQ(max_nesting_level, 5); |
| EXPECT_EQ(task_environment.NowTicks(), start_time + kDelayPerTask * 5); |
| } |
| |
| TEST_F(TaskEnvironmentTest, NestedRunInFastForwardBy) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| constexpr TimeDelta kDelayPerTask = Milliseconds(1); |
| const TimeTicks start_time = task_environment.NowTicks(); |
| |
| std::vector<RunLoop*> run_loops; |
| |
| RepeatingClosure post_and_runloop_task; |
| post_and_runloop_task = BindLambdaForTesting([&]() { |
| // Run 4 nested run loops on top of the initial FastForwardBy(). |
| if (run_loops.size() < 4U) { |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_and_runloop_task, kDelayPerTask); |
| |
| RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed); |
| run_loops.push_back(&run_loop); |
| run_loop.Run(); |
| } else { |
| for (RunLoop* run_loop : run_loops) { |
| run_loop->Quit(); |
| } |
| } |
| }); |
| |
| // Initial task is FastForwardBy(). |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_and_runloop_task, kDelayPerTask); |
| task_environment.FastForwardBy(kDelayPerTask); |
| |
| EXPECT_EQ(run_loops.size(), 4U); |
| EXPECT_EQ(task_environment.NowTicks(), start_time + kDelayPerTask * 5); |
| } |
| |
| TEST_F(TaskEnvironmentTest, |
| CrossThreadImmediateTaskPostingDoesntAffectMockTime) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| int count = 0; |
| |
| // Post tasks delayd between 0 and 999 seconds. |
| for (int i = 0; i < 1000; ++i) { |
| const TimeDelta delay = Seconds(i); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce( |
| [](TimeTicks expected_run_time, int* count) { |
| EXPECT_EQ(expected_run_time, TimeTicks::Now()); |
| ++*count; |
| }, |
| TimeTicks::Now() + delay, &count), |
| delay); |
| } |
| |
| // Having a bunch of tasks running in parallel and replying to the main thread |
| // shouldn't affect the rest of this test. Wait for the first task to run |
| // before proceeding with the test to increase the likelihood of exercising |
| // races. |
| base::WaitableEvent first_reply_is_incoming; |
| for (int i = 0; i < 1000; ++i) { |
| ThreadPool::PostTaskAndReply( |
| FROM_HERE, |
| BindOnce(&WaitableEvent::Signal, Unretained(&first_reply_is_incoming)), |
| DoNothing()); |
| } |
| first_reply_is_incoming.Wait(); |
| |
| task_environment.FastForwardBy(Seconds(1000)); |
| |
| // If this test flakes it's because there's an error with MockTimeDomain. |
| EXPECT_EQ(count, 1000); |
| |
| // Flush any remaining asynchronous tasks with Unretained() state. |
| task_environment.RunUntilIdle(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, MultiThreadedMockTime) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| constexpr TimeDelta kOneMs = Milliseconds(1); |
| const TimeTicks start_time = task_environment.NowTicks(); |
| const TimeTicks end_time = start_time + Milliseconds(1'000); |
| |
| // Last TimeTicks::Now() seen from either contexts. |
| TimeTicks last_main_thread_ticks = start_time; |
| TimeTicks last_thread_pool_ticks = start_time; |
| |
| RepeatingClosure post_main_thread_delayed_task; |
| post_main_thread_delayed_task = BindLambdaForTesting([&]() { |
| // Expect that time only moves forward. |
| EXPECT_GE(task_environment.NowTicks(), last_main_thread_ticks); |
| |
| // Post four tasks to exercise the system some more but only if this is the |
| // first task at its runtime (otherwise we end up with 4^10'000 tasks by |
| // the end!). |
| if (last_main_thread_ticks < task_environment.NowTicks() && |
| task_environment.NowTicks() < end_time) { |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_main_thread_delayed_task, kOneMs); |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_main_thread_delayed_task, kOneMs); |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_main_thread_delayed_task, kOneMs); |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_main_thread_delayed_task, kOneMs); |
| } |
| |
| last_main_thread_ticks = task_environment.NowTicks(); |
| }); |
| |
| RepeatingClosure post_thread_pool_delayed_task; |
| post_thread_pool_delayed_task = BindLambdaForTesting([&]() { |
| // Expect that time only moves forward. |
| EXPECT_GE(task_environment.NowTicks(), last_thread_pool_ticks); |
| |
| // Post four tasks to exercise the system some more but only if this is the |
| // first task at its runtime (otherwise we end up with 4^10'000 tasks by |
| // the end!). |
| if (last_thread_pool_ticks < task_environment.NowTicks() && |
| task_environment.NowTicks() < end_time) { |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_thread_pool_delayed_task, kOneMs); |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_thread_pool_delayed_task, kOneMs); |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_thread_pool_delayed_task, kOneMs); |
| SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_thread_pool_delayed_task, kOneMs); |
| |
| EXPECT_LT(task_environment.NowTicks(), end_time); |
| } |
| |
| last_thread_pool_ticks = task_environment.NowTicks(); |
| }); |
| |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, post_main_thread_delayed_task, kOneMs); |
| ThreadPool::CreateSequencedTaskRunner({})->PostDelayedTask( |
| FROM_HERE, post_thread_pool_delayed_task, kOneMs); |
| |
| task_environment.FastForwardUntilNoTasksRemain(); |
| |
| EXPECT_EQ(last_main_thread_ticks, end_time); |
| EXPECT_EQ(last_thread_pool_ticks, end_time); |
| EXPECT_EQ(task_environment.NowTicks(), end_time); |
| } |
| |
| // This test ensures the implementation of FastForwardBy() doesn't fast-forward |
| // beyond the cap it reaches idle with pending delayed tasks further ahead on |
| // the main thread. |
| TEST_F(TaskEnvironmentTest, MultiThreadedFastForwardBy) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const TimeTicks start_time = task_environment.NowTicks(); |
| |
| // The 1s delayed task in the pool should run but not the 5s delayed task on |
| // the main thread and fast-forward by should be capped at +2s. |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE), Seconds(5)); |
| ThreadPool::PostDelayedTask(FROM_HERE, {}, MakeExpectedRunClosure(FROM_HERE), |
| Seconds(1)); |
| task_environment.FastForwardBy(Seconds(2)); |
| |
| EXPECT_EQ(task_environment.NowTicks(), start_time + Seconds(2)); |
| } |
| |
| // Verify that ThreadPoolExecutionMode::QUEUED doesn't prevent running tasks and |
| // advancing time on the main thread. |
| TEST_F(TaskEnvironmentTest, MultiThreadedMockTimeAndThreadPoolQueuedMode) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME, |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| // Atomic because it's updated from concurrent tasks in the ThreadPool |
| // (could use std::memory_order_releaxed on all accesses but keeping implicit |
| // operators because the test reads better that way). |
| std::atomic_int count = 0; |
| const TimeTicks start_time = task_environment.NowTicks(); |
| |
| RunLoop run_loop; |
| |
| // Neither of these should run automatically per |
| // ThreadPoolExecutionMode::QUEUED. |
| ThreadPool::PostTask(FROM_HERE, |
| BindLambdaForTesting([&]() { count += 128; })); |
| ThreadPool::PostDelayedTask( |
| FROM_HERE, {}, BindLambdaForTesting([&]() { count += 256; }), Seconds(5)); |
| |
| // Time should auto-advance to +500s in RunLoop::Run() without having to run |
| // the above forcefully QUEUED tasks. |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { count += 1; })); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| count += 2; |
| run_loop.Quit(); |
| }), |
| Seconds(500)); |
| |
| int expected_value = 0; |
| EXPECT_EQ(expected_value, count); |
| run_loop.Run(); |
| expected_value += 1; |
| expected_value += 2; |
| EXPECT_EQ(expected_value, count); |
| EXPECT_EQ(task_environment.NowTicks() - start_time, Seconds(500)); |
| |
| // Fast-forward through all remaining tasks, this should unblock QUEUED tasks |
| // in the thread pool but shouldn't need to advance time to process them. |
| task_environment.FastForwardUntilNoTasksRemain(); |
| expected_value += 128; |
| expected_value += 256; |
| EXPECT_EQ(expected_value, count); |
| EXPECT_EQ(task_environment.NowTicks() - start_time, Seconds(500)); |
| |
| // Test advancing time to a QUEUED task in the future. |
| ThreadPool::PostDelayedTask( |
| FROM_HERE, BindLambdaForTesting([&]() { count += 512; }), Seconds(5)); |
| task_environment.FastForwardBy(Seconds(7)); |
| expected_value += 512; |
| EXPECT_EQ(expected_value, count); |
| EXPECT_EQ(task_environment.NowTicks() - start_time, Seconds(507)); |
| |
| // Confirm that QUEUED mode is still active after the above fast forwarding |
| // (only the main thread task should run from RunLoop). |
| ThreadPool::PostTask(FROM_HERE, |
| BindLambdaForTesting([&]() { count += 1024; })); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { count += 2048; })); |
| PlatformThread::Sleep(Milliseconds(1)); |
| RunLoop().RunUntilIdle(); |
| expected_value += 2048; |
| EXPECT_EQ(expected_value, count); |
| EXPECT_EQ(task_environment.NowTicks() - start_time, Seconds(507)); |
| |
| // Run the remaining task to avoid use-after-free on |count| from |
| // ~TaskEnvironment(). |
| task_environment.RunUntilIdle(); |
| expected_value += 1024; |
| EXPECT_EQ(expected_value, count); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // Regression test to ensure that TaskEnvironment enables the MTA in the |
| // thread pool (so that the test environment matches that of the browser process |
| // and com_init_util.h's assertions are happy in unit tests). |
| TEST_F(TaskEnvironmentTest, ThreadPoolPoolAllowsMTA) { |
| TaskEnvironment task_environment; |
| ThreadPool::PostTask(FROM_HERE, BindOnce(&win::AssertComApartmentType, |
| win::ComApartmentType::MTA)); |
| task_environment.RunUntilIdle(); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| TEST_F(TaskEnvironmentTest, SetsDefaultRunTimeout) { |
| const RunLoop::RunLoopTimeout* old_run_timeout = |
| ScopedRunLoopTimeout::GetTimeoutForCurrentThread(); |
| |
| { |
| TaskEnvironment task_environment; |
| |
| // TaskEnvironment should set a default Run() timeout that fails the |
| // calling test (before test_launcher_timeout()). |
| |
| const RunLoop::RunLoopTimeout* run_timeout = |
| ScopedRunLoopTimeout::GetTimeoutForCurrentThread(); |
| EXPECT_NE(run_timeout, old_run_timeout); |
| EXPECT_TRUE(run_timeout); |
| if (!debug::BeingDebugged()) { |
| EXPECT_LT(run_timeout->timeout, TestTimeouts::test_launcher_timeout()); |
| } |
| static auto& static_on_timeout_cb = run_timeout->on_timeout; |
| #if defined(__clang__) && defined(_MSC_VER) |
| EXPECT_FATAL_FAILURE( |
| static_on_timeout_cb.Run(FROM_HERE), |
| "RunLoop::Run() timed out. Timeout set at " |
| // We don't test the line number but it would be present. |
| "TaskEnvironment@base\\test\\task_environment.cc:"); |
| #else |
| EXPECT_FATAL_FAILURE( |
| static_on_timeout_cb.Run(FROM_HERE), |
| "RunLoop::Run() timed out. Timeout set at " |
| // We don't test the line number but it would be present. |
| "TaskEnvironment@base/test/task_environment.cc:"); |
| #endif |
| } |
| |
| EXPECT_EQ(ScopedRunLoopTimeout::GetTimeoutForCurrentThread(), |
| old_run_timeout); |
| } |
| |
| TEST_F(TaskEnvironmentTest, DescribeCurrentTasksHasPendingMainThreadTasks) { |
| TaskEnvironment task_environment; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, DoNothing()); |
| |
| test::MockLog mock_log; |
| mock_log.StartCapturingLogs(); |
| |
| // Thread pool tasks (none here) are logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("ThreadPool currently running tasks"))) |
| .WillOnce(Return(true)); |
| // The pending task posted above to the main thread is logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("task_environment_unittest.cc"))) |
| .WillOnce(Return(true)); |
| task_environment.DescribeCurrentTasks(); |
| |
| task_environment.RunUntilIdle(); |
| |
| // Thread pool tasks (none here) are logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("ThreadPool currently running tasks"))) |
| .WillOnce(Return(true)); |
| // Pending tasks (none left) are logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("\"immediate_work_queue_size\":0"))) |
| .WillOnce(Return(true)); |
| task_environment.DescribeCurrentTasks(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, DescribeCurrentTasksHasThreadPoolTasks) { |
| TaskEnvironment task_environment; |
| |
| // Let the test block until the thread pool task is running. |
| base::WaitableEvent wait_for_thread_pool_task_start; |
| // Let the thread pool task block until the test has a chance to see it |
| // running. |
| base::WaitableEvent block_thread_pool_task; |
| |
| scoped_refptr<SequencedTaskRunner> thread_pool_task_runner = |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {WithBaseSyncPrimitives(), TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); |
| thread_pool_task_runner->PostTask(FROM_HERE, BindLambdaForTesting([&]() { |
| // The test waits until this task is |
| // running. |
| wait_for_thread_pool_task_start.Signal(); |
| // Wait until the test is done with this |
| // task. |
| block_thread_pool_task.Wait(); |
| })); |
| wait_for_thread_pool_task_start.Wait(); |
| |
| test::MockLog mock_log; |
| mock_log.StartCapturingLogs(); |
| |
| // The pending task posted above is logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("task_environment_unittest.cc"))) |
| .WillOnce(Return(true)); |
| // Pending tasks (none here) are logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("\"immediate_work_queue_size\":0"))) |
| .WillOnce(Return(true)); |
| task_environment.DescribeCurrentTasks(); |
| |
| block_thread_pool_task.Signal(); |
| // Wait for the thread pool task to complete. |
| task_environment.RunUntilIdle(); |
| |
| // The current thread pool tasks (none left) are logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| Not(HasSubstr("task_environment_unittest.cc")))) |
| .WillOnce(Return(true)); |
| // Main thread pending tasks (none here) are logged. |
| EXPECT_CALL(mock_log, Log(::logging::LOG_INFO, _, _, _, |
| HasSubstr("\"immediate_work_queue_size\":0"))) |
| .WillOnce(Return(true)); |
| task_environment.DescribeCurrentTasks(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, Basic) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME, |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| int counter = 0; |
| |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| BindOnce([](int* counter) { *counter += 1; }, Unretained(&counter))); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| BindOnce([](int* counter) { *counter += 32; }, Unretained(&counter))); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce([](int* counter) { *counter += 256; }, Unretained(&counter)), |
| Seconds(3)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce([](int* counter) { *counter += 64; }, Unretained(&counter)), |
| Seconds(1)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce([](int* counter) { *counter += 1024; }, Unretained(&counter)), |
| Minutes(20)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| BindOnce([](int* counter) { *counter += 4096; }, Unretained(&counter)), |
| Days(20)); |
| |
| int expected_value = 0; |
| EXPECT_EQ(expected_value, counter); |
| task_environment.RunUntilIdle(); |
| expected_value += 1; |
| expected_value += 32; |
| EXPECT_EQ(expected_value, counter); |
| |
| task_environment.RunUntilIdle(); |
| EXPECT_EQ(expected_value, counter); |
| |
| task_environment.FastForwardBy(Seconds(1)); |
| expected_value += 64; |
| EXPECT_EQ(expected_value, counter); |
| |
| task_environment.FastForwardBy(Seconds(5)); |
| expected_value += 256; |
| EXPECT_EQ(expected_value, counter); |
| |
| task_environment.FastForwardUntilNoTasksRemain(); |
| expected_value += 1024; |
| expected_value += 4096; |
| EXPECT_EQ(expected_value, counter); |
| } |
| |
| TEST_F(TaskEnvironmentTest, RunLoopDriveable) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME, |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| int counter = 0; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce([](int* counter) { *counter += 1; }, |
| Unretained(&counter))); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce([](int* counter) { *counter += 32; }, |
| Unretained(&counter))); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce([](int* counter) { *counter += 256; }, |
| Unretained(&counter)), |
| Seconds(3)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce([](int* counter) { *counter += 64; }, |
| Unretained(&counter)), |
| Seconds(1)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce([](int* counter) { *counter += 1024; }, |
| Unretained(&counter)), |
| Minutes(20)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce([](int* counter) { *counter += 4096; }, |
| Unretained(&counter)), |
| Days(20)); |
| |
| int expected_value = 0; |
| EXPECT_EQ(expected_value, counter); |
| RunLoop().RunUntilIdle(); |
| expected_value += 1; |
| expected_value += 32; |
| EXPECT_EQ(expected_value, counter); |
| |
| RunLoop().RunUntilIdle(); |
| EXPECT_EQ(expected_value, counter); |
| |
| { |
| RunLoop run_loop; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), Seconds(1)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce([](int* counter) { *counter += 8192; }, |
| Unretained(&counter)), |
| Seconds(1)); |
| |
| // The QuitClosure() should be ordered between the 64 and the 8192 |
| // increments and should preempt the latter. |
| run_loop.Run(); |
| expected_value += 64; |
| EXPECT_EQ(expected_value, counter); |
| |
| // Running until idle should process the 8192 increment whose delay has |
| // expired in the previous Run(). |
| RunLoop().RunUntilIdle(); |
| expected_value += 8192; |
| EXPECT_EQ(expected_value, counter); |
| } |
| |
| { |
| RunLoop run_loop; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitWhenIdleClosure(), Seconds(5)); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce([](int* counter) { *counter += 16384; }, |
| Unretained(&counter)), |
| Seconds(5)); |
| |
| // The QuitWhenIdleClosure() shouldn't preempt equally delayed tasks and as |
| // such the 16384 increment should be processed before quitting. |
| run_loop.Run(); |
| expected_value += 256; |
| expected_value += 16384; |
| EXPECT_EQ(expected_value, counter); |
| } |
| |
| // Process the remaining tasks (note: do not mimic this elsewhere, |
| // TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() is a better API to |
| // do this, this is just done here for the purpose of extensively testing the |
| // RunLoop approach). |
| |
| // Disable Run() timeout here, otherwise we'll fast-forward to it before we |
| // reach the quit task. |
| ScopedDisableRunLoopTimeout disable_timeout; |
| |
| RunLoop run_loop; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitWhenIdleClosure(), Days(50)); |
| |
| run_loop.Run(); |
| expected_value += 1024; |
| expected_value += 4096; |
| EXPECT_EQ(expected_value, counter); |
| } |
| |
| // Regression test for crbug.com/1263149 |
| TEST_F(TaskEnvironmentTest, RunLoopGetsTurnAfterYieldingToPool) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| base::RunLoop run_loop; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Seconds(1)); |
| ThreadPool::PostTask(FROM_HERE, base::DoNothing()); |
| |
| run_loop.Run(); |
| } |
| |
| // Regression test for crbug.com/1263149#c4 |
| TEST_F(TaskEnvironmentTest, ThreadPoolAdvancesTimeUnderIdleMainThread) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| base::RunLoop run_loop; |
| ThreadPool::PostDelayedTask(FROM_HERE, base::DoNothing(), base::Seconds(1)); |
| ThreadPool::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(), |
| base::Seconds(2)); |
| |
| run_loop.Run(); |
| } |
| |
| // Regression test for |
| // https://chromium-review.googlesource.com/c/chromium/src/+/3255105/5 which |
| // incorrectly tried to address crbug.com/1263149 with |
| // ThreadPool::FlushForTesting(), stalling thread pool tasks that need main |
| // thread collaboration. |
| TEST_F(TaskEnvironmentTest, MainThreadCanContributeWhileFlushingPool) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| base::RunLoop run_loop; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Seconds(1)); |
| TestWaitableEvent wait_for_collaboration; |
| ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&]() { |
| task_environment.GetMainThreadTaskRunner()->PostTask( |
| FROM_HERE, |
| BindOnce(&TestWaitableEvent::Signal, |
| Unretained(&wait_for_collaboration))); |
| wait_for_collaboration.Wait(); |
| })); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, CancelPendingTask) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME, |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| CancelableOnceClosure task1(BindOnce([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, task1.callback(), Seconds(1)); |
| EXPECT_TRUE(task_environment.MainThreadIsIdle()); |
| EXPECT_EQ(1u, task_environment.GetPendingMainThreadTaskCount()); |
| EXPECT_EQ(Seconds(1), task_environment.NextMainThreadPendingTaskDelay()); |
| EXPECT_TRUE(task_environment.MainThreadIsIdle()); |
| task1.Cancel(); |
| EXPECT_TRUE(task_environment.MainThreadIsIdle()); |
| EXPECT_EQ(TimeDelta::Max(), |
| task_environment.NextMainThreadPendingTaskDelay()); |
| |
| CancelableRepeatingClosure task2(BindRepeating([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, task2.callback(), Seconds(1)); |
| task2.Cancel(); |
| EXPECT_EQ(0u, task_environment.GetPendingMainThreadTaskCount()); |
| |
| CancelableRepeatingClosure task3(BindRepeating([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, task3.callback(), Seconds(1)); |
| task3.Cancel(); |
| EXPECT_EQ(TimeDelta::Max(), |
| task_environment.NextMainThreadPendingTaskDelay()); |
| |
| CancelableRepeatingClosure task4(BindRepeating([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, task4.callback(), Seconds(1)); |
| task4.Cancel(); |
| EXPECT_TRUE(task_environment.MainThreadIsIdle()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, CancelPendingImmediateTask) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| EXPECT_TRUE(task_environment.MainThreadIsIdle()); |
| |
| CancelableOnceClosure task1(BindOnce([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, |
| task1.callback()); |
| EXPECT_FALSE(task_environment.MainThreadIsIdle()); |
| |
| task1.Cancel(); |
| EXPECT_TRUE(task_environment.MainThreadIsIdle()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, NoFastForwardToCancelledTask) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME, |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| TimeTicks start_time = task_environment.NowTicks(); |
| CancelableRepeatingClosure task(BindRepeating([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, task.callback(), Seconds(1)); |
| EXPECT_EQ(Seconds(1), task_environment.NextMainThreadPendingTaskDelay()); |
| task.Cancel(); |
| task_environment.FastForwardUntilNoTasksRemain(); |
| EXPECT_EQ(start_time, task_environment.NowTicks()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, NextTaskIsDelayed) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| EXPECT_FALSE(task_environment.NextTaskIsDelayed()); |
| CancelableRepeatingClosure task(BindRepeating([]() {})); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, task.callback(), Seconds(1)); |
| EXPECT_TRUE(task_environment.NextTaskIsDelayed()); |
| task.Cancel(); |
| EXPECT_FALSE(task_environment.NextTaskIsDelayed()); |
| |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, BindOnce([]() {}), Seconds(2)); |
| EXPECT_TRUE(task_environment.NextTaskIsDelayed()); |
| task_environment.FastForwardUntilNoTasksRemain(); |
| EXPECT_FALSE(task_environment.NextTaskIsDelayed()); |
| |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, |
| BindOnce([]() {})); |
| EXPECT_FALSE(task_environment.NextTaskIsDelayed()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, NextMainThreadPendingTaskDelayWithImmediateTask) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| EXPECT_EQ(TimeDelta::Max(), |
| task_environment.NextMainThreadPendingTaskDelay()); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, |
| BindOnce([]() {})); |
| EXPECT_EQ(TimeDelta(), task_environment.NextMainThreadPendingTaskDelay()); |
| } |
| |
| TEST_F(TaskEnvironmentTest, TimeSourceMockTimeAlsoMocksNow) { |
| TaskEnvironment task_environment(TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const TimeTicks start_ticks = task_environment.NowTicks(); |
| EXPECT_EQ(TimeTicks::Now(), start_ticks); |
| |
| const Time start_time = Time::Now(); |
| |
| constexpr TimeDelta kDelay = Seconds(10); |
| task_environment.FastForwardBy(kDelay); |
| EXPECT_EQ(TimeTicks::Now(), start_ticks + kDelay); |
| EXPECT_EQ(Time::Now(), start_time + kDelay); |
| } |
| |
| TEST_F(TaskEnvironmentTest, SingleThread) { |
| SingleThreadTaskEnvironment task_environment; |
| EXPECT_THAT(ThreadPoolInstance::Get(), IsNull()); |
| |
| bool ran = false; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { ran = true; })); |
| RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(ran); |
| |
| EXPECT_DCHECK_DEATH(ThreadPool::PostTask(FROM_HERE, {}, DoNothing())); |
| } |
| |
| // Verify that traits other than ThreadingMode can be applied to |
| // SingleThreadTaskEnvironment. |
| TEST_F(TaskEnvironmentTest, SingleThreadMockTime) { |
| SingleThreadTaskEnvironment task_environment( |
| TaskEnvironment::TimeSource::MOCK_TIME); |
| |
| const TimeTicks start_time = TimeTicks::Now(); |
| |
| constexpr TimeDelta kDelay = Seconds(100); |
| |
| int counter = 0; |
| SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { counter += 1; }), kDelay); |
| SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { counter += 2; })); |
| |
| int expected_value = 0; |
| EXPECT_EQ(expected_value, counter); |
| |
| task_environment.RunUntilIdle(); |
| expected_value += 2; |
| EXPECT_EQ(expected_value, counter); |
| |
| task_environment.FastForwardUntilNoTasksRemain(); |
| expected_value += 1; |
| EXPECT_EQ(expected_value, counter); |
| EXPECT_EQ(TimeTicks::Now(), start_time + kDelay); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| namespace { |
| |
| enum class ApartmentType { |
| kSTA, |
| kMTA, |
| }; |
| |
| void InitializeSTAApartment() { |
| base::win::ScopedCOMInitializer initializer; |
| EXPECT_TRUE(initializer.Succeeded()); |
| } |
| |
| void InitializeMTAApartment() { |
| base::win::ScopedCOMInitializer initializer( |
| base::win::ScopedCOMInitializer::kMTA); |
| EXPECT_TRUE(initializer.Succeeded()); |
| } |
| |
| void InitializeCOMOnWorker( |
| TaskEnvironment::ThreadPoolCOMEnvironment com_environment, |
| ApartmentType apartment_type) { |
| TaskEnvironment task_environment(com_environment); |
| ThreadPool::PostTask(FROM_HERE, BindOnce(apartment_type == ApartmentType::kSTA |
| ? &InitializeSTAApartment |
| : &InitializeMTAApartment)); |
| task_environment.RunUntilIdle(); |
| } |
| |
| } // namespace |
| |
| TEST_F(TaskEnvironmentTest, DefaultCOMEnvironment) { |
| // Attempt to initialize an MTA COM apartment. Expect this to succeed since |
| // the thread is already in an MTA apartment. |
| InitializeCOMOnWorker(TaskEnvironment::ThreadPoolCOMEnvironment::DEFAULT, |
| ApartmentType::kMTA); |
| |
| // Attempt to initialize an STA COM apartment. Expect this to fail since the |
| // thread is already in an MTA apartment. |
| EXPECT_DCHECK_DEATH(InitializeCOMOnWorker( |
| TaskEnvironment::ThreadPoolCOMEnvironment::DEFAULT, ApartmentType::kSTA)); |
| } |
| |
| TEST_F(TaskEnvironmentTest, NoCOMEnvironment) { |
| // Attempt to initialize both MTA and STA COM apartments. Both should succeed |
| // when the thread is not already in an apartment. |
| InitializeCOMOnWorker(TaskEnvironment::ThreadPoolCOMEnvironment::NONE, |
| ApartmentType::kMTA); |
| InitializeCOMOnWorker(TaskEnvironment::ThreadPoolCOMEnvironment::NONE, |
| ApartmentType::kSTA); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| // TODO(crbug.com/1318840): Re-enable this test |
| #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) |
| #define MAYBE_ParallelExecutionFence DISABLED_ParallelExecutionFence |
| #else |
| #define MAYBE_ParallelExecutionFence ParallelExecutionFence |
| #endif |
| TEST_F(TaskEnvironmentTest, MAYBE_ParallelExecutionFence) { |
| TaskEnvironment task_environment; |
| |
| constexpr int kNumParallelTasks = |
| TaskEnvironment::kNumForegroundThreadPoolThreads; |
| |
| TestWaitableEvent resume_main_thread; |
| TestWaitableEvent all_runs_done; |
| // Counters, all accessed/modified with memory_order_relaxed as no memory |
| // ordering is necessary between operations. |
| std::atomic_int completed_runs{0}; |
| std::atomic_int next_run{1}; |
| |
| // Each task will repost itself until run 500. Run #50 will signal |
| // |resume_main_thread|. |
| RepeatingClosure task = BindLambdaForTesting([&]() { |
| int this_run = next_run.fetch_add(1, std::memory_order_relaxed); |
| |
| if (this_run == 50) { |
| resume_main_thread.Signal(); |
| } |
| |
| // Sleep after signaling to increase the likelihood the main thread installs |
| // the fence during this run and must wait on this task. |
| if (this_run >= 50 && this_run < 50 + kNumParallelTasks) { |
| PlatformThread::Sleep(Milliseconds(5)); |
| } |
| |
| // Repost self until the last kNumParallelTasks. |
| if (this_run <= 500 - kNumParallelTasks) { |
| ThreadPool::PostTask(task); |
| } |
| |
| completed_runs.fetch_add(1, std::memory_order_relaxed); |
| |
| if (this_run == 500) { |
| all_runs_done.Signal(); |
| } |
| }); |
| for (int i = 0; i < kNumParallelTasks; ++i) { |
| ThreadPool::PostTask(task); |
| } |
| |
| resume_main_thread.Wait(); |
| ASSERT_GE(next_run.load(std::memory_order_relaxed), 50); |
| |
| { |
| // Confirm that no run happens while the fence is up. |
| TaskEnvironment::ParallelExecutionFence fence; |
| |
| // All runs are complete. |
| const int completed_runs1 = completed_runs.load(std::memory_order_relaxed); |
| const int next_run1 = next_run.load(std::memory_order_relaxed); |
| EXPECT_EQ(completed_runs1, next_run1 - 1); |
| |
| // Given a bit more time, no additional run starts nor completes. |
| PlatformThread::Sleep(Milliseconds(30)); |
| const int completed_runs2 = completed_runs.load(std::memory_order_relaxed); |
| const int next_run2 = next_run.load(std::memory_order_relaxed); |
| EXPECT_EQ(completed_runs1, completed_runs2); |
| EXPECT_EQ(next_run1, next_run2); |
| } |
| |
| // Runs resume automatically after taking down the fence (without needing to |
| // call RunUntilIdle()). |
| all_runs_done.Wait(); |
| ASSERT_EQ(completed_runs.load(std::memory_order_relaxed), 500); |
| ASSERT_EQ(next_run.load(std::memory_order_relaxed), 501); |
| } |
| |
| TEST_F(TaskEnvironmentTest, ParallelExecutionFenceWithoutTaskEnvironment) { |
| // Noops (doesn't crash) without a TaskEnvironment. |
| TaskEnvironment::ParallelExecutionFence fence; |
| } |
| |
| TEST_F(TaskEnvironmentTest, |
| ParallelExecutionFenceWithSingleThreadTaskEnvironment) { |
| SingleThreadTaskEnvironment task_environment; |
| // Noops (doesn't crash), with a SingleThreadTaskEnvironment/ |
| TaskEnvironment::ParallelExecutionFence fence; |
| } |
| |
| // Android doesn't support death tests, see base/test/gtest_util.h |
| #if !BUILDFLAG(IS_ANDROID) |
| TEST_F(TaskEnvironmentTest, ParallelExecutionFenceNonMainThreadDeath) { |
| TaskEnvironment task_environment; |
| |
| ThreadPool::PostTask(BindOnce([]() { |
| #if CHECK_WILL_STREAM() |
| const char kFailureLog[] = "ParallelExecutionFence invoked from worker"; |
| #else |
| const char kFailureLog[] = ""; |
| #endif |
| EXPECT_DEATH_IF_SUPPORTED( |
| { TaskEnvironment::ParallelExecutionFence fence(kFailureLog); }, |
| kFailureLog); |
| })); |
| |
| task_environment.RunUntilIdle(); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| namespace { |
| bool FailOnTaskEnvironmentLog(int severity, |
| const char* file, |
| int line, |
| size_t message_start, |
| const std::string& str) { |
| StringPiece file_str(file); |
| if (file_str.find("task_environment.cc") != StringPiece::npos) { |
| ADD_FAILURE() << str; |
| return true; |
| } |
| return false; |
| } |
| } // namespace |
| |
| // Regression test for crbug.com/1293931 |
| TEST_F(TaskEnvironmentTest, DisallowRunTasksRetriesForFullTimeout) { |
| TaskEnvironment task_environment; |
| |
| // Verify that steps below can let 1 second pass without generating logs. |
| auto previous_handler = logging::GetLogMessageHandler(); |
| logging::SetLogMessageHandler(&FailOnTaskEnvironmentLog); |
| |
| TestWaitableEvent worker_running; |
| TestWaitableEvent resume_worker_task; |
| |
| ThreadPool::PostTask(BindLambdaForTesting([&]() { |
| worker_running.Signal(); |
| resume_worker_task.Wait(); |
| })); |
| |
| // Churn on this task so that TestTaskTracker::task_completed_cv_ gets |
| // signaled a bunch and reproduces the bug's conditions |
| // (TestTaskTracker::DisallowRunTasks gets early chances to quit). |
| RepeatingClosure infinite_repost = BindLambdaForTesting([&]() { |
| if (!resume_worker_task.IsSignaled()) { |
| ThreadPool::PostTask(infinite_repost); |
| } |
| }); |
| ThreadPool::PostTask(infinite_repost); |
| |
| // Allow ThreadPool quiescence after 1 second of test. |
| ThreadPool::PostDelayedTask( |
| FROM_HERE, |
| BindOnce(&TestWaitableEvent::Signal, Unretained(&resume_worker_task)), |
| Seconds(1)); |
| |
| worker_running.Wait(); |
| { |
| // Attempt to instantiate a ParallelExecutionFence. Without the fix to |
| // crbug.com/1293931, this would result in quickly exiting DisallowRunTasks |
| // without waiting for the intended 5 seconds timeout and would emit |
| // erroneous WARNING logs about slow tasks. This test passses if it doesn't |
| // trip FailOnTaskEnvironmentLog(). |
| TaskEnvironment::ParallelExecutionFence fence; |
| } |
| |
| // Flush the last |infinite_repost| task to avoid a UAF on |
| // |resume_worker_task|. |
| task_environment.RunUntilIdle(); |
| |
| logging::SetLogMessageHandler(previous_handler); |
| } |
| |
| TEST_F(TaskEnvironmentTest, RunUntilQuit_RunsMainThread) { |
| TaskEnvironment task_environment; |
| bool task_run = false; |
| auto quit = task_environment.QuitClosure(); |
| |
| SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| task_run = true; |
| quit.Run(); |
| })); |
| task_environment.RunUntilQuit(); |
| |
| ASSERT_TRUE(task_run); |
| } |
| |
| TEST_F(TaskEnvironmentTest, RunUntilQuit_RunsThreadPool) { |
| TaskEnvironment task_environment; |
| bool task_run = false; |
| auto quit = task_environment.QuitClosure(); |
| |
| ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&]() { |
| task_run = true; |
| quit.Run(); |
| })); |
| task_environment.RunUntilQuit(); |
| |
| ASSERT_TRUE(task_run); |
| } |
| |
| namespace { |
| |
| class TestLogger { |
| public: |
| std::vector<std::string> GetLog() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return log_; |
| } |
| |
| void LogMessage(std::string s) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| log_.push_back(std::move(s)); |
| } |
| |
| // If n=0 then executes `done` and returns. Otherwise adds `n` to the log and |
| // reschedules itself with (n - 1). |
| void CountDown(int n, OnceClosure done) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (n == 0) { |
| std::move(done).Run(); |
| return; |
| } |
| |
| log_.push_back(NumberToString(n)); |
| |
| SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindOnce(&TestLogger::CountDown, Unretained(this), n - 1, |
| std::move(done))); |
| } |
| |
| private: |
| std::vector<std::string> log_ GUARDED_BY_CONTEXT(sequence_checker_); |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| } // namespace |
| |
| TEST_F(TaskEnvironmentTest, RunUntilQuit_QueuedExecution) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| SequenceBound<TestLogger> logger(ThreadPool::CreateSequencedTaskRunner({})); |
| logger.AsyncCall(&TestLogger::CountDown) |
| .WithArgs(5, task_environment.QuitClosure()); |
| // Because `task_environment` was created with |
| // ThreadPoolExecutionMode::QUEUED, we are guaranteed that LogMessage() will |
| // be called after the first run on CountDown() and before the rest. |
| logger.AsyncCall(&TestLogger::LogMessage).WithArgs("Test"); |
| task_environment.RunUntilQuit(); |
| |
| // Get the log and confirm that LogMessage() ran when expected. |
| std::vector<std::string> actual_log; |
| auto quit = task_environment.QuitClosure(); |
| logger.AsyncCall(&TestLogger::GetLog) |
| .Then(BindLambdaForTesting([&](std::vector<std::string> log) { |
| actual_log = log; |
| quit.Run(); |
| })); |
| task_environment.RunUntilQuit(); |
| |
| ASSERT_THAT(actual_log, |
| testing::ElementsAre("5", "Test", "4", "3", "2", "1")); |
| } |
| |
| TEST_F(TaskEnvironmentTest, RunUntilQuit_ThreadPoolStaysQueued) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| ThreadPool::PostTask(FROM_HERE, task_environment.QuitClosure()); |
| task_environment.RunUntilQuit(); |
| |
| // RunUntilQuit() let the thread pool execute until the quit closure was run. |
| // Verify that execution is now queued again. |
| |
| bool task_run = false; |
| ThreadPool::PostTask(FROM_HERE, |
| BindLambdaForTesting([&]() { task_run = true; })); |
| // Wait a little bit to let the task run if execution is not queued. |
| PlatformThread::Sleep(Milliseconds(10)); |
| |
| ASSERT_FALSE(task_run); |
| |
| // Run the queued task now (if we don't, it'll run when `task_environment` is |
| // destroyed, and `task_run` is out of scope). |
| task_environment.RunUntilIdle(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, RunUntilQuit_QuitClosureInvalidatedByRun) { |
| TaskEnvironment task_environment( |
| TaskEnvironment::ThreadPoolExecutionMode::QUEUED); |
| |
| auto quit1 = task_environment.QuitClosure(); |
| auto quit2 = task_environment.QuitClosure(); |
| quit1.Run(); |
| task_environment.RunUntilQuit(); // Invalidates `quit1` and `quit2`. |
| auto quit3 = task_environment.QuitClosure(); |
| |
| std::vector<std::string> log; |
| // Running `quit1` or `quit2` will have no effect. |
| SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, quit1); |
| SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, quit2); |
| // This line will be logged. |
| SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { log.push_back("after quit2"); })); |
| // `quit3` will terminate execution. |
| SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, quit3); |
| // This line will *not* be logged. |
| SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { log.push_back("after quit3"); })); |
| task_environment.RunUntilQuit(); |
| |
| ASSERT_THAT(log, testing::ElementsAre("after quit2")); |
| |
| // Run the queued task now (if we don't, it might run when `task_environment` |
| // is destroyed, and `log` is out of scope). |
| task_environment.RunUntilIdle(); |
| } |
| |
| TEST_F(TaskEnvironmentTest, RunUntilQuit_MustCallQuitClosureFirst) { |
| TaskEnvironment task_environment; |
| EXPECT_DCHECK_DEATH_WITH( |
| task_environment.RunUntilQuit(), |
| R"(QuitClosure\(\) not called before RunUntilQuit\(\))"); |
| } |
| |
| } // namespace test |
| } // namespace base |