blob: 139241315b8e4dd2ab8935c7c1a7c99c383e69ae [file] [log] [blame]
// 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/cpp/env.h>
#include <lib/fdf/env.h>
#include <lib/zx/clock.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <atomic>
#include <unordered_set>
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_destroy_all_dispatchers();
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();
ShutdownAllDispatchers(nullptr);
g_instance = nullptr;
}
DriverRuntime* DriverRuntime::GetInstance() { return g_instance; }
fdf::UnownedSynchronizedDispatcher DriverRuntime::StartBackgroundDispatcher() {
AssertCurrentThreadIsInitialThread();
ZX_ASSERT_MSG(!is_shutdown_,
"Cannot start any more dispatchers after calling ShutdownAllDispatchers.");
fdf_internal::TestSynchronizedDispatcher& dispatcher =
background_dispatchers_.emplace_back(fdf_internal::kDispatcherManaged);
return dispatcher.driver_dispatcher().borrow();
}
void DriverRuntime::ShutdownAllDispatchers(fdf_dispatcher_t* dut_initial_dispatcher) {
AssertCurrentThreadIsInitialThread();
if (is_shutdown_) {
fdf_testing_set_default_dispatcher(dut_initial_dispatcher);
return;
}
std::unordered_set<const void*> dispatcher_owners;
for (auto& bg_dispatcher : background_dispatchers_) {
const void* owner = bg_dispatcher.owner();
auto [_iter, inserted] = shutdown_background_dispatcher_owners_.insert(owner);
if (inserted) {
dispatcher_owners.insert(owner);
}
}
dispatcher_owners.insert(foreground_dispatcher_->owner());
std::atomic_size_t counter = dispatcher_owners.size();
for (const void* owner : dispatcher_owners) {
auto shutdown = std::make_unique<fdf_env::DriverShutdown>();
auto shutdown_ptr = shutdown.get();
auto shutdown_callback = [shutdown = std::move(shutdown),
&counter](const void* shutdown_driver) { counter -= 1; };
zx_status_t status = shutdown_ptr->Begin(owner, std::move(shutdown_callback));
if (status != ZX_OK) {
counter -= 1;
}
}
while (counter != 0) {
RunUntilIdle();
}
fdf_testing_set_default_dispatcher(dut_initial_dispatcher);
is_shutdown_ = true;
}
void DriverRuntime::ResetForegroundDispatcher(fit::closure post_shutdown) {
AssertCurrentThreadIsInitialThread();
std::atomic_bool done = false;
auto shutdown = std::make_unique<fdf_env::DriverShutdown>();
auto shutdown_ptr = shutdown.get();
auto shutdown_callback = [shutdown = std::move(shutdown),
post_shutdown = std::move(post_shutdown),
&done](const void* shutdown_driver) mutable {
post_shutdown();
done = true;
};
zx_status_t status =
shutdown_ptr->Begin(foreground_dispatcher_->owner(), std::move(shutdown_callback));
if (status != ZX_OK) {
done = true;
}
while (!done) {
RunUntilIdle();
}
foreground_dispatcher_.reset();
foreground_dispatcher_.emplace(fdf_internal::kDispatcherDefault);
}
void DriverRuntime::ShutdownBackgroundDispatcher(fdf_dispatcher_t* dispatcher,
fit::closure post_shutdown) {
AssertCurrentThreadIsInitialThread();
const void* owner = nullptr;
for (auto& bg_dispatcher : background_dispatchers_) {
if (bg_dispatcher.driver_dispatcher().get() == dispatcher) {
owner = bg_dispatcher.owner();
auto [_iter, inserted] = shutdown_background_dispatcher_owners_.insert(owner);
ZX_ASSERT_MSG(inserted, "Cannot shutdown the dispatcher twice.");
break;
}
}
ZX_ASSERT_MSG(owner != nullptr, "Did not find background dispatcher.");
std::atomic_bool done = false;
auto shutdown = std::make_unique<fdf_env::DriverShutdown>();
auto shutdown_ptr = shutdown.get();
auto shutdown_callback = [shutdown = std::move(shutdown),
post_shutdown = std::move(post_shutdown),
&done](const void* shutdown_driver) mutable {
post_shutdown();
done = true;
};
zx_status_t status = shutdown_ptr->Begin(owner, std::move(shutdown_callback));
if (status != ZX_OK) {
done = true;
}
while (!done) {
RunUntilIdle();
}
}
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() {
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();
ZX_ASSERT_MSG(!is_shutdown_, "Cannot Run after calling ShutdownAllDispatchers.");
return fdf_testing_run_until_idle();
}
zx_status_t DriverRuntime::RunInternal(zx::time deadline, bool once) {
AssertCurrentThreadIsInitialThread();
ZX_ASSERT_MSG(!is_shutdown_, "Cannot Run after calling ShutdownAllDispatchers.");
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