1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/test/run_until.h"
6
7 #include "base/functional/callback_forward.h"
8 #include "base/functional/callback_helpers.h"
9 #include "base/synchronization/atomic_flag.h"
10 #include "base/task/bind_post_task.h"
11 #include "base/task/single_thread_task_runner.h"
12 #include "base/task/thread_pool.h"
13 #include "base/test/bind.h"
14 #include "base/test/task_environment.h"
15 #include "base/test/test_timeouts.h"
16 #include "base/timer/timer.h"
17 #include "testing/gtest/include/gtest/gtest-spi.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19
20 namespace base::test {
21
22 namespace {
23
24 template <typename Lambda>
RunLater(Lambda lambda)25 void RunLater(Lambda lambda) {
26 SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
27 FROM_HERE, base::BindLambdaForTesting(lambda));
28 }
29
PostDelayedTask(base::OnceClosure closure,base::TimeDelta delay)30 void PostDelayedTask(base::OnceClosure closure, base::TimeDelta delay) {
31 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
32 FROM_HERE, std::move(closure), delay);
33 }
34
35 } // namespace
36
37 class RunUntilTest : public ::testing::Test {
38 public:
39 RunUntilTest() = default;
40 RunUntilTest(const RunUntilTest&) = delete;
41 RunUntilTest& operator=(const RunUntilTest&) = delete;
42 ~RunUntilTest() override = default;
43
44 private:
45 test::SingleThreadTaskEnvironment environment_;
46 };
47
TEST_F(RunUntilTest,ShouldReturnTrueIfPredicateIsAlreadyFulfilled)48 TEST_F(RunUntilTest, ShouldReturnTrueIfPredicateIsAlreadyFulfilled) {
49 EXPECT_TRUE(RunUntil([] { return true; }));
50 }
51
TEST_F(RunUntilTest,ShouldReturnTrueOncePredicateIsFulfilled)52 TEST_F(RunUntilTest, ShouldReturnTrueOncePredicateIsFulfilled) {
53 bool done = false;
54
55 RunLater([&done] { done = true; });
56
57 EXPECT_TRUE(RunUntil([&done] { return done; }));
58 }
59
TEST_F(RunUntilTest,ShouldNotSimplyActivelyInvokePredicateInALoop)60 TEST_F(RunUntilTest, ShouldNotSimplyActivelyInvokePredicateInALoop) {
61 bool done = false;
62 int call_count = 0;
63
64 PostDelayedTask(base::BindLambdaForTesting([&done] { done = true; }),
65 base::Milliseconds(50));
66
67 EXPECT_TRUE(RunUntil([&] {
68 call_count++;
69 return done;
70 }));
71
72 // Ensure the predicate is not called a ton of times.
73 EXPECT_LT(call_count, 10);
74 }
75
TEST_F(RunUntilTest,ShouldNotSimplyReturnOnFirstIdle)76 TEST_F(RunUntilTest, ShouldNotSimplyReturnOnFirstIdle) {
77 bool done = false;
78
79 PostDelayedTask(base::DoNothing(), base::Milliseconds(1));
80 PostDelayedTask(base::DoNothing(), base::Milliseconds(5));
81 PostDelayedTask(base::BindLambdaForTesting([&done] { done = true; }),
82 base::Milliseconds(10));
83
84 EXPECT_TRUE(RunUntil([&] { return done; }));
85 }
86
TEST_F(RunUntilTest,ShouldAlwaysLetOtherTasksRunFirstEvenIfPredicateIsAlreadyFulfilled)87 TEST_F(RunUntilTest,
88 ShouldAlwaysLetOtherTasksRunFirstEvenIfPredicateIsAlreadyFulfilled) {
89 // This ensures that no tests can (accidentally) rely on `RunUntil`
90 // immediately returning.
91 bool other_job_done = false;
92 RunLater([&other_job_done] { other_job_done = true; });
93
94 EXPECT_TRUE(RunUntil([] { return true; }));
95
96 EXPECT_TRUE(other_job_done);
97 }
98
TEST_F(RunUntilTest,ShouldWorkEvenWhenTimerIsRunning)99 TEST_F(RunUntilTest, ShouldWorkEvenWhenTimerIsRunning) {
100 bool done = false;
101
102 base::RepeatingTimer timer;
103 timer.Start(FROM_HERE, base::Seconds(1), base::DoNothing());
104
105 PostDelayedTask(base::BindLambdaForTesting([&done] { done = true; }),
106 base::Milliseconds(10));
107
108 EXPECT_TRUE(RunUntil([&] { return done; }));
109 }
110
TEST_F(RunUntilTest,ShouldReturnFalseIfTimeoutHappens)111 TEST_F(RunUntilTest, ShouldReturnFalseIfTimeoutHappens) {
112 test::ScopedRunLoopTimeout timeout(FROM_HERE, Milliseconds(1));
113
114 // `ScopedRunLoopTimeout` will automatically fail the test when a timeout
115 // happens, so we use EXPECT_NONFATAL_FAILURE to handle this failure.
116 // EXPECT_NONFATAL_FAILURE only works on static objects.
117 static bool success;
118
119 EXPECT_NONFATAL_FAILURE(
120 { success = RunUntil([] { return false; }); }, "timed out");
121
122 EXPECT_FALSE(success);
123 }
124
125 // Tests that RunUntil supports MOCK_TIME when used with a delayed task posted
126 // directly to the main thread. This verifies that time advances correctly and
127 // the condition is satisfied after the expected delay.
TEST(RunUntilTestWithThreadPool,SupportsMockTime)128 TEST(RunUntilTestWithThreadPool, SupportsMockTime) {
129 base::test::SingleThreadTaskEnvironment task_environment(
130 base::test::TaskEnvironment::TimeSource::MOCK_TIME);
131
132 base::TimeTicks start_time = base::TimeTicks::Now();
133 bool done;
134
135 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
136 FROM_HERE, base::BindOnce([](bool* flag) { *flag = true; }, &done),
137 base::Days(1));
138
139 EXPECT_TRUE(base::test::RunUntil([&]() { return done; }));
140
141 EXPECT_EQ(base::TimeTicks::Now() - start_time, base::Days(1));
142 }
143
144 // Documents that this API can be flaky if the condition is global (time, global
145 // var, etc.) and doesn't result in waking the main thread.
TEST(RunUntilTestWithThreadPool,TimesOutWhenMainThreadSleepsForever)146 TEST(RunUntilTestWithThreadPool, TimesOutWhenMainThreadSleepsForever) {
147 TaskEnvironment task_environment;
148
149 base::AtomicFlag done;
150 const auto start_time = TimeTicks::Now();
151
152 ThreadPool::PostDelayedTask(
153 FROM_HERE, BindLambdaForTesting([&]() { done.Set(); }), Milliseconds(1));
154
155 // Program a timeout wakeup on the main thread, it doesn't need to do
156 // anything, being awake will cause the RunUntil predicate to be checked once
157 // idle again.
158 PostDelayedTask(base::DoNothing(), TestTimeouts::tiny_timeout());
159
160 EXPECT_TRUE(RunUntil([&]() { return done.IsSet(); }));
161
162 // Reached timeout on main thread despite condition being satisfied on
163 // ThreadPool earlier... Ideally we could flip this expectation to EXPECT_LT
164 // but for now it documents the reality.
165 auto wait_time = TimeTicks::Now() - start_time;
166
167 // TODO(crbug.com/368805258): The main thread on iOS seems to wakeup without
168 // waiting for the delayed task to fire, causing the condition to be checked
169 // early, unexpectedly.
170 #if !BUILDFLAG(IS_IOS)
171 EXPECT_GE(wait_time, TestTimeouts::tiny_timeout());
172 #else
173 // Just check if RunUntil did it's job.
174 EXPECT_GE(wait_time, Milliseconds(1));
175 #endif
176 EXPECT_TRUE(done.IsSet());
177 }
178
179 // Same as "TimesOutWhenMainThreadSleepsForever" but under MOCK_TIME.
180 // We would similarly like this to exit RunUntil after the condition is
181 // satisfied after 1ms but this documents that this is not currently WAI.
TEST(RunUntilTestWithMockTime,ConditionOnlyObservedIfWorkIsDone)182 TEST(RunUntilTestWithMockTime, ConditionOnlyObservedIfWorkIsDone) {
183 TaskEnvironment task_environment{TaskEnvironment::TimeSource::MOCK_TIME};
184
185 base::AtomicFlag done;
186 const auto start_time = TimeTicks::Now();
187
188 ThreadPool::PostDelayedTask(FROM_HERE,
189 BindLambdaForTesting([&done]() { done.Set(); }),
190 Milliseconds(1));
191 PostDelayedTask(base::DoNothing(), TestTimeouts::tiny_timeout());
192 EXPECT_TRUE(RunUntil([&]() { return done.IsSet(); }));
193 // Should be exactly EQ under MOCK_TIME.
194 EXPECT_EQ(TimeTicks::Now() - start_time, TestTimeouts::tiny_timeout());
195 }
196
197 } // namespace base::test
198