本文档包含使用 C++ 和 Rust 进行检查的 Codelab。
您可从以下位置获取代码:
此 Codelab 分为多个部分,每个部分都有自己的子目录。此 Codelab 的起点是第 1 部分,每个部分的代码都包含前面部分的解决方案。
学习此 Codelab 时,您可以继续将解决方案添加到“part_1”,也可以在现有解决方案的基础上进行构建(跳过此步骤)。
前提条件
设置您的开发环境。
此 Codelab 假定您已完成使用入门,并且满足以下条件:
- 一棵签出并建造的 Fuchsia 树。
- 运行 Fuchsia 的设备或模拟器 (
ffx emu
)。 - 工作站,用于将组件 (
fx serve
) 提供给 Fuchsia 设备或模拟器。
如需构建并运行此 Codelab 中的示例,请将以下参数添加到 fx set
调用中:
C++
fx set core.x64 \
--with //examples/diagnostics/inspect/codelab/cpp \
--with //examples/diagnostics/inspect/codelab/cpp:tests
Rust
fx set core.x64 \
--with //examples/diagnostics/inspect/codelab/rust \
--with //examples/diagnostics/inspect/codelab/rust:tests
第 1 部分:有漏洞的组件
// Implementation of a string reverser.
@discoverable
closed protocol Reverser {
// Returns the input string reversed character-by-character.
strict Reverse(struct {
input string:1024;
}) -> (struct {
response string:1024;
});
};
此协议有一个名为 Reverse
的方法,该方法只会反转传递给它的任何字符串。系统提供了该协议的实现,但存在一个严重 bug。此 bug 会使尝试调用 Reverse
方法的客户端看到其调用无限期挂起。您应自行修复此错误。
运行组件
有一个客户端应用将启动 Reverser 组件,并将其其余命令行参数作为字符串发送到 Reverse:
查看使用情况
根据您希望运行的 Codelab 的哪一部分,您可以启动
client_i
组件,其中i
为 [1, 5] 范围内的数字。例如,如需启动与 Codelab 第 2 部分中的倒序器通信的客户端,请执行以下操作:C++
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm
Rust
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm
运行第 1 部分代码,并反转字符串“Hello”
如果仅指定单个字符串“Hello”,请修改 common.fragment.cml 的
program.args
部分,请构建并运行以下内容:C++
ffx component run /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm
如需查看命令输出,请查看日志:
ffx log --tags inspect_cpp_codelab
此命令会输出一些包含错误的输出。
Rust
ffx component run /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
如需查看命令输出,请查看日志:
ffx log --tags inspect_rust_codelab
我们在日志中看到,该组件获得了“Hello”作为输入,但我们没有看到正确的反转输出。
正如您在日志中看到的,反向器无法正常运行。
尝试使用更多参数运行客户端:
将字符串“World”添加到 common.hard.cml 的
program.args
部分:{ program: { args: [ "Hello", "World", ], }, }
构建并运行以下代码:
C++
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm ```
Rust
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm ```
我们可以看到,该组件输出了第一个输入,但没有看到预期输出和第二个输入。
现在,您可以查看代码以排查问题。
浏览代码
现在,您可以重现问题了,现在看看客户端正在做什么:
C++
在 client main 中:
// Repeatedly send strings to be reversed to the other component.
for (int i = 1; i < argc; i++) {
FX_LOGS(INFO) << "Input: " << argv[i];
std::string output;
status = zx::make_result(reverser->Reverse(argv[i], &output));
if (status.is_error()) {
FX_LOGS(ERROR) << "Error: Failed to reverse string.";
return status.status_value();
}
FX_LOGS(INFO) << "Output: " << output;
}
Rust
在 client main 中:
for string in args.strings {
info!("Input: {}", string);
match reverser.reverse(&string).await {
Ok(output) => info!("Output: {}", output),
Err(e) => error!(error = ?e, "Failed to reverse string"),
}
}
在以下代码段中,客户端会调用 Reverse
方法,但似乎从未得到响应。似乎没有错误消息或输出。
请查看本部分 Codelab 的服务器代码。有很多标准组件设置:
C++
在第 1 部分主要内容中:
Logging 初始化
fuchsia_logging::SetTags({"inspect_cpp_codelab", "part1"});
创建异步执行器
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
提供公共服务
context->outgoing()->AddPublicService(Reverser::CreateDefaultHandler());
Rust
在第 1 部分主要内容中:
Logging 初始化
#[fuchsia::main(logging_tags = ["inspect_rust_codelab", "part1"])]
ServiceFs 初始化
let mut fs = ServiceFs::new();
ServiceFs 集合
let running_service_fs = fs.collect::<()>().map(Ok);
提供公共服务
fs.dir("svc").add_fidl_service(move |stream| reverser_factory.spawn_new(stream)); fs.take_and_serve_directory_handle()?;
查看反向器的定义:
C++
在 reverser.h 中:
class Reverser final : public fuchsia::examples::inspect::Reverser {
public:
// CODELAB: Create a new constructor for Reverser that takes an Inspect node.
// Implementation of Reverser.Reverse().
void Reverse(std::string input, ReverseCallback callback) override;
// Return a request handler for the Reverser protocol that binds incoming requests to new
// Reversers.
static fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> CreateDefaultHandler();
};
此类实现了 Reverser
协议。一个名为 CreateDefaultHandler
的辅助方法会构造一个 InterfaceRequestHandler
,用于为传入请求创建新的 Reverser
。
Rust
在 reverser.rs 中:
pub struct ReverserServerFactory {}
impl ReverserServerFactory {
// CODELAB: Create a new() constructor that takes an Inspect node.
pub fn new() -> Self {
Self {}
}
pub fn spawn_new(&self, stream: ReverserRequestStream) {
// CODELAB: Add stats about incoming connections.
ReverserServer::new().spawn(stream);
}
}
struct ReverserServer {}
impl ReverserServer {
// CODELAB: Create a new() constructor that takes an Inspect node.
fn new() -> Self {
Self {}
}
pub fn spawn(self, mut stream: ReverserRequestStream) {
fasync::Task::local(async move {
while let Some(request) = stream.try_next().await.expect("serve reverser") {
// CODELAB: Add stats about incoming requests.
let ReverserRequest::Reverse { input, responder: _ } = request;
let _result = input.chars().rev().collect::<String>();
// Yes, this is silly. Just for codelab purposes.
fasync::Timer::new(fasync::Time::after(10.hours())).await
}
})
.detach();
}
}
此结构体提供 Reverser
协议。ReverserServerFactory
(稍后会更有意义)会在建立与 Reverser
的新连接时构造 ReverserServer
。
添加检查
现在,您已经了解了代码结构,可以开始使用 Inspect 功能对代码进行插桩以找出问题。
您以前可能通过输出或记录调试过了程序。虽然这种方法通常有效,但随着时间的推移,持续运行的异步组件经常会输出大量有关其内部状态的日志。此 Codelab 展示了 Inspect 如何在无需浏览日志的情况下提供组件的当前状态的快照。
添加检查依赖项:
C++
在 BUILD.gn 中:
source_set("lib") { sources = [ "reverser.cc", "reverser.h", ] public_deps = [ "//examples/diagnostics/inspect/codelab/fidl:fuchsia.examples.inspect_hlcpp", "//sdk/lib/inspect/component/cpp", ] }
Rust
在
deps
中的rustc_binary("bin")
下的 BUILD.gn 中:"//src/lib/diagnostics/inspect/runtime/rust", "//src/lib/diagnostics/inspect/rust", "//src/lib/fuchsia",
初始化检查:
C++
在 main.cc 中:
#include <lib/inspect/component/cpp/component.h> inspect::ComponentInspector inspector(loop.dispatcher(), {});
Rust
在 main.rs 中:
use fuchsia_inspect::component; let _inspect_server_task = inspect_runtime::publish( component::inspector(), inspect_runtime::PublishOptions::default(), );
您现在正在使用 Inspect。
添加一个简单的“version”属性,以显示正在运行的版本:
C++
inspector.root().RecordString("version", "part2");
此代码段会执行以下操作:
获取 Inspect 层次结构的“根”节点。
组件的 Inspect 层次结构由一个节点树组成,每个节点包含任意数量的属性。
使用
CreateString
创建新的媒体资源。这会在根上添加新的
StringProperty
。此StringProperty
称为“版本”,其值为“part2”。属性设为“part1”在检查器中放置新属性。
属性的生命周期与
Create
返回的对象相关联,销毁对象会导致该属性消失。可选的第三个参数会将新属性放置在inspector
中,而不是返回它。因此,新属性与检查器本身一样长(组件的整个执行过程)。
Rust
component::inspector().root().record_string("version", "part2");
此代码段会执行以下操作:
- 获取 Inspect 层次结构的“根”节点。
组件的 Inspect 层次结构由一个节点树组成,每个节点包含任意数量的属性。
- 使用
record_string
创建新的媒体资源。
这会在根上添加新的
StringProperty
。此StringProperty
称为“版本”,其值为“part2”。属性设为“part1”- 它会将其记录在根节点中。
创建属性的常用方法是通过节点上的
create_*
方法。使用这些方法创建的属性的生命周期与返回的对象相关联,销毁对象会导致该属性消失。该库提供了record_*
便捷方法,可用于创建属性并将属性的生命周期与调用该方法的节点关联起来。因此,新属性与节点本身一样长(在这种情况下,与根节点一样长,因此组件的整个执行过程也一样)。
读取检查数据
现在,您已向组件中添加了 Inspect,接下来可以阅读它的内容了:
重新构建和更新目标系统
fx build && fx ota
运行客户端:
C++
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm ffx log --tags inspect_cpp_codelab
Rust
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm ffx log --tags inspect_rust_codelab
使用
ffx inspect
查看您的输出:ffx inspect show
这会转储整个系统的所有检查数据,其中可能包含大量数据。
由于
ffx inspect
支持 glob 匹配,因此请运行以下命令:C++
$ ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser' # or `ffx inspect show --manifest inspect_cpp_codelab` metadata: filename = fuchsia.inspect.Tree component_url = fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/part_1.cm timestamp = 4728864898476 payload: root: version = part1
Rust
$ ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser' # or `ffx inspect show --manifest inspect_rust_codelab` metadata: filename = fuchsia.inspect.Tree component_url = fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/part_1.cm timestamp = 4728864898476 payload: root: version = part1
您还可以查看 JSON 格式的输出:
C++
$ ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser' [ { "data_source": "Inspect", "metadata": { "errors": null, "filename": "fuchsia.inspect.Tree", "component_url": "fuchsia-pkg://fuchsia.com/inspect_pp_codelab#meta/part_1.cm", "timestamp": 5031116776282 }, "moniker": "core/ffx-laboratory\\:client_part_5/reverser", "payload": { "root": { "version": "part1", }, "version": 1 } ]
Rust
$ ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser' [ { "data_source": "Inspect", "metadata": { "errors": null, "filename": "fuchsia.inspect.Tree", "component_url": "fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/part_1.cm", "timestamp": 5031116776282 }, "moniker": "core/ffx-laboratory\\:client_part_5/reverser", "payload": { "root": { "version": "part1", }, "version": 1 } ]
对代码进行插桩以查找 bug
现在,您已经初始化了 Inspect 并知道如何读取数据,接下来可以对代码进行插桩并发现 bug。
前面的输出显示了组件的实际运行情况,以及组件并未完全挂起。否则,检查读取会挂起。
为每个连接添加新信息,以观察组件是否正在处理连接。
向根节点添加新的子节点,以包含有关
reverser
服务的统计信息:C++
context->outgoing()->AddPublicService( Reverser::CreateDefaultHandler(inspector.root().CreateChild("reverser_service")));
Rust
let reverser_factory = ReverserServerFactory::new(component::inspector().root().create_child("reverser_service"));
更新您的服务器以接受此节点:
C++
更新 reverser.h 和 reverser.cc 中
CreateDefaultHandler
的定义:#include <lib/inspect/cpp/inspect.h> fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> Reverser::CreateDefaultHandler( inspect::Node node) {
Rust
更新
ReverserServerFactory::new
以在 reverser.rs 中接受此节点:use fuchsia_inspect as inspect; pub struct ReverserServerFactory { node: inspect::Node, // ... } impl ReverserServerFactory { pub fn new(node: inspect::Node) -> Self { // ... Self { node, // ... } } // ... }
添加媒体资源以跟踪连接数量:
C++
fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> Reverser::CreateDefaultHandler( inspect::Node node) { // ... // Return a handler for incoming FIDL connections to Reverser. // // The returned closure contains a binding set, which is used to bind incoming requests to a // particular implementation of a FIDL interface. This particular binding set is configured to // bind incoming requests to unique_ptr<Reverser>, which means the binding set itself takes // ownership of the created Reversers and frees them when the connection is closed. return [connection_count = node.CreateUint("connection_count", 0), node = std::move(node), // ...
Rust
use { // ... fuchsia_inspect::NumericProperty, // ... }; pub struct ReverserServerFactory { node: inspect::Node, // ... connection_count: inspect::UintProperty, } impl ReverserServerFactory { pub fn new(node: inspect::Node) -> Self { // ... let connection_count = node.create_uint("connection_count", 0); Self { node, // ... connection_count, } } pub fn spawn_new(&self, stream: ReverserRequestStream) { self.connection_count.add(1);
以下代码段演示了如何创建一个名为
connection_count
的新UintProperty
(包含 64 位无符号整数)并将其设置为 0。在处理程序(为每个连接运行)中,属性按 1 递增。重新构建,然后重新运行组件,然后运行
ffx inspect
:C++
$ ffx --machine json-pretty inspect show --manifest inspect_cpp_codelab
Rust
$ ffx --machine json-pretty inspect show --manifest inspect_rust_codelab
您现在应该会看到:
... "payload": { "root": { "version": "part1", "reverser_service": { "connection_count": 1, } } }
上面的输出表明客户端已成功连接到服务,因此挂起问题可能是由 Reverser 实现本身引起的。具体而言,了解以下内容将对您有所帮助:
如果客户端挂起时连接仍处于打开状态。
是否调用了
Reverse
方法。
练习:为每个连接创建一个子节点,并在 Reverser 中记录“request_count”。
提示:有一个实用函数可用于生成唯一名称:
C++
auto child = node.CreateChild(node.UniqueName("connection-"));
Rust
let node = self.node.create_child(inspect::unique_name("connection"));
这将创建以“connection”开头的唯一名称。
C++
提示:您会发现,为 Reverser 创建带有 inspect::Node
的构造函数会很有帮助。此 Codelab 的第 3 部分介绍了为什么这种模式非常有用。
Rust
提示:您会发现,为 ReverserServer
创建一个接受 inspect::Node
的构造函数会很有帮助,原因与为 ReverserServerFactory
创建的原因相同。
提示:您需要在 Reverser 上创建一个成员来保留
request_count
属性。其类型为inspect::UintProperty
。跟进:请求数是否可以为您提供所需的所有信息?还要添加
response_count
。高级:能否添加所有连接上的所有请求计数?Reverser 对象必须共享某种状态。您可能会发现将 Reverser 的参数重构为单独的结构体很有帮助(如需了解此方法,请参阅第 2 部分中的解决方案)。
完成本练习并运行 ffx inspect
后,您应该会看到如下内容:
...
"payload": {
"root": {
"version": "part1",
"reverser_service": {
"connection_count": 1,
"connection0": {
"request_count": 1,
}
}
}
}
上面的输出显示,连接仍处于打开状态,并且收到了一个请求。
C++
如果您还添加了“response_count”,您可能已经注意到 bug。
Reverse
方法会收到 callback
,但绝不会使用 output
值调用该方法。
Rust
如果您还添加了“response_count”,您可能已经注意到 bug。
Reverse
方法会收到 responder
,但绝不会使用 result
值调用该方法。
发送响应:
C++
// At the end of Reverser::Reverse callback(std::move(output));
Rust
responder.send(&result).expect("send reverse request response");
再次运行客户端:
C++
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm Creating component instance: client_part_1 ffx log --tags inspect_cpp_codelab [00039.129068][39163][39165][inspect_cpp_codelab, client] INFO: Input: Hello [00039.194151][39163][39165][inspect_cpp_codelab, client] INFO: Output: olleH [00039.194170][39163][39165][inspect_cpp_codelab, client] INFO: Input: World [00039.194402][39163][39165][inspect_cpp_codelab, client] INFO: Output: dlroW [00039.194407][39163][39165][inspect_cpp_codelab, client] INFO: Done reversing! Please use `ffx component stop`
Rust
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm Creating component instance: client_part_1 ffx log --tags inspect_rust_codelab [00039.129068][39163][39165][inspect_rust_codelab, client] INFO: Input: Hello [00039.194151][39163][39165][inspect_rust_codelab, client] INFO: Output: olleH [00039.194170][39163][39165][inspect_rust_codelab, client] INFO: Input: World [00039.194402][39163][39165][inspect_rust_codelab, client] INFO: Output: dlroW [00039.194407][39163][39165][inspect_rust_codelab, client] INFO: Done reversing! Please use `ffx component stop`
在您执行 ffx component stop
之前,该组件会继续运行。只要该组件运行,您就可以运行 ffx inspect
并观察输出。
第 1 部分到此结束。您可以提交截至目前的更改:
git commit -am "solution to part 1"
第 2 部分:诊断组件间问题
您收到了错误报告。“FizzBuzz”团队表示 他们未从你的组件接收数据
除了提供 Reverser 协议之外,该组件还会联系“FizzBuzz”服务并输出响应:
C++
fuchsia::examples::inspect::FizzBuzzPtr fizz_buzz;
context->svc()->Connect(fizz_buzz.NewRequest());
fizz_buzz->Execute(30, [](std::string result) { FX_LOGS(INFO) << "Got FizzBuzz: " << result; });
Rust
let fizzbuzz_fut = async move {
let fizzbuzz = client::connect_to_protocol::<FizzBuzzMarker>()
.context("failed to connect to fizzbuzz")?;
match fizzbuzz.execute(30u32).await {
Ok(result) => info!(%result, "Got FizzBuzz"),
Err(_) => {}
};
Ok(())
};
如果您看到日志,会发现此日志永远不会输出。
C++
ffx log --tags inspect_cpp_codelab
Rust
ffx log --tags inspect_rust_codelab
您需要诊断并解决这个问题。
使用“检查”功能诊断问题
运行该组件,看看会发生什么情况:
C++
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm
Rust
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm
幸运的是,FizzBuzz 团队使用 Inspect 功能对组件进行了插桩测试。
和之前一样,使用
ffx inspect
读取 FizzBuzz 检查数据,您会得到以下内容:"payload": { "root": { "fizzbuzz_service": { "closed_connection_count": 0, "incoming_connection_count": 0, "request_count": 0, ...
此输出信息表明 FizzBuzz 未收到任何连接。
添加检查以找出问题:
C++
// CODELAB: Instrument our connection to FizzBuzz using Inspect. Is there an error? fuchsia::examples::inspect::FizzBuzzPtr fizz_buzz; context->svc()->Connect(fizz_buzz.NewRequest()); fizz_buzz.set_error_handler([&](zx_status_t status) { // CODELAB: Add Inspect here to see if there is a response. }); fizz_buzz->Execute(30, [](std::string result) { // CODELAB: Add Inspect here to see if there was a response. FX_LOGS(INFO) << "Got FizzBuzz: " << result; });
Rust
let fizzbuzz_fut = async move { let fizzbuzz = client::connect_to_protocol::<FizzBuzzMarker>() .context("failed to connect to fizzbuzz")?; match fizzbuzz.execute(30u32).await { Ok(result) => { // CODELAB: Add Inspect here to see if there is a response. info!(%result, "Got FizzBuzz"); } Err(_) => { // CODELAB: Add Inspect here to see if there is an error } }; Ok(()) };
练习:向 FizzBuzz 连接添加检查以找出问题
提示:您可从上面的代码段入手,它为连接尝试提供了一个错误处理程序。
C++
后续跟进:可以将状态存储在某个地方吗?您可以使用 zx_status_get_string(status)
将其转换为字符串。
高级:inspector
具有一个名为 Health()
的方法,用于公布一个特殊位置的整体健康状况。由于我们的服务只有在能够连接到 FizzBuzz 的情况下,才能正常运行,因此,您可以整合以下内容:
/*
"fuchsia.inspect.Health": {
"status": "STARTING_UP"
}
*/
inspector.Health().StartingUp();
/*
"fuchsia.inspect.Health": {
"status": "OK"
}
*/
inspector.Health().Ok();
/*
"fuchsia.inspect.Health": {
"status": "UNHEALTHY",
"message": "Something went wrong!"
}
*/
inspector.Health().Unhealthy("Something went wrong!");
Rust
高级:fuchsia_inspect::component
具有一个名为 health()
的函数,该函数会返回一个对象,用于在特殊位置(检查树根目录的节点子项)公布总体运行状况。由于我们的服务只有在能够连接到 FizzBuzz 的情况下才能正常运行,因此,您可以整合以下内容:
/*
"fuchsia.inspect.Health": {
"status": "STARTING_UP"
}
*/
fuchsia_inspect::component::health().set_starting_up();
/*
"fuchsia.inspect.Health": {
"status": "OK"
}
*/
fuchsia_inspect::component::health().set_ok();
/*
"fuchsia.inspect.Health": {
"status": "UNHEALTHY",
"message": "Something went wrong!"
}
*/
fuchsia_inspect::component::health().set_unhealthy("something went wrong!");
完成本练习后,您应该会看到系统正在调用连接错误处理程序并显示“未找到”错误。检查输出显示 FizzBuzz 正在运行,因此可能配置有误。遗憾的是,目前并非所有内容都使用 Inspect,因此请查看以下日志:
C++
$ ffx log --filter FizzBuzz
...
... No capability available at path /svc/fuchsia.examples.inspect.FizzBuzz
for component /core/ffx-laboratory:client_part_2/reverser, verify the
component has the proper `use` declaration. ...
Rust
$ ffx log --filter FizzBuzz
...
... No capability available at path /svc/fuchsia.examples.inspect.FizzBuzz
for component /core/ffx-laboratory:client_part_2/reverser, verify the
component has the proper `use` declaration. ...
沙盒错误是一个常见的陷阱,有时很难发现。
查看第 2 部分元,您会发现它缺少相应服务:
C++
将 Fizzbuzz
的 use
条目添加到 part_2/meta
use: [
{ protocol: "fuchsia.examples.inspect.FizzBuzz" },
],
Rust
将 Fizzbuzz
的 use
条目添加到 part_2/meta
use: [
{ protocol: "fuchsia.examples.inspect.FizzBuzz" },
],
添加“fuchsia.examples.inspect.FizzBuzz”后,请重新构建并再次运行。现在,您应该会在日志中看到 FizzBuzz 和 OK 状态:
C++
$ ffx log --tags inspect_cpp_codelab
[inspect_cpp_codelab, part2] INFO: main.cc(57): Got FizzBuzz: 1 2 Fizz
4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz
22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz
Rust
$ ffx log --tags inspect_rust_codelab
[inspect_rust_codelab, part2] INFO: main.rs(52): Got FizzBuzz: 1 2 Fizz
4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz
22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz
第 2 部分到此结束。
现在,您可以提交您的解决方案了:
git commit -am "solution for part 2"
第 3 部分:用于检查的单元测试
Fuchsia 上的所有代码都应进行测试,这也适用于检查数据。
虽然通常不要求测试 Inspect 数据,但对于其他工具(例如分类或反馈)所依赖的 Inspect 数据,您需要对其进行测试。
Reverser 具有基本的单元测试。运行:
C++
单元测试位于 reverser_unittests.cc 中。
fx test inspect_cpp_codelab_unittests
Rust
单元测试位于 reverser.rs > mod testing 中。
fx test inspect_rust_codelab_unittests
单元测试可确保 Reverser 正常运行(不会挂起!),但它不会检查 Inspect 输出是否符合预期。
将节点传入构造函数是一种依赖项注入,它允许您传入依赖项的测试版本以检查其状态。
打开 Reverser 的代码如下所示:
C++
binding_set_.AddBinding(std::make_unique<Reverser>(ReverserStats::CreateDefault()),
ptr.NewRequest());
// Alternatively
binding_set_.AddBinding(std::make_unique<Reverser>(inspect::Node()),
ptr.NewRequest());
Rust
let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<ReverserMarker>()?;
let reverser = ReverserServer::new(ReverserServerMetrics::default());
reverser.spawn(stream);
检查节点的默认版本会传递到 Reverser 中。这样,逆向器代码便可以在测试中正常运行,但不支持对 Inspect 输出进行断言。
C++
练习:更改 OpenReverser
以将 Reverser 的依赖项作为参数,并在构建 Reverser 时使用它。
提示:在测试函数中创建一个
inspect::Inspector
。您可以使用inspector.GetRoot()
获取根目录。提示:您需要在根目录中创建一个子级以传入
OpenReverser
。
Rust
练习:更改 open_reverser
以将 ReverserServerFactory
的依赖项作为参数,并在构建 Reverser 时使用它。
提示:在测试函数中创建一个
fuchsia_inspect::Inspector
。您可以使用inspector.root()
获取根目录。注意:请勿直接在测试中使用
component::inspector()
,因为这样会创建一个静态检查器,该检查器将在所有测试中处于活动状态,并可能导致不稳定或意外行为。对于单元测试,通常使用新的fuchsia_inspect::Inspector
提示:您需要在根目录中创建一个子级以传入
ReverserServerFactory::new
。
跟进:创建多个反向器连接,并分别对其进行测试。
完成本练习后,您的单元测试将在检查层次结构中设置实际值。
添加代码以在 Inspect 中测试输出:
C++
#include <lib/inspect/testing/cpp/inspect.h>
fpromise::result<inspect::Hierarchy> hierarchy =
RunPromise(inspect::ReadFromInspector(inspector));
ASSERT_TRUE(hierarchy.is_ok());
上面的代码段会读取包含 Inspect 数据的底层虚拟内存对象 (VMO),并将其解析为一个可读的层次结构。
您现在可以读取各个属性和子级,如下所示:
auto* global_count =
hierarchy.value().node().get_property<inspect::UintPropertyValue>("request_count");
ASSERT_TRUE(global_count);
EXPECT_EQ(3u, global_count->value());
auto* connection_0 = hierarchy.value().GetByPath({"connection_0x0"});
ASSERT_TRUE(connection_0);
auto* requests_0 =
connection_0->node().get_property<inspect::UintPropertyValue>("request_count");
ASSERT_TRUE(requests_0);
EXPECT_EQ(2u, requests_0->value());
Rust
use diagnostics_assertions::assert_data_tree;
let inspector = inspect::Inspector::default();
assert_data_tree!(inspector, root: {
reverser_service: {
total_requests: 3u64,
connection_count: 2u64,
"connection0": {
request_count: 2u64,
response_count: 2u64,
},
"connection1": {
request_count: 1u64,
response_count: 1u64,
},
}
});
上述代码段从包含 Inspect 数据的底层虚拟内存对象 (VMO) 中读取快照,并将其解析为可读层次结构。
练习:为其余的 Inspect 数据添加断言。
第 3 部分到此结束。
您可以在以下时间提交更改:
git commit -am "solution to part 3"
第 4 部分:用于检查的集成测试
集成测试是 Fuchsia 软件开发工作流的重要组成部分。通过集成测试,您可以观察实际组件在系统上运行时的行为。
运行集成测试
您可以按如下方式针对 Codelab 运行集成测试:
C++
$ fx test inspect_cpp_codelab_integration_tests
Rust
$ fx test inspect_rust_codelab_integration_tests
查看代码
我们来看看集成测试的设置方式:
查看集成测试的组件清单:
C++
在 part_4/meta 中找到组件清单 (cml)
Rust
在 part_4/meta 中找到组件清单 (cml)
{
...
use: [
{ protocol: "fuchsia.diagnostics.ArchiveAccessor" },
]
}
此文件使用父级的 fuchsia.diagnostics.ArchiveAccessor
协议。此协议适用于所有测试,以便读取有关测试领域下所有组件的诊断信息。
查看集成测试本身。各个测试用例相当简单:
C++
在 part4/tests/integration_test.cc 中找到集成测试。
TEST_F(IntegrationTestPart4, StartWithFizzBuzz) { auto ptr = ConnectToReverser({.include_fizzbuzz = true}); bool error = false; ptr.set_error_handler([&](zx_status_t unused) { error = true; }); bool done = false; std::string result; ptr->Reverse("hello", [&](std::string value) { result = std::move(value); done = true; }); RunLoopUntil([&] { return done || error; }); ASSERT_FALSE(error); EXPECT_EQ("olleh", result); // CODELAB: Check that the component was connected to FizzBuzz. }
StartComponentAndConnect
负责创建新的测试环境,并在其中启动 Codelab 组件。include_fizzbuzz_service
选项用于指示该方法可选择性地包含 FizzBuzz。如果未能按照第 2 部分中的说明连接到 FizzBuzz,此功能会测试您的 Inspect 输出是否符合预期。Rust
在 part4/tests/integration_test.rs 中找到集成测试。
#[fuchsia::test] async fn start_with_fizzbuzz() -> Result<(), Error> { let test = IntegrationTest::start(4, TestOptions::default()).await?; let reverser = test.connect_to_reverser()?; let result = reverser.reverse("hello").await?; assert_eq!(result, "olleh"); // CODELAB: Check that the component was connected to FizzBuzz. Ok(()) }
IntegrationTest::start
负责创建新的测试环境,并在其中启动 Codelab 组件。include_fizzbuzz
选项用于指示方法选择性地启动 FizzBuzz 组件。如果未能按照第 2 部分中的说明连接到 FizzBuzz,此功能会测试您的检查输出是否符合预期。将以下方法添加到您的测试夹具中,以从 ArchiveAccessor 服务读取数据:
C++
#include <rapidjson/document.h> #include <rapidjson/pointer.h> std::string GetInspectJson() { fuchsia::diagnostics::ArchiveAccessorPtr archive; auto svc = sys::ServiceDirectory::CreateFromNamespace(); svc->Connect(archive.NewRequest()); while (true) { ContentVector current_entries; fuchsia::diagnostics::BatchIteratorPtr iterator; fuchsia::diagnostics::StreamParameters stream_parameters; stream_parameters.set_data_type(fuchsia::diagnostics::DataType::INSPECT); stream_parameters.set_stream_mode(fuchsia::diagnostics::StreamMode::SNAPSHOT); stream_parameters.set_format(fuchsia::diagnostics::Format::JSON); { std::vector<fuchsia::diagnostics::SelectorArgument> args; args.emplace_back(); args[0].set_raw_selector(ReverserMonikerForSelectors() + ":root"); fuchsia::diagnostics::ClientSelectorConfiguration client_selector_config; client_selector_config.set_selectors(std::move(args)); stream_parameters.set_client_selector_configuration(std::move(client_selector_config)); } archive->StreamDiagnostics(std::move(stream_parameters), iterator.NewRequest()); bool done = false; iterator->GetNext([&](auto result) { if (result.is_response()) { current_entries = std::move(result.response().batch); } done = true; }); RunLoopUntil([&] { return done; }); // Should be at most one component. ZX_ASSERT(current_entries.size() <= 1); if (!current_entries.empty()) { std::string json; fsl::StringFromVmo(current_entries[0].json(), &json); // Ensure the component is either OK or UNHEALTHY. if (json.find("OK") != std::string::npos || json.find("UNHEALTHY") != std::string::npos) { return json; } } // Retry with delay until the data appears. usleep(150000); } return ""; }
Rust
use { anyhow::format_err, diagnostics_assertions::{assert_data_tree, AnyProperty}, diagnostics_reader::{ArchiveReader, DiagnosticsHierarchy, Inspect}, }; async fn get_inspect_hierarchy(test: &IntegrationTest) -> Result<DiagnosticsHierarchy, Error> { let moniker = test.reverser_moniker_for_selectors(); ArchiveReader::new() .add_selector(format!("{}:root", moniker)) .snapshot::<Inspect>() .await? .into_iter() .next() .and_then(|result| result.payload) .ok_or(format_err!("expected one inspect hierarchy")) }
锻炼。在测试中使用返回的数据,并向返回的数据添加断言:
C++
rapidjson::Document document; document.Parse(GetInspectJson());
为返回的 JSON 数据添加断言。
提示:输出 JSON 输出以查看架构可能会有帮助。
提示:您可以按路径读取值,如下所示:
提示:您可以
EXPECT_EQ
的方式,将预期值作为 speedjson::Value:rapidjson::Value("OK")
传入。
rapidjson::GetValueByPointerWithDefault( document, "/payload/root/fuchsia.inspect.Health/status", "")
Rust
let hierarchy = get_inspect_hierarchy(&test).await?;
对返回的
DiagnosticsHierarchy
添加断言。- 提示:输出 JSON 输出以查看架构可能会有帮助。
您的集成测试现在将确保您的检查输出正确无误。
第 4 部分到此结束。
您可以在下列情况下提交您的解决方案:
git commit -am "solution to part 4"
第 5 部分:反馈选择器
此部分正在建设中。
待办事项:编写反馈选择器并将测试添加到集成测试中。
TODO:反馈选择器和其他流水线