首页
/ Flutter 测试日志失败解析指南:dart-log-failure-parser Agent 技能的四种失败模式与源码印证

Flutter 测试日志失败解析指南:dart-log-failure-parser Agent 技能的四种失败模式与源码印证

2026-09-05 20:52:55作者:毕习沙Eudora

本文以 Flutter 仓库中的 dart-log-failure-parser 技能文件 为核心,系统讲解如何从 Dart 与 Flutter 的 CI 测试日志中定位具体失败原因。你将掌握该技能定义的两步分析工作流、四类失败模式(错误块、Task Result JSON、失败测试清单、构建失败)的识别方法,以及每种模式在仓库源码中的真实生成位置,从而能够把一段冗长的失败日志精确归因到未格式化的文件、具体的测试名或编译/链接错误,而不是停留在"某条命令退出码非零"这一表层结论。

一、技能定位:这个 Skill 是给谁、在什么场景下用的

dart-log-failure-parser 是 Flutter 仓库 .agents/skills 目录下收录的 Agent 技能之一,其 frontmatter 元数据非常精简:

name: dart-log-failure-parser
description: Parse failures from Dart and Flutter test logs.

它的用途单一而明确:解析 Dart 和 Flutter 测试日志中的失败。Flutter 仓库的 CI 会产出体量很大的日志(分析器全量扫描、devicelab 设备任务、engine 编译等),人工排查时很容易被顶层的"命令失败"信息带偏。这个技能的价值在于把"读日志找失败"这一经验固化为可复用的规则集,让 Agent(或人)按统一模式快速定位失败细节。

该技能所在目录的治理规则见 .agents/skills/README.md,其中有几点与本技能直接相关:

  • 技能面向 Flutter 贡献者,要求作者先自行使用过该技能,且 PR 中需附带提示词与 Agent 输出示例;
  • 推荐实践包括"一个 CLI 工具对应一个技能""指令越结构化、规则化越好""脚本用 Dart 编写";
  • 技能可通过 dev/tools 目录下的 dart_skills_lint 工具校验,例如:
dart run dart_skills_lint:cli --skills-directory ../../.agents/skills --check-trailing-whitespace --check-absolute-paths --check-relative-paths

常用标志包括 --fix(预览修复,不改动文件)、--fix-apply(自动应用可修复的规则)、--check-trailing-whitespace(禁止行尾空白)、--check-absolute-paths(禁止绝对路径链接)、--check-relative-paths(相对链接必须指向已存在文件)。此外还可以在 dev/tools 目录下运行 dart test test/validate_skills_test.dart 执行自动化校验测试。

理解这些背景很重要:本文讨论的四种失败模式,正是这类技能中"规则化指令"的典型范例——每条规则都对应日志中一段可被精确搜索的固定文本。

二、工作流第一步:通读原始日志,拒绝"只看顶层命令"

技能定义的 Workflow 第一步是 Analyze Raw Log Output(分析原始日志输出),包含两条硬性要求:

  1. Do not skim the output; check the entire log. —— 不要略读输出,必须检查完整日志;
  2. 失败结论必须包含具体细节——例如哪个文件未格式化(unformatted files)、哪些具体测试名失败,而不只是"顶层某条命令失败了"。

第二条要求直接针对 CI 排障中最常见的问题:日志末尾往往只显示 Command exited with exit code N,而真正的错误可能在数百行之前的某个输出块里。因此分析的第一步是"全文扫描",第二步才是"按模式匹配"。

三、工作流第二步:四种失败模式的识别与源码印证

