#include "base/test/run_until.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);
}
}
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;
}));
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) {
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));
static bool success;
EXPECT_NONFATAL_FAILURE(
{ success = RunUntil([] { return false; }); }, "timed out");
EXPECT_FALSE(success);
}
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));
}
TEST(RunUntilTestWithThreadPool, TimesOutWhenMainThreadSleepsForever) {
TaskEnvironment task_environment;
base::AtomicFlag done;
const auto start_time = TimeTicks::Now();
ThreadPool::PostDelayedTask(
FROM_HERE, BindLambdaForTesting([&]() { done.Set(); }), Milliseconds(1));
PostDelayedTask(base::DoNothing(), TestTimeouts::tiny_timeout());
EXPECT_TRUE(RunUntil([&]() { return done.IsSet(); }));
auto wait_time = TimeTicks::Now() - start_time;
#if !BUILDFLAG(IS_IOS)
EXPECT_GE(wait_time, TestTimeouts::tiny_timeout());
#else
EXPECT_GE(wait_time, Milliseconds(1));
#endif
EXPECT_TRUE(done.IsSet());
}
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(); }));
EXPECT_EQ(TimeTicks::Now() - start_time, TestTimeouts::tiny_timeout());
}
}