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