| // 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. |
| |
| #ifndef LIB_DRIVER_TESTING_CPP_DRIVER_RUNTIME_H_ |
| #define LIB_DRIVER_TESTING_CPP_DRIVER_RUNTIME_H_ |
| |
| #include <lib/async/cpp/executor.h> |
| #include <lib/driver/runtime/testing/cpp/internal/dispatcher.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fit/function.h> |
| #include <lib/fpromise/bridge.h> |
| |
| #include <list> |
| #include <set> |
| |
| namespace fdf_testing { |
| |
| namespace internal { |
| |
| // Starts the runtime environment on creation, and resets it on destruction. |
| class DriverRuntimeEnv final { |
| public: |
| DriverRuntimeEnv(); |
| ~DriverRuntimeEnv(); |
| }; |
| |
| } // namespace internal |
| |
| // |DriverRuntime| is a RAII client to the driver runtime for a unit test. It can be used to |
| // start background dispatchers, and run the foreground dispatcher. On destruction it will shutdown |
| // all created dispatchers, and reset the driver runtime. |
| // |
| // There can only be one active instance of this class during a test case. The instance is commonly |
| // the first member of a test fixture class. After constructing an instance of |DriverRuntime|, |
| // other code may get the instance via the static |GetInstance| accessor. |
| // |
| // # Thread safety |
| // |
| // This class is thread-unsafe. Instances must be created and used from the unit test's main thread. |
| // The exception to this is the |Quit| and |ResetQuit| functions which can be called from any |
| // thread. |
| class DriverRuntime final { |
| public: |
| // This class wraps a fpromise::promise for an asynchronous task. This prevents the user from |
| // being exposed directly to the fpromise type inside. |
| // Use |DriverRuntime::RunToCompletion| to get the result of the task. |
| template <typename ResultType> |
| class [[nodiscard]] AsyncTask { |
| public: |
| explicit AsyncTask(fpromise::promise<ResultType> promise) : promise_(std::move(promise)) {} |
| |
| private: |
| friend DriverRuntime; |
| fpromise::promise<ResultType> promise_; |
| }; |
| |
| // Starts the driver runtime environment. If the env fails to start, this will throw an assert. |
| // This will also create and attach a foreground driver dispatcher to the current thread. |
| // Foreground dispatchers will not be ran in the background on the driver runtime's managed |
| // thread pool, but instead must be run using the Run* methods. |
| explicit DriverRuntime(); |
| |
| // Resets the driver runtime environment. |
| ~DriverRuntime(); |
| |
| // Gets the active instance of the DriverRuntime. The instance is stored in thread_local storage. |
| static DriverRuntime* GetInstance(); |
| |
| // Returns the current foreground driver dispatcher. |
| fdf::UnownedSynchronizedDispatcher GetForegroundDispatcher(); |
| |
| // Starts a background driver dispatcher and returns it. Background dispatchers are automatically |
| // ran on the managed thread pool inside the driver runtime. Objects that live on background |
| // dispatchers are not safe to access directly from a test, but instead must be wrapped inside of |
| // an |async_patterns::TestDispatcherBound|. |
| fdf::UnownedSynchronizedDispatcher StartBackgroundDispatcher(); |
| |
| // Shuts down all of the driver dispatchers that exist. This will return when all shutdowns |
| // have completed. If this has already been done returns immediately. |
| // |
| // |dut_initial_dispatcher| will be set as the current dispatcher context before this returns |
| // to help with synchronization_checkers inside of the driver during teardown afterwards. |
| // It should be the same dispatcher that the dut (driver under test) was created with. |
| // If a nullptr is given then there will not be a current dispatcher context after this returns. |
| void ShutdownAllDispatchers(fdf_dispatcher_t* dut_initial_dispatcher); |
| |
| // Shuts down the foreground dispatcher and runs the given closure afterwards in the context of |
| // the dispatcher. Creates a new foreground dispatcher to replace it. |
| // Returns when the shutdown has completed fully and the new dispatcher is created. |
| void ResetForegroundDispatcher(fit::closure post_shutdown); |
| |
| // Shuts down the given dispatcher and runs the given closure afterwards in the context of |
| // the dispatcher. Returns when the shutdown has completed fully. |
| void ShutdownBackgroundDispatcher(fdf_dispatcher_t* dispatcher, fit::closure post_shutdown); |
| |
| // Runs the foreground dispatcher until it is quit. |
| void Run(); |
| |
| // Runs the foreground dispatcher for at most |timeout|. Returns |true| if the timeout has been |
| // reached before it is quit. |
| bool RunWithTimeout(zx::duration timeout = zx::sec(1)); |
| |
| // Runs the foreground dispatcher until the condition returns true. |
| // |
| // Providing this for parity with async::Loop. Avoid this when possible as it is a bad pattern, |
| // the condition callback may have data race and use after free problems. |
| // Prefer to use the |Run| and |Quit| combination, or the |RunUntilIdle| call. |
| // |
| // |step| specifies the interval at which this method should wake up to poll |
| // |condition|. If |step| is |zx::duration::infinite()|, no polling timer is |
| // set. Instead, the condition is checked initially and after anything happens |
| // on the dispatcher (e.g. a task executes). This is useful when the caller knows |
| // that |condition| will be made true by a task running on the dispatcher. This will |
| // generally be the case unless |condition| is made true on a different |
| // thread. |
| void RunUntil(fit::function<bool()> condition, zx::duration step = zx::msec(10)); |
| |
| // Runs the foreground dispatcher until the condition returns true or the timeout is reached. |
| // Returns |true| if the condition was met, and |false| if the timeout was |
| // reached. |
| // |
| // Providing this for parity with async::Loop. Avoid this when possible as it is a bad pattern, |
| // the condition callback may have data race and use after free problems. |
| // Prefer to use the |RunWithTimeout| and |Quit| combination, or the |RunUntilIdle| call. |
| // |
| // |step| specifies the interval at which this method should wake up to poll |
| // |condition|. If |step| is |zx::duration::infinite()|, no polling timer is |
| // set. Instead, the condition is checked initially and after anything happens |
| // on the dispatcher (e.g. a task executes). This is useful when the caller knows |
| // that |condition| will be made true by a task running on the dispatcher. This will |
| // generally be the case unless |condition| is made true on a different |
| // thread. |
| bool RunWithTimeoutOrUntil(fit::function<bool()> condition, zx::duration timeout = zx::sec(1), |
| zx::duration step = zx::msec(10)); |
| |
| // Runs the foreground dispatcher until idle. |
| void RunUntilIdle(); |
| |
| // Runs the foreground dispatcher until the |task| has completed. |
| // Returns the result of the |task| when complete. |
| template <typename Result> |
| Result RunToCompletion(AsyncTask<Result> async_task) { |
| AssertCurrentThreadIsInitialThread(); |
| return RunPromise(std::move(async_task.promise_)).take_value(); |
| } |
| |
| // Runs the foreground dispatcher until the given |fpromise::promise| completes, and returns the |
| // |fpromise::result| it produced. |
| // |
| // If the |fpromise::promise| never completes, this method will run forever. |
| template <typename PromiseType> |
| typename PromiseType::result_type RunPromise(PromiseType promise); |
| |
| // Runs the foreground dispatcher while spawning a new thread to perform |work|, and |
| // returns the result of calling |work|. Runs the foreground dispatcher until |work| returns. |
| // |
| // |Callable| should take zero arguments. |
| template <typename Callable> |
| auto PerformBlockingWork(Callable&& work) { |
| using R = std::invoke_result_t<Callable>; |
| if constexpr (std::is_same_v<R, void>) { |
| // Use an |std::monostate| such that |PerformBlockingWorkImpl| does not |
| // have to contend with void values which are tricky to work with. |
| PerformBlockingWorkImpl<std::monostate>([work = std::forward<Callable>(work)]() mutable { |
| work(); |
| return std::monostate{}; |
| }); |
| return; |
| } else { |
| return PerformBlockingWorkImpl<R>(std::forward<Callable>(work)); |
| } |
| } |
| |
| // Quits the foreground dispatcher. |
| // Active invocations of |Run| will eventually terminate upon completion of their |
| // current unit of work. |
| // |
| // Subsequent calls to |Run| will return immediately until |ResetQuit| is called. |
| void Quit(); |
| |
| // Resets the quit state of the foreground dispatcher so that it can be restarted |
| // using |Run|. |
| // |
| // This function must only be called when the foreground dispatcher is not running. |
| // The caller must ensure all active invocations of |Run| have terminated before |
| // resetting the quit state. |
| // |
| // Returns |ZX_OK| if the foreground dispatcher's quit state was correctly reset. |
| // Returns |ZX_ERR_BAD_STATE| if the foreground dispatcher was shutting down or if |
| // it was currently actively running |
| zx_status_t ResetQuit(); |
| |
| // Creates a closure that calls |Quit|. |
| fit::closure QuitClosure(); |
| |
| private: |
| // Runs the foreground dispatcher. Dispatches events until there are none remaining, and then |
| // returns without waiting. This is useful for unit testing, because the behavior doesn't depend |
| // on time. |
| // |
| // Returns |ZX_OK| if the dispatcher reaches an idle state. |
| // Returns |ZX_ERR_CANCELED| if it is quit. |
| zx_status_t RunUntilIdleInternal(); |
| |
| // Runs the foreground dispatcher. |
| // |
| // Dispatches events until the |deadline| expires or it is quit. |
| // Use |ZX_TIME_INFINITE| to dispatch events indefinitely. |
| // |
| // If |once| is true, performs a single unit of work then returns. |
| // |
| // Returns |ZX_OK| if the dispatcher returns after one cycle. |
| // Returns |ZX_ERR_TIMED_OUT| if the deadline expired. |
| // Returns |ZX_ERR_CANCELED| if it quit. |
| zx_status_t RunInternal(zx::time deadline = zx::time::infinite(), bool once = false); |
| |
| template <typename Result, typename Callable> |
| Result PerformBlockingWorkImpl(Callable&& work); |
| |
| // Asserts that the current thread is the same as the initial thread. Used to enforce |
| // calls are only made from the main test thread. |
| void AssertCurrentThreadIsInitialThread(); |
| |
| internal::DriverRuntimeEnv env_; |
| |
| // We will use this ensure calls are being made only on the main thread. |
| std::thread::id initial_thread_id; |
| |
| // This is an optional so we can replace it through |ResetForegroundDispatcher|. |
| std::optional<fdf_internal::TestSynchronizedDispatcher> foreground_dispatcher_; |
| std::list<fdf_internal::TestSynchronizedDispatcher> background_dispatchers_; |
| std::set<const void*> shutdown_background_dispatcher_owners_; |
| |
| // We will use this prevent doing ShutdownAllDispatchers multiple times. |
| bool is_shutdown_ = false; |
| }; |
| |
| // Internal template implementation details |
| // This is mirrored from: |
| // `//sdk/lib/async-loop-testing/cpp/include/lib/async-loop/testing/cpp/real_loop.h` |
| // ======================================== |
| |
| template <typename PromiseType> |
| typename PromiseType::result_type DriverRuntime::RunPromise(PromiseType promise) { |
| async::Executor e(foreground_dispatcher_->dispatcher()); |
| std::optional<typename PromiseType::result_type> res; |
| e.schedule_task( |
| promise.then([&res](typename PromiseType::result_type& v) { res = std::move(v); })); |
| |
| // We set |step| to infinity, as the automatic-wake-up timer shouldn't be |
| // necessary. A well-behaved promise must always wake up the executor when |
| // there's more work to be done. |
| RunUntil([&res]() { return res.has_value(); }, zx::duration::infinite()); |
| return std::move(res.value()); |
| } |
| |
| template <typename Result, typename Callable> |
| Result DriverRuntime::PerformBlockingWorkImpl(Callable&& work) { |
| fpromise::bridge<Result> bridge; |
| std::thread t([completer = std::move(bridge.completer), |
| work = std::forward<Callable>(work)]() mutable { completer.complete_ok(work()); }); |
| auto defer = fit::defer([&] { t.join(); }); |
| return RunPromise(bridge.consumer.promise()).take_value(); |
| } |
| |
| } // namespace fdf_testing |
| |
| #endif // LIB_DRIVER_TESTING_CPP_DRIVER_RUNTIME_H_ |