blob: ec07c949b74832413af3b696a1d8c1d19b6c0f18 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/async-testing/test_loop.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/time.h>
#include <lib/async/cpp/wait.h>
#include <lib/async/default.h>
#include <lib/async/dispatcher.h>
#include <lib/async/task.h>
#include <lib/fit/function.h>
#include <lib/zx/event.h>
#include <lib/zx/time.h>
#include <sys/socket.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <array>
#include <memory>
#include <string>
#include <utility>
#include <zxtest/zxtest.h>
namespace {
// Initializes |wait| to wait on |event| to call |closure| once |trigger| is
// signaled.
void InitWait(async::Wait* wait, fit::closure closure, const zx::event& event,
zx_signals_t trigger) {
wait->set_handler([closure = std::move(closure)](async_dispatcher_t*, async::Wait*, zx_status_t,
const zx_packet_signal_t*) { closure(); });
wait->set_object(event.get());
wait->set_trigger(trigger);
}
TEST(TestLoopTest, DefaultDispatcherIsSetAndUnset) {
ASSERT_NULL(async_get_default_dispatcher());
{
async::TestLoop loop;
EXPECT_EQ(loop.dispatcher(), async_get_default_dispatcher());
}
EXPECT_NULL(async_get_default_dispatcher());
}
TEST(TestLoopTest, RandomSeedFromEnv) {
const char* kTestRandomSeed = "1234";
char* old_random_seed = getenv("TEST_LOOP_RANDOM_SEED");
setenv("TEST_LOOP_RANDOM_SEED", kTestRandomSeed, /*overwrite=*/true);
async::TestLoop loop;
EXPECT_EQ(loop.initial_state(), 1234u);
if (old_random_seed) {
setenv("TEST_LOOP_RANDOM_SEED", old_random_seed, /*overwrite=*/true);
} else {
unsetenv("TEST_LOOP_RANDOM_SEED");
}
}
TEST(TestLoopDispatcher, FakeClockTimeIsCorrect) {
async::TestLoop loop;
EXPECT_EQ(0, loop.Now().get());
EXPECT_EQ(0, async::Now(loop.dispatcher()).get());
loop.RunUntilIdle();
EXPECT_EQ(0, loop.Now().get());
EXPECT_EQ(0, async::Now(loop.dispatcher()).get());
loop.RunFor(zx::nsec(1));
EXPECT_EQ(1, loop.Now().get());
EXPECT_EQ(1, async::Now(loop.dispatcher()).get());
loop.RunUntil(zx::time() + zx::nsec(3));
EXPECT_EQ(3, loop.Now().get());
EXPECT_EQ(3, async::Now(loop.dispatcher()).get());
loop.RunFor(zx::nsec(7));
EXPECT_EQ(10, loop.Now().get());
EXPECT_EQ(10, async::Now(loop.dispatcher()).get());
loop.RunUntil(zx::time() + zx::nsec(12));
EXPECT_EQ(12, loop.Now().get());
EXPECT_EQ(12, async::Now(loop.dispatcher()).get());
// t = 12, so nothing should happen in trying to reset the clock to t = 10.
loop.RunUntil(zx::time() + zx::nsec(10));
EXPECT_EQ(12, loop.Now().get());
EXPECT_EQ(12, async::Now(loop.dispatcher()).get());
}
TEST(TestLoopTest, TasksAreDispatched) {
async::TestLoop loop;
bool called = false;
async::PostDelayedTask(
loop.dispatcher(), [&called] { called = true; }, zx::sec(2));
// t = 1: nothing should happen.
loop.RunFor(zx::sec(1));
EXPECT_FALSE(called);
// t = 2: task should be dispatched.
loop.RunFor(zx::sec(1));
EXPECT_TRUE(called);
called = false;
async::PostTask(loop.dispatcher(), [&called] { called = true; });
loop.RunUntilIdle();
EXPECT_TRUE(called);
}
TEST(TestLoopTest, QuitAndReset) {
async::TestLoop loop;
async::PostDelayedTask(
loop.dispatcher(), [] {}, zx::sec(1));
loop.Quit();
// Loop has quit, so time does not advance and no work is done.
EXPECT_FALSE(loop.RunFor(zx::sec(1)));
EXPECT_EQ(loop.Now(), zx::time(0));
// Loop has reset, so time does advance and work is done.
EXPECT_TRUE(loop.RunFor(zx::sec(1)));
EXPECT_EQ(loop.Now(), zx::time(0) + zx::sec(1));
// Quit task is posted, followed by another task. The quit task is
// dispatched and work is reported.
async::PostTask(loop.dispatcher(), [&loop] { loop.Quit(); });
async::PostTask(loop.dispatcher(), [] {});
EXPECT_TRUE(loop.RunUntilIdle());
// Loop was quit, but it is now reset the remaining task will be dispatched
// on the next run.
EXPECT_TRUE(loop.RunUntilIdle());
}
TEST(TestLoopTest, RunRepeatedly) {
async::TestLoop loop;
for (int i = 0; i <= 60; ++i) {
async::PostDelayedTask(
loop.dispatcher(), [] {}, zx::sec(i));
}
// Run the loop repeatedly at ten second intervals until the delayed tasks
// are all dispatched.
loop.RunRepeatedlyFor(zx::sec(10));
EXPECT_GE(loop.Now(), zx::time(0) + zx::min(1));
// There should be nothing further to dispatch.
EXPECT_FALSE(loop.RunUntilIdle());
}
TEST(TestLoopTest, SameDeadlinesDispatchInPostingOrder) {
async::TestLoop loop;
bool calledA = false;
bool calledB = false;
async::PostTask(loop.dispatcher(), [&] {
EXPECT_FALSE(calledB);
calledA = true;
});
async::PostTask(loop.dispatcher(), [&] {
EXPECT_TRUE(calledA);
calledB = true;
});
loop.RunUntilIdle();
EXPECT_TRUE(calledA);
EXPECT_TRUE(calledB);
calledA = false;
calledB = false;
async::PostDelayedTask(
loop.dispatcher(),
[&] {
EXPECT_FALSE(calledB);
calledA = true;
},
zx::sec(5));
async::PostDelayedTask(
loop.dispatcher(),
[&] {
EXPECT_TRUE(calledA);
calledB = true;
},
zx::sec(5));
loop.RunFor(zx::sec(5));
EXPECT_TRUE(calledA);
EXPECT_TRUE(calledB);
}
// Test tasks that post tasks.
TEST(TestLoopTest, NestedTasksAreDispatched) {
async::TestLoop loop;
bool called = false;
async::PostTask(loop.dispatcher(), [&] {
async::PostDelayedTask(
loop.dispatcher(),
[&] {
async::PostDelayedTask(
loop.dispatcher(), [&] { called = true; }, zx::min(25));
},
zx::min(35));
});
loop.RunFor(zx::hour(1));
EXPECT_TRUE(called);
}
TEST(TestLoopTest, TimeIsCorrectWhileDispatching) {
async::TestLoop loop;
bool called = false;
async::PostTask(loop.dispatcher(), [&] {
EXPECT_EQ(0, loop.Now().get());
async::PostDelayedTask(
loop.dispatcher(),
[&] {
EXPECT_EQ(10, loop.Now().get());
async::PostDelayedTask(
loop.dispatcher(),
[&] {
EXPECT_EQ(15, loop.Now().get());
async::PostTask(loop.dispatcher(), [&] {
EXPECT_EQ(15, loop.Now().get());
called = true;
});
},
zx::nsec(5));
},
zx::nsec(10));
});
loop.RunFor(zx::nsec(15));
EXPECT_TRUE(called);
}
TEST(TestLoopTest, TasksAreCanceled) {
async::TestLoop loop;
bool calledA = false;
bool calledB = false;
bool calledC = false;
async::TaskClosure taskA([&calledA] { calledA = true; });
async::TaskClosure taskB([&calledB] { calledB = true; });
async::TaskClosure taskC([&calledC] { calledC = true; });
ASSERT_OK(taskA.Post(loop.dispatcher()));
ASSERT_OK(taskB.Post(loop.dispatcher()));
ASSERT_OK(taskC.Post(loop.dispatcher()));
ASSERT_OK(taskA.Cancel());
ASSERT_OK(taskC.Cancel());
loop.RunUntilIdle();
EXPECT_FALSE(calledA);
EXPECT_TRUE(calledB);
EXPECT_FALSE(calledC);
}
TEST(TestLoopTest, TimeIsAdvanced) {
async::TestLoop loop;
bool called = false;
async::TaskClosure task([&called] { called = true; });
auto time1 = async::Now(loop.dispatcher());
ASSERT_EQ(ZX_OK, task.PostDelayed(loop.dispatcher(), zx::duration(1)));
loop.RunUntilIdle();
EXPECT_FALSE(called);
EXPECT_EQ(time1.get(), async::Now(loop.dispatcher()).get());
loop.AdvanceTimeByEpsilon();
auto time2 = async::Now(loop.dispatcher());
EXPECT_FALSE(called);
EXPECT_GT(time2.get(), time1.get());
loop.RunUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(time2.get(), async::Now(loop.dispatcher()).get());
}
TEST(TestLoopTest, WaitsAreDispatched) {
async::TestLoop loop;
async::Wait wait;
zx::event event;
bool called = false;
ASSERT_EQ(ZX_OK, zx::event::create(0u, &event));
InitWait(
&wait, [&called] { called = true; }, event, ZX_USER_SIGNAL_0);
ASSERT_EQ(ZX_OK, wait.Begin(loop.dispatcher()));
// |wait| has not yet been triggered.
loop.RunUntilIdle();
EXPECT_FALSE(called);
ASSERT_EQ(ZX_OK, event.signal(0u, ZX_USER_SIGNAL_1));
// |wait| will only be triggered by |ZX_USER_SIGNAL_0|.
loop.RunUntilIdle();
EXPECT_FALSE(called);
ASSERT_EQ(ZX_OK, event.signal(0u, ZX_USER_SIGNAL_0));
loop.RunUntilIdle();
EXPECT_TRUE(called);
}
// Test waits that trigger waits.
TEST(TestLoopTest, NestedWaitsAreDispatched) {
async::TestLoop loop;
zx::event event;
async::Wait waitA;
async::Wait waitB;
async::Wait waitC;
bool calledA = false;
bool calledB = false;
bool calledC = false;
ASSERT_EQ(ZX_OK, zx::event::create(0u, &event));
InitWait(
&waitA,
[&] {
InitWait(
&waitB,
[&] {
InitWait(
&waitC, [&] { calledC = true; }, event, ZX_USER_SIGNAL_2);
waitC.Begin(loop.dispatcher());
calledB = true;
},
event, ZX_USER_SIGNAL_1);
waitB.Begin(loop.dispatcher());
calledA = true;
},
event, ZX_USER_SIGNAL_0);
ASSERT_EQ(ZX_OK, waitA.Begin(loop.dispatcher()));
loop.RunUntilIdle();
EXPECT_FALSE(calledA);
EXPECT_FALSE(calledB);
EXPECT_FALSE(calledC);
ASSERT_EQ(ZX_OK, event.signal(0u, ZX_USER_SIGNAL_0));
loop.RunUntilIdle();
EXPECT_TRUE(calledA);
EXPECT_FALSE(calledB);
EXPECT_FALSE(calledC);
ASSERT_EQ(ZX_OK, event.signal(0u, ZX_USER_SIGNAL_1));
loop.RunUntilIdle();
EXPECT_TRUE(calledA);
EXPECT_TRUE(calledB);
EXPECT_FALSE(calledC);
ASSERT_EQ(ZX_OK, event.signal(0u, ZX_USER_SIGNAL_2));
loop.RunUntilIdle();
EXPECT_TRUE(calledA);
EXPECT_TRUE(calledB);
EXPECT_TRUE(calledC);
}
TEST(TestLoopTest, WaitsAreCanceled) {
async::TestLoop loop;
zx::event event;
async::Wait waitA;
async::Wait waitB;
async::Wait waitC;
bool calledA = false;
bool calledB = false;
bool calledC = false;
ASSERT_EQ(ZX_OK, zx::event::create(0u, &event));
InitWait(
&waitA, [&calledA] { calledA = true; }, event, ZX_USER_SIGNAL_0);
InitWait(
&waitB, [&calledB] { calledB = true; }, event, ZX_USER_SIGNAL_0);
InitWait(
&waitC, [&calledC] { calledC = true; }, event, ZX_USER_SIGNAL_0);
ASSERT_OK(waitA.Begin(loop.dispatcher()));
ASSERT_OK(waitB.Begin(loop.dispatcher()));
ASSERT_OK(waitC.Begin(loop.dispatcher()));
ASSERT_OK(waitA.Cancel());
ASSERT_OK(waitC.Cancel());
ASSERT_OK(event.signal(0u, ZX_USER_SIGNAL_0));
loop.RunUntilIdle();
EXPECT_FALSE(calledA);
EXPECT_TRUE(calledB);
EXPECT_FALSE(calledC);
}
// Test a task that begins a wait to post a task.
TEST(TestLoopTest, NestedTasksAndWaitsAreDispatched) {
async::TestLoop loop;
zx::event event;
async::Wait wait;
bool wait_begun = false;
bool wait_dispatched = false;
bool inner_task_dispatched = false;
ASSERT_OK(zx::event::create(0u, &event));
InitWait(
&wait,
[&] {
async::PostDelayedTask(
loop.dispatcher(), [&] { inner_task_dispatched = true; }, zx::min(2));
wait_dispatched = true;
},
event, ZX_USER_SIGNAL_0);
async::PostDelayedTask(
loop.dispatcher(),
[&] {
wait.Begin(loop.dispatcher());
wait_begun = true;
},
zx::min(3));
loop.RunFor(zx::min(3));
EXPECT_TRUE(wait_begun);
EXPECT_FALSE(wait_dispatched);
EXPECT_FALSE(inner_task_dispatched);
ASSERT_OK(event.signal(0u, ZX_USER_SIGNAL_0));
loop.RunUntilIdle();
EXPECT_TRUE(wait_begun);
EXPECT_TRUE(wait_dispatched);
EXPECT_FALSE(inner_task_dispatched);
loop.RunFor(zx::min(2));
EXPECT_TRUE(wait_begun);
EXPECT_TRUE(wait_dispatched);
EXPECT_TRUE(inner_task_dispatched);
}
TEST(TestLoopTest, DefaultDispatcherIsCurrentLoop) {
async::TestLoop loop;
auto subloop = loop.StartNewLoop();
bool main_loop_task_run = false;
async_dispatcher_t* main_loop_task_dispatcher = nullptr;
bool sub_loop_task_run = false;
async_dispatcher_t* sub_loop_task_dispatcher = nullptr;
async::PostTask(loop.dispatcher(), [&] {
main_loop_task_run = true;
main_loop_task_dispatcher = async_get_default_dispatcher();
});
async::PostTask(subloop->dispatcher(), [&] {
sub_loop_task_run = true;
sub_loop_task_dispatcher = async_get_default_dispatcher();
});
loop.RunUntilIdle();
EXPECT_TRUE(main_loop_task_run);
EXPECT_EQ(main_loop_task_dispatcher, loop.dispatcher());
EXPECT_TRUE(sub_loop_task_run);
EXPECT_EQ(sub_loop_task_dispatcher, subloop->dispatcher());
}
TEST(TestLoopTest, HugeAmountOfTaskAreDispatched) {
// This value must be less than or equal to kernel.port.max-observers.
constexpr size_t kPostCount = 50000;
async::TestLoop loop;
zx::event event;
ASSERT_OK(zx::event::create(0u, &event));
size_t called_count = 0;
size_t wait_count = 0;
// Creating the array on the heap as its size is greater than the available
// stack.
auto waits_ptr = std::make_unique<std::array<async::Wait, kPostCount>>();
auto& waits = *waits_ptr;
for (size_t i = 0; i < kPostCount; ++i) {
InitWait(
&waits[i], [&] { wait_count++; }, event, ZX_USER_SIGNAL_0);
ASSERT_OK(waits[i].Begin(loop.dispatcher()));
}
ASSERT_OK(event.signal(0u, ZX_USER_SIGNAL_0));
for (size_t i = 0; i < kPostCount; ++i) {
async::PostTask(loop.dispatcher(), [&] { called_count++; });
}
loop.RunUntilIdle();
EXPECT_EQ(kPostCount, called_count);
EXPECT_EQ(kPostCount, wait_count);
}
TEST(TestLoopTest, TasksAreDispatchedOnManyLoops) {
async::TestLoop loop;
auto loopA = loop.StartNewLoop();
auto loopB = loop.StartNewLoop();
auto loopC = loop.StartNewLoop();
bool called = false;
bool calledA = false;
bool calledB = false;
bool calledC = false;
async::TaskClosure taskC([&calledC] { calledC = true; });
async::PostTask(loopB->dispatcher(), [&calledB] { calledB = true; });
async::PostDelayedTask(
loop.dispatcher(), [&called] { called = true; }, zx::sec(1));
ASSERT_OK(taskC.PostDelayed(loopC->dispatcher(), zx::sec(1)));
async::PostDelayedTask(
loopA->dispatcher(), [&calledA] { calledA = true; }, zx::sec(2));
loop.RunUntilIdle();
EXPECT_FALSE(called);
EXPECT_FALSE(calledA);
EXPECT_TRUE(calledB);
EXPECT_FALSE(calledC);
taskC.Cancel();
loop.RunFor(zx::sec(1));
EXPECT_TRUE(called);
EXPECT_FALSE(calledA);
EXPECT_TRUE(calledB);
EXPECT_FALSE(calledC);
loop.RunFor(zx::sec(1));
EXPECT_TRUE(called);
EXPECT_TRUE(calledA);
EXPECT_TRUE(calledB);
EXPECT_FALSE(calledC);
}
TEST(TestLoopTest, WaitsAreDispatchedOnManyLoops) {
async::TestLoop loop;
auto loopA = loop.StartNewLoop();
auto loopB = loop.StartNewLoop();
auto loopC = loop.StartNewLoop();
async::Wait wait;
async::Wait waitA;
async::Wait waitB;
async::Wait waitC;
bool called = false;
bool calledA = false;
bool calledB = false;
bool calledC = false;
zx::event event;
ASSERT_EQ(ZX_OK, zx::event::create(0u, &event));
InitWait(
&wait, [&called] { called = true; }, event, ZX_USER_SIGNAL_0);
InitWait(
&waitA, [&calledA] { calledA = true; }, event, ZX_USER_SIGNAL_0);
InitWait(
&waitB, [&calledB] { calledB = true; }, event, ZX_USER_SIGNAL_0);
InitWait(
&waitC, [&calledC] { calledC = true; }, event, ZX_USER_SIGNAL_0);
ASSERT_OK(wait.Begin(loop.dispatcher()));
ASSERT_OK(waitA.Begin(loopA->dispatcher()));
ASSERT_OK(waitB.Begin(loopB->dispatcher()));
ASSERT_OK(waitC.Begin(loopC->dispatcher()));
ASSERT_OK(waitB.Cancel());
ASSERT_OK(event.signal(0u, ZX_USER_SIGNAL_0));
loop.RunUntilIdle();
EXPECT_TRUE(called);
EXPECT_TRUE(calledA);
EXPECT_FALSE(calledB);
EXPECT_TRUE(calledC);
}
// Populates |order| with the order in which two tasks and two waits on four
// loops were dispatched, given a |loop|.
void DetermineDispatchOrder(std::unique_ptr<async::TestLoop> loop, int (*order)[4]) {
auto loopA = loop->StartNewLoop();
auto loopB = loop->StartNewLoop();
auto loopC = loop->StartNewLoop();
async::Wait wait;
async::Wait waitB;
zx::event event;
int i = 0;
ASSERT_OK(zx::event::create(0u, &event));
InitWait(
&wait, [&] { (*order)[0] = ++i; }, event, ZX_USER_SIGNAL_0);
async::PostTask(loopA->dispatcher(), [&] { (*order)[1] = ++i; });
InitWait(
&waitB, [&] { (*order)[2] = ++i; }, event, ZX_USER_SIGNAL_0);
async::PostTask(loopC->dispatcher(), [&] { (*order)[3] = ++i; });
ASSERT_OK(wait.Begin(loop->dispatcher()));
ASSERT_OK(waitB.Begin(loopB->dispatcher()));
ASSERT_OK(event.signal(0u, ZX_USER_SIGNAL_0));
loop->RunUntilIdle();
EXPECT_EQ(4, i);
EXPECT_NE(0, (*order)[0]);
EXPECT_NE(0, (*order)[1]);
EXPECT_NE(0, (*order)[2]);
EXPECT_NE(0, (*order)[3]);
}
void SeedTestLoopWithEnv(uint32_t random_seed, std::unique_ptr<async::TestLoop>* loop) {
char buf[12];
snprintf(buf, sizeof(buf), "%u", random_seed);
EXPECT_EQ(0, setenv("TEST_LOOP_RANDOM_SEED", buf, 1));
*loop = std::make_unique<async::TestLoop>();
EXPECT_EQ(0, unsetenv("TEST_LOOP_RANDOM_SEED"));
}
void DispatchOrderIsDeterministicFor(uint32_t random_seed) {
int expected_order[4] = {0, 0, 0, 0};
std::unique_ptr<async::TestLoop> loop;
ASSERT_NO_FAILURES(SeedTestLoopWithEnv(random_seed, &loop));
ASSERT_NO_FAILURES(DetermineDispatchOrder(std::move(loop), &expected_order));
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 2; j++) {
int actual_order[4] = {0, 0, 0, 0};
if (j == 0) {
ASSERT_NO_FAILURES(SeedTestLoopWithEnv(random_seed, &loop));
} else {
loop = std::make_unique<async::TestLoop>(random_seed);
}
ASSERT_NO_FAILURES(DetermineDispatchOrder(std::move(loop), &actual_order));
EXPECT_EQ(expected_order[0], actual_order[0]);
EXPECT_EQ(expected_order[1], actual_order[1]);
EXPECT_EQ(expected_order[2], actual_order[2]);
EXPECT_EQ(expected_order[3], actual_order[3]);
}
}
}
TEST(TestLoopTest, DispatchOrderIsDeterministic) {
DispatchOrderIsDeterministicFor(1);
DispatchOrderIsDeterministicFor(43);
DispatchOrderIsDeterministicFor(893);
DispatchOrderIsDeterministicFor(39408);
DispatchOrderIsDeterministicFor(844018);
DispatchOrderIsDeterministicFor(83018299);
DispatchOrderIsDeterministicFor(3213);
DispatchOrderIsDeterministicFor(139133113);
DispatchOrderIsDeterministicFor(1323234373);
}
void BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(uint32_t random_seed) {
std::unique_ptr<async::TestLoop> loop;
ASSERT_NO_FAILURES(SeedTestLoopWithEnv(random_seed, &loop));
auto loopB = loop->StartNewLoop();
std::vector<int> elements;
async::PostTask(loop->dispatcher(), [&] {
async::PostTask(loopB->dispatcher(), [&] { elements.push_back(0); });
EXPECT_TRUE(loop->BlockCurrentSubLoopAndRunOthersUntil(
[&] { return elements.size() == 1 && elements[0] == 0; }));
elements.push_back(1);
});
loop->RunUntilIdle();
EXPECT_EQ(elements.size(), 2);
EXPECT_EQ(elements[0], 0);
EXPECT_EQ(elements[1], 1);
}
TEST(TestLoopTest, BlockCurrentSubLoopAndRunOthersUntilOtherLoop) {
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(1);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(43);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(893);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(39408);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(844018);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(83018299);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(3213);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(139133113);
BlockCurrentSubLoopAndRunOthersUntilOtherLoopFor(1323234373);
}
void BlocksFinishWhenOtherLoopQuitFor(uint32_t random_seed) {
std::unique_ptr<async::TestLoop> loop;
ASSERT_NO_FAILURES(SeedTestLoopWithEnv(random_seed, &loop));
auto loopB = loop->StartNewLoop();
auto loopC = loop->StartNewLoop();
async::PostTask(loop->dispatcher(), [&] { loop->Quit(); });
bool loopB_called = false;
async::PostTask(loopB->dispatcher(), [&] { loopB_called = true; });
bool block_current_subloop = false;
async::PostTask(loopC->dispatcher(), [&] {
block_current_subloop =
loop->BlockCurrentSubLoopAndRunOthersUntil([&] { return loopB_called; });
});
auto time = loop->Now();
loop->RunFor(zx::sec(1));
// Run until Idle for the case where the Quit() task is scheduled before the
// blocking task. This ensure that all task have been run, but doesn't advance
// the time.
loop->RunUntilIdle();
EXPECT_TRUE(loopB_called);
EXPECT_TRUE(block_current_subloop);
// Time should not have flown, as a Quit() has been posted.
EXPECT_EQ(time.get(), loop->Now().get());
}
TEST(TestLoopTest, BlocksFinishWhenOtherLoopQuit) {
BlocksFinishWhenOtherLoopQuitFor(1);
BlocksFinishWhenOtherLoopQuitFor(43);
BlocksFinishWhenOtherLoopQuitFor(893);
BlocksFinishWhenOtherLoopQuitFor(39408);
BlocksFinishWhenOtherLoopQuitFor(844018);
BlocksFinishWhenOtherLoopQuitFor(83018299);
BlocksFinishWhenOtherLoopQuitFor(3213);
BlocksFinishWhenOtherLoopQuitFor(139133113);
BlocksFinishWhenOtherLoopQuitFor(1323234373);
}
void BlocksWhileOtherLoopAdvanceTimeFor(uint32_t random_seed) {
std::unique_ptr<async::TestLoop> loop;
ASSERT_NO_FAILURES(SeedTestLoopWithEnv(random_seed, &loop));
auto loopB = loop->StartNewLoop();
auto initial_time = loop->Now();
bool block_current_subloop = false;
async::PostTask(loop->dispatcher(), [&] { loop->AdvanceTimeByEpsilon(); });
async::PostTask(loopB->dispatcher(), [&] {
block_current_subloop =
loop->BlockCurrentSubLoopAndRunOthersUntil([&] { return loop->Now() > initial_time; });
});
loop->RunUntilIdle();
EXPECT_TRUE(block_current_subloop);
EXPECT_TRUE(loop->Now() > initial_time);
}
TEST(TestLoopTest, BlocksWhileOtherLoopAdvanceTime) {
BlocksWhileOtherLoopAdvanceTimeFor(1);
BlocksWhileOtherLoopAdvanceTimeFor(43);
BlocksWhileOtherLoopAdvanceTimeFor(893);
BlocksWhileOtherLoopAdvanceTimeFor(39408);
BlocksWhileOtherLoopAdvanceTimeFor(844018);
BlocksWhileOtherLoopAdvanceTimeFor(83018299);
BlocksWhileOtherLoopAdvanceTimeFor(3213);
BlocksWhileOtherLoopAdvanceTimeFor(139133113);
BlocksWhileOtherLoopAdvanceTimeFor(1323234373);
}
// Test that non-async-dispatcher loops run fine.
struct ExternalLoop : async_test_subloop_t {
ExternalLoop() { ops = &kOps; }
// The loop keeps a state, that is repeatedly incremented.
// 0: advance to 1
// 1: wait until |time_ >= kState1Deadline|, advance to 2
// 2: advance to 3
// 3: blocked, needs to be manually advanced
// 4: advance to 5
// 5: done, do not increment
// 6: finalized
int state_ = 0;
// The current time, according to the TestLoop.
zx_time_t time_ = ZX_TIME_INFINITE_PAST;
constexpr static zx_time_t kState1Deadline = 1000;
constexpr static int kStateFinalized = 6;
// Returns the minimum time for the next transition starting from |state|.
// If |ZX_TIME_INFINITE| is returned, the state should not be advanced.
static zx_time_t NextTransitionTime(int state) {
switch (state) {
case 0:
case 2:
case 4:
// Advance immediately.
return ZX_TIME_INFINITE_PAST;
case 1:
return kState1Deadline;
case 3:
case 5:
return ZX_TIME_INFINITE;
default:
ZX_ASSERT(false);
}
}
static void advance_time_to(async_test_subloop_t* self_generic, zx_time_t time) {
ExternalLoop* self = static_cast<ExternalLoop*>(self_generic);
ZX_ASSERT(self->state_ != kStateFinalized);
static_cast<ExternalLoop*>(self)->time_ = time;
}
static uint8_t dispatch_next_due_message(async_test_subloop_t* self_generic) {
ExternalLoop* self = static_cast<ExternalLoop*>(self_generic);
ZX_ASSERT(self->state_ != kStateFinalized);
zx_time_t transition_time = NextTransitionTime(self->state_);
if (transition_time != ZX_TIME_INFINITE && transition_time <= self->time_) {
self->state_++;
return true;
} else {
return false;
}
}
static uint8_t has_pending_work(async_test_subloop_t* self_generic) {
ExternalLoop* self = static_cast<ExternalLoop*>(self_generic);
ZX_ASSERT(self->state_ != kStateFinalized);
zx_time_t transition_time = NextTransitionTime(self->state_);
return (transition_time != ZX_TIME_INFINITE && transition_time <= self->time_);
}
static zx_time_t get_next_task_due_time(async_test_subloop_t* self_generic) {
ExternalLoop* self = static_cast<ExternalLoop*>(self_generic);
ZX_ASSERT(self->state_ != kStateFinalized);
return NextTransitionTime(self->state_);
}
static void finalize(async_test_subloop_t* self_generic) {
ExternalLoop* self = static_cast<ExternalLoop*>(self_generic);
ZX_ASSERT(self->state_ != kStateFinalized);
self->state_ = kStateFinalized;
}
constexpr static async_test_subloop_ops_t kOps = {advance_time_to, dispatch_next_due_message,
has_pending_work, get_next_task_due_time,
finalize};
};
TEST(TestLoopTest, ExternalLoopIsRunAndFinalized) {
auto loop = std::make_unique<async::TestLoop>();
ExternalLoop subloop;
auto token = loop->RegisterLoop(&subloop);
EXPECT_TRUE(loop->RunUntilIdle());
EXPECT_EQ(1, subloop.state_);
EXPECT_TRUE(loop->RunUntil(zx::time(subloop.kState1Deadline)));
EXPECT_EQ(3, subloop.state_);
EXPECT_LE(subloop.kState1Deadline, subloop.time_);
subloop.state_ = 4;
EXPECT_TRUE(loop->RunUntilIdle());
EXPECT_EQ(5, subloop.state_);
token.reset();
EXPECT_EQ(subloop.kStateFinalized, subloop.state_);
}
TEST(TestLoopTest, BlockCurrentSubLoopAndRunOthersUntilTrue) {
async::TestLoop loop;
bool block_current_subloop = false;
async::PostTask(loop.dispatcher(), [&] {
block_current_subloop = loop.BlockCurrentSubLoopAndRunOthersUntil([] { return true; });
});
loop.RunUntilIdle();
EXPECT_TRUE(block_current_subloop);
}
TEST(TestLoopTest, BlockCurrentSubLoopAndRunOthersUntilFalse) {
async::TestLoop loop;
bool block_current_subloop = true;
async::PostTask(loop.dispatcher(), [&] {
block_current_subloop = loop.BlockCurrentSubLoopAndRunOthersUntil([] { return false; });
});
loop.RunUntilIdle();
EXPECT_FALSE(block_current_subloop);
}
struct Task {
async_task_t task;
bool handled = false;
};
void ReentrantHandler(async_dispatcher_t* dispatcher, async_task_t* task, zx_status_t result) {
// This is reentrant.
auto* typed_task = reinterpret_cast<Task*>(task);
typed_task->handled = true;
async::Now(dispatcher);
}
// This test just verifies that reentrant tasks dont hang and that proper lock releases exists.
TEST(TestLoopTest, TaskHandlerIsReentrant) {
async::TestLoop loop;
auto task_factory = [] {
Task task;
task.task.state = ASYNC_STATE_INIT;
task.task.handler = &ReentrantHandler;
return task;
};
auto dispatched_task = task_factory();
// Dispatching it
EXPECT_OK(async_post_task(loop.dispatcher(), &dispatched_task.task));
loop.RunUntilIdle();
EXPECT_TRUE(dispatched_task.handled);
// Cancelling it.
auto cancelled_task = task_factory();
EXPECT_OK(async_post_task(loop.dispatcher(), &cancelled_task.task));
EXPECT_OK(async_cancel_task(loop.dispatcher(), &cancelled_task.task));
// On Shutdown
auto shutdown_task = task_factory();
{
async::TestLoop shutdown_loop;
EXPECT_OK(async_post_task(shutdown_loop.dispatcher(), &shutdown_task.task));
}
EXPECT_TRUE(shutdown_task.handled);
}
void ReentrantWaitHandler(async_dispatcher_t* dispatcher, async_wait_t* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
async::Now(dispatcher);
}
TEST(TestLoopTest, WaitHandlerIsReentrant) {
async::TestLoop loop;
zx::event event;
ASSERT_OK(zx::event::create(0, &event));
auto wait_factory = [&event] {
async_wait_t wait;
wait.state = ASYNC_STATE_INIT;
wait.handler = &ReentrantWaitHandler;
wait.object = event.borrow()->get();
wait.trigger = ZX_USER_SIGNAL_0;
wait.options = 0;
return wait;
};
auto dispatched_wait = wait_factory();
// Dispatching activated wait.
ASSERT_OK(async_begin_wait(loop.dispatcher(), &dispatched_wait));
event.signal(0, ZX_USER_SIGNAL_0);
loop.RunUntilIdle();
// Cancelling it.
auto cancelled_wait = wait_factory();
ASSERT_OK(async_begin_wait(loop.dispatcher(), &cancelled_wait));
ASSERT_OK(async_cancel_wait(loop.dispatcher(), &cancelled_wait));
loop.RunUntilIdle();
// On Shutdown
auto shutdown_wait = wait_factory();
{
async::TestLoop shutdown_loop;
ASSERT_OK(async_begin_wait(shutdown_loop.dispatcher(), &shutdown_wait));
}
}
} // namespace