首页
/ 深入理解 Flutter 仓库 LUCI 构建失败:基础设施故障与测试失败的分诊与重跑指南

深入理解 Flutter 仓库 LUCI 构建失败:基础设施故障与测试失败的分诊与重跑指南

2026-09-06 18:47:20作者:滑思眉Philip

在 Flutter 官方仓库中,绝大多数测试已经迁移到基于 LUCI(Layered Universal Continuous Integration,分层通用持续集成)的 CI 系统上运行。这套系统为 framework、engine 等多个 Flutter 仓库同时服务,覆盖 post-submit framework 构建面板(提交后测试)与 pre-submit framework 试跑构建(通过 LUCI Milo 查看)。本篇指南以 framework 为例,系统讲解当一次 LUCI 构建失败(build failure)发生时,如何通过构建面板快速判断失败类型(基础设施故障或真实测试失败),并按照官方推荐的处置流程完成问题上报、日志排查与任务重跑,帮助 PR 作者与仓库维护者高效解锁被阻塞的合并流程。读完本文,你将掌握 Flutter 基础设施上"紫色 = infra 故障、红色 = 测试失败"的颜色语义、构建总览页五个信息区的用法,以及 pre-submit / post-submit 两条重跑通道的完整操作步骤。

LUCI 构建失败的两大类型与面板颜色语义

在 Flutter 的构建面板中,每一个方框(box)代表一次构建结果,颜色是第一时间判断失败类型的信号:

颜色 含义 对应章节
紫色方框 基础设施故障(infra failure) 下文"处理基础设施故障"
红色方框 测试失败(test failure) 下文"处理测试失败"
绿色方框 构建成功
带感叹号图标的绿色方框 发生过 flake(最终重跑成功) Reducing-Test-Flakiness.md

这两种类型在 post-submit 框架构建面板与 pre-submit 构建控制台中都遵循同样的颜色约定(官方文档分别以连续色块示例:紫色或红色方框夹在绿色成功结果之间)。在动手排查之前,先确认两个常用的构建入口:

关于 LUCI 在 Flutter 中的角色,仓库侧有更完整的说明:根据 dev/bots/README.md,LUCI 机器人对每次 PR 与提交运行 test.dart 脚本,统一完成 tools、framework 的测试;该基础设施由两份 .ci.yaml 配置驱动:framework 的 .ci.yaml 与 engine 的 engine/src/flutter/.ci.yaml。构建面板中每个 builder 的步骤、超时、标签与触发条件,都直接来源于这些 YAML 文件中对 target 的定义。

处理基础设施故障(Infra Failure)

什么是 infra failure

基础设施故障(infra failure)指与测试代码本身无关的失败,典型成因包括:

  • 网络连接问题(transient network issue);
  • 硬件故障或设备离线(如测试中途设备消失、adb: device ... not found);
  • recipe 脚本破损(recipe breakage);
  • CIPD 依赖问题(依赖包拉取或解析失败)。

这类失败在面板上以紫色方框呈现,例如 post-submit 构建面板中"连续绿色方块中夹着一个紫色方块",pre-submit 构建控制台中也一样(连续绿色矩形中出现一个紫色矩形)。

infra failure 构建的总览页结构

官方文档给出了一个典型示例:Linux color_filter_and_fade_perf__e2e_summary。打开其 LUCI overview 页面后,自上而下有五个信息区:

  • (i) 该 builder 的历史构建列表链接:用于判断故障是否是偶发、是否在更早的提交上已经出现。
  • (ii) 失败性质的快速预览:此处通常会直接标注本次失败是否为 infra failure。
  • (iii) 该 builder 的步骤列表:这些步骤由右侧关联的 recipe 定义,可以从 dev/bots/README.md 了解 recipe 的工作方式——Flutter 为不同测试准备了多套 recipe,recipe 本质是带导入限制的 Python 脚本,并通过 recipe_modules 共享公共动作;builder 使用哪套 recipe 由 infra 中的 builder 配置决定。
  • (iv) 真正导致构建失败的步骤:由于 recipe 会串起多个步骤,前序步骤成功不代表整体成功,需定位到具体失败的 step。
  • (v) stdout 详细日志入口:点击对应步骤的 stdout 即可阅读该步骤完整输出,故障根因信息通常在日志末尾的非零退出码附近。

infra failure 处置清单

遇到紫色失败,按以下顺序操作:

  1. 通过点击 (i) 检查该 builder 更早的构建,确认该 infra 故障是否已经反复出现;
  2. 检查 Flutter 的 infra bug 池(标签 team: infra 且状态为 open),确认是否已有对应 issue;
  3. 若没有现成 issue,使用 infra bug 模板 提交一个新的基础设施 bug;
  4. 若需要立即获得帮助,可在 Discord 的 hackers-infra 频道提问(也可参考仓库侧的 Chat 了解社区沟通渠道约定);
  5. 如果判定这只是一次 infra flake、需要重跑任务,则按场景执行下文"如何重跑失败的构建"。

处理测试失败(Test Failure)

如何识别测试失败

测试失败(test failure)是指构建本身运行正常、但某个或某些测试断言未通过。它在面板上以红色方框呈现:post-submit 构建面板中"绿色方块间夹着红色方块",pre-submit 构建控制台同理。

测试失败构建的总览页结构、五个信息区与 infra failure 完全一致,直接复用上文 (i)~(v) 的读图方法,其中需要重点关注的是 (ii) 的失败信息预览与 (v) 的 stdout 详细日志——它们共同决定这次失败究竟是真实代码缺陷还是偶发 flake。

