// Copyright 2023 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/run_until.h" #include "base/functional/callback_forward.h" #include "base/functional/callback_helpers.h" #include "base/synchronization/atomic_flag.h" #include "base/task/bind_post_task.h" #include "base/task/single_thread_task_runner.h" #include "base/task/thread_pool.h" #include "base/test/bind.h" #include "base/test/task_environment.h" #include "base/test/test_timeouts.h" #include "base/timer/timer.h" #include "testing/gtest/include/gtest/gtest-spi.h" #include "testing/gtest/include/gtest/gtest.h" namespace base::test { namespace { template void RunLater(Lambda lambda) { SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindLambdaForTesting(lambda)); } void PostDelayedTask(base::OnceClosure closure, base::TimeDelta delay) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, std::move(closure), delay); } } // namespace class RunUntilTest : public ::testing::Test { public: RunUntilTest() = default; RunUntilTest(const RunUntilTest&) = delete; RunUntilTest& operator=(const RunUntilTest&) = delete; ~RunUntilTest() override = default; private: test::SingleThreadTaskEnvironment environment_; }; TEST_F(RunUntilTest, ShouldReturnTrueIfPredicateIsAlreadyFulfilled) { EXPECT_TRUE(RunUntil([] { return true; })); } TEST_F(RunUntilTest, ShouldReturnTrueOncePredicateIsFulfilled) { bool done = false; RunLater([&done] { done = true; }); EXPECT_TRUE(RunUntil([&done] { return done; })); } TEST_F(RunUntilTest, ShouldNotSimplyActivelyInvokePredicateInALoop) { bool done = false; int call_count = 0; PostDelayedTask(base::BindLambdaForTesting([&done] { done = true; }), base::Milliseconds(50)); EXPECT_TRUE(RunUntil([&] { call_count++; return done; })); // Ensure the predicate is not called a ton of times. EXPECT_LT(call_count, 10); } TEST_F(RunUntilTest, ShouldNotSimplyReturnOnFirstIdle) { bool done = false; PostDelayedTask(base::DoNothing(), base::Milliseconds(1)); PostDelayedTask(base::DoNothing(), base::Milliseconds(5)); PostDelayedTask(base::BindLambdaForTesting([&done] { done = true; }), base::Milliseconds(10)); EXPECT_TRUE(RunUntil([&] { return done; })); } TEST_F(RunUntilTest, ShouldAlwaysLetOtherTasksRunFirstEvenIfPredicateIsAlreadyFulfilled) { // This ensures that no tests can (accidentally) rely on `RunUntil` // immediately returning. bool other_job_done = false; RunLater([&other_job_done] { other_job_done = true; }); EXPECT_TRUE(RunUntil([] { return true; })); EXPECT_TRUE(other_job_done); } TEST_F(RunUntilTest, ShouldWorkEvenWhenTimerIsRunning) { bool done = false; base::RepeatingTimer timer; timer.Start(FROM_HERE, base::Seconds(1), base::DoNothing()); PostDelayedTask(base::BindLambdaForTesting([&done] { done = true; }), base::Milliseconds(10)); EXPECT_TRUE(RunUntil([&] { return done; })); } TEST_F(RunUntilTest, ShouldReturnFalseIfTimeoutHappens) { test::ScopedRunLoopTimeout timeout(FROM_HERE, Milliseconds(1)); // `ScopedRunLoopTimeout` will automatically fail the test when a timeout // happens, so we use EXPECT_NONFATAL_FAILURE to handle this failure. // EXPECT_NONFATAL_FAILURE only works on static objects. static bool success; EXPECT_NONFATAL_FAILURE( { success = RunUntil([] { return false; }); }, "timed out"); EXPECT_FALSE(success); } // Tests that RunUntil supports MOCK_TIME when used with a delayed task posted // directly to the main thread. This verifies that time advances correctly and // the condition is satisfied after the expected delay. TEST(RunUntilTestWithThreadPool, SupportsMockTime) { base::test::SingleThreadTaskEnvironment task_environment( base::test::TaskEnvironment::TimeSource::MOCK_TIME); base::TimeTicks start_time = base::TimeTicks::Now(); bool done; base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce([](bool* flag) { *flag = true; }, &done), base::Days(1)); EXPECT_TRUE(base::test::RunUntil([&]() { return done; })); EXPECT_EQ(base::TimeTicks::Now() - start_time, base::Days(1)); } // Documents that this API can be flaky if the condition is global (time, global // var, etc.) and doesn't result in waking the main thread. TEST(RunUntilTestWithThreadPool, TimesOutWhenMainThreadSleepsForever) { TaskEnvironment task_environment; base::AtomicFlag done; const auto start_time = TimeTicks::Now(); ThreadPool::PostDelayedTask( FROM_HERE, BindLambdaForTesting([&]() { done.Set(); }), Milliseconds(1)); // Program a timeout wakeup on the main thread, it doesn't need to do // anything, being awake will cause the RunUntil predicate to be checked once // idle again. PostDelayedTask(base::DoNothing(), TestTimeouts::tiny_timeout()); EXPECT_TRUE(RunUntil([&]() { return done.IsSet(); })); // Reached timeout on main thread despite condition being satisfied on // ThreadPool earlier... Ideally we could flip this expectation to EXPECT_LT // but for now it documents the reality. auto wait_time = TimeTicks::Now() - start_time; // TODO(crbug.com/368805258): The main thread on iOS seems to wakeup without // waiting for the delayed task to fire, causing the condition to be checked // early, unexpectedly. #if !BUILDFLAG(IS_IOS) EXPECT_GE(wait_time, TestTimeouts::tiny_timeout()); #else // Just check if RunUntil did it's job. EXPECT_GE(wait_time, Milliseconds(1)); #endif EXPECT_TRUE(done.IsSet()); } // Same as "TimesOutWhenMainThreadSleepsForever" but under MOCK_TIME. // We would similarly like this to exit RunUntil after the condition is // satisfied after 1ms but this documents that this is not currently WAI. TEST(RunUntilTestWithMockTime, ConditionOnlyObservedIfWorkIsDone) { TaskEnvironment task_environment{TaskEnvironment::TimeSource::MOCK_TIME}; base::AtomicFlag done; const auto start_time = TimeTicks::Now(); ThreadPool::PostDelayedTask(FROM_HERE, BindLambdaForTesting([&done]() { done.Set(); }), Milliseconds(1)); PostDelayedTask(base::DoNothing(), TestTimeouts::tiny_timeout()); EXPECT_TRUE(RunUntil([&]() { return done.IsSet(); })); // Should be exactly EQ under MOCK_TIME. EXPECT_EQ(TimeTicks::Now() - start_time, TestTimeouts::tiny_timeout()); } } // namespace base::test