blob: 1c05c7ce4bbedb6a05f6b557daa1e5d20041f00c [file] [log] [blame] [edit]
// 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.
// TODO(joshuseaton): Once std lands in Zircon, simplify everything below.
#include <lib/async-testing/test_loop.h>
#include <lib/async-testing/test_loop_dispatcher.h>
#include <lib/async/default.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <utility>
namespace async {
namespace {
// Deterministically updates |m| to point to a pseudo-random number.
void Randomize(uint32_t* m) {
uint32_t n = *m;
n ^= (n << 13);
n ^= (n >> 17);
n ^= (n << 5);
*m = n;
}
// Generates a random seed if the environment variable TEST_LOOP_RANDOM_SEED
// is unset; else returns the value of the latter.
uint32_t GetRandomSeed() {
uint32_t random_seed;
const char* preset = getenv("TEST_LOOP_RANDOM_SEED");
if (preset) {
size_t preset_length = strlen(preset);
char* endptr = nullptr;
long unsigned preset_seed = strtoul(preset, &endptr, 10);
ZX_ASSERT_MSG(preset_seed > 0 && endptr == preset + preset_length,
"ERROR: \"%s\" does not give a valid random seed\n", preset);
random_seed = static_cast<uint32_t>(preset_seed);
} else {
zx_cprng_draw(&random_seed, sizeof(uint32_t));
}
return random_seed;
}
} // namespace
class TestLoop::TestSubloop {
public:
explicit TestSubloop(async_test_subloop_t* subloop) : subloop_(subloop) {}
void AdvanceTimeTo(zx::time time) {
return subloop_->ops->advance_time_to(subloop_.get(), time.get());
}
bool DispatchNextDueMessage() { return subloop_->ops->dispatch_next_due_message(subloop_.get()); }
bool HasPendingWork() { return subloop_->ops->has_pending_work(subloop_.get()); }
zx::time GetNextTaskDueTime() {
return zx::time(subloop_->ops->get_next_task_due_time(subloop_.get()));
}
async_test_subloop_t* get() { return subloop_.get(); }
private:
struct SubloopDeleter {
void operator()(async_test_subloop_t* loop) { loop->ops->finalize(loop); }
};
std::unique_ptr<async_test_subloop_t, SubloopDeleter> subloop_;
};
class TestLoop::TestSubloopToken : public SubloopToken {
public:
TestSubloopToken(TestLoop* loop, async_test_subloop_t* subloop)
: loop_(loop), subloop_(subloop) {}
~TestSubloopToken() override {
auto& subloops = loop_->subloops_;
for (auto iterator = subloops.begin(); iterator != subloops.end(); iterator++) {
if (iterator->get() == subloop_) {
subloops.erase(iterator);
break;
}
}
}
private:
TestLoop* const loop_;
async_test_subloop_t* subloop_;
};
class TestLoop::TestLoopInterface : public LoopInterface {
public:
TestLoopInterface(std::unique_ptr<SubloopToken> token, async_dispatcher_t* dispatcher)
: token_(std::move(token)), dispatcher_(dispatcher) {}
~TestLoopInterface() override = default;
async_dispatcher_t* dispatcher() override { return dispatcher_; }
private:
std::unique_ptr<SubloopToken> token_;
async_dispatcher_t* dispatcher_;
};
TestLoop::TestLoop() : TestLoop(0) {}
TestLoop::TestLoop(uint32_t state)
: initial_state_((state != 0) ? state : GetRandomSeed()), state_(initial_state_) {
default_loop_ = StartNewLoop();
default_dispatcher_ = default_loop_->dispatcher();
async_set_default_dispatcher(default_dispatcher_);
printf("\nTEST_LOOP_RANDOM_SEED=\"%u\"\n", initial_state_);
}
TestLoop::~TestLoop() { async_set_default_dispatcher(nullptr); }
async_dispatcher_t* TestLoop::dispatcher() { return default_dispatcher_; }
bool TestLoop::BlockCurrentSubLoopAndRunOthersUntil(fit::function<bool()> condition) {
ZX_ASSERT(is_running_);
ZX_ASSERT(!IsLockedSubLoop(current_subloop_));
locked_subloops_.push_back(current_subloop_);
bool success = false;
// Store initial deadline.
auto initial_deadline = deadline_;
// Control advancing time. It is necessary to prevent Run() from advancing the
// time if |condition()| becomes true in the current run.
deadline_ = std::min(Now(), initial_deadline);
while (!success) {
// Run tasks, which may advance the current time up to |deadline_| but no further.
bool did_work = Run();
success = condition();
if (!did_work) {
// No work happened and the loop caught up with its deadline, no more
// event should be handled.
if (initial_deadline <= Now()) {
break;
}
// Advance the time to the next task due time.
deadline_ = std::min(GetNextTaskDueTime(), initial_deadline);
}
}
// Restore the initial deadline.
ZX_ASSERT(deadline_ <= initial_deadline);
deadline_ = initial_deadline;
ZX_ASSERT(locked_subloops_.back() == current_subloop_);
locked_subloops_.pop_back();
return success;
}
std::unique_ptr<LoopInterface> TestLoop::StartNewLoop() {
async_dispatcher_t* dispatcher_interface;
async_test_subloop_t* subloop;
NewTestLoopDispatcher(&dispatcher_interface, &subloop);
return std::make_unique<TestLoopInterface>(RegisterLoop(subloop), dispatcher_interface);
}
std::unique_ptr<SubloopToken> TestLoop::RegisterLoop(async_test_subloop_t* subloop) {
TestSubloop wrapped_subloop{subloop};
wrapped_subloop.AdvanceTimeTo(Now());
subloops_.push_back(std::move(wrapped_subloop));
return std::make_unique<TestSubloopToken>(this, subloop);
}
zx::time TestLoop::Now() const { return current_time_; }
void TestLoop::Quit() { has_quit_ = true; }
void TestLoop::AdvanceTimeByEpsilon() { AdvanceTimeTo(Now() + zx::duration(1)); }
bool TestLoop::RunUntil(zx::time deadline) {
ZX_ASSERT(!is_running_);
is_running_ = true;
deadline_ = deadline;
bool did_work = Run();
has_quit_ = false;
is_running_ = false;
return did_work;
}
bool TestLoop::RunFor(zx::duration duration) { return RunUntil(Now() + duration); }
bool TestLoop::RunUntilIdle() { return RunUntil(Now()); }
bool TestLoop::HasPendingWork() {
for (auto& subloop : subloops_) {
if (IsLockedSubLoop(&subloop)) {
continue;
}
if (subloop.HasPendingWork()) {
return true;
}
}
return false;
}
zx::time TestLoop::GetNextTaskDueTime() {
zx::time next_due_time = zx::time::infinite();
for (auto& subloop : subloops_) {
if (IsLockedSubLoop(&subloop)) {
continue;
}
next_due_time = std::min<zx::time>(next_due_time, subloop.GetNextTaskDueTime());
}
return next_due_time;
}
void TestLoop::AdvanceTimeTo(zx::time time) {
if (current_time_ < time) {
current_time_ = time;
for (auto& subloop : subloops_) {
subloop.AdvanceTimeTo(time);
}
}
}
bool TestLoop::IsLockedSubLoop(TestSubloop* subloop) {
return std::find(locked_subloops_.begin(), locked_subloops_.end(), subloop) !=
locked_subloops_.end();
}
bool TestLoop::Run() {
TestSubloop* initial_loop = current_subloop_;
bool did_work = false;
while (!has_quit_ || !locked_subloops_.empty()) {
if (!HasPendingWork()) {
zx::time next_due_time = GetNextTaskDueTime();
if (next_due_time > deadline_) {
AdvanceTimeTo(deadline_);
break;
}
AdvanceTimeTo(next_due_time);
}
Randomize(&state_);
size_t current_index = state_ % subloops_.size();
current_subloop_ = &subloops_[current_index];
if (IsLockedSubLoop(current_subloop_)) {
if (current_subloop_ == initial_loop) {
did_work = true;
break;
}
continue;
}
did_work |= current_subloop_->DispatchNextDueMessage();
}
current_subloop_ = initial_loop;
return did_work;
}
} // namespace async