Test and debug the driver

Fuchsia supports step-through debugging of components using the Fuchsia debugger (zxdb). The debugger attaches to the host process where a component is running, and allows the developer to set breakpoints and step through code execution. The Test Runner Framework enables developers to write tests that exercise driver components.

In this section, you'll use the Fuchsia debugger (zxdb) to inspect a running driver and build a test component to exercise the driver's functionality.

Connect the debugger

To connect the Fuchsia debugger to the driver component, you'll need to determine the PID of the host process. Use the ffx driver list-hosts command to discover the PID of the host process where the driver is loaded:

ffx driver list-hosts

The command outputs a list similar to the following. Locate the driver host where the qemu_edu driver is listed:

Driver Host: 5053
    fuchsia-boot:///#meta/block.core.cm
    fuchsia-boot:///#meta/bus-pci.cm
    fuchsia-boot:///#meta/cpu-trace.cm
    fuchsia-boot:///#meta/fvm.cm
    fuchsia-boot:///#meta/hid.cm
    fuchsia-boot:///#meta/netdevice-migration.cm
    fuchsia-boot:///#meta/network-device.cm
    fuchsia-boot:///#meta/platform-bus-x86.cm
    fuchsia-boot:///#meta/platform-bus.cm
    fuchsia-boot:///#meta/ramdisk.cm
    fuchsia-boot:///#meta/sysmem.cm
    fuchsia-boot:///#meta/virtio_block.cm
    fuchsia-boot:///#meta/virtio_ethernet.cm
    fuchsia-pkg://fuchsia.com/virtual_audio#meta/virtual_audio_driver.cm

Driver Host: 7774
    fuchsia-boot:///#meta/intel-rtc.cm

Driver Host: 7855
    fuchsia-boot:///#meta/pc-ps2.cm

Driver Host: 44887 
    fuchsia-pkg://bazel.pkg.component/qemu_edu#meta/qemu_edu.cm 

Make a note of the PID for the qemu_edu driver host. In the above example, the PID is 44887.

Start the Fuchsia debugger with ffx debug connect:

ffx debug connect

Once the debugger connects to the target device, attach to the qemu_edu driver host from the zxdb prompt:

[zxdb] attach HOST_PID

Replace HOST_PID with the PID of the driver host identified in the previous step. For example:

[zxdb] attach 44887

Set a breakpoint in the driver's ComputeFactorial function:

[zxdb] break QemuEduServer::ComputeFactorial

The command prints output similar to the following to indicate where the breakpoint is set:

