1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
// 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 <typename Lambda>
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
|