blob: 7ce12dab884d063160bd4bbf17b274986fe50871 [file] [log] [blame] [edit]
// Copyright 2023 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/cpp/task.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/fdf/env.h>
#include <lib/zx/clock.h>
#include <zircon/assert.h>
#include <zircon/status.h>
namespace fdf_testing {
namespace {
thread_local DriverRuntime* g_instance = nullptr;
} // namespace
namespace internal {
DriverRuntimeEnv::DriverRuntimeEnv() {
zx_status_t status = fdf_env_start();
ZX_ASSERT_MSG(ZX_OK == status, "Failed to initialize driver runtime env: %s",
zx_status_get_string(status));
}
DriverRuntimeEnv::~DriverRuntimeEnv() { fdf_env_reset(); }
} // namespace internal
DriverRuntime::DriverRuntime()
: initial_thread_id(std::this_thread::get_id()),
foreground_dispatcher_(fdf_internal::kDispatcherDefault) {
ZX_ASSERT_MSG(g_instance == nullptr,
"Cannot create more than one instance of DriverRuntime at a time.");
g_instance = this;
}
DriverRuntime::~DriverRuntime() {
AssertCurrentThreadIsInitialThread();
g_instance = nullptr;
}
DriverRuntime* DriverRuntime::GetInstance() { return g_instance; }
fdf::UnownedSynchronizedDispatcher DriverRuntime::StartBackgroundDispatcher() {
AssertCurrentThreadIsInitialThread();
fdf_internal::TestSynchronizedDispatcher& dispatcher =
background_dispatchers_.emplace_back(fdf_internal::kDispatcherManaged);
return dispatcher.driver_dispatcher().borrow();
}
void DriverRuntime::Run() {
AssertCurrentThreadIsInitialThread();
RunInternal();
ResetQuit();
}
bool DriverRuntime::RunWithTimeout(zx::duration timeout) {
AssertCurrentThreadIsInitialThread();
// This cannot be a local variable because the delayed task below can execute
// after this function returns.
auto canceled = std::make_shared<bool>(false);
bool timed_out = false;
async::PostDelayedTask(
foreground_dispatcher_.dispatcher(),
[this, canceled, &timed_out] {
if (*canceled) {
return;
}
timed_out = true;
Quit();
},
timeout);
RunInternal();
ResetQuit();
// Another task can call Quit() on the message loop, which exits the
// message loop before the delayed task executes, in which case |timed_out| is
// still false here because the delayed task hasn't run yet.
// Since the message loop isn't destroyed then (as it usually would after
// Quit()), and presumably can be reused after this function returns we
// still need to prevent the delayed task to quit it again at some later time
// using the canceled pointer.
if (!timed_out) {
*canceled = true;
}
return timed_out;
}
void DriverRuntime::RunUntil(fit::function<bool()> condition, zx::duration step) {
AssertCurrentThreadIsInitialThread();
RunWithTimeoutOrUntil(std::move(condition), zx::duration::infinite(), step);
}
bool DriverRuntime::RunWithTimeoutOrUntil(fit::function<bool()> condition, zx::duration timeout,
zx::duration step) {
AssertCurrentThreadIsInitialThread();
const zx::time timeout_deadline = zx::deadline_after(timeout);
while (zx::clock::get_monotonic() < timeout_deadline) {
if (condition()) {
ResetQuit();
return true;
}
if (step == zx::duration::infinite()) {
// Performs a single unit of work, possibly blocking until there is work
// to do or the timeout deadline arrives.
RunInternal(timeout_deadline, true);
} else {
// Performs work until the step deadline arrives.
RunWithTimeout(step);
}
}
ResetQuit();
return condition();
}
void DriverRuntime::RunUntilIdle() {
AssertCurrentThreadIsInitialThread();
RunUntilIdleInternal();
ResetQuit();
}
void DriverRuntime::Quit() { fdf_testing_quit(); }
zx_status_t DriverRuntime::ResetQuit() { return fdf_testing_reset_quit(); }
fit::closure DriverRuntime::QuitClosure() {
return []() { fdf_testing_quit(); };
}
zx_status_t DriverRuntime::RunUntilIdleInternal() {
AssertCurrentThreadIsInitialThread();
return fdf_testing_run_until_idle();
}
zx_status_t DriverRuntime::RunInternal(zx::time deadline, bool once) {
AssertCurrentThreadIsInitialThread();
return fdf_testing_run(deadline.get(), once);
}
void DriverRuntime::AssertCurrentThreadIsInitialThread() {
ZX_ASSERT_MSG(std::this_thread::get_id() == initial_thread_id, "%s",
"This class is thread-unsafe. Call only allowed from the main test thread.");
}
} // namespace fdf_testing