Flutter 测试日志失败解析指南:dart-log-failure-parser Agent 技能的四种失败模式与源码印证
本文以 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(分析原始日志输出),包含两条硬性要求:
- Do not skim the output; check the entire log. —— 不要略读输出,必须检查完整日志;
- 失败结论必须包含具体细节——例如哪个文件未格式化(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 #1、ERROR #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.dart、dev/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.dart 的 rerunTask 中:
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.dart、dev/bots/analyze_snippet_code.dart |
| B Task Result JSON | devicelab 任务失败 | Task result: |
dev/devicelab/lib/framework/runner.dart、dev/devicelab/lib/framework/task_result.dart |
| C 失败测试清单 | 通用 Dart 测试 | Failing tests: |
Dart 测试运行器标准输出,样例见 dev/bots/test/analyze_test.dart 中保留的预期日志 |
| D 构建失败 | engine 编译/链接失败 | FAILED:、error:、undefined reference to、1 build failed: |
Ninja/编译器输出,无单一生成文件,属日志特征匹配 |
从源码结构看,Pattern A 和 B 的关键词都能一一对应到 dev/bots 与 dev/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.dart 与 dev/devicelab/lib/framework/runner.dart 的源码印证,读者既能直接照单排查现有日志,也能理解每个搜索关键词背后"为什么长这样"的实现原因。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00