用 cargo-nextest 跑异步测试时被超时坑了一次

环境是 Rust 1.79,Linux x86_64,项目里有一批 tokio 异步测试,cargo test 单线程跑一次大约 3 分钟。为了加快反馈,我换成了 cargo-nextest(0.9.70),默认多进程并行,本地跑下来 40 秒左右。问题出在接入 CI 后:原来全绿的用例,在 nextest 里偶发失败,报错都是 test timed out after 60s,但同样的用例用 cargo test 连跑十次都过。

我的做法是先确认不是业务代码问题,再改 nextest 的超时策略。在 .config/nextest.toml 里加了这么一段:

[profile.ci]
slow-timeout = { period = "30s", terminate-after = 4 }
leak-timeout = "500ms"

[[profile.ci.overrides]]
filter = 'test(/integration_/)'
slow-timeout = { period = "120s", terminate-after = 2 }

意思是:普通用例 30 秒没结束就标记为 slow,连续 4 个周期(即 120 秒)才杀掉;名字里带 integration_ 的用例放宽到单周期 120 秒。跑法用 cargo nextest run --profile ci。另外把 RUST_TEST_THREADS 这类环境变量从 CI 配置里删了,因为 nextest 自己按 CPU 核数调度,那套变量在它下面不起作用,留着只会让人误判。

机制上说,nextest 默认的进程模型和 libtest 不一样:它是每个测试一个独立进程,进程之间不共享 tokio runtime。我那些偶发超时的用例,恰好都在测试体里 tokio::spawn 了后台任务,并且依赖一个全局的 OnceCell 做初始化。并行度一高,初始化竞争加上进程启动开销,单个用例的墙钟时间被拉长,触发了默认的 60 秒阈值。代价是 nextest 对每个测试单独拉进程,冷启动比 libtest 重,短小的单元测试多了反而可能更慢;另外它不读 #[should_panic] 之外的一些 libtest 特性,需要自己核对。

失效的地方我也碰到了:第一,slow-timeout 只对 nextest 生效,本地有人还在用 cargo test,两边的超时语义不一致,最好在 CI 和文档里统一写明用哪个入口;第二,override 的 filter 语法是 nextest 自己的一套,写错不会报错,只会静默不匹配,我一开始把 filter = 'test(integration_)' 当成子串匹配,实际它要的是和测试全名匹配,最后改成带斜杠的正则形式才生效;第三,如果测试里有依赖端口或临时目录的,并行进程会互相踩,超时只是表象,得先修资源隔离。

你们在 CI 里是继续用 cargo test 保持语义一致,还是全面切 nextest 并按测试类型分别配超时?

0
赞同度 100% · 0 条评论 · 6 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。