| // 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 |