使用 zxdb 對測試進行偵錯

本頁面提供透過 fx test 指令使用 Fuchsia 偵錯工具 (zxdb) 的詳細資料和範例。

輸入 fx 測試中的偵錯工具

fx test 指令支援 --break-on-failure--breakpoint 旗標,可讓您使用 zxdb 對測試進行偵錯。如果測試使用相容的測試執行元件 (也就是今天的 gTest、gUnit 或 Rust),則新增 --break-on-failure 標記會導致測試失敗暫停執行,而進入 zxdb 偵錯環境,例如:

$ fx test --break-on-failure rust_crasher_test.cm

<...fx test startup...>

Running 1 tests

Starting: fuchsia-pkg://fuchsia.com/crasher_test#meta/rust_crasher_test.cm
Command: fx ffx test run --max-severity-logs WARN --break-on-failure fuchsia-pkg://fuchsia.com/crasher_test?hash=1cceb326c127e245f0052367142aee001f82a73c6f33091fe7999d43a94b1b34#meta/rust_crasher_test.cm

Status: [duration: 13.3s]  [tasks: 3 running, 15/18 complete]
  Running 1 tests                      [                                                                                                     ]           0.0%
⚠️  zxdb caught test failure in rust_crasher_test.cm, type `frame` to get started.
   14 LLVM_LIBC_FUNCTION(void, abort, ()) {
   15   for (;;) {
 ▶ 16     CRASH_WITH_UNIQUE_BACKTRACE();
   17     _zx_process_exit(ZX_TASK_RETCODE_EXCEPTION_KILL);
   18   }
══════════════════════════
 Invalid opcode exception
══════════════════════════
 Process 1 (koid=107752) thread 1 (koid=107754)
 Faulting instruction: 0x4159210ab797

🛑 process 1 __llvm_libc::__abort_impl__() • abort.cc:16
[zxdb]

此時,您可以照常使用 zxdb。然而,由於執行緒已處於嚴重例外狀況,因此無法使用一般執行指令,例如 stepnextuntil。偵錯工作階段期間,系統會提供 printframebacktrace 等檢查指令。

同時執行測試案例

視提供給 fx test 的選項或測試執行元件預設設定而定,多個測試案例可能會平行失敗。支援的測試執行程式會為每個測試案例產生一個個別程序,而預設設定允許同時執行多個測試程序。

搭配 fx test 執行時,zxdb 會附加至測試領域中的「所有」程序。測試案例失敗將「只會」停止該特定程序。

只有在測試案例失敗次數等於平行測試案例數量時,平行執行才會停止。任何程序從 zxdb 卸離後,就會立即開始另一個測試案例程序。

zxdb 專為處理多程序偵錯而設計。您可以使用 process 名詞或 status 指令檢查目前附加的程序及其執行狀態。目前為「有效」的程序會以「▶」符號標示。

詳情請參閱互動模型 (或使用 help 指令)。

關閉偵錯工具

檢查測試失敗後,您可以從測試程序卸離測試程序,例如使用 killdetachcontinue

如上一節所述,多個測試案例可能會同時失敗。如果您沒有明確從所有附加程序卸離,zxdb 會留在前景中。您可以使用 process 名詞查看所有附加的程序。

您也可以使用特定指令,從所有程序 (例如 quitdetach *ctrl+d) 卸離,並立即繼續執行測試套件。

教學課程

本教學課程將逐步說明如何使用 fx test 指令和 Fuchsia 偵錯工具 (zxdb),逐步偵錯工作流程。

1. 瞭解測試案例

Rust

Rust 測試是由 Rust 測試執行器執行。有別於 C++ 測試的 gTest 或 gUnit 執行器,Rust 測試執行元件會預設為平行執行測試案例。這樣做會在使用 --break-on-failure 功能時產生不同體驗。如要瞭解對平行測試程序偵錯時預期的預期結果,請參閱平行程序一節。系統支援此情況,可與 zxdb 正常搭配運作。

以下是一些經過縮寫的 rust 測試程式碼範例 (修改後的原始內容),以求精簡:

...
let mut log_helper2 = LogSinkHelper::new(&directory);
log_helper2.write_log("my msg1");
log_helper.write_log("my msg2");

let mut expected = vec!["my msg1".to_owned(), "my msg3".to_owned()];
expected.sort();
let mut actual = vec![recv_logs.next().await.unwrap(), recv_logs.next().await.unwrap()];
actual.sort();

assert_eq!(expected, actual);
...

C++

根據預設,gTest 測試執行元件會依序執行測試案例,因此一次只會對一項測試失敗進行偵錯。系統支援同時執行測試案例,只要將 --parallel-cases 標記新增至 fx test 指令即可。讓我們來看看使用 gTest 的 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);
...

