调试间歇性测试失败

什么是间歇性故障(又名橙色)?

间歇性故障是指以看似随机的方式间歇性发生的测试失败。通常,您会编写一个在您的计算机上本地运行良好的测试,但在各种 CI 环境中运行数千次(其中一些在高负载下运行)时,它可能会开始随机失败。

间歇性故障也称为橙色,因为相应的测试作业在 treeherder 上显示为橙色。

这些间歇性故障在 Bugzilla 中进行跟踪。当测试开始出现间歇性故障时,Bugzilla 中会提交一个错误(通常由 Mozilla 代码管理员提交)。

一旦某个测试故障存在错误,该测试的所有其他类似故障都将作为该错误内的注释进行报告。这些报告通常每周发布一次,如下所示

在过去 7 天中,与该错误相关的 2740 次推送中出现了 5 次失败 (0.002 次失败/推送)。

请参阅 此处的示例

有时,测试开始更频繁地失败,然后每天发布这些报告。

为了帮助解决(不幸的是)不断增长的间歇性故障列表,Stockwell 项目在一段时间前启动了(在 其维基 上阅读有关该项目目标的更多信息)。

该项目定义了一种场景,在这种场景下,非常频繁失败的测试会被禁用。理想情况下,我们应该尝试避免这种情况,因为这意味着减少我们的测试覆盖率,但有时我们没有时间调查失败,禁用它是唯一剩下的选择。

查找间歇性故障

您将毫无困难地发现某个特定测试是否为间歇性故障,因为会为此提交一个错误,并且您将在 Bugzilla 中看到它(监视您选择的 Bugzilla 组件 是避免错过故障报告的好方法)。

但是,在上下文中查看间歇性故障仍然很有用。 Treeherder 上的间歇性故障视图 显示按频率排序的间歇性故障。

您也可以在 Bugzilla 中查看间歇性故障。转到 设置页面 并启用“查看错误时,显示其对应的 Orange Factor 页面”。

在本地重现测试失败

修复间歇性故障的第一步是重现它。

有时只能在自动化中重现故障,但值得在本地尝试,因为这使得调试变得更加简单。

首先,尝试隔离运行测试。您可以使用 --repeat--run-until-failure 标志到 mach mochitest 来稍微自动化此过程。最好在无头模式 (--headless) 或虚拟机中(或在 Linux 上使用 Xnest)执行此类操作,以避免锁定您的机器。

但是,有时测试只有在与一个或多个其他测试一起运行时才会失败。您可以使用 --start-at--end-at 标志与 mach mochitest 一起运行一组测试。

对于某些作业(但并非所有作业),您可以从 TaskCluster 获取交互式 shell

还有一个 e10s 测试调试技巧的实用页面 值得一读。

由于间歇性故障通常是由竞争条件引起的,因此启用 Chaos 模式有时很有用。这会稍微更改计时和事件排序。最简单的做法是在特定测试中启用它,方法是调用 SimpleTest.testInChaosMode。您还可以设置 MOZ_CHAOSMODE 环境变量,甚至直接编辑 mfbt/ChaosMode.cpp

一些测试会间歇性地泄漏。在您的 mozconfig 中使用 ac_add_options --enable-logrefcnt 来潜在地找到它们。

rr 工具有 自己的 chaos 模式。这有时也可以重现通常无法重现的故障。虽然使用 rr 调试 JS 错误很困难,但通常,如果您能够可靠地重现故障,您至少可以进行实验(见下文)以尝试修复。

这不起作用

如果您无法在本地重现,则还有其他选择。

一种有用的方法是向测试中添加其他日志记录,然后再次推送。有时日志缓冲会使输出变得奇怪;您可以添加对 SimpleTest.requestCompleteLog() 的调用来解决此问题。

您可以使用 mach try DIR 在 try 上运行单个测试目录。您还可以使用 --rebuild 标志多次重新触发测试作业;或者您也可以轻松地从 treeherder 执行此操作。

解决

如果测试在每次失败时都在不同的地方失败,则可能是超时。当前的 mochitest 超时时间为 45 秒,因此如果间歇性故障的成功运行时间约为 40 秒,则可能只是真正的超时。如果在较慢的构建(例如 Linux 32 调试)上最常看到此故障,则尤其如此。在这种情况下,您可以拆分测试或在测试开头某个位置调用 requestLongerTimeout此处提供一个示例)。

有时问题是测试中特定位置的竞争条件。您可以通过添加短暂等待来测试此理论,以查看故障是否消失,例如

yield new Promise(r => setTimeout(r, 100));

请参阅 DevToolsUtils 中的 waitForTickwaitForTime 函数以了解类似的功能。

您可以使用类似的技巧在特定点“暂停”测试。这在本地调试时很有用,因为它会将 Firefox 保持打开并处于响应状态,并在您选择的确切位置。使用 yield new Promise(r => r); 执行此操作。

shared-head.js 还提供了一些帮助程序,例如 once,用于绑定具有其他日志记录的事件。

您还可以通过注释掉测试的块或在其中添加早期 return 来对测试进行二分查找。您可以并行执行这些实验中的许多个,而无需等待第一个实验完成。

验证

很难验证间歇性故障是否真的已修复。您可以做的一件事是推送到 try,然后在 treeherder 中多次重新触发作业。您应该重新触发的次数取决于故障的频率。