|  | // 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> | 
|  |  | 
|  | 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)); | 
|  | } | 
|  |  | 
|  | printf("\nTEST_LOOP_RANDOM_SEED=\"%u\"\n", random_seed); | 
|  | 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(fbl::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() | 
|  | : time_keeper_(new TestLoopTimeKeeper()), initial_state_(GetRandomSeed()), state_(initial_state_) { | 
|  | dispatchers_.push_back(fbl::make_unique<TestLoopDispatcher>(time_keeper_.get())); | 
|  | async_set_default_dispatcher(dispatchers_[0].get()); | 
|  | } | 
|  |  | 
|  | 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 |