2. 執行測試

Rust

使用 fx test --break-on-failure 指令執行測試,例如:

$ fx test -o --break-on-failure archivist-unittests

<...fx test startup...>

Running 1 tests

Starting: fuchsia-pkg://fuchsia.com/archivist-tests#meta/archivist-unittests.cm
Command: fx ffx test run --max-severity-logs WARN --break-on-failure fuchsia-pkg://fuchsia.com/archivist-tests?hash=9a531e48fe82d86edef22f86f7e9b819d18a7d678f0823912d9224dd91f8926f#meta/archivist-unittests.cm
Running test 'fuchsia-pkg://fuchsia.com/archivist-tests?hash=9a531e48fe82d86edef22f86f7e9b819d18a7d678f0823912d9224dd91f8926f#meta/archivist-unittests.cm'

[RUNNING] archivist::tests::can_log_and_retrive_log
[101430.272555][5631048][5631050][<root>][can_log_and_retrive_log] WARN: Failed to create event source for log sink requests err=Error connecting to protocol path: /events/log_sink_requested_event_stream

Caused by:
    NOT_FOUND
[101430.277339][5631048][5631050][<root>][can_log_and_retrive_log] WARN: Failed to create event source for InspectSink requests err=Error connecting to protocol path: /events/inspect_sink_requested_event_stream
[101430.336160][5631048][5631050][<root>][can_log_and_retrive_log] INFO: archivist: Entering core loop.
[101430.395986][5631048][5631050][<root>][can_log_and_retrive_log] ERROR: [src/lib/diagnostics/log/rust/src/lib.rs(62)] PANIC info=panicked at ../../src/diagnostics/archivist/src/archivist.rs:544:9:
assertion `left == right` failed
  left: ["my msg1", "my msg2"]
 right: ["my msg1", "my msg3"]

👋 zxdb is loading symbols to debug test failure in archivist-unittests.cm, please wait.
⚠️  test failure in archivist-unittests.cm, type `frame` or `help` to get started.
   11 namespace LIBC_NAMESPACE {
   12
 ▶ 13 LLVM_LIBC_FUNCTION(void, abort, ()) { CRASH_WITH_UNIQUE_BACKTRACE(); }
   14
   15 }  // namespace LIBC_NAMESPACE
══════════════════════════
 Invalid opcode exception
══════════════════════════
 Process 10 (koid=5495424) thread 1 (koid=5495428)
 Faulting instruction: 0x41a5680114d7

🛑 process 10 __llvm_libc::__abort_impl__() • abort.cc:13
[zxdb]

請注意,測試的輸出內容會混亂,這是因為 Rust 測試執行元件會預設平行執行測試案例。如要避免這種情況發生,請使用 --parallel-cases 選項做為 fx test,例如:fx test --parallel-cases 1 --break-on-failure archivist-unittests

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 caught test failure in debug_agent_unit_tests.cm, type `frame` to get started.
   5381      (defined(__x86_64__) || defined(__i386__)))
   5382       // with clang/gcc we can achieve the same effect on x86 by invoking int3
 ▶ 5383       asm("int3");
   5384 #elif GTEST_HAS_BUILTIN(__builtin_trap)
   5385       __builtin_trap();
