诊断和测试 Codelab

本文档包含一个用于通过诊断和测试进行调试的 Codelab。它目前适用于在 fuchsia.git 中编写测试的开发者。

前提条件

设置您的开发环境。

此 Codelab 假定您已完成使用入门,并且满足以下条件:

  1. 一棵签出并建造的 Fuchsia 树。
  2. 运行 Fuchsia 的设备或模拟器 (ffx emu)。
  3. 工作站,用于将组件 (fx serve) 提供给 Fuchsia 设备或模拟器。

如需构建并运行此 Codelab 中的示例,请将以下参数添加到 fx set 调用中:

fx set core.x64 \
--release \
--with //examples/diagnostics/workshop \
--with //examples/diagnostics/workshop:tests

简介

这里有一个提供名为 ProfileStore 的协议的示例组件:

@discoverable
closed protocol ProfileStore {
    strict Open(resource struct {
        key string:KEY_LENGTH;
        channel server_end:Profile;
    });

    strict OpenReader(resource struct {
        key string:KEY_LENGTH;
        channel server_end:ProfileReader;
    });

    strict Delete(struct {
        key string:KEY_LENGTH;
    }) -> (struct {
        success bool;
    });

    strict CreateOrOpen(resource struct {
        key string:KEY_LENGTH;
        channel server_end:Profile;
    });
};

此协议允许创建、删除和检查包含名称和余额的用户个人资料。该组件存在错误 - 无法删除个人资料。

此 Codelab 的代码位于 //examples/diagnostics/workshop

运行组件

除了提供 ProfileStore 的主要组件之外,还有许多组件会连接到 ProfileStore 并与之交互。所有组件都在 fuchsia-pkg://fuchsia.com/profile_store_example 软件包中。

  • #meta/profile_store.cm - 提供 ProfileStore
  • #meta/add_olive.cm - 连接到 ProfileStore 并添加名为“Olive”的配置文件
  • #meta/add_balance_olive.cm - 连接到 ProfileStore 并为“Olive”配置文件添加余额
  • #meta/withdraw_balance_olive.cm - 连接到 ProfileStore 并从“Olive”个人资料中提取余额
  • #meta/add_jane.cm - 连接到 ProfileStore 并添加名为“Jane”的个人资料
  • #meta/delete_olive.cm - 连接到 ProfileStore 并删除“Olive”配置文件

capability 通过 #meta/laboratory_server.cm 组件进行路由。

您可以使用 ffx component 命令与组件交互,并使用 ffx log 检查组件的输出。首先,在 shell 中运行 ffx log --tags workshop。此 shell 将包含组件的所有输出。在另一个 shell 中,运行玩具组件:

# 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

通过诊断进行调试

诊断提供了多种产品,可帮助组件作者在开发时和现场调试其组件。

在本次研讨会中,我们将探索三项核心技术:

结构化日志记录

诊断功能会提供结构化日志记录库,以允许组件写入日志。为帮助您找到 bug,我们将向配置文件存储组件添加一些日志。

向组件添加日志记录的第一步是将日志记录库添加到二进制文件依赖项中。为此,请按如下所示更新 BUILD.gn

source_set("lib") {
  ...
  public_deps = [
    ...
    "//sdk/lib/syslog/cpp",
  ]
}

在我们调用其中一个日志记录宏时,日志记录就会初始化。不过,这些库提供了一些应在 main() 中调用的实用程序,例如配置标记(如果仅需要,这是可选的)。

标记对于以后查询一组组件的日志很有用。为方便起见,我们可以添加 workshop 标记:

#include <lib/syslog/cpp/log_settings.h>
...
fuchsia_logging::SetTags({"workshop", "profile_store_server"});

现在,该写入一些日志了。我们将使用 FX_SLOG 宏,该宏允许写入结构化键和值。

例如,当我们在 ProfileStore::Open 上收到请求时,但配置文件不存在,我们可以添加以下日志:

#include <lib/syslog/cpp/macros.h>
...
FX_SLOG(WARNING, "Profile doesn't exist", KV("key", key.c_str()));

请尝试添加该日志,构建 (fx build),重新启动组件 (ffx component start ...),然后运行 ffx log --tags workshop

我们可以添加哪些其他日志来帮助识别日志?请进行实验!

解决方案可在此补丁中找到。

检查

检查允许组件公开有关自身的状态。与流式日志不同,Inspect 表示的是组件当前状态的实时视图。

建议您先仔细阅读检查快速入门。如果您想深入了解 Inspect,还可以学习 Inspect Codelab

首先,添加库依赖项:

source_set("lib") {
  ...
  public_deps = [
    ...
    "//sdk/lib/inspect/component/cpp",
  ]
}

接下来,在 main.cc 中初始化 Inspect:

