blob: a21fd2334cdcb4b81909600cfb7c67deea7ae190 [file] [log] [blame] [view]
# Diagnostics and testing codelab
This document contains a codelab for debugging with diagnostics and tests. It is currently
intended for developers writing tests within fuchsia.git.
## Prerequisites
Set up your development environment.
This codelab assumes you have completed [Getting Started](/docs/get-started/README.md) and have:
1. A checked out and built Fuchsia tree.
2. A device or emulator (`ffx emu`) that runs Fuchsia.
3. A workstation to serve components (`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
```
## Introduction
There is an example component that serves a protocol called [ProfileStore][profile-store]:
```fidl
{% 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](/examples/diagnostics/workshop).
## Run the component
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' profile
Capabilities 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:
```bash
# 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
```
## Debugging with diagnostics
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:
- [Structured logging](#structured-logging)
- [Inspect](#inspect)
- [Triage](#triage)
### Structured logging
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][profile-store-build] 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>
...
syslog::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](https://fuchsia-review.googlesource.com/c/fuchsia/+/684632).
### Inspect
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][inspect-quickstart] would be a good first step. If you'd
like to dive deeper into Inspect, you can also follow the [Inspect codelab][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?
- Consider adding an `inspect::Node` to each `Profile`, and create
them using `CreateChild` on the node you passed to `ProfileStore`.
- Consider using `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
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][triage-codelab] would be a good first step as well as reading
through the [triage config guide][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
## Verifying with tests
This section covers adding tests to verify the fix.
This example contains [example unit tests][example-unittests] and an
[example integration test][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.
### Adding new unit tests
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:
```c++
#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.
```bash
# 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.
### Adding a new integration test
The `fx testgen` command autogenerates integration test boilerplate setup to use
[RealmBuilder][realm-builder]. To use it, you will need to find the compiled component manifest
of our profile_store component in our output directory.
```bash
# 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:
* In the newly generated `examples/diagnostics/workshop/tests/BUILD.gn`
* Replace `{COMPONENT_FIDL_BUILD_TARGET}` with the build target for the ProfileStore fidl -
`//examples/diagnostics/workshop/fidl:fuchsia.examples.diagnostics`
* Replace `{COMPONENT_BUILD_TARGET}` with the build target for the ProfileStore component -
`//examples/diagnostics/workshop:profile_store`/
* In `examples/diagnostics/workshop/BUILD.gn`
* Add "tests" to deps in the `group("tests")` definition. This ensures GN can find the new test.
Next, verify that the test builds and runs.
```bash
# 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.
[example-integration-test]: /examples/diagnostics/workshop/example-integration
[example-unittests]: /examples/diagnostics/workshop/profile_unittest_example.cc
[inspect-codelab]: /docs/development/diagnostics/inspect/codelab/codelab.md
[inspect-quickstart]: /docs/development/diagnostics/inspect/quickstart.md
[gtest]: https://github.com/google/googletest
[profile-store]: /examples/diagnostics/workshop/fidl/profile_store.test.fidl
[profile-store-build]: /examples/diagnostics/workshop/BUILD.gn
[realm-builder]: /docs/development/testing/components/realm_builder.md
[triage-codelab]: /docs/development/diagnostics/triage/codelab.md
[triage-config-guide]: /docs/development/diagnostics/triage/config.md