测试失败处置清单

  1. 通过 (i) 检查该失败是否也出现在更早的构建或提交上,区分"新引入的回归"与"存量问题";
  2. 依据 (ii) 的错误信息与 (v) 的详细日志进行调试,判断失败是否由本次代码改动引起的真实测试失败。调试时可参考 Fix-failing-checks 中给出的典型输出形态——例如 flutter test 框架捕获的异常通常形如:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: exactly one matching candidate
  Actual: _TextWidgetFinder:<Found 0 widgets with text
"AsyncSnapshot<String>(ConnectionState.waiting, null, null, null)": []>
   Which: means none were found but one was expected
...
This was caught by the test expectation on the following line:
  file:///b/s/w/ir/x/w/flutter/packages/flutter/test/widgets/async_test.dart line 115
════════════════════════════════════════════════════════════════════════════════════════════════════

这类输出会给出失败断言所在文件与行号、测试描述与完整堆栈,可以直接据此在本地复现并修复。

  1. 检查该问题是否已存在于 Flutter issues 列表
  2. 检查是否是已知 flake:搜索标签为 c: flake 的 issues 列表,若命中则无需重复上报;
  3. 若无现成 issue 则按需提交新 bug;
  4. 如果需要重跑,参照 infra failure 一节中的步骤 6(重跑机制对两类失败通用)。

关于 flake 的完整治理流程(预防、每周自动检测、分诊、修复与重新启用),详见 Reducing-Test-Flakiness.md。该文同样依赖本指南介绍的方法——例如定位 test step 与阅读其 stdout 以区分"同一提交多次重跑后成功"与"单次运行内由 test runner 多轮重跑"两类 flake。

如何重跑失败的构建

无论是 infra flake 还是偶发测试失败,重跑(rerun)都是最常见的解锁手段。重跑入口按构建所属阶段分为两类。

pre-submit(PR 提交前检查)重跑

在 GitHub PR 的 check run 页面点击 Re-run 即可对该检查重跑:

  • 该操作仅限 flutter-hackers 组成员使用;
  • 若你没有相应权限,请在 Chat 文档介绍的 Discord #hackers-infra 频道请团队成员代为重跑。

post-submit(提交后主干构建)重跑

登录 framework build dashboard,点击对应任务方框,再点击 RERUN 按钮即可:

  • 由于基础设施存在技术限制,该操作目前仅限 Googler(Google 内部成员);
  • 非 Googler 需要到 #hackers-infra 频道联系 Googler 协助重跑。

结合 .ci.yaml 理解 builder 与步骤的构成

要真正"读懂"一次 LUCI 失败,理解 builder 配置来源很有帮助。框架仓库的 builder 定义集中在根目录 .ci.yaml 中,每个 builder 条目包含以下关键字段,与失败排查直接相关:

字段 作用 排查关联
name builder 名称,如 Linux snippets 与面板中失败方框的名称一一对应
recipe 指定运行该 builder 的 recipe,如 flutter/flutter_dronedevicelab/devicelab_drone 决定 (iii) 步骤列表的具体来源
bringup 是否处于试验/灰度阶段(新接入测试先用 bringup: true 观察) 值为 true 的 builder 失败不会导致整体树变红,是 flutter 治理 flake 的手段
timeout 构建超时(分钟) 超时类失败需结合 execution details 排查
properties 传给 recipe 的属性,如 tagsshard shard 划分了测试分片,便于在 stdout 中定位具体 shard
runIf 触发条件文件列表 判断该 builder 是否应被本次改动触发

.ci.yaml 中的 Linux snippets 为例,其片段形如:

- name: Linux snippets
  recipe: flutter/flutter_drone
  bringup: true
  timeout: 30
  properties:
    tags: >
      ["framework", "hostonly", "shard", "linux"]
    shard: snippets
  runIf:
    - dev/snippets/**
    - dev/bots/**
    - .ci.yaml
    - engine/**
    - DEPS

当某个 shard 报错时,日志中通常需要按 shard 名在 stdout 中检索对应输出;而 bringup: true 的 target(例如 .ci.yaml 中处于试验状态的 android_defines_testexternal_textures_integration_test 等)失败时应优先怀疑是新接入测试本身的不稳定或环境准备问题,而非框架回归。

此外,根目录的 TESTOWNERS 记录了每个测试的归属团队,当需要为某个失败测试提交 bug 或指派分诊时,可据此快速找到负责方。

总结:一次构建失败的标准处置路径

综合上文,面对一次 LUCI 构建失败,推荐的完整路径是:

  1. 看颜色定性:紫色 = infra failure,红色 = test failure;
  2. 读总览页定位:用 (i) 判断历史、(iii)/(iv) 定位失败步骤、(v) 阅读 stdout 日志找根因(可同时参考 Fix-failing-checks 中"查看测试输出"的具体导航方式);
  3. 查池子去重:infra 故障查 team: infra 标签池,测试失败查 issues 列表与 c: flake 标签池
  4. 分类处置:真缺陷 → 定位修复;infra 故障 → 上报或求助 #hackers-infra;确认为 flake → 按场景选择 pre-submit 的 Re-run 或 post-submit 的 RERUN
  5. 复盘沉淀:反复出现的测试不稳定应进入 Reducing-Test-Flakiness 描述的标记 bringup: true、跟踪与重新启用流程。

如果失败的检查与主分支状态、Google 内部测试或 ci.yaml 校验相关,属于另一类 PR 阻塞场景,可直接查阅 如何修复 PR 的失败检查(Fix-failing-checks) 获取对应处理建议。

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