🛑 thread 1 testing::UnitTest::AddTestPartResult(testing::UnitTest*, testing::TestPartResult::Type, const char*, int, std::__2::string const&, std::__2::string const&) • gtest.cc:5383
[zxdb]

3. 檢查失敗情況

Rust

我們發現測試失敗,Rust 測試會在失敗時發出 abortzxdb 會發出通知並回報。我們可以透過 list 查看目前影格的程式碼,例如:

[zxdb] list
    8
    9 #include "src/__support/common.h"
   10
   11 namespace LIBC_NAMESPACE {
   12
 ▶ 13 LLVM_LIBC_FUNCTION(void, abort, ()) { CRASH_WITH_UNIQUE_BACKTRACE(); }
   14
   15 }  // namespace LIBC_NAMESPACE
[zxdb]

但這不是我們想的程式碼。我們查看堆疊追蹤時,會發現測試中的程式碼位於頁框 #17:

[zxdb] frame
▶ 0…12 «Rust library» (-r expands)
  13 std::panicking::begin_panic_handler(…) • library/std/src/panicking.rs:645
  14 core::panicking::panic_fmt(…) • library/core/src/panicking.rs:72
  15 core::panicking::assert_failed_inner(…) • library/core/src/panicking.rs:402
  16 core::panicking::assert_failed<…>(…) • /b/s/w/ir/x/w/fuchsia-third_party-rust/library/core/src/panicking.rs:357
  17 archivist_lib_lib_test::archivist::tests::can_log_and_retrive_log::test_entry_point::λ(…) • archivist.rs:544
  18 core::future::future::«impl»::poll<…>(…) • future/future.rs:123
  19 fuchsia_async::test_support::«impl»::run_singlethreaded::λ::λ(…) • test_support.rs:26
  20 fuchsia_async::test_support::«impl»::run_singlethreaded::λ::λ(…) • test_support.rs:121
  21 fuchsia_async::atomic_future::«impl»::poll<…>(…) • atomic_future.rs:78
  22 fuchsia_async::atomic_future::AtomicFuture::try_poll(…) • atomic_future.rs:223
  23 fuchsia_async::runtime::fuchsia::executor::common::Inner::try_poll(…) • executor/common.rs:588
  24 fuchsia_async::runtime::fuchsia::executor::common::Inner::poll_ready_tasks(…) • executor/common.rs:148
  25 fuchsia_async::runtime::fuchsia::executor::common::Inner::worker_lifecycle<…>(…) • executor/common.rs:448
  26 fuchsia_async::runtime::fuchsia::executor::local::LocalExecutor::run<…>(…) • executor/local.rs:100
  27 fuchsia_async::runtime::fuchsia::executor::local::LocalExecutor::run_singlethreaded<…>(…) • executor/local.rs:68
  28 fuchsia_async::test_support::«impl»::run_singlethreaded::λ() • test_support.rs:119
  29 fuchsia_async::test_support::Config::in_parallel(…) • test_support.rs:214
  30 fuchsia_async::test_support::«impl»::run_singlethreaded(…) • test_support.rs:116
  31 fuchsia_async::test_support::run_singlethreaded_test<…>(…) • test_support.rs:226
  32 fuchsia::test_singlethreaded<…>(…) • fuchsia/src/lib.rs:188
  33 archivist_lib_lib_test::archivist::tests::can_log_and_retrive_log() • archivist.rs:519
  34 archivist_lib_lib_test::archivist::tests::can_log_and_retrive_log::λ(…) • archivist.rs:520
  35 core::ops::function::FnOnce::call_once<…>(…) • /b/s/w/ir/x/w/fuchsia-third_party-rust/library/core/src/ops/function.rs:250
  36 core::ops::function::FnOnce::call_once<…>(…) • library/core/src/ops/function.rs:250 (inline)
  37 test::__rust_begin_short_backtrace<…>(…) • library/test/src/lib.rs:621
  38 test::run_test_in_spawned_subprocess(…) • library/test/src/lib.rs:749
  39 test::test_main_static_abort(…) • library/test/src/lib.rs:197
  40 archivist_lib_lib_test::main() • archivist/src/lib.rs:1
  41…58 «Rust startup» (-r expands)
