• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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