# 教程:使用 zxdb 调试测试
本教程将介绍如何使用 fx test
命令和 Fuchsia 调试器 (zxdb
) 完成调试工作流。
如需详细了解如何使用 zxdb 调试测试,请参阅使用 zxdb 调试测试
了解测试用例
Rust
Rust 测试由 Rust 测试运行程序执行。与 C++ 测试的 gTest 或 gUnit 运行程序不同,Rust 测试运行程序默认并行运行测试用例。这会带来不同的 --break-on-failure
功能使用体验。如需详细了解调试并行测试进程时的预期情况,请参阅并行执行测试用例。支持并行测试流程。
以下示例基于一些示例 Rust 测试代码:
#[fuchsia::test]
async fn add_test() {
let (proxy, stream) = create_proxy_and_stream::<CalculatorMarker>();
// Run two tasks: The calculator_fake & the calculator_line method we're interested
// in testing.
let fake_task = calculator_fake(stream).fuse();
let calculator_line_task = calculator_line("1 + 2", &proxy).fuse();
futures::pin_mut!(fake_task, calculator_line_task);
futures::select! {
actual = calculator_line_task => {
let actual = actual.expect("Calculator didn't return value");
assert_eq!(actual, 5.0);
},
_ = fake_task => {
panic!("Fake should never complete.")
}
};
}
您可以使用 fx set
将此测试目标添加到 build 图中:
fx set workbench_eng.x64 --with-test //examples/fidl/calculator/rust/client:hermetic_tests
C++
默认情况下,gTest 测试运行程序会按顺序执行测试用例,因此一次只能调试一个测试失败。您可以通过向 fx test
命令添加 --parallel-cases
标志来并行执行测试用例。
以下示例基于一些示例 C++ 测试代码:
// Inject 1 process.
auto process1 = std::make_unique<MockProcess>(nullptr, kProcessKoid1, kProcessName1);
process1->AddThread(kProcess1ThreadKoid1);
harness.debug_agent()->InjectProcessForTest(std::move(process1));
// And another, with 2 threads.
auto process2 = std::make_unique<MockProcess>(nullptr, kProcessKoid2, kProcessName2);
process2->AddThread(kProcess2ThreadKoid1);
process2->AddThread(kProcess2ThreadKoid2);
harness.debug_agent()->InjectProcessForTest(std::move(process2));
reply = {};
remote_api->OnStatus(request, &reply);
ASSERT_EQ(reply.processes.size(), 3u); // <-- This will fail, since reply.processes.size() == 2
EXPECT_EQ(reply.processes[0].process_koid, kProcessKoid1);
EXPECT_EQ(reply.processes[0].process_name, kProcessName1);
...
您可以使用 fx set
将此测试目标添加到 build 图中:
fx set workbench_eng.x64 --with-test //src/developer/debug:tests
执行测试
Rust
使用 fx test --break-on-failure
命令执行测试,例如:
fx test -o --break-on-failure calculator-client-rust-unittests
输出如下所示:
<...fx test startup...>
...
[RUNNING] tests::add_test
[RUNNING] tests::divide_test
[RUNNING] tests::multiply_test
[PASSED] parse::tests::parse_expression
[RUNNING] tests::pow_test
[PASSED] parse::tests::parse_expression_with_negative_numbers
[RUNNING] tests::subtract_test
[PASSED] parse::tests::parse_expression_with_multiplication
[PASSED] parse::tests::parse_expression_with_subtraction
[PASSED] parse::tests::parse_expression_with_pow
[PASSED] parse::tests::parse_operators
[01234.052188][218417][218422][<root>][add_test] ERROR: [examples/fidl/calculator/rust/client/src/main.rs(110)] PANIC info=panicked at ../../examples/fidl/calculato
r/rust/client/src/main.rs:110:17:
assertion `left == right` failed
left: 3.0
right: 5.0
[PASSED] parse::tests::parse_expression_with_division
[PASSED] tests::multiply_test
[PASSED] tests::divide_test
...
👋 zxdb is loading symbols to debug test failure in calculator-client-rust-unittest, please wait.
⚠ test failure in calculator-client-rust-unittest, type `frame` or `help` to get started.
108 actual = calculator_line_task => {
109 let actual = actual.expect("Calculator didn't return value");
▶ 110 assert_eq!(actual, 5.0);
111 },
112 _ = fake_task => {
🛑 process 8 calculator_client_bin_test::tests::add_test::test_entry_point::λ(core::task::wake::Context*) • main.rs:110
[zxdb]
请注意,测试的输出结果是混杂的,这是因为 Rust 测试运行程序默认以并行方式运行测试用例。您可以将 --parallel-cases
选项与 fx test
搭配使用来避免此问题,例如:fx test --parallel-cases 1 --break-on-failure
calculator-client-rust-unittests
。此标志不是工具正常运行所必需的,但它有助于调试,因为它可以防止多个测试的输出交错,从而使输出更易于阅读。
使用该选项后,输出如下所示:
fx test --parallel-cases 1 -o --break-on-failure calculator-client-rust-unittests
...
[RUNNING] parse::tests::parse_operators
[PASSED] parse::tests::parse_operators
[RUNNING] tests::add_test
[01391.909144][249125][249127][<root>][add_test] ERROR: [examples/fidl/calculator/rust/client/src/main.rs(110)] PANIC info=panicked at ../../examples/fidl/calculato
r/rust/client/src/main.rs:110:17:
assertion `left == right` failed
left: 3.0
right: 5.0
Status: [duration: 5.0s]
Running 1 tests [ ] 0.0%
fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests#meta/calculator-client-rust-unittests.cm [0.5s]
👋 zxdb is loading symbols to debug test failure in calculator-client-rust-unittest, please wait.
⚠ test failure in calculator-client-rust-unittest, type `frame` or `help` to get started.
108 actual = calculator_line_task => {
109 let actual = actual.expect("Calculator didn't return value");
▶ 110 assert_eq!(actual, 5.0);
111 },
112 _ = fake_task => {
🛑 process 2 calculator_client_bin_test::tests::add_test::test_entry_point::λ(core::task::wake::Context*) • main.rs:110
[zxdb]
C++
使用 fx test --break-on-failure
命令执行测试,例如:
fx test -o --break-on-failure debug_agent_unit_tests
输出如下所示:
<...fx test startup...>
Starting: fuchsia-pkg://fuchsia.com/debug_agent_unit_tests#meta/debug_agent_unit_tests.cm (NOT HERMETIC)
Command: fx ffx test run --realm /core/testing/system-tests --max-severity-logs WARN --break-on-failure fuchsia-pkg://fuchsia.com/debug_agent_unit_tests?hash=3f6d97801bb147034a344e3fe1bb69291a7b690b9d3d075246ddcba59397ac12#meta/debug_agent_unit_tests.cm
Status: [duration: 30.9s] [tasks: 3 running, 15/19 complete]
Running 2 tests [ ] 0.0%
👋 zxdb is loading symbols to debug test failure in debug_agent_unit_tests.cm, please wait.
⚠️ test failure in debug_agent_unit_tests.cm, type `frame` or `help` to get started.
103 remote_api->OnStatus(request, &reply);
104
▶ 105 ASSERT_EQ(reply.processes.size(), 3u);
106 EXPECT_EQ(reply.processes[0].process_koid, kProcessKoid1);
107 EXPECT_EQ(reply.processes[0].process_name, kProcessName1);
🛑 thread 1 debug_agent::DebugAgentTests_OnGlobalStatus_Test::TestBody(debug_agent::DebugAgentTests_OnGlobalStatus_Test*) • debug_agent_unittest.cc:105
[zxdb]
检查失败情况
Rust
该示例包含测试失败,因此 Rust 测试会在失败时发出 abort
,zxdb
会注意到并报告此问题。zxdb
还会分析中止时的调用堆栈,并方便地将我们直接带到失败的源代码中。您可以使用 list
查看当前帧中的其他代码行,例如:
list
105 let calculator_line_task = calculator_line("1 + 2", &proxy).fuse();
106 futures::pin_mut!(fake_task, calculator_line_task);
107 futures::select! {
108 actual = calculator_line_task => {
109 let actual = actual.expect("Calculator didn't return value");
▶ 110 assert_eq!(actual, 5.0);
111 },
112 _ = fake_task => {
113 panic!("Fake should never complete.")
114 }
115 };
116 }
117
118 #[fuchsia::test]
119 async fn subtract_test() {
120 let (proxy, stream) = create_proxy_and_stream::<CalculatorMarker>()
您还可以使用 frame
检查整个调用堆栈,例如:
frame
0…12 «Rust library» (-r expands)
13 std::panicking::begin_panic_handler(…) • library/std/src/panicking.rs:697
14 core::panicking::panic_fmt(…) • library/core/src/panicking.rs:75
15 core::panicking::assert_failed_inner(…) • library/core/src/panicking.rs:448
16 core::panicking::assert_failed<…>(…) • fuchsia-third_party-rust/library/core/src/panicking.rs:403
▶ 17 calculator_client_bin_test::tests::add_test::test_entry_point::λ(…) • main.rs:110
18 core::future::future::«impl»::poll<…>(…) • future/future.rs:133
19…44 «Polled event in fuchsia::test_singlethreaded» (-r expands)
45 calculator_client_bin_test::tests::add_test() • main.rs:98
46 calculator_client_bin_test::tests::add_test::λ(…) • main.rs:99
47 core::ops::function::FnOnce::call_once<…>(…) • fuchsia-third_party-rust/library/core/src/ops/function.rs:253
48 core::ops::function::FnOnce::call_once<…>(…) • library/core/src/ops/function.rs:253 (inline)
49…70 «Rust test startup» (-r expands)
或者,在异步上下文中,您可以使用 async-backtrace
,例如:
async-backtrace
Task(id = 0)
└─ calculator_client_bin_test::tests::divide_test::test_entry_point • select_mod.rs:321
└─ select!
└─ (terminated)
└─ calculator_client_bin_test::tests::calculator_fake • main.rs:93
└─ futures_util::stream::try_stream::try_for_each::TryForEach
Task(id = 1)
└─ diagnostics_log::fuchsia::filter::«impl»::listen_to_interest_changes • fuchsia/filter.rs:63
└─ fidl::client::QueryResponseFut
您运行的所有命令都位于帧 #17 的上下文中,如 ▶
所示。
您可以再次列出源代码,并提供一些额外的背景信息:
list -c 10
100 let (proxy, stream) = create_proxy_and_stream::<CalculatorMarker>();
101
102 // Run two tasks: The calculator_fake & the calculator_line method we're interested
103 // in testing.
104 let fake_task = calculator_fake(stream).fuse();
105 let calculator_line_task = calculator_line("1 + 2", &proxy).fuse();
106 futures::pin_mut!(fake_task, calculator_line_task);
107 futures::select! {
108 actual = calculator_line_task => {
109 let actual = actual.expect("Calculator didn't return value");
▶ 110 assert_eq!(actual, 5.0);
111 },
112 _ = fake_task => {
113 panic!("Fake should never complete.")
114 }
115 };
116 }
117
118 #[fuchsia::test]
119 async fn subtract_test() {
120 let (proxy, stream) = create_proxy_and_stream::<CalculatorMarker>();
如需了解测试失败的原因,请输出一些变量以查看发生了什么情况。actual
帧包含一个局部变量,该变量应包含通过对 log_helper
和 log_helper2
实例调用 write_log
并通过 mpsc 渠道 recv_logs
接收它们而添加的一些字符串:
print actual
3
测试的预期结果似乎略有不正确。计算器本应返回“1 + 2”等于 3,但测试却预期它等于 5!计算器返回了正确答案,但测试预期结果不正确。您现在可以从失败的测试用例中分离出来,并修复测试预期。
detach
<...fx test output continues...>
Failed tests: tests::add_test
11 out of 12 tests passed...
Test fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests?hash=b105775fa7c39eb67195a09d63be6c4314eeef8e93eb542616c0b5dbda73b8e2#meta/calculator-client-rust-unittests.cm produced unex
pected high-severity logs:
----------------xxxxx----------------
[09353.731026][1225676][1225678][<root>][add_test] ERROR: [examples/fidl/calculator/rust/client/src/main.rs(110)] PANIC info=panicked at ../../examples/fidl/calculator/rust/client/src/main
.rs:110:17:
assertion `left == right` failed
left: 3.0
right: 5.0
----------------xxxxx----------------
Failing this test. See: https://fuchsia.dev/fuchsia-src/development/diagnostics/test_and_logs#restricting_log_severity
fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests?hash=b105775fa7c39eb67195a09d63be6c4314eeef8e93eb542616c0b5dbda73b8e2#meta/calculator-client-rust-unittests.cm completed with res
ult: FAILED
The test was executed in the hermetic realm. If your test depends on system capabilities, pass in correct realm. See https://fuchsia.dev/go/components/non-hermetic-tests
Tests failed.
现在,您可以通过对代码进行以下更改来修复测试:
- assert_eq!(actual, 5.0);
+ assert_eq!(actual, 3.0);
您现在可以再次运行测试:
fx test --break-on-failure calculator-client-rust-unittests
输出应如下所示:
<...fx test startup...>
Running 1 tests
Status: [duration: 13.5s]
Running 1 tests
Starting: fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests#meta/calculator-client-rust-unittests.cm
Command: fx --dir /usr/local/google/home/jruthe/upstream/fuchsia/out/default ffx test run --max-severity-logs WARN --parallel 1 --no-exception-channel --break-on-failure fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests?hash=abc77325b830d25e47d1de85b764f2b7a0d8975269dfc654f3a7f9a6859b851a#meta/calculator-client-rust-unittests.cm
Running test 'fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests?hash=abc77325b830d25e47d1de85b764f2b7a0d8975269dfc654f3a7f9a6859b851a#meta/calculator-client-rust-unittests.cm'
[RUNNING] parse::tests::parse_expression
[PASSED] parse::tests::parse_expression
[RUNNING] parse::tests::parse_expression_with_division
[PASSED] parse::tests::parse_expression_with_division
[RUNNING] parse::tests::parse_expression_with_multiplication
[PASSED] parse::tests::parse_expression_with_multiplication
[RUNNING] parse::tests::parse_expression_with_negative_numbers
[PASSED] parse::tests::parse_expression_with_negative_numbers
[RUNNING] parse::tests::parse_expression_with_pow
[PASSED] parse::tests::parse_expression_with_pow
[RUNNING] parse::tests::parse_expression_with_subtraction
[PASSED] parse::tests::parse_expression_with_subtraction
[RUNNING] parse::tests::parse_operators
[PASSED] parse::tests::parse_operators
[RUNNING] tests::add_test
[PASSED] tests::add_test
[RUNNING] tests::divide_test
[PASSED] tests::divide_test
[RUNNING] tests::multiply_test
[PASSED] tests::multiply_test
[RUNNING] tests::pow_test
[PASSED] tests::pow_test
[RUNNING] tests::subtract_test
[PASSED] tests::subtract_test
12 out of 12 tests passed...
fuchsia-pkg://fuchsia.com/calculator-client-rust-unittests?hash=abc77325b830d25e47d1de85b764f2b7a0d8975269dfc654f3a7f9a6859b851a#meta/calculator-client-rust-unittests.cm completed with res
ult: PASSED
Deleting 1 files at /tmp/tmprwdcy73n: ffx_logs/ffx.log
To keep these files, set --ffx-output-directory.
Status: [duration: 14.8s] [tests: PASSED: 1 FAILED: 0 SKIPPED: 0]
C++
该示例包含一个测试失败,gTest 有一个选项可在测试失败的路径中插入软件断点,zxdb
选择了该选项。zxdb
还会根据此信息确定测试代码的位置,并直接跳转到测试中的帧。您可以使用 list
查看当前帧的其他代码行,例如:
list
100 harness.debug_agent()->InjectProcessForTest(std::move(process2));
101
102 reply = {};
103 remote_api->OnStatus(request, &reply);
104
▶ 105 ASSERT_EQ(reply.processes.size(), 3u);
106 EXPECT_EQ(reply.processes[0].process_koid, kProcessKoid1);
107 EXPECT_EQ(reply.processes[0].process_name, kProcessName1);
108 ASSERT_EQ(reply.processes[0].threads.size(), 1u);
您可以使用 list
的 -c
标志查看更多源代码行:
list -c 10
95 constexpr uint64_t kProcess2ThreadKoid2 = 0x2;
96
97 auto process2 = std::make_unique<MockProcess>(nullptr, kProcessKoid2, kProcessName2);
98 process2->AddThread(kProcess2ThreadKoid1);
99 process2->AddThread(kProcess2ThreadKoid2);
100 harness.debug_agent()->InjectProcessForTest(std::move(process2));
101
102 reply = {};
103 remote_api->OnStatus(request, &reply);
104
▶ 105 ASSERT_EQ(reply.processes.size(), 3u);
106 EXPECT_EQ(reply.processes[0].process_koid, kProcessKoid1);
107 EXPECT_EQ(reply.processes[0].process_name, kProcessName1);
108 ASSERT_EQ(reply.processes[0].threads.size(), 1u);
109 EXPECT_EQ(reply.processes[0].threads[0].id.process, kProcessKoid1);
110 EXPECT_EQ(reply.processes[0].threads[0].id.thread, kProcess1ThreadKoid1);
111
112 EXPECT_EQ(reply.processes[1].process_koid, kProcessKoid2);
113 EXPECT_EQ(reply.processes[1].process_name, kProcessName2);
114 ASSERT_EQ(reply.processes[1].threads.size(), 2u);
115 EXPECT_EQ(reply.processes[1].threads[0].id.process, kProcessKoid2);
[zxdb]
您还可以使用 frame
命令检查完整的堆栈轨迹:
frame
0 testing::UnitTest::AddTestPartResult(…) • gtest.cc:5383
1 testing::internal::AssertHelper::operator=(…) • gtest.cc:476
▶ 2 debug_agent::DebugAgentTests_OnGlobalStatus_Test::TestBody(…) • debug_agent_unittest.cc:105
3 testing::internal::HandleSehExceptionsInMethodIfSupported<…>(…) • gtest.cc:2635
4 testing::internal::HandleExceptionsInMethodIfSupported<…>(…) • gtest.cc:2690
5 testing::Test::Run(…) • gtest.cc:2710
6 testing::TestInfo::Run(…) • gtest.cc:2859
7 testing::TestSuite::Run(…) • gtest.cc:3038
8 testing::internal::UnitTestImpl::RunAllTests(…) • gtest.cc:5942
9 testing::internal::HandleSehExceptionsInMethodIfSupported<…>(…) • gtest.cc:2635
10 testing::internal::HandleExceptionsInMethodIfSupported<…>(…) • gtest.cc:2690
11 testing::UnitTest::Run(…) • gtest.cc:5506
12 RUN_ALL_TESTS() • gtest.h:2318
13 main(…) • run_all_unittests.cc:20
14…17 «libc startup» (-r expands)
[zxdb]
请注意,▶
指向测试的源代码框架,表明所有命令都在此上下文中执行。您可以使用 frame
命令和堆栈轨迹中的关联编号来选择其他帧。
如需了解测试失败的原因,请输出一些变量以查看发生了什么情况。reply
帧包含一个局部变量,该变量应已通过对 remote_api->OnStatus
的函数调用填充:
print reply
{
processes = {
[0] = {
process_koid = 4660
process_name = "process-1"
components = {}
threads = {
[0] = {
id = {process = 4660, thread = 1}
name = "test thread"
state = kRunning
blocked_reason = kNotBlocked
stack_amount = kNone
frames = {}
}
}
}
[1] = {
process_koid = 22136
process_name = "process-2"
components = {}
threads = {
[0] = {
id = {process = 22136, thread = 1}
name = "test thread"
state = kRunning
blocked_reason = kNotBlocked
stack_amount = kNone
frames = {}
}
[1] = {
id = {process = 22136, thread = 2}
name = "test thread"
state = kRunning
blocked_reason = kNotBlocked
stack_amount = kNone
frames = {}
}
}
}
}
limbo = {}
breakpoints = {}
filters = {}
}
从输出中可以看出,reply
变量已填充了一些信息,预期 processes
向量的大小应为 3。打印 reply
的成员变量以查看更多信息。
您还可以打印该向量的大小方法(尚未实现常规函数调用支持):
print reply.processes
{
[0] = {
process_koid = 4660
process_name = "process-1"
components = {}
threads = {
[0] = {
id = {process = 4660, thread = 1}
name = "test thread"
state = kRunning
blocked_reason = kNotBlocked
stack_amount = kNone
frames = {}
}
}
}
[1] = {
process_koid = 22136
process_name = "process-2"
components = {}
threads = {
[0] = {
id = {process = 22136, thread = 1}
name = "test thread"
state = kRunning
blocked_reason = kNotBlocked
stack_amount = kNone
frames = {}
}
[1] = {
id = {process = 22136, thread = 2}
name = "test thread"
state = kRunning
blocked_reason = kNotBlocked
stack_amount = kNone
frames = {}
}
}
}
}
[zxdb] print reply.processes.size()
2
测试的预期结果似乎略有不正确。您仅注入了 2 个模拟进程,但测试预期为 3 个。您只需更新测试,将 reply.processes
向量的大小预期值从 3 改为 2。您现在可以使用 quit
关闭 zxdb,然后更新并修复测试:
quit
<...fx test output continues...>
Failed tests: DebugAgentTests.OnGlobalStatus <-- Failed test case that we debugged.
175 out of 176 attempted tests passed, 2 tests skipped...
fuchsia-pkg://fuchsia.com/debug_agent_unit_tests?hash=3f6d97801bb147034a344e3fe1bb69291a7b690b9d3d075246ddcba59397ac12#meta/debug_agent_unit_tests.cm completed with result: FAILED
Tests failed.
FAILED: fuchsia-pkg://fuchsia.com/debug_agent_unit_tests#meta/debug_agent_unit_tests.cm
现在,您已找到测试失败的原因,接下来可以修复测试:
-ASSERT_EQ(reply.processes.size(), 3u)
+ASSERT_EQ(reply.processes.size(), 2u)
然后,运行 fx test
:
fx test --break-on-failure debug_agent_unit_tests
输出应如下所示:
You are using the new fx test, which is currently ready for general use ✅
See details here: https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/scripts/fxtest/rewrite
To go back to the old fx test, use `fx --enable=legacy_fxtest test`, and please file a bug under b/293917801.
Default flags loaded from /usr/local/google/home/jruthe/.fxtestrc:
[]
Logging all output to: /usr/local/google/home/jruthe/upstream/fuchsia/out/workbench_eng.x64/fxtest-2024-03-25T15:56:31.874893.log.json.gz
Use the `--logpath` argument to specify a log location or `--no-log` to disable
To show all output, specify the `-o/--output` flag.
Found 913 total tests in //out/workbench_eng.x64/tests.json
Plan to run 1 test
Refreshing 1 target
> fx build src/developer/debug/debug_agent:debug_agent_unit_tests host_x64/debug_agent_unit_tests
Use --no-build to skip building
Executing build. Status output suspended.
ninja: Entering directory `/usr/local/google/home/jruthe/upstream/fuchsia/out/workbench_eng.x64'
[22/22](0) STAMP obj/src/developer/debug/debug_agent/debug_agent_unit_tests.stamp
Running 1 test
Starting: fuchsia-pkg://fuchsia.com/debug_agent_unit_tests#meta/debug_agent_unit_tests.cm (NOT HERMETIC)
Command: fx ffx test run --realm /core/testing/system-tests --max-severity-logs WARN --break-on-failure fuchsia-pkg://fuchsia.com/debug_agent_unit_tests?hash=399ff8d9871a6f0d53557c3d7c233cad645061016d44a7855dcea2c7b8af8101#meta/debug_agent_unit_tests.cm
Deleting 1 files at /tmp/tmp8m56ht95: ffx_logs/ffx.log
To keep these files, set --ffx-output-directory.
PASSED: fuchsia-pkg://fuchsia.com/debug_agent_unit_tests#meta/debug_agent_unit_tests.cm
Status: [duration: 16.9s] [tests: PASS: 1 FAIL: 0 SKIP: 0]
Running 1 tests [=====================================================================================================] 100.0%
zxdb 不再显示,因为您已成功修复所有测试失败问题!