blob: 1d9265e767bccb88b66831dcd157485ca0c9cedf [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.
#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_