Pattern A:错误块(以 ╡ERROR # 开头的方框)

适用场景:仓库自带的分析/校验脚本失败,例如 Linux 上的 analyze 任务。搜索模式是 ╡ERROR #。技能给出的典型样例:

╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════
║ Command: bin/cache/dart-sdk/bin/dart --enable-asserts /b/s/w/ir/x/w/flutter/dev/bots/analyze_snippet_code.dart --verbose
║ Command exited with exit code 255 but expected zero exit code.
║ Working directory: /b/s/w/ir/x/w/flutter
╚═══════════════════════════════════════════════════════════════════════════════

这个方框并非日志的偶然格式,而是由仓库 CI 脚本刻意生成的。其来源是 dev/bots/utils.dart 中的 foundError(List<String> messages) 函数:

  • 标题被动态拼接为 'ERROR #${_errorMessages.length + 1}',即每个错误块按出现顺序编号(ERROR #1ERROR #2……);
  • 用 Unicode 制表符 ╔═╡ … ╞═ / ║ / ╚ 包成红色方框(代码注释明确说明目的是 "Make the error message easy to notice in the logs by wrapping it in a red box"),宽度根据终端列数计算,width = max(15, columns - 1);
  • 打印错误块的同时会把此前暂存的日志(_pendingLogs)一并冲刷输出,保证错误块前后有完整上下文。

配套的退出路径在 reportErrorsAndExit:它会再次汇总输出所有错误块,并打印一行提示:

You may find the errors by searching for "╡ERROR #" in the logs.

这句提示本身就是 Pattern A 搜索关键词 ╡ERROR # 的出处。另外,样例中出现的 --verbose 标志与该文件的注释相互印证:该文件中的打印控制逻辑同时用于实现 test.dart--verbose 模式(默认隐藏 printProgress 之间的日志,超时或有错误时转为全量输出),所以排障时加上 --verbose 往往能拿到更完整的上下文。

样例命令指向的脚本是 analyze_snippet_code.dart,它是 CI 中"分析代码片段"的入口。仓库的测试代码里保留了大量这类错误块的构造样例,可用于对照模式,例如 dev/bots/test/analyze_test.dartdev/bots/test/check_code_samples_test.dart 等文件中均可搜索到 ╔═╡ERROR #1╞ 字样的预期输出字符串。

Pattern B:Task Result JSON(devicelab 任务结果)

适用场景:devicelab 性能/集成任务失败。搜索模式是文本 Task result:,其后紧跟一个 JSON 对象,例如:

Task result:
{
  "success": false,
  "reason": "Task failed: PathNotFoundException: Cannot open file..."
}

这段输出的生成点在 dev/devicelab/lib/framework/runner.dartrerunTask 中:

print('Task result:');
print(const JsonEncoder.withIndent('  ').convert(result));

即:先原样打印 Task result:,再把任务结果对象用两空格缩进的 JSON 编码器序列化输出——这与技能文档中展示的 JSON 缩进样式完全一致。序列化对象是 TaskResult 类(定义于 dev/devicelab/lib/framework/task_result.dart),从结构看,其 JSON 形态包含 success 布尔字段与 reason 描述字段,技能文档中 "reason": "Task failed: PathNotFoundException: ..." 的写法正对应"任务进程异常/非零退出"这一失败形态。

排障要点:Pattern B 的关键信息在 reason 里(异常类型 + 消息),它通常指向文件缺失、设备通信失败、指标解析错误等具体原因;看到 success: false 后应优先读 reason,再回溯该任务 stdout 段。

Pattern C:Failing tests 清单(通用 Dart 测试)

适用场景:普通 dart test / flutter test 跑批。失败测试列表出现在日志末尾,以 Failing tests: 开头,每行格式为"测试文件路径: 测试名",例如:

Failing tests:
  test/general.shard/cache_test.dart: FontSubset artifacts for all platforms on arm64 hosts
  test/general.shard/cache_test.dart: FontSubset artifacts on arm64 linux

这是 Dart 测试运行器的标准汇总格式。分析时的要点是逐行拆解:

  • 冒号前是测试文件相对路径,可据此直接定位测试源码;
  • 冒号后是具体用例名,同一文件可能有多条失败用例(如样例中 cache_test.dart 出现两行),说明要逐条处理而非只看第一条;
  • 样例中的用例名(如 "FontSubset artifacts on arm64 linux")带有明显的平台特征,提示这类失败可能与宿主平台架构(arm64)相关,可结合运行环境判断是真实回归还是平台兼容问题。

Pattern D:构建失败(编译期失败)

适用场景:engine 等 C++/平台代码在编译期失败(如 engine 测试)。由于此类失败没有统一的"错误块"格式,技能给出的是一组并列的指示特征,在日志或 check-runs API 摘要中查找:

特征 含义
FAILED: 开头的行 Ninja 构建目标失败
error:fatal error: 编译器错误消息
undefined reference to 链接器错误消息
1 build failed: [<build_name>] check-runs API 输出中的汇总消息,括号内即失败的构建名

这四条特征覆盖了构建失败从底层(Ninja 目标、编译器、链接器)到上层(check-runs 摘要)的不同观察面:完整构建日志中应优先抓 FAILED: 行确定是哪个目标挂了,再向上下文抓 error: / undefined reference to 定位到具体编译单元;若手里只有 CI 状态 API 的摘要,则用 1 build failed: [<build_name>] 反向找到对应构建的完整日志。

四、四种模式速查与源码对照

模式 典型场景 搜索关键词 仓库内生成/佐证位置
A 错误块 analyze / 校验脚本失败 ╡ERROR # dev/bots/utils.dartdev/bots/analyze_snippet_code.dart
B Task Result JSON devicelab 任务失败 Task result: dev/devicelab/lib/framework/runner.dartdev/devicelab/lib/framework/task_result.dart
C 失败测试清单 通用 Dart 测试 Failing tests: Dart 测试运行器标准输出,样例见 dev/bots/test/analyze_test.dart 中保留的预期日志
D 构建失败 engine 编译/链接失败 FAILED:error:undefined reference to1 build failed: Ninja/编译器输出,无单一生成文件,属日志特征匹配

从源码结构看,Pattern A 和 B 的关键词都能一一对应到 dev/botsdev/devicelab 中的具体打印语句,这意味着在 Flutter 仓库的 CI 日志中,这两个模式是确定性格式(由仓库自身代码保证),可以用精确字符串搜索;Pattern C 依赖 Dart 测试运行器的输出约定;Pattern D 则是跨工具链(Ninja、GCC/Clang)的启发式特征。

五、使用建议与适用边界

  • 适用前提:以上模式针对当前仓库的 CI 工具链输出格式。Pattern A 的方框格式由 dev/bots/utils.dart 中的 foundError/reportErrorsAndExit 决定,Pattern B 的格式由 devicelab runner 决定;若上游工具升级改变了打印格式,需要同步更新技能中的搜索关键词。
  • 分析纪律:遵循技能的第一步要求,先通读全量日志,再套用四种模式;输出结论时落到"未格式化的具体文件""失败的测试文件+用例名""失败的 Ninja 目标与编译器错误"这一粒度。
  • 技能复用:如果团队要为其他 CLI 工具编写类似技能,可参照 .agents/skills/README.md 的推荐实践——"告诉 Agent 它需要知道的东西(而不是一般性常识)、指令结构化规则化、按需提供只读数据访问方式"——并用上文 dart_skills_lint 命令完成格式与链接校验。

总结:这篇技能文档虽短,却把 Flutter 仓库 CI 日志中最常见的四类失败固化成了可搜索的文本模式;结合 dev/bots/utils.dartdev/devicelab/lib/framework/runner.dart 的源码印证,读者既能直接照单排查现有日志,也能理解每个搜索关键词背后"为什么长这样"的实现原因。

登录后查看全文
热门项目推荐
相关项目推荐