[zxdb]

如要查看測試的原始碼,請在 list 指令中使用索引做為前置字串,使用 frame 名詞,例如:

[zxdb] frame 17 list
   539         expected.sort();
   540
   541         let mut actual = vec![recv_logs.next().await.unwrap(), recv_logs.next().await.unwrap()];
   542         actual.sort();
   543
 ▶ 544         assert_eq!(expected, actual);
   545
   546         // can log after killing log sink proxy
   547         log_helper.kill_log_sink();
   548         log_helper.write_log("my msg1");
   549         log_helper.write_log("my msg2");
   550
   551         assert_eq!(
   552             expected,
   553             vec! {recv_logs.next().await.unwrap(),recv_logs.next().await.unwrap()}
   554         );

這樣造成的不便,我們就必須在每個指令之前輸入 frame 17,才能與我們感興趣的程式碼部分互動。請注意,frame 輸出內容的「▶」。指向框架 0,表示這是「活躍」影格。讓我們選取影格做為「有效」影格,其中僅包含來自上方影格索引的 frame 名詞,以便直接處理想關注的項目:

[zxdb] frame 17
archivist_lib_lib_test::archivist::tests::can_log_and_retrive_log::test_entry_point::λ(…) • archivist.rs:528
[zxdb] frame
  0…12 «Rust library» (-r expands)
  13 std::panicking::begin_panic_handler(…) • library/std/src/panicking.rs:645
  14 core::panicking::panic_fmt(…) • library/core/src/panicking.rs:72
  15 core::panicking::assert_failed_inner(…) • library/core/src/panicking.rs:402
  16 core::panicking::assert_failed<…>(…) • /b/s/w/ir/x/w/fuchsia-third_party-rust/library/core/src/panicking.rs:357
▶ 17 archivist_lib_lib_test::archivist::tests::can_log_and_retrive_log::test_entry_point::λ(…) • archivist.rs:544
  18 core::future::future::«impl»::poll<…>(…) • future/future.rs:123
  19 fuchsia_async::test_support::«impl»::run_singlethreaded::λ::λ(…) • test_support.rs:26
  20 fuchsia_async::test_support::«impl»::run_singlethreaded::λ::λ(…) • test_support.rs:121
...

現在,我們執行的所有指令都將在畫面 #17 的內容中。我們再次列出原始碼以進行確認,並提供一些額外的背景資訊:

[zxdb] list -c 10
   534         let mut log_helper2 = LogSinkHelper::new(&directory);
   535         log_helper2.write_log("my msg1");
   536         log_helper.write_log("my msg2");
   537
   538         let mut expected = vec!["my msg1".to_owned(), "my msg3".to_owned()];
   539         expected.sort();
   540
   541         let mut actual = vec![recv_logs.next().await.unwrap(), recv_logs.next().await.unwrap()];
   542         actual.sort();
   543
 ▶ 544         assert_eq!(expected, actual);
   545
   546         // can log after killing log sink proxy
   547         log_helper.kill_log_sink();
   548         log_helper.write_log("my msg1");
   549         log_helper.write_log("my msg2");
   550
   551         assert_eq!(
   552             expected,
   553             vec! {recv_logs.next().await.unwrap(),recv_logs.next().await.unwrap()}
   554         );

