跳到內容

用代理除錯失敗的測試

本指南說明如何讓代理針對列印陳述式解釋不了的失敗,改用除錯器來處理。

debug 工具會驅動真正的 Debug Adapter Protocol 工作階段。停止的框架能一次回答關於那個時刻的每個問題——每個區域變數、每個呼叫者,以及在該框架自身範圍內求值的運算式——而列印陳述式只回答你在執行前就想到要問的那個問題。

這項工具會把狀態攤在檯面上,讓代理可以追問為什麼什麼都不能用,而不是用猜的。只有同時符合以下兩者,配接器才能真正啟動:

  • 簽出的內容中有專案標記,以及
  • 配接器的執行檔能解析得到。

請代理檢查:

debug(op: "adapters")

它會回報找到什麼,或說明配接器需要這兩個條件同時成立。

語言 配接器 標記 安裝
Dart / Flutter dart debug_adapter pubspec.yaml 隨 SDK 提供——不必做任何事
Python python3 -m debugpy.adapter pyproject.toml, setup.py, requirements.txt pip install debugpy
Rust / C / C++ lldb-dap Cargo.toml, CMakeLists.txt 隨 LLVM 提供
Go dlv dap go.mod go install github.com/go-delve/delve/cmd/dlv@latest

順序很重要,而工具會強制要求這個順序:中斷點可以在工作階段存在之前就設定,因為那正是協定要求的順序。

請代理做類似這樣的事:

bin/main.dart 裡的 compute 函式回傳了錯誤的值。在指定 product 的那一行設定中斷點,在除錯器中啟動它,然後告訴我 a 和 b 實際上是多少。

它大致上會執行:

debug(op: "breakpoints", file: "bin/main.dart", lines: [7])
debug(op: "launch", program: "bin/main.dart")

啟動後會回報它停在哪裡:

Launched bin/main.dart on dart (session dart-1). Stopped: breakpoint. Use `stack` to see where.
debug(op: "stack")
→ #1 compute (bin/main.dart:7:3)
#2 main (bin/main.dart:2:18)

接著走進去:

debug(op: "scopes", frame_id: 1)
→ Locals (reference 12)
debug(op: "variables", reference: 12)
→ a (int) = 6
b (int) = 7
product (int) = 42

帶有子項的物件會回報 [expand: reference N]——但只有在背後確實有東西時才會如此,所以代理用不到的展開項目永遠不會被提供。

對於任何不是以區域變數呈現的內容:

debug(op: "evaluate", expression: "config.timeout.inSeconds", frame_id: 1)
你想要什麼 操作
執行到下一個停止點 continue
下一行,同一個框架 step_over
進入呼叫 step_in
返回呼叫者 step_out
停止正在執行的程式 pause

用於卡死的行程,而不是你正在啟動的行程:

debug(op: "attach", port: 5858)
debug(op: "terminate")

一定要結束工作階段。除錯配接器擁有一個停止的行程,並持有那個行程所持有的一切——連接埠、鎖定、資料庫連線。

對深層物件圖展開 variables 本質上是沒有界的——一個 Flutter widget 就能觸及整棵元素樹。每個範圍的子項上限為 50,值上限為 400 個字元,堆疊框架上限為 30。一個塞爆情境視窗的工具結果,就是終結它原本在協助的那次執行的工具。