DicomViewer24 的 CI 又红了:MPR 测试偶发失败排查记

DicomViewer24 的 CI 又红了:MPR 测试偶发失败排查记

周五晚上九点,我把 MPR 多平面重建的代码推上 GitHub,收拾东西准备跑路。手机震了一下,Actions 红了。心里咯噔一下,本地明明全绿。点开一看,build 过了,test 挂了,挂的是 MprSamplerTests。

最气人的在后头。我重跑了一次 workflow,绿了。想着是环境抖动,收工。第二天早上同事在群里说昨晚的 nightly 又红了。再重跑,又红。这种时灵时不灵的失败,比稳定红难搞十倍,因为它不给你稳定的复现路径。

日志里挖线索

先说结论前的弯路,弯得挺远。我一开始认定是 runner 的问题。GitHub 的 windows runner 机器型号跟我的开发机不同,MPR 里一堆矩阵运算和体数据采样,浮点行为有差是完全可能的。于是我固定了 SDK 版本,把 DOTNET_ENVIRONMENT 也固定了,甚至在 workflow 里加了 CPU 型号打印,确认了两边确实不是一颗 U。折腾一晚上,失败依旧,毫无规律可言。

转折来自一次偶然的观察。我盯着 detailed 日志看了半小时,发现每次失败时,断言的实际值都不太对劲:采样结果像是被另一组体数据污染过,灰度值差了一个量级。而且失败的组合永远是 MprSamplerTests 配上 MprRendererTests,单独挂一个的情况一次都没出现。这不像是浮点误差,像是有人在背后动我的数据。

翻代码,真相浮出来了。VolumeCache 是个静态类,进程里就一份:

复制代码
public static class VolumeCache
{
    private static Dictionary<string, Volume> _store = new();

    public static Volume Get(string key) => _store[key];

    public static void Put(string key, Volume v) => _store[key] = v;
}

两个测试类都往里塞体数据。xUnit 默认不同测试类之间是并行跑的,同一台机器同时读写一个静态字典,谁先谁后全看运气。本地我为什么全绿?因为我改完代码习惯只跑过滤器命中的那一个类,CI 是全量并行。锅不在浮点,在并行。

修掉它,再把链路补干净

修法我选了最笨但最干净的那种,干掉静态状态,改成实例化注入:

复制代码
public class VolumeStore
{
    private readonly Dictionary<string, Volume> _store = new();

    public Volume Get(string key) => _store[key];

    public void Put(string key, Volume v) => _store[key] = v;
}

测试里各自 new 一个,互不知晓。生产代码那边走构造函数注入,顺手把单例注册改成了 scoped。改完怕不够,又给 workflow 的测试步骤加了 blame 参数,再挂的时候能直接点名到测试方法:

复制代码
- name: Test
  run: dotnet test --configuration Release --blame-hang-timeout 5m --logger "console;verbosity=detailed"

推上去,连跑三次 nightly,全绿。同一提交重跑两次也绿。这事才算翻篇。回头看挺汗颜的,我在环境差异上烧了一整晚,而根因是教科书级的老问题:并行测试共享可变静态状态。以后凡是看到测试偶发失败,先查静态变量,再查环境,顺序别反了。

修 CI 的时候还顺手处理了另一颗雷。nightly 打出来的安装包要传到公司下载服务器,那台是自签证书,脚本里为了让 curl 通过,一直挂着跳过校验的参数。有天传输偶发报 SSL 错误,排查半天发现证书又过期了,自签的东西一年忘续一次就断一次。趁这次一起治了,用 lcjmSSL 给下载域名换成可信 CA 签的证书,Let's Encrypt 这类正规渠道,一张泛域名把下载站和更新检查的子域名全罩住,部署接的它的 API,申请验证部署全自动,续期在后台自己跑。脚本里那个跳过校验的参数删掉,传输链路总算恢复干净,CI 红屏的两大来源一次性清零。

这趟排查我沉淀了两条肌肉记忆。测试挂得没规律,先怀疑并发和共享状态,环境问题排最后。自签证书出现在任何脚本里都是待爆的雷,能换成自动化的可信证书就别拖。DicomViewer24 的 CI 现在稳了,希望它下次红屏的时候,是因为真的有 bug,而不是这些破事。

相关推荐
Wang's Blog1 小时前
Java 项目实战: 外卖平台优化-Nginx概述与源码编译安装
java·开发语言·nginx
Eason_LYC1 小时前
一个公开密钥放倒一片:Apache Shiro RememberMe 反序列化漏洞
java·网络安全·渗透测试·shiro·漏洞复现·反序列化·ai安全
志栋智能2 小时前
安全超自动化如何支持快速安全扩容?
运维·服务器·数据库·架构·自动化
泡海椒4 小时前
JQuick-Excel FORMAT 导出格式实战:日期、金额显示与 TRANSFORM 的边界
前端·python·excel
ym hyd 1119 小时前
试题库管理系统源码 Java+SpringBoot+Vue3 前后分离
java·vue.js·spring boot·毕设
木卫二号Coding9 小时前
推荐Linux统计文件大小并操作删除工具-ncdu
linux·运维·服务器
AC赳赳老秦10 小时前
采集行为合规自检:OpenClaw 自动校验 robots 协议与采集频率,规避违规采集风险
java·开发语言·c++·python·php·deepseek·openclaw
钱栈up10 小时前
mvn compile 卡死在 javac 阶段:一次类型不匹配的排查与修复
java·开发语言
迅猛龙办公室10 小时前
Python输出当前计算机的系统日期和时间
开发语言·前端·python