太好了!現在測試失敗的原因,我們去輸出一些變數 看看實際情況這個影格中有一個本機變數 actual,該變數應含有我們新增的字串,方法是在 log_helperlog_helper2 例項上呼叫 write_log,並透過 mpsc 管道 recv_logs 接收這些字串:

[zxdb] print expected
vec!["my msg1", "my msg3"]
[zxdb] print actual
vec!["my msg1", "my msg2"]

哈哈,我們的測試預期有點錯誤。我們預期 "my msg3" 是第二個字串,但實際上卻記錄到 "my msg2"。我們可以修正測試,預期"my msg2"。現在可以從測試中卸離,繼續並完成測試套件:

[zxdb] quit

<...fx test output continues...>

Failed tests: archivist::tests::can_log_and_retrive_log
122 out of 123 tests passed...

Test fuchsia-pkg://fuchsia.com/archivist-tests?hash=8bcb30a2bfb923a4b42d1f0ea590af613ab0b1aa1ac67ada56ae4d325f3330a0#meta/archivist-unittests.cm produced unexpected high-severity logs:
----------------xxxxx----------------
[105255.347070][5853309][5853311][<root>][can_log_and_retrive_log] ERROR: [src/lib/diagnostics/log/rust/src/lib.rs(62)] PANIC info=panicked at ../../src/diagnostics/archivist/src/archivist.rs:544:9:
assertion `left == right` failed
  left: ["my msg1", "my msg2"]
 right: ["my msg1", "my msg3"]

----------------xxxxx----------------
Failing this test. See: https://fuchsia.dev/fuchsia-src/development/diagnostics/test_and_logs#restricting_log_severity

fuchsia-pkg://fuchsia.com/archivist-tests?hash=8bcb30a2bfb923a4b42d1f0ea590af613ab0b1aa1ac67ada56ae4d325f3330a0#meta/archivist-unittests.cm completed with result: 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.
Deleting 1 files at /tmp/tmpgr0otc3w: ffx_logs/ffx.log
To keep these files, set --ffx-output-directory.

現在我們可以修正測試:

- let mut expected = vec!["my msg1".to_owned(), "my msg3".to_owned()];
+ let mut expected = vec!["my msg1".to_owned(), "my msg2".to_owned()];

然後再次執行測試:

$ fx test --break-on-failure archivist-unittests

<...fx test startup...>

Running 1 tests

Starting: fuchsia-pkg://fuchsia.com/archivist-tests#meta/archivist-unittests.cm
Command: fx ffx test run --max-severity-logs WARN --break-on-failure fuchsia-pkg://fuchsia.com/archivist-tests?hash=454897cb1be6b88c2aeb4b5abf474894b629d30ca50f7dfaa23497fd3848a566#meta/archivist-unittests.cm
Running test 'fuchsia-pkg://fuchsia.com/archivist-tests?hash=454897cb1be6b88c2aeb4b5abf474894b629d30ca50f7dfaa23497fd3848a566#meta/archivist-unittests.cm'
[RUNNING] accessor::tests::accessor_skips_invalid_selectors
[RUNNING] accessor::tests::batch_iterator_on_ready_is_called
[RUNNING] accessor::tests::batch_iterator_terminates_on_client_disconnect
[RUNNING] accessor::tests::buffered_iterator_handles_peer_closed
[RUNNING] accessor::tests::buffered_iterator_handles_two_consecutive_buffer_waits
[RUNNING] accessor::tests::logs_only_accept_basic_component_selectors
[RUNNING] accessor::tests::socket_writer_does_not_handle_cbor
[RUNNING] accessor::tests::socket_writer_handles_closed_socket
[RUNNING] accessor::tests::socket_writer_handles_text
[RUNNING] archivist::tests::can_log_and_retrive_log
[PASSED]  accessor::tests::socket_writer_handles_text
[RUNNING] archivist::tests::log_from_multiple_sock
[PASSED]  accessor::tests::buffered_iterator_handles_two_consecutive_buffer_waits
<...lots of tests...>
[RUNNING] logs::tests::unfiltered_stats
[PASSED]  logs::tests::test_debuglog_drainer
[RUNNING] utils::tests::drop_test
[PASSED]  logs::tests::test_filter_by_pid
[PASSED]  logs::tests::test_filter_by_min_severity
[PASSED]  logs::tests::test_filter_by_tags
[PASSED]  logs::tests::test_filter_by_tid
[PASSED]  logs::tests::test_log_manager_dump
[PASSED]  logs::tests::test_log_manager_simple
[PASSED]  logs::tests::unfiltered_stats
[PASSED]  logs::tests::test_structured_log
[PASSED]  logs::tests::attributed_inspect_two_mixed_streams_different_identities
[PASSED]  utils::tests::drop_test