#include <lib/inspect/component/cpp/component.h>
...
// Immediately following the line defining "startup".
auto inspector = std::make_unique<inspect::ComponentInspector>(async_get_default_dispatcher(), inspect::PublishOptions{});
inspector->Health().Ok();

现在,您可以在重启 profile_store 后查看 Inspect:

# Note that double \\ is necessary! ':' must be escaped in a "diagnostic selector."
ffx inspect show core/ffx-laboratory\\:profile_store/profile_store

您应该会看到该组件的状态为“正常”。检查与类层次结构集成时非常有用。任意值以 inspect::Node 为根基,包括更多节点!尝试修改 ProfileStore,以便进行以下编译:

  // In main.cc
  std::unique_ptr<ProfileStore> profile_store =
      std::make_unique<ProfileStore>(loop.dispatcher(), inspector->GetRoot().CreateChild("profiles"));

提示:如果您更改 ProfileStore 的构造函数,则需要更新 ProfileStoreTests 类。您可以直接传递 inspect::Node() 作为新参数。

现在,您已经配置了基本的检查,那么可以添加什么说明来帮助预防/发现此组件中的 bug? - 考虑为每个 Profile 添加一个 inspect::Node,并在传递给 ProfileStore 的节点上使用 CreateChild 创建这些对象。 - 请考虑使用 inspect::LazyNode (node.CreateLazyNode(...)) 动态创建层次结构。

您可以在以下补丁中找到可能的解决方案: https://fuchsia-review.googlesource.com/c/fuchsia/+/682671

分类

借助分类功能,您可以编写规则,以自动处理检查快照并发现潜在问题,或收集快照可能包含的统计信息。

最好是先通读分类 Codelab 以及通读分类配置指南

首先,在 examples/diagnostics/workshop/triage/profile_store.triage 处创建一个包含以下内容的新文件:

{
  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.",
    }
  }
}

如果您按照上一部分中的“检查”快速入门中的说明操作,请运行 ffx triage --config examples/diagnostics/workshop/triage/。如果 profile_store 正在运行并且报告其状态为正常,那么您不会看到任何失败情况!请尝试将 main.cc 中对 Health().Ok() 的调用更改为 Health().StartingUp(),然后再次运行 ffx triage --config examples/diagnostics/workshop/triage/。这一次,您应该会看到警告。

请尝试编写一个分类配置,它本可能有助于在实地收集的快照中发现 bug。

您可以在以下补丁中找到可能的解决方案(基于 Inspect 解决方案构建): https://fuchsia-review.googlesource.com/c/fuchsia/+/684762

通过测试进行验证

本部分介绍了如何添加测试来验证修正效果。

此示例包含示例单元测试示例集成测试,其中包括一些因 bug 而停用的测试。

请尝试编写有助于防止组件中出现 bug 的新测试。您可以随意修改示例测试,也可以按照下面的流程从头开始创建新测试。

添加新的单元测试

单元测试的代码结构在很大程度上取决于所使用的运行时。本部分逐步介绍了如何设置新的单元测试,以验证 ProfileStore C++ 类的行为。

examples/diagnostics/workshop/profile_store_unittest.cc 中创建一个包含以下内容的新文件:

#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();
}

这会设置一个最低限度的单元测试,该测试会创建 ProfileStore,并创建要在异步测试循环下与其交互的客户端。接下来,您将为测试创建一个组件清单,用于定义如何运行测试。 在 examples/diagnostics/workshop/meta/profile_store_unittests.cm 中为测试创建新组件清单,其中包含以下内容:

{
    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",
        },
    ],
}

最后,向 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",
  ]
}

接下来,验证测试是否已构建并运行。

# 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

现在,您可以修改测试代码以验证行为。

添加新的集成测试

fx testgen 命令会自动生成集成测试样板设置,以使用 RealmBuilder。若要使用它,您需要在输出目录中找到 profile_store 组件的已编译组件清单。

# find the manifest in output directory.
find $(fx get-build-dir) -name profile_store.cm

# generate integration tests.
fx testgen --cm-location find result --out-dir examples/diagnostics/workshop/tests -c

这应该会在 examples/diagnostics/workshop/tests 下生成几个文件。在运行测试之前,需要更新一些构建规则:

  • 在新生成的 examples/diagnostics/workshop/tests/BUILD.gn
    • {COMPONENT_FIDL_BUILD_TARGET} 替换为 ProfileStore fidl 的构建目标://examples/diagnostics/workshop/fidl:fuchsia.examples.diagnostics
    • {COMPONENT_BUILD_TARGET} 替换为 ProfileStore 组件的构建目标://examples/diagnostics/workshop:profile_store/
  • examples/diagnostics/workshop/BUILD.gn
    • group("tests") 定义中将“tests”添加到依赖项。这样可以确保 GN 能够找到新测试。

接下来,验证测试是否已构建并运行。

# 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

测试运行后,您就可以修改样板以编写有用的测试了。