blob: 4cd32cb54716c9b8d9c11f251e8639556b840335 [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.
// TODO(joshuseaton): Once std lands in Zircon, simplify everything below.
#include <lib/async-testutils/test_loop.h>
#include <stdlib.h>
#include <fbl/algorithm.h>
#include <lib/async-testutils/time-keeper.h>
#include <lib/async/default.h>
#include <lib/zircon-internal/xorshiftrand.h>
#include <zircon/syscalls.h>
#include <utility>
namespace async {
namespace {
// Determinisitically updates |m| to point to a pseudo-random number.
void Randomize(uint32_t* m) {
rand32_t r = { .n = *m };
*m = rand32(&r);
}
// 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::TestLoopTimeKeeper : public TimeKeeper {
public:
TestLoopTimeKeeper() = default;
~TestLoopTimeKeeper() = default;
zx::time Now() const override { return current_time_; }
void RegisterTimer(zx::time deadline, TimerDispatcher* dispatcher) override {
if (deadline <= current_time_) {
dispatcher->FireTimer();
return;
}
for (auto& entry : table_) {
if (entry.dispatcher == dispatcher) {
entry.deadlines.push_back(deadline);
return;
}
}
DeadlinesByDispatcher entry{};
entry.dispatcher = dispatcher;
entry.deadlines.push_back(deadline);
table_.push_back(std::move(entry));
}
void CancelTimers(TimerDispatcher* dispatcher) override {
size_t ind = 0;
for (; ind < table_.size(); ++ind){
if (table_[ind].dispatcher == dispatcher){
table_.erase(ind);
return;
}
}
}
void AdvanceTimeTo(zx::time time) {
if (time < current_time_) { return; }
current_time_ = time;
for (auto& entry : table_) {
if (entry.deadlines.size() == 0) {
continue;
}
zx::time min_deadline =
*fbl::min_element(entry.deadlines.begin(), entry.deadlines.end());
if (min_deadline > time){
continue;
}
entry.dispatcher->FireTimer();
size_t end = entry.deadlines.size() - 1;
for (size_t i = 0; i <= entry.deadlines.size(); ++i){
zx::time back = entry.deadlines[end];
entry.deadlines.pop_back();
if (back > time) {
entry.deadlines.insert(0, back);
continue;
}
--end;
}
}
}
private:
struct DeadlinesByDispatcher {
TimerDispatcher* dispatcher;
fbl::Vector<zx::time> deadlines;
};
zx::time current_time_;
fbl::Vector<DeadlinesByDispatcher> table_;
};
class TestLoop::TestLoopInterface : public LoopInterface {
public:
TestLoopInterface(TestLoop* loop, TestLoopDispatcher* dispatcher)
: loop_(loop), dispatcher_(dispatcher) {}
~TestLoopInterface() override {
auto& dispatchers = loop_->dispatchers_;
for (size_t index = 0; index < dispatchers.size(); ++index) {
if (dispatchers[index].get() == dispatcher_) {
dispatchers.erase(index);
break;
}
}
dispatcher_ = nullptr;
}
async_dispatcher_t* dispatcher() override { return dispatcher_; }
private:
TestLoop* const loop_;
TestLoopDispatcher* dispatcher_;
};
TestLoop::TestLoop() : TestLoop(0) {}
TestLoop::TestLoop(uint32_t state)
: time_keeper_(new TestLoopTimeKeeper()),
initial_state_((state != 0)? state : GetRandomSeed()), state_(initial_state_) {
dispatchers_.push_back(fbl::make_unique<TestLoopDispatcher>(time_keeper_.get()));
async_set_default_dispatcher(dispatchers_[0].get());
printf("\nTEST_LOOP_RANDOM_SEED=\"%u\"\n", initial_state_);
}
TestLoop::~TestLoop() {
async_set_default_dispatcher(nullptr);
}
async_dispatcher_t* TestLoop::dispatcher() {
return dispatchers_[0].get();
}
fbl::unique_ptr<LoopInterface> TestLoop::StartNewLoop() {
dispatchers_.push_back(fbl::make_unique<TestLoopDispatcher>(time_keeper_.get()));
auto* new_dispatcher = dispatchers_[dispatchers_.size() - 1].get();
return fbl::make_unique<TestLoopInterface>(this, new_dispatcher);
}
zx::time TestLoop::Now() const {
return time_keeper_->Now();
}
void TestLoop::Quit() {
has_quit_ = true;
}
void TestLoop::AdvanceTimeByEpsilon() {
time_keeper_->AdvanceTimeTo(Now() + zx::duration(1));
}
bool TestLoop::RunUntil(zx::time deadline) {
ZX_ASSERT(!is_running_);
is_running_ = true;
bool did_work = false;
while (!has_quit_) {
if (!HasPendingWork()) {
zx::time next_due_time = GetNextTaskDueTime();
if (next_due_time > deadline) {
time_keeper_->AdvanceTimeTo(deadline);
break;
}
time_keeper_->AdvanceTimeTo(next_due_time);
}
Randomize(&state_);
size_t current_index = state_ % dispatchers_.size();
auto& current_dispatcher = dispatchers_[current_index];
async_set_default_dispatcher(current_dispatcher.get());
did_work |= current_dispatcher->DispatchNextDueMessage();
async_set_default_dispatcher(dispatchers_[0].get());
}
is_running_ = false;
has_quit_ = 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& dispatcher : dispatchers_) {
if (dispatcher->HasPendingWork()) { return true; }
}
return false;
}
zx::time TestLoop::GetNextTaskDueTime() const {
zx::time next_due_time = zx::time::infinite();
for (auto& dispatcher : dispatchers_) {
next_due_time =
fbl::min<zx::time>(next_due_time, dispatcher->GetNextTaskDueTime());
}
return next_due_time;
}
} // namespace async