Follow this quick start to write a driver unit test based on the simple unit test code example:
Include library dependencies
Include this library dependency:
#include <lib/driver/testing/cpp/fixtures/gtest_fixture.h>
The DriverTestFixture is a base class that driver unit test fixture classes can inherit from. Tests define a configuration class to pass into the fixture through a template parameter. The fixture takes care of setting up the test environment and driver on the correct dispatchers, starting and stoping the driver as requested.
Create fixture configuration class
The DriverTestFixture
base class takes in a configuration class
through a template parameter.
This configuration class must be provided with certain values
that dictate how the test should run.
Here is an example of a configuration class:
class FixtureConfig final {
public:
static constexpr bool kDriverOnForeground = true;
static constexpr bool kAutoStartDriver = true;
static constexpr bool kAutoStopDriver = true;
using DriverType = simple::SimpleDriver;
using EnvironmentType = SimpleDriverTestEnvironment;
};
Define environment type class
The EnvironmentType
must be an isolated class
that provides your driver’s custom dependencies.
It does not need to provide framework dependencies (except for compat::DeviceServer
),
as the fixture does that already.
If no extra dependencies are needed, use fdf_testing::MinimalEnvironment
which provides a default compat::DeviceServer
.
Here's an example of a basic test with the minimal environment:
#include <lib/driver/testing/cpp/fixtures/gtest_fixture.h>
class FixtureConfig final {
public:
static constexpr bool kDriverOnForeground = true;
static constexpr bool kAutoStartDriver = true;
static constexpr bool kAutoStopDriver = true;
using DriverType = MyDriverType;
using EnvironmentType = fdf_testing::MinimalEnvironment;
};
class MyFixture : public fdf_testing::DriverTestFixture<FixtureConfiguration> {};
TEST_F(MyFixture, MyTest) {
driver().DoSomething();
}
Run unit tests
Driver unit tests are executed from within the test folder of the driver itself. For example, execute the following command to run the driver tests for the iwlwifi driver:
tools/bazel test third_party/iwlwifi/test:iwlwifi_test_pkg
DriverTestFixture configuration arguments
DriverType
The type of the driver under test. This must be an inheritor of fdf::DriverBase
.
Use DriverType
to define the reference type for only the fixture's functions
(for example, driver()
and RunInDriverContext()
).
Use the driver registration symbol (created by the FUCHSIA_DRIVER_EXPORT
macro)
for the driver lifecycle management.
When using a custom driver type,
ensure the custom DriverType
contains a public static function
with the signature shown below:
static DriverRegistration GetDriverRegistration()
The test uses this registration instead to manage the driver lifecycle.
EnvironmentType
A class that contains custom dependencies for the driver under test. The environment will always live on a background dispatcher.
It must be default constructible, derive from the fdf_testing::Environment class
,
and override the following function:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override;
The function is called automatically on the background environment dispatcher
during initialization.
It must add its components into the provided fdf::OutgoingDirectory object
,
generally done through the AddService
method.
The OutgoingDirectory
backs the driver's incoming namespace, hence its name,
to_driver_vfs
.
Example custom environment:
class MyFidlServer : public fidl::WireServer<fuchsia_examples_gizmo::Proto> {...};
class CustomEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) {
device_server_.Init(component::kDefaultInstance, "root");
EXPECT_EQ(ZX_OK, device_server_.Serve(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), &to_driver_vfs));
EXPECT_EQ(ZX_OK, to_driver_vfs.AddService<fuchsia_examples_gizmo::Service::Proto>(
custom_server_.CreateInstanceHandler()).status_value());
return zx::ok();
}
private:
compat::DeviceServer device_server_;
MyFidlServer custom_server_;
};
kDriverOnForeground (prefer = true)
Whether to have the driver under test run on the foreground dispatcher, or to run it on a dedicated background dispatcher.
When this is true, the test can access the driver under test
using the driver()
method and directly make calls into it,
but sync client tasks must go through RunInBackground()
.
When this is false, the test can run tasks on the driver context
using the RunInDriverContext()
methods,
but sync client tasks can be run directly.
kAutoStartDriver (prefer = true)
If true, the test will automatically start the driver
on construction of DriverTestFixture
, and expect a successful start.
kAutoStopDriver (prefer = true)
If true, the test will automatically stop the driver
on destruction of DriverTestFixture
, and expect a successful stop.
Methods available to the test under all configs
The following methods are available to the test under all configurations.
runtime
Access the driver runtime object. This can be used to create new background dispatchers or to run the foreground dispatcher. The user does not need to explicitly create dispatchers for the environment or the driver as the fixture has already done that.
Connect
Connects to an instance of a service member that the driver under test provides. This can be either a driver transport or a zircon channel transport based service.
ConnectThroughDevfs
Connects to a protocol that the driver has exported through devfs
.
This can be given the node_name of the devfs
node,
or a list of node names to traverse before reaching the devfs
node.
RunInEnvironmentTypeContext
Runs a task on the EnvironmentType
instance that the test is using.
RunInNodeContext
Runs a task on the fdf_testing::TestNode
instance that the test is using.
This can be used to validate the driver’s interactions with the driver framework node
(like checking how many children have been added).
Methods Available to the Test With config-based restrictions
The following methods are available to the test with certain config-based restrictions (in parentheses).
StartDriver and StartDriverCustomized (kAutoStartDriver = false)
This can be used to manually start the driver under test.
Should only be used if kAutoStartDriver
is false.
The customized variant can be used to modify the start arguments
that the driver is given.
StopDriver (kAutoStopDriver = false)
This can be used to manually stop the driver under test.
Should only be used if kAutoStopDriver
is false.
RunInDriverContext (kDriverOnForeground = false)
This can be used to run a callback on the driver under test. The callback input will have a reference to the driver. All accesses to the driver must go through this as it is unsafe to touch the driver on the main test thread under this configuration.
driver (kDriverOnForeground = true)
This can be used to access the driver directly from the test. Since the driver is on the foreground it is safe to access this on the main test thread.
RunInBackground (kDriverOnForeground = true)
Runs a task on a background dispatcher, separate from the driver.
This is done to avoid deadlocking with the driver when making sync client calls
with the kDriverOnForeground
configuration.
Run* functions warning
Be careful when using the Run* functions
(RunInDriverContext
, RunInBackground
, RunInEnvironmentTypeContext
, RunInNodeContext
).
These tasks run on specific dispatchers, so it might be unsafe to:
- Pass raw pointers into them from another context (main thread or a different Run* kind) to use in the function
- Return a raw pointer (through a captured ref or return type) out of them to use on the main thread or to capture/use in another Run* function (except for a Run* function of the same kind).