This guide provides a detailed understanding of the Driver Framework v2 testing libraries and how to use them including the DriverRuntime, TestNode, TestEnvironment, and DriverUnderTest classes.
The following image shows the entirety of the driver testing framework:
At a glance, the DriverRuntime
automatically creates the
foreground dispatcher
and attaches it to the main testing thread.
The foreground dispatcher synchronously runs tests and uses what
it needs in the test environment, including FIDL services, through the
background dispatcher(s).
DriverUnderTest
wraps the driver being tested and provides the ability
to call its lifecycle hooks.
Throughout this tutorial, you will dive into each specific part of the driver testing framework and learn how to write code to make your tests work. It may help to return to this image as you dive into each of its parts. Once you are familiar with the testing framework, refer to the quick start and reference as needed.
Set up testing framework
Set up dispatchers and create driver runtime instance
The first step in setting up the testing framework is to create the driver runtime instance and start the background dispatchers.
Create driver runtime instance
When you start your test, you create the instance of your driver runtime. This automatically creates the foreground dispatcher:
fdf_testing::DriverRuntime runtime_;
Examples:
Start background dispatcher
Use the StartBackgroundDispatcher method on the DriverRuntime to start the background dispatcher(s)manually (if needed):
fdf::UnownedSynchronizedDispatcher env_dispatcher_ = runtime_.StartBackgroundDispatcher();
This method gives a pointer for the background dispatcher where you can put your environment dependencies.
Examples:
Create TestNode object
The second step in setting up the testing framework is to create the test node object. TestNode is a mock server for the driver framework node protocol.
Use the wrapper class, TestDispatcherBound
, to instantiate the class by
creating the TestNode object. This is done through a wrapper class for memory
and thread safety:
async_patterns::TestDispatcherBound<fdf_testing::TestNode> node_server_{
env_dispatcher(), std::in_place, std::string("root")};
At this point, you aren't serving anything yet, just representing the root node of your tests. The driver being tested binds to this node. It is completely detached from the tree so that you can hook your driver to it.
The point of the node protocol is to add children into the node topology. Similar to how components live inside a tree structure, so do drivers. The node protocol is how you add children to the node topology in the driver framework. The driver binds to the node and can add children to node for other drivers to bind to.
Examples:
Create TestEnvironment object
The third step in setting up the testing framework is to create the test environment object. The TestEnvironment provides FIDL services to the driver, simulating the FIDL server that's accessible to the driver.
As with TestNode
, use the wrapper class, TestDispatcherBound
, to instantiate
the class by creating the TestEnvironment
object. This is done through a wrapper
class for memory and thread safety:
async_patterns::TestDispatcherBound<fdf_testing::TestEnvironment> test_environment_{
env_dispatcher(), std::in_place};
Examples:
Set up custom FIDL server class
The fourth step in setting up the testing framework is to setup the custom FIDL
server class. This is an optional step, required to support any FIDLs that the
driver needs for this particular unit test. There are three parts to this
set-up: create the custom FIDL server class, get the server handler, and
move the server handler into the TestEnvironment
class.
Create custom FIDL server class
This example code in Driver FIDL test, lines 121-124 creates server classes for Zircon and Driver to support both Zircon and Driver services.
async_patterns::TestDispatcherBound<ZirconProtocolServer> zircon_proto_server_{env_dispatcher(),
std::in_place};
async_patterns::TestDispatcherBound<DriverProtocolServer> driver_proto_server_{env_dispatcher(),
std::in_place};
Get custom FIDL server handler
This example code in Driver FIDL test, lines 71-74 gets handlers for both the Zircon and Driver services:
fuchsia_driver_component_test::ZirconService::InstanceHandler zircon_proto_handler =
zircon_proto_server_.SyncCall(&ZirconProtocolServer::GetInstanceHandler);
fuchsia_driver_component_test::DriverService::InstanceHandler driver_proto_handler =
driver_proto_server_.SyncCall(&DriverProtocolServer::GetInstanceHandler);
Move custom FIDL server handler into test environment
This example code in Driver FIDL test, lines 76-87 moves both the Zircon and Driver handlers into the test environment:
test_environment_.SyncCall([zircon_proto_handler = std::move(zircon_proto_handler),
driver_proto_handler = std::move(driver_proto_handler)](
fdf_testing::TestEnvironment* env) mutable {
zx::result result =
env->incoming_directory().AddService<fuchsia_driver_component_test::ZirconService>(
std::move(zircon_proto_handler));
ASSERT_EQ(ZX_OK, result.status_value());
result = env->incoming_directory().AddService<fuchsia_driver_component_test::DriverService>(
std::move(driver_proto_handler));
ASSERT_EQ(ZX_OK, result.status_value());
});
Call CreateStartArgsAndServe
The fifth step in setting up the testing framework is to call the
CreateStartArgsAndServe method on the the TestNode class.
Calling this method returns three objects:
driver start_args
table, outgoing_directory_client
,
and incoming_directory_server
:
Calling this method also starts serving the node protocol (fdf::Node
). This is
a channel from the TestNode
class to the client of TestNode
in the
DriverStartArgs
table.
zx::result start_args = node_server_.SyncCall(&fdf_testing::TestNode::CreateStartArgsAndServe);
ASSERT_EQ(ZX_OK, start_args.status_value());
Examples:
Initialize test environment
The sixth step in setting up the testing framework is to initialize the
test environment. Calling the
initialize() function on the TestEnvironment class
moves the incoming directory server (from CreateStartArgsResult
)
into the test environment:
The purpose of this call is to tell the test environment to start serving on
the channel from the server end. The channel now goes from the DriverStartArgs
incoming namespace client to the test environment class.
zx::result init_result =
test_environment_.SyncCall(&fdf_testing::TestEnvironment::Initialize,
std::move(start_args->incoming_directory_server));
ASSERT_EQ(ZX_OK, init_result.status_value());
Both the TestNode
and TestEnvironment
are things that the driver needs to run and
so are passed to the driver through its start arguments. Note, however, they go
in as different channels.
TestEnvironment
contains the VFS (virtual file system) that provides FIDL
services to the driver. When your driver connects to a protocol through it's
incoming directory, the server providing the protocol is on the test
environment, simulating that the FIDL server is accessible by the driver.
Examples:
Run actual tests
Start driver
DriverUnderTest
is a wrapper class that provides life cycle hooks for the driver being tested.
To start the driver, call the start
method on the DriverUnderTest class:
The driver lives on the
foreground dispatcher.
The start
method is an asynchronous operation and won't wait
for the foreground dispatcher to run.
The start
method is passed to the RunToCompletion
method and
the RunToCompletion
method runs the foreground dispatcher
until the start class is complete.
zx::result result = runtime().RunToCompletion(driver_.SyncCall(
&fdf_testing::DriverUnderTest<TestDriver>::Start, std::move(start_args())));
Once start happens, the driver and everything in the testing environment runs on the foreground dispatcher.
Examples:
Run tests
The next step is to run the actual unit tests themselves. At this point, you can call into any of the driver methods to test the driver.
Each test is unique in what it's testing on the driver. For example, starting
the PcielwlwifiDriver triggers AddNode
for the wlanphy virtual device. The
iwlwifi driver lifecycle test, line 176,
gets the number of children nodes:
size_t GetNodeNumber() { return node_server_.SyncCall(&TestNodeLocal::GetchildrenCount); }
And checks that the number of nodes is 1 (line 204:
EXPECT_EQ(GetNodeNumber(), (size_t)1);
Set up channel for outgoing directory, if needed
There's one last object left in the CreateStartArgs
table: the outgoing
directory. Use this object to validate something the driver interacts with, for
example, if the driver is exporting services.
The following code sets up a channel that will allow you to connect any FIDLs your driver is exporting as part of its outgoing directory (Driver FIDL test, lines 100-102):
zx_status_t status = fdio_open_at(driver_outgoing_.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
svc_endpoints->server.TakeChannel().release());
Call PrepareStop
The final step is to call PrepareStop
and pass to RunToCompletion
:
Like the start
method, PrepareStop
method is asynchronous, and hence it is
passed to the RunToCompletion
method which runs the foreground dispatcher until
PrepareStop
is complete
(Driver FIDL test, line 159):
zx::result result = runtime().RunToCompletion(driver_.PrepareStop());
RunToCompletion
goes until the driver is prepared to fully stop. At this point,
all of the classes go out of scope and are turned down automatically. All
objects go out of scope until everything is gone.