상황
재시도 동작을 고치는 PR이 들어온다. 새 테스트가 몇 개 붙어 있고, PR의 CI는 모두 통과했다. 리뷰도 끝났다. 병합 버튼을 누르고 "PR CI가 이 파일들을 그대로 검증했다"는 코멘트를 남긴다. 몇 분 뒤 기본 브랜치의 전체 CI가 한 샤드에서 실패한다. 실패한 네 개는 전부 방금 병합한 PR이 추가한 테스트다.
초록이 거짓말을 한 이유
그 PR은 기본 브랜치보다 서른 커밋 넘게 뒤처진 지점에서 갈라져 있었다. 그 사이 다른 PR들이 테스트가 기대는 provider 코드를 바꿨다. PR의 CI는 브랜치 자체를 검증했을 뿐, 병합 후에 실제로 존재하게 될 트리를 검증하지 않았다. 충돌이 없다는 건 텍스트가 겹치지 않는다는 뜻이지, 동작이 맞물린다는 뜻이 아니다. 같은 테스트 파일이 PR head에서도, 병합 전 기본 브랜치에서도 통과했다는 사실이 바로 그 증거다. 둘 중 어느 쪽도 병합 결과가 아니었다.
병합 전에 돌릴 것
브랜치가 많이 뒤처져 있고 바뀐 코드에 닿는 테스트가 있다면, 기본 브랜치의 현재 head 위에 그 브랜치를 시험 병합한 임시 워크트리를 만들고 거기서 영향받는 테스트 파일을 돌린다. 저장소가 허용한다면 더 간단한 방법은 rebase를 요구해 CI가 최신 base 위에서 다시 돌게 하는 것이다. 어느 쪽이든 증거는 "병합될 트리"에서 나와야 한다.
어떤 테스트가 영향받는지 고르는 법
변경한 파일 옆의 테스트만으로는 부족하다. 같은 날 다른 병합에서, 마커 파일의 의미를 바꾼 PR이 그 파일 이름을 직접 확인하는 다른 테스트를 깨뜨렸다. 병합 트리에서 돌린 건 수정한 파일과 관련된 테스트뿐이었다. PR이 계약을 바꾼다면, 즉 파일 이름, 이벤트 이름, 설정 키, 상태 값 같은 것을 바꾼다면 그 이름으로 테스트 전체를 검색하고, 그것을 단정하는 파일을 전부 돌려라.
로컬 환경이 거짓말할 때
로컬에서 재현이 안 될 수도 있다. 기본 브랜치에서조차 환경 때문에 같은 파일의 테스트가 수십 개 실패한다면, 로컬 결과는 기준선과 비교할 때만 의미가 있다. 변경 전과 변경 후를 같은 환경에서 돌려 실패 집합이 같은지 비교하라. 그게 불가능하면 실패한 CI 작업을 한 번 재실행해 flake인지 확인하고, 판단은 CI의 병합 트리 결과에 맡긴다.
확인 방법
병합 코멘트에 "CI 통과"라고 쓰기 전에, 그 CI가 돈 커밋이 어떤 base 위에 있는지 확인한다. 기본 브랜치와의 거리가 크다면 병합 트리에서 돌린 테스트 목록과 결과를 함께 적는다. 이미 잘못된 주장을 남겼다면, 빨간 기본 브랜치를 발견한 즉시 같은 자리에 정정을 단다.
The situation
A pull request fixing retry behaviour comes in with a handful of new tests, and every CI check on it is green. Review is done. You merge it and leave a comment saying the pull request's CI already covered those files as they are. A few minutes later the default branch's full CI fails on one shard. All four failures are tests the pull request you just merged added.
Why the green lied
The branch had forked more than thirty commits behind the default branch. In between, other pull requests had changed the provider code those tests lean on. The pull request's CI verified the branch itself, not the tree that would exist after the merge. No conflicts only means the text does not overlap; it says nothing about whether the behaviour fits together. The proof is that the same test file passed on the pull request's head and on the default branch before the merge. Neither of those was the merge result.
What to run before merging
If the branch is far behind and has tests that touch changed code, create a temporary worktree that test-merges the branch onto the default branch's current head, and run the affected test files there. If your repository allows it, the simpler route is to require a rebase so CI reruns on the current base. Either way, the evidence has to come from the tree that will actually be merged.
How to pick the affected tests
Tests next to the files you changed are not enough. In another merge the same day, a pull request that changed what a marker file meant broke a different test that checked for that file by name. The merged-tree run had only covered the tests around the edited files. When a pull request changes a contract, such as a file name, an event name, a config key or a status value, search the whole test tree for that name and run every file that asserts it.
When the local environment lies
You may not be able to reproduce it locally. If dozens of tests in the same file already fail on the default branch because of your environment, a local result only means something next to a baseline. Run before and after in the same environment and compare the failure sets. If that is not possible, rerun the failed CI job once to rule out a flake, and let CI's merged-tree result make the call.
How to confirm
Before writing "CI passed" in a merge comment, check which base the commit CI ran on actually sits on. If it is far from the default branch, write down the list of tests you ran on the merged tree and their results. If you have already posted the wrong claim, put a correction in the same place as soon as you see the red default branch.
场景
一个修复重试行为的拉取请求进来了,附带几个新测试,所有 CI 检查都是绿色的,评审也已完成。你合并了它,并留言说拉取请求的 CI 已经按原样覆盖了这些文件。几分钟后,默认分支的完整 CI 在一个分片上失败。四个失败全是刚合并的那个拉取请求自己新增的测试。
绿色为什么撒了谎
这个分支是在落后默认分支三十多个提交的位置分出来的。在这期间,其他拉取请求改动了这些测试所依赖的 provider 代码。拉取请求的 CI 验证的是分支本身,而不是合并之后才会存在的那棵代码树。没有冲突只说明文本没有重叠,并不说明行为能够咬合。证据就是:同一个测试文件在拉取请求的 head 上能通过,在合并前的默认分支上也能通过。但这两者都不是合并结果。
合并前要跑什么
如果分支落后很多,并且有测试触及已经改动的代码,就建一个临时工作树,把该分支试合并到默认分支当前的 head 上,然后在那里运行受影响的测试文件。如果仓库允许,更简单的做法是要求 rebase,让 CI 在最新基线上重新运行。无论哪种方式,证据都必须来自真正要被合并的那棵树。
如何挑出受影响的测试
只跑改动文件旁边的测试是不够的。同一天的另一次合并里,一个改变了某个标记文件含义的拉取请求,弄坏了另一个按文件名检查该文件的测试。而在合并树上跑的只有被修改文件周边的测试。当拉取请求改变了某个契约,比如文件名、事件名、配置键或状态值时,就用这个名字搜索整个测试目录,把所有断言它的文件都跑一遍。
当本地环境不可信时
你可能无法在本地复现。如果因为环境原因,同一个文件里的几十个测试在默认分支上就已经失败,那么本地结果只有和基线对照时才有意义。在同一个环境里分别运行改动前和改动后,比较两次的失败集合。如果做不到,就把失败的 CI 任务重跑一次以排除偶发失败,然后由 CI 在合并树上的结果来下结论。
如何确认
在合并留言里写“CI 已通过”之前,先确认 CI 运行的那个提交到底基于哪个基线。如果它离默认分支很远,就把在合并树上运行的测试清单和结果一起写下来。如果已经发出了错误的说法,一看到默认分支变红,就在原处补上更正。
状況
リトライの挙動を直すプルリクエストが届く。新しいテストがいくつか付いていて、CI のチェックはすべて緑、レビューも終わっている。マージして、「プルリクエストの CI がこれらのファイルをそのまま検証済み」とコメントを残す。数分後、デフォルトブランチのフル CI がひとつのシャードで落ちる。四つの失敗はすべて、いまマージしたプルリクエストが追加したテストだ。
緑が嘘をついた理由
そのブランチは、デフォルトブランチより三十コミット以上遅れた地点から分岐していた。その間に別のプルリクエストが、テストが頼っている provider のコードを変えていた。プルリクエストの CI が検証したのはブランチそのものであって、マージ後に初めて存在するツリーではない。コンフリクトがないというのはテキストが重ならないという意味で、挙動が噛み合うという意味ではない。同じテストファイルがプルリクエストの head でも、マージ前のデフォルトブランチでも通っていたことがその証拠だ。どちらもマージ結果ではなかった。
マージ前に実行するもの
ブランチが大きく遅れていて、変更されたコードに触れるテストがあるなら、デフォルトブランチの現在の head にそのブランチを試しにマージした一時ワークツリーを作り、そこで影響を受けるテストファイルを実行する。リポジトリが許すなら、もっと簡単なのは rebase を求めて、CI を最新の base で走らせ直すことだ。どちらにしても、証拠は実際にマージされるツリーから出てこなければならない。
影響を受けるテストの選び方
変更したファイルの隣にあるテストだけでは足りない。同じ日の別のマージでは、マーカーファイルの意味を変えたプルリクエストが、そのファイルを名前で確認している別のテストを壊した。マージ後のツリーで実行していたのは、編集したファイル周辺のテストだけだった。プルリクエストが契約、つまりファイル名、イベント名、設定キー、状態値などを変えるなら、その名前でテスト全体を検索し、それを検証しているファイルをすべて実行する。
ローカル環境が当てにならないとき
ローカルで再現できないこともある。環境のせいで、同じファイルのテストがデフォルトブランチですでに何十個も落ちるなら、ローカルの結果はベースラインと並べたときにしか意味を持たない。同じ環境で変更前と変更後を実行し、失敗の集合を比べる。それができないなら、落ちた CI ジョブを一度だけ再実行して flake を除外し、判断は CI のマージツリーでの結果に任せる。
確認方法
マージコメントに「CI 通過」と書く前に、CI が走ったコミットが実際にどの base の上にあるのかを確かめる。デフォルトブランチとの距離が大きいなら、マージ後のツリーで実行したテストの一覧と結果を一緒に書く。すでに間違った主張を書いてしまっていたら、赤いデフォルトブランチに気づいた時点で、同じ場所に訂正を付ける。