[zxdb] break QemuEduServer::ComputeFactorial
Created Breakpoint 1 @ QemuEduServer::ComputeFactorial
   47 void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
 ◉ 48                                      ComputeFactorialCompleter::Sync& completer) {
   49   auto edu_device = device_.lock();

Step through the driver function

In a separate terminal, run the eductl tool again:

bazel run //fuchsia-codelab/qemu_edu/tools:pkg.eductl_tool -- fact 12

In the zxdb terminal, verify that the debugger has hit the breakpoint in the driver's ComputeFactorial function. For example:

🛑 thread 2 on bp 1 qemu_edu::QemuEduServer::ComputeFactorial(qemu_edu::QemuEduServer*, fidl::WireServer<fuchsia_examples_qemuedu::Device>::ComputeFactorialRequestView, fidl::Completer<fidl::internal::WireCompleterBase<fuchsia_examples_qemuedu::Device::ComputeFactorial> >::Sync&) • qemu_edu.cc:144
   46 // Driver Service: Compute factorial on the edu device
   47 void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
 ▶ 48                                      ComputeFactorialCompleter::Sync& completer) {
   49   auto edu_device = device_.lock();
   50   if (!edu_device) {

Use the list command at the zxdb prompt to show where execution is currently paused:

[zxdb] list

The command prints output similar to the following:

   46 // Driver Service: Compute factorial on the edu device
   47 void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
 ▶ 48                                      ComputeFactorialCompleter::Sync& completer) {
   49   auto edu_device = device_.lock();
   50   if (!edu_device) {
   51     FDF_LOG(ERROR, "Unable to access device resources.");
   52     completer.ReplyError(ZX_ERR_BAD_STATE);
   53     return;
   54   }
   55
   56   uint32_t input = request->input;
   57
   58   edu_device->ComputeFactorial(input);

Step into the ComputeFactorial function using the next command:

[zxdb] next

Print the contents of the request passed into the function:

[zxdb] print request

The command prints output containing the factorial input value:

(*)0x747c1f2e98 ➔ {input = 12}

Exit the debugger session and disconnect:

[zxdb] exit

Create a new system test component

In this section, you'll create a new test component that exercises the exposed functions of the qemu_edu driver.

Create a new project directory in your Bazel workspace for a new test component:

mkdir -p fuchsia-codelab/qemu_edu/tests

After you complete this section, the project should have the following directory structure:

//fuchsia-codelab/qemu_edu/tests
                  |- BUILD.bazel
                  |- meta
                  |   |- qemu_edu_system_test.cml
                  |- qemu_edu_system_test.cc

Create the qemu_edu/tests/BUILD.bazel file and add the following statement to include the necessary build rules from the Fuchsia SDK:

qemu_edu/tests/BUILD.bazel:

load(
    "@rules_fuchsia//fuchsia:defs.bzl",
    "fuchsia_cc_test",
    "fuchsia_component_manifest",
    "fuchsia_test_component",
    "fuchsia_test_package",
    "if_fuchsia",
)

Create a new qemu_edu/tests/meta/qemu_edu_system_test.cml component manifest file to the project with the following contents:

qemu_edu/tests/meta/qemu_edu_system_test.cml:

{
    include: [
        "syslog/client.shard.cml",
        "sys/testing/elf_test_runner.shard.cml",
    ],
    program: {
        binary: 'bin/qemu_edu_system_test',
    },
    use: [
        {
            directory: "dev",
            rights: [ "rw*" ],
            path: "/dev",
        },
    ],
    // Required to enable access to devfs
    facets: {
        "fuchsia.test": { type: "devices" },
    },
}

Similar to eductl, the test component discovers and accesses the driver using the dev directory capability. This component also includes the elf_test_runner.shard.cml, which enables it to run using the Test Runner Framework.

Create a new qemu_edu/tests/qemu_edu_system_test.cc file with the following contents to implement the tests:

qemu_edu/tests/qemu_edu_system_test.cc:

#include <dirent.h>
#include <fcntl.h>
#include <fidl/examples.qemuedu/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <sys/types.h>

#include <filesystem>

#include <gtest/gtest.h>

namespace {

constexpr char kDevfsRootPath[] = "/dev/sys/platform";
constexpr char kEduDevicePath[] = "qemu-edu";

class QemuEduSystemTest : public testing::Test {
 public:
  void SetUp() {
    auto device_path = SearchDevicePath();
    ASSERT_TRUE(device_path.has_value());
    int device = open(device_path.value().c_str(), O_RDWR);
    ASSERT_GE(device, 0);

    fidl::ClientEnd<examples_qemuedu::Device> client_end;
    ASSERT_EQ(fdio_get_service_handle(device, client_end.channel().reset_and_get_address()), ZX_OK);
    device_ = fidl::WireSyncClient(std::move(client_end));
  }

  // Search for the device file entry in devfs
  std::optional<std::string> SearchDevicePath() {
    for (auto const& dir_entry : std::filesystem::recursive_directory_iterator(kDevfsRootPath)) {
      if (dir_entry.path().string().find(kEduDevicePath) != std::string::npos) {
        return {dir_entry.path()};
      }
    }

    return {};
  }

  fidl::WireSyncClient<examples_qemuedu::Device>& device() { return device_; }

 private:
  fidl::WireSyncClient<examples_qemuedu::Device> device_;
};

TEST_F(QemuEduSystemTest, LivenessCheck) {
  fidl::WireResult result = device()->LivenessCheck();
  ASSERT_EQ(result.status(), ZX_OK);
  ASSERT_TRUE(result->value()->result);
}

TEST_F(QemuEduSystemTest, ComputeFactorial) {
  std::array<uint32_t, 11> kExpected = {
      1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800,
  };
  for (int i = 0; i < kExpected.size(); i++) {
    fidl::WireResult result = device()->ComputeFactorial(i);
    ASSERT_EQ(result.status(), ZX_OK);
    EXPECT_EQ(result->value()->output, kExpected[i]);
  }
}

}  // namespace

Each test case opens the device driver and exercises one of its exposed functions.

Add the following new rules to the project's build configuration to build the test component into a Fuchsia test package:

qemu_edu/tests/BUILD.bazel:

fuchsia_cc_test(
    name = "qemu_edu_system_test",
    size = "small",
    srcs = [
        "qemu_edu_system_test.cc",
    ],
    deps = ["@com_google_googletest//:gtest_main"] + if_fuchsia([
        "//fuchsia-codelab/qemu_edu/fidl:examples.qemuedu_cc",
        "@fuchsia_sdk//pkg/fdio",
    ]),
)

fuchsia_component_manifest(
    name = "manifest",
    src = "meta/qemu_edu_system_test.cml",
    includes = [
        "@fuchsia_sdk//pkg/sys/testing:elf_test_runner",
        "@fuchsia_sdk//pkg/syslog:client",
    ],
)

fuchsia_test_component(
    name = "component",
    manifest = "manifest",
    deps = [
        ":qemu_edu_system_test",
    ],
)

fuchsia_test_package(
    name = "pkg",
    package_name = "qemu_edu_system_test",
    visibility = ["//visibility:public"],
    components = [
        ":component",
    ],
)

Run the system test

Use the bazel run command to build and execute the test component target:

bazel run //fuchsia-codelab/qemu_edu/tests:pkg.component

The bazel run command performs the following steps:

  1. Build the component and package.
  2. Publish the package to a local package repository.
  3. Register the package repository with the target device.
  4. Use ffx test run to execute the component's test suite.

Verify that all the tests pass successfully:

Running test 'fuchsia-pkg://bazel.test.pkg.system.test.component/qemu_edu_system_test#meta/qemu_edu_system_test.cm'
[RUNNING]   main
[stdout - main]
Running main() from gmock_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from QemuEduSystemTest
[ RUN      ] QemuEduSystemTest.LivenessCheck
[       OK ] QemuEduSystemTest.LivenessCheck (4 ms)
[ RUN      ] QemuEduSystemTest.ComputeFactorial
[       OK ] QemuEduSystemTest.ComputeFactorial (4 ms)
[----------] 2 tests from QemuEduSystemTest (9 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (9 ms total)
[  PASSED  ] 2 tests.
[PASSED]    main

What's Next?

Congratulations! You've successfully debugged and added tests to your Fuchsia driver.

Now that you have experienced the basics of developing drivers on Fuchsia, take your knowledge to the next level and dive deeper with the:

Driver concepts