123 out of 123 tests passed...
fuchsia-pkg://fuchsia.com/archivist-tests?hash=454897cb1be6b88c2aeb4b5abf474894b629d30ca50f7dfaa23497fd3848a566#meta/archivist-unittests.cm completed with result: PASSED
Deleting 1 files at /tmp/tmpho9yjjz9: ffx_logs/ffx.log
To keep these files, set --ffx-output-directory.

Status: [duration: 36.4s] [tests: PASS: 1 FAIL: 0 SKIP: 0]
  Running 1 tests                            [====================================================================================================================]            100.0%

C++

我們發現了測試失敗,因此 gTest 可以選擇在測試失敗的路徑中插入軟體中斷點,這已納入測試中。我們可以透過 list 查看目前影格的程式碼,例如:

[zxdb] list
...
   5381      (defined(__x86_64__) || defined(__i386__)))
   5382       // with clang/gcc we can achieve the same effect on x86 by invoking int3
 ▶ 5383       asm("int3");
   5384 #elif GTEST_HAS_BUILTIN(__builtin_trap)
   5385       __builtin_trap();
...

但這不是我們想的程式碼。我們查看堆疊追蹤時,會發現測試中的程式碼位於頁框 #2:

[zxdb] 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 <-- This is the test's source code.
  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 名詞,搭配索引做為 list 指令的前置字串,即可查看測試的原始碼,例如:

[zxdb] frame 2 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); <-- This assertion failed.
   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);

造成不便,敬請見諒。在每個指令之前,我們都必須先輸入 frame 2,才能與所需程式碼的一部分互動。請注意,frame 輸出內容的「▶」。指向框架 0,表示這是「使用中」影格。我們現在選取影格做為「有效」影格,其中僅有 frame 名詞,且具有上方影格的索引,以便直接處理想要關注的項目:

[zxdb] frame 2
debug_agent::DebugAgentTests_OnGlobalStatus_Test::TestBody(…) • debug_agent_unittest.cc:105
[zxdb] 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)

現在,我們執行的所有指令都會在畫面 #2 的內容中。我們再次列出原始碼來確認情況:

[zxdb] 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);
   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);

太棒了!現在測試失敗的原因,我們去列印部分變數 看看具體情形這個頁框中有 reply 本機變數,應由對 remote_api->OnStatus 的函式呼叫填入:

[zxdb] 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 的成員變數,以獲得更清晰的相片。我們也可以列印該向量的大小方法 (尚未實作一般函式呼叫支援):

[zxdb] 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

Aha,因此測試的預期結果有誤,我們在測試中只插入了 2 個模擬程序,但預期會有 3 個。如果只需要更新測試,reply.processes 向量的大小應為 2,而非 3。我們可以立即關閉偵錯工具,以完成測試,然後修正測試:

[zxdb] quit

<...fx test output continues...>

Failed tests: DebugAgentTests.OnGlobalStatus <-- Failed test 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

🛑 Debugger integration is currently experimental, follow https://fxbug.dev/319320287 for updates 🛑
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%

偵錯工具不會再出現,因為我們沒有任何其他測試失敗!太厲害了 \o/