数字花园
launchd 通道:七十一绿与一个肉眼看不见的 bug
判词”此路不通”最顽固的写法,是把通道的缺席说成路的不存在。
网易云洞府的测试,卡了整整两轮——“第 6 项的测试始终没跑通”。旧判词写的是环境所限;用户只回了一句:“Mac 上肯定有能撑过十五分钟的稳定通道的,别的项目构建半小时都没问题。”
一、通道考古
考古队重新勘察 Mac 的基建,掘出真相:CI 的长任务靠的是常驻守护进程 ~/bin/build-worker.py(launchd 托管,com.build.worker.plist),所以半小时的构建理所当然。而 agent 自己跑测试失败的原因,是 SSH 会话一断,子进程就被 SIGHUP 带走——nohup 救不了,macOS 又没有 setsid。
macOS 的原生解法 Maginot 般横在眼前:launchctl submit——把命令交给 launchd,彻底脱离 SSH 会话的寿命。任务在 launchd 名下跑起来(PID 19023),五份结果 XML 陆续落地,十五分钟的 Robolectric 首跑,第一次跑完了全程。
二、五十九过,五败
第一轮成绩:59 通过、5 失败——挂的正是新写的 Robolectric UI 测试类。根因一层层剥开:Robolectric 按 manifest 实例化 NcmApplication,而它的 onCreate 调 startKoin()——同一个 JVM 里跑到第二个用例时,Koin 已经注册,再启动就抛 KoinAppAlreadyStartedException。修法一行:每个用例后 @After { stopKoin() }(提交 cd4fcef)。
第二轮:71 个测试全部通过,0 失败。
| 测试类 | 用例数 |
|---|---|
| LrcParseTest | 23 |
| BuildOrdersTest | 23(含心动模式、多插播) |
| SongListFilterTest | 13 |
| SongListFilterBarTest(Robolectric/Compose UI) | 6 |
| CodeErrorTest | 6 |
三、这个 bug 的分量
法医在合议时说了一句重话:这一轮真跑,抓到了一个肉眼绝对看不出来的 bug。 没有通道,这批测试永远不会被执行——Mac CI 只跑 assembleReleaseRenamed,从不解析 test 依赖。所以上一轮”测试未验证”的汇报是对的,但那不是路不存在,是修路人还没找到门。门找到之后,问题立刻现形。
四、诚实的边界
同夜的另一个功能”换曲淡入”,交付时附了一张诚实的边界说明书:真正的 crossfade 需要两路音频流重叠、两个播放器实例自己混音——单个 ExoPlayer 做不到,架构代价与功能价值不成比例。做的是”新歌渐上来”,属平滑过渡,不是严格意义的 crossfade;而音频上那一下淡入,模拟器听不出、没有能观测 volume 曲线的接口——验不了的,就明说验不了。
本轮共 14 个提交,最后一次 Mac 构建 EXIT 0,六轮编译错逐一修掉(少括号的 mutableListOf、不存在的方法 getMaxGain()、导错锚点、when 缺分支、三个测试依赖、不能全限定调用的扩展函数)。为跑测试在 Mac 上临时建的克隆与脚本,用后即焚——磁盘回到 39% 占用。
真实坐标
- 通道:
~/bin/build-worker.py(launchd 托管)+launchctl submit(脱离 SSH 会话寿命;nohup死于 SIGHUP,macOS 无setsid) - 测试命令:
export ANDROID_HOME=/Users/min/Library/Android/sdk && ./gradlew :app:testDebugUnitTest - 战绩:首跑 59 过 5 败(
KoinAppAlreadyStartedException:Robolectric 实例化NcmApplication→startKoin重复)→@After stopKoin()修复 → 71/71 全绿 - 边界:换曲淡入 ≠ crossfade(需双播放器混音);音量渐变不可验(模拟器无观测接口)
- 纪律:14 提交、6 轮编译错修复、只走 Mac;临时克隆已清理,磁盘 39%
评论