This document contains a codelab for debugging with diagnostics and tests. It is currently intended for developers writing tests within fuchsia.git.
Set up your development environment.
This codelab assumes you have completed Getting Started and have:
ffx emu
) that runs Fuchsia.fx serve
) to your Fuchsia device or emulator.To build and run the examples in this codelab, add the following arguments to your fx set
invocation:
Note: Replace core.x64 with your product and board configuration.
fx set core.x64 \ --release \ --with //examples/diagnostics/workshop \ --with //examples/diagnostics/workshop:tests
There is an example component that serves a protocol called ProfileStore:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/workshop/fidl/profile_store.test.fidl" region_tag="profile_store_fidl" adjust_indentation="auto" %}
This protocol allows creation, deletion, and inspection of user profiles, which contain a name and balance. The component has a bug - profile deletion does not work.
Code for the codelab is located in //examples/diagnostics/workshop.
In addition to the main component serving ProfileStore, there are a number of components that connect to and interact with ProfileStore. All components are in the fuchsia-pkg://fuchsia.com/profile_store_example
package.
#meta/profile_store.cm
- serves ProfileStore#meta/add_olive.cm
- Connects to ProfileStore and adds a profile called ‘Olive’#meta/add_balance_olive.cm
- Connects to ProfileStore and adds balance to the ‘Olive’ profile#meta/withdraw_balance_olive.cm
- Connects to ProfileStore and withdraws balance from the ‘Olive’ profile#meta/add_jane.cm
- Connects to ProfileStore and adds a profile called ‘Jane’#meta/delete_olive.cm
- Connects to ProfileStore and deletes the ‘Olive’ profileCapabilities are routed by the #meta/laboratory_server.cm
component.
You can interact with the components using the ffx component
command, and inspect output from components using ffx log
. First, run ffx log --tags workshop
in a shell. This shell will contain all output from components. In a different shell, run the toy components:
# setup server ffx component create /core/ffx-laboratory:profile_store fuchsia-pkg://fuchsia.com/profile_store_example#meta/laboratory_server.cm # setup first client ffx component create /core/ffx-laboratory:profile_store/clients:add_olive fuchsia-pkg://fuchsia.com/profile_store_example#meta/add_olive.cm # see the results of the previous two steps ffx component show profile_store # add a profile key and read it ffx component start /core/ffx-laboratory:profile_store/clients:add_olive ffx component create /core/ffx-laboratory:profile_store/clients:reader fuchsia-pkg://fuchsia.com/profile_store_example#meta/profile_reader.cm ffx component start /core/ffx-laboratory:profile_store/clients:reader # demonstrate persistence ffx component stop /core/ffx-laboratory:profile_store/profile_store ffx component start /core/ffx-laboratory:profile_store/clients:reader # update balance ffx component create /core/ffx-laboratory:profile_store/clients:add_balance_olive fuchsia-pkg://fuchsia.com/profile_store_example#meta/add_balance_olive.cm ffx component start /core/ffx-laboratory:profile_store/clients:add_balance_olive ffx component start /core/ffx-laboratory:profile_store/clients:reader # add second profile ffx component create /core/ffx-laboratory:profile_store/clients:add_jane fuchsia-pkg://fuchsia.com/profile_store_example#meta/add_jane.cm ffx component start /core/ffx-laboratory:profile_store/clients:add_jane ffx component start /core/ffx-laboratory:profile_store/clients:reader # update balance ffx component create /core/ffx-laboratory:profile_store/clients:withdraw_balance_olive fuchsia-pkg://fuchsia.com/profile_store_example#meta/withdraw_balance_olive.cm ffx component start /core/ffx-laboratory:profile_store/clients:withdraw_balance_olive ffx component start /core/ffx-laboratory:profile_store/clients:reader # delete olive (this will not work as there is a bug in the server code) ffx component create /core/ffx-laboratory:profile_store/clients:delete_olive fuchsia-pkg://fuchsia.com/profile_store_example#meta/delete_olive.cm ffx component start /core/ffx-laboratory:profile_store/clients:delete_olive ffx component start /core/ffx-laboratory:profile_store/clients:reader
Diagnostics provides multiple products that help component authors debug their components both while developing and in the field.
For this workshop we'll be exploring three core technologies:
Diagnostics provides structured logging libraries to allow components to write logs. To help find the bug, we'll be adding a few logs to the profile store component.
The first step when adding logging to a component, is to include the logging library in your binary dependencies. To do this, update your BUILD.gn as follows:
source_set("lib") { ... public_deps = [ ... "//sdk/lib/syslog/cpp", ] }
Logging is initialized the moment we call one of the logging macros. However, the libraries provide some utilities that should be called in main()
such as configuring the tags (if desired only, this is optional).
Tags can be useful to later query the logs of a group of components. For our purposes we can add the workshop
tag:
#include <lib/syslog/cpp/log_settings.h> ... fuchsia_logging::SetTags({"workshop", "profile_store_server"});
Now, it‘s time to write some logs. We’ll be using the FX_SLOG
macro which allows to write structured keys and values.
For example, we can add the following log when we get a request on ProfileStore::Open
but the profile file doesn't exist:
#include <lib/syslog/cpp/macros.h> ... FX_SLOG(WARNING, "Profile doesn't exist", KV("key", key.c_str()));
Try adding that log, build (fx build
), relaunch your component (ffx component start ...
) and then run: ffx log --tags workshop
.
What other logs could we add that would help identify the log? Please experiment!
A solution can be found in this patch.
Inspect allows components to expose state about themselves. Unlike logs, which are a stream, Inspect represents a live view into the component current state.
Reading through the Inspect quickstart would be a good first step. If you'd like to dive deeper into Inspect, you can also follow the Inspect codelab.
To get started, first add library dependencies:
source_set("lib") { ... public_deps = [ ... "//sdk/lib/sys/inspect/cpp", ] }
Next, initialize Inspect in main.cc
:
#include <lib/sys/inspect/cpp/component.h> ... // Immediately following the line defining "startup". auto inspector = std::make_unique<sys::ComponentInspector>(startup.get()); inspector->Health().Ok();
Now you can view Inspect after restarting the profile_store
:
# Note that double \\ is necessary! ':' must be escaped in a "diagnostic selector." ffx inspect show core/ffx-laboratory\\:profile_store/profile_store
You should see your component's status is “OK”. Inspect is most useful when integrated with your class hierarchy. Arbitrary values are rooted on inspect::Node
, including more Nodes! Try to modify ProfileStore such that the following compiles:
// In main.cc std::unique_ptr<ProfileStore> profile_store = std::make_unique<ProfileStore>(loop.dispatcher(), inspector->GetRoot().CreateChild("profiles"));
Hint: You will need to update the ProfileStoreTests
class if you change the constructor for ProfileStore
. You can just pass inspect::Node()
as the new parameter.
Now that you have basic Inspect configured, what intrumentation could be useful to add to help prevent/find the bug in this component?
inspect::Node
to each Profile
, and create them using CreateChild
on the node you passed to ProfileStore
.inspect::LazyNode
(node.CreateLazyNode(...)
) to dynamically create hierarchies.A possible solution can be found in this patch: https://fuchsia-review.googlesource.com/c/fuchsia/+/682671
Triage allows to write rules to automatically process inspect snapshots and find potential issues or gather stats that the snapshots might contain.
Reading through the Triage codelab would be a good first step as well as reading through the triage config guide.
To get started, create a new file at examples/diagnostics/workshop/triage/profile_store.triage
with the following contents:
{ select: { profile_status: "INSPECT:core/ffx-laboratory\\:profile_store/profile_store:fuchsia.inspect.Health:status", }, act: { profile_status_ok: { type: "Warning", trigger: "profile_status != 'OK'", print: "Profile store is not healthy.", } } }
Note: Before checking in a .triage
file, you would need to update a list in the BUILD.gn
file. For this example, that step is omitted.
If you followed the Inspect quick start in the last section, run ffx triage --config examples/diagnostics/workshop/triage/
. If profile_store
is running and reporting its status is OK, you won't see any failures! Try changing the call to Health().Ok()
in main.cc
to Health().StartingUp()
and run ffx triage --config examples/diagnostics/workshop/triage/
again. This time you should see the warning.
Try writing a triage configuration that could have helped spot the bug in snapshots gathered in the field.
A possible solution (built on top of the Inspect solution) can be found in this patch: https://fuchsia-review.googlesource.com/c/fuchsia/+/684762
This section covers adding tests to verify the fix.
This example contains example unit tests and an example integration test, including some tests that are disabled due to the bug.
Try writing new tests that could help prevent the bug in the component. Feel free to either modify the example tests, or create new tests from scratch using the flows below.
The code structure of unit tests is highly dependent on the runtime used. This section walks through the process of setting up new unit tests that verify the behavior of the ProfileStore C++ class.
Create a new file in examples/diagnostics/workshop/profile_store_unittest.cc
with these contents:
#include "src/lib/testing/loop_fixture/test_loop_fixture.h" #include <gtest/gtest.h> #include "fuchsia/examples/diagnostics/cpp/fidl.h" #include "lib/fidl/cpp/interface_ptr.h" #include "profile_store.h" class ProfileStoreTests : public gtest::TestLoopFixture {}; TEST_F(ProfileStoreTests, SampleTest) { ProfileStore store(dispatcher()); fidl::InterfacePtr<fuchsia::examples::diagnostics::ProfileStore> store_ptr; store.AddBinding(store_ptr.NewRequest(dispatcher())); store_ptr->Delete("my_key", [&](bool successful) { EXPECT_FALSE(successful); }); RunLoopUntilIdle(); }
This sets up a minimal unit test which creates a ProfileStore, and creates a client to interact with it under an asynchronous test loop. Next, you will create a component manifest for the test, which defines how to run the test. Create a new component manifest for the test in examples/diagnostics/workshop/meta/profile_store_unittests.cm
with the following contents:
{ include: [ // Needed for gtest runners "//src/sys/test_runners/gtest/default.shard.cml", // Needed so that logs are created "syslog/client.shard.cml", ], program: { binary: "bin/profile_store_unittests", }, use: [ { // ProfileStore uses /data to store profiles. We'll use the tmp // storage provided to the test. storage: "tmp", path: "/data", }, ], }
Finally, add new build rules to examples/diagnostics/workshop/BUILD.gn
:
# Builds the test binary. executable("test_bin") { testonly = true output_name = "profile_store_unittests" sources = [ "profile_store_unittest.cc", ] deps = [ ":lib", "//src/lib/fxl/test:gtest_main", "//src/lib/testing/loop_fixture", ] } # Creates a test component and test package. fuchsia_unittest_package("profile_store_unittests") { deps = [ ":test_bin" ] manifest = "meta/profile_store_unittests.cml" } # Update the existing group("tests") to include the new package as a dep group("tests") { testonly = true deps = [ # new dependency ":profile_store_unittests", ":profile_store_example_unittests", "example-integration:tests", ] }
Next, verify that the test builds and runs.
# Build is needed the first time so that fx test becomes aware of the new test. # For subsequent test executions, fx build is automatically invoked. fx build examples/diagnostics/workshop:tests fx test profile_store_unittests
You are now ready to modify the test code to verify behavior.
The fx testgen
command autogenerates integration test boilerplate setup to use RealmBuilder. To use it, you will need to find the compiled component manifest of our profile_store component in our output directory.
# find the manifest in output directory. find $(fx get-build-dir) -name profile_store.cm # generate integration tests. fx testgen --cm-location {{ '<var>' }}find result{{ '</var>' }} --out-dir examples/diagnostics/workshop/tests -c
This should generate a few files under examples/diagnostics/workshop/tests
. Before running the tests, there are a few build rules that need to be updated:
examples/diagnostics/workshop/tests/BUILD.gn
{COMPONENT_FIDL_BUILD_TARGET}
with the build target for the ProfileStore fidl - //examples/diagnostics/workshop/fidl:fuchsia.examples.diagnostics
{COMPONENT_BUILD_TARGET}
with the build target for the ProfileStore component - //examples/diagnostics/workshop:profile_store
/examples/diagnostics/workshop/BUILD.gn
group("tests")
definition. This ensures GN can find the new test.Next, verify that the test builds and runs.
# Build is needed the first time so that fx test becomes aware of the new test. # For subsequent test executions, fx build is automatically invoked. fx build examples/diagnostics/workshop:tests fx test profile_store_test
Once the test runs, you are ready to modify the boilerplate to write useful tests.