에이전트 패치는 플래키(flaky)한 테스트를 건너뛰는 경우, 프리즈 기록(freeze record)이 하나의 피처 스냅샷에서 두 번의 실행 불일치를 저장하고 해당 패치가 수정하지 않은 속성 파일 이름을 지정했을 때만 발생할 수 있습니다. 나중에 녹색으로 통과하는 실행은 그 기록이 아닙니다. 동일한 diff에서 다시 작성된 속성은 독립적인 증거가 될 수 없습니다.
격리(Quarantine)는 모델이 생성한 diff가 광범위하고 단일 빨간 테스트가 노이즈일 수 있기 때문에 존재합니다. 또한, 건너뛰기가 CI를 녹색으로 만드는 유일한 변경 사항일 때 회귀(regressions)를 숨기기도 합니다. 아래의 검사는 닫힌 방식으로 실패합니다.
누락된 필드, 일치하는 실행, 또는 diff에서 수정된 오라클 모두 건너뛰기를 거부합니다. 이 절차는 실험 보고서가 아니라 명세입니다. 본 기사에서는 명령어, 검사기(checker), 샘플 속성을 실행하지 않았으며, 플레이스홀더 해시 값은 측정이 아닙니다.
프리즈 기록이 저장해야 하는 것
여섯 개의 필드가 필수적입니다. 메모는 함께 존재할 수 있지만, 구멍을 채우지는 못합니다.
| Field | What it holds | Reject the skip when |
|---|
| test_id | 하나의 pytest 노드 id | Collection returns zero tests or more than one |
| ... | | |
| 한 커밋 수 만료(commit-count expiry)가 이러한 필드 옆에 있을 수 있습니다. 이것만으로는 건너뛰기를 승인하지 않습니다. 검토자는 여전히 불일치와 부모 블롭(parent blob)이 필요합니다. | | |
CI와 검토자가 동일한 파일을 열도록 객체를 .freeze/record.json에 저장하세요. 필드를 풀 리퀘스트 댓글 안에 묻지 마세요. 댓글은 검사기의 입력값이 아닙니다.
일치하는 실행이 잘못된 증거인 이유
두 번의 통과는 해당 스냅샷에서 테스트가 통과할 수 있음을 보여줄 뿐입니다. 이전 실패가 비결정적(nondeterministic)이었음을 보여주지는 않습니다. 두 번의 실패는 안정적인 오류, 즉 제품 결함이나 깨진 피처를 보여주는 것이지, 프리즈 후보가 아닙니다.
불일치(Disagreement)는 피처 바이트는 변하지 않았는데 결과만 변경된 가장 작은 관찰입니다. 이것은 약한 관찰입니다. 30번 중 한 번 근처에서 발생한 실패는 종종 두 개의 동일한 결과를 생성하며 결코 자격을 얻지 못합니다.
그런 편향(bias)은 의도된 것입니다. 게이트는 안정적인 버그를 한 번의 운 좋은 재실행(rerun) 후에 격리하는 것보다, 희귀한 조각(flake)을 남기기를 더 선호합니다. 이 기록을 비율 추정치(rate estimate)가 아니라, 짧고 목격된 예외(witnessed exception)로 간주하십시오.
스냅샷은 테스트가 읽기만 하는 파일이어야 합니다. 만약 테스트가 피처(fixture)를 재작성한다면, 두 번째 해시 값이 달라지고 기록은 무효화됩니다. 네트워크, 클럭 또는 공유 서비스(shared service)를 호출하는 테스트는 범위를 벗어납니다. 왜냐하면 파일 해시 값으로는 그들의 불일치를 설명할 수 없기 때문입니다.
번호가 매겨진 절차
이 다섯 단계를 순서대로 따르십시오. 첫 번째 거절 지점에서 멈추십시오. 단계 5에서 승인(acceptance)이 출력되기 전까지는 건너뛰기 표시(skip mark)를 추가하지 마십시오.
- 피처를
.freeze/로 복사하고, 어떤 테스트 프로세스가 시작되기 전에 그 SHA-256을 기록합니다.
- 해당 단일 테스트 ID를 테스트 프로세스 외부에서 두 번 실행하고, 각 종료 상태(exit status)를 저장합니다.
HEAD^의 속성 파일(property file) 블롭을 해결하고, 경로가 커밋 차이(commit diff)에 없는지 확인합니다.
- 해당 부모 속성(parent property)으로 복사된 피처를 실행합니다. 속성 실패는 프리즈(freeze)를 막습니다.
record.json을 작성하고 체커(checker)를 실행합니다. 종료 코드 0만 건너뛰기 표시를 유지할 수 있습니다.
각 단계는 아래에서 제안된 명령과 함께 자세히 설명됩니다. 녹색 작업(green job)을 신뢰하기 전에 러너(runner)에서 pytest 종료 코드를 확인하십시오. 종료 상태 5는 테스트가 수집되지 않았음을 의미합니다. 이것을 실패한 단언(failed assertion)이나 플레이크(flake)가 아니라, 선택 오류(selection error)로 간주하십시오.
1. 피처 스냅샷 찍기 (Snapshot the fixture)
먼저 바이트를 고정(Pin)합니다. 복사본은 나중에 검토자(reviewers)들이 재해시(re-hash)하는 대상이며, 심지어 나중 명령이 워크트리(worktree)를 건드린다 하더라도 마찬가지입니다.
install -d .freeze
sha256sum fixtures/order_snapshot.json | awk '{print $1}' | tee .freeze/fixture.sha256
cp -- fixtures/order_snapshot.json .freeze/order_snapshot.json
tee 출력을 pytest를 시작하기 전에 복사본의 두 번째 해시 값과 비교하십시오. 이 두 다이제스트(digest)가 다르다면, 멈추고 복사본을 수정하십시오. 재현할 수 없는 스냅샷으로는 진행하지 마십시오.
2. 두 번 실행 기록하기 (Capture two runs)
노드 ID를 두 번 실행합니다. 테스트 내부에서 재시도(retry)하지 마십시오. 프로세스 내 재시도는 이 기록이 포착하고자 하는 불일치를 숨길 것입니다.
python -m pytest -q tests/test_order.py::test_total --tb=no > .freeze/run1.log
echo $? | tee .freeze/run1.exit
python -m pytest -q tests/test_order.py::test_total --tb=no > .freeze/run2.log
...
상태 0은 passed로, 그 외의 모든 상태는 failed로 매핑하고, status 5는 제외합니다. 두 불리언 값을 동일한 fixture_sha256에 disagreement로 작성합니다. 만약 이 불리언 값들이 일치하면 중단합니다. 어떤 불리언 값이 관찰되든 해당 패치는 freeze 대상이 아닙니다.
실패한 실행에서 나온 단언(assertion) 텍스트를 재실행하지 않고도 검토자가 볼 수 있도록 원본 로그와 종료 상태를 함께 보관해야 합니다. 만약 로그가 비어 있다면, 종료 상태는 유효한 증거로 사용될 수 없습니다.
3. 부모 오라클 고정(Pin the parent oracle)
속성 파일은 이미 부모 커밋에 존재해야 합니다. 에이전트 패치는 이를 수정할 수 없습니다. 이 분기(split)는 증인(witness)을 프로덕션 코드를 편집하는 것과 동일한 변경에서 재작성함으로써 모델이 게이트를 통과하지 못하게 만듭니다.
git rev-parse 'HEAD^:tests/properties/test_order_props.py' | tee .freeze/oracle.blob
git diff --name-only HEAD^ HEAD -- tests/properties/test_order_props.py
diff 목록은 비어 있어야 합니다. 내용이 비어 있지 않으면, 속성 실행 결과가 green하더라도 freeze는 거부됩니다. 패치에 의해 추가된 속성 파일 역시 HEAD^에 대한 git rev-parse가 실패하기 때문에 자격 미달입니다. 그 실패를 그대로 두십시오. 그것을 잡아서 계속 진행하지 마십시오.
체커(checker)는 오직 패치 커밋이 HEAD가 가리키는 시점일 때만 실행해야 합니다. 인터랙티브 리베이스(interactive rebase) 중에는 HEAD^가 잘못된 부모일 수 있으며, blob 고정은 잘못된 파일을 인증할 것입니다.
4. 복사본에서 부모 속성 실행(Execute the parent property on the copy)
속성을 환경 변수를 통해 복사된 스냅샷에 지정하십시오. 라이브 데이터베이스나 네트워크 피처에는 지정하지 마십시오.
FIXTURE_SNAPSHOT=.freeze/order_snapshot.json python -m pytest -q tests/properties/test_order_props.py --tb=line
아래 샘플은 제안된 속성입니다. 여기서는 실행되지 않았습니다. 이 코드는 fixture가 qty, unit_minor, 그리고 total_minor를 저장하며, 세금 라인이 lines 밖에 존재하지 않는다고 가정합니다. 스키마가 다르면, 부모 커밋이 조정된 불변성을 유지해야 합니다. 에이전트 패치는 이를 도입하는 커밋이 아니어야 합니다.
import json, os
from pathlib import Path
...
만약 이 함수가 스냅샷에서 실패하면, 프리즈(freeze)는 중단됩니다. 프로덕션 코드를 수정하거나, 별도의 검토된 커밋에서 fixture를 교체하십시오. 에이전트 diff 내부의 assert를 약화시키지 마십시오.
정수 minor 단위는 float, 세금 또는 변환율로 전송되지 않습니다. 그러한 스키마에 대한 부모 오라클은 반올림 규칙을 명시해야 합니다. 이 assert를 변경 없이 재사용하는 것은 잘못된 관계를 인증하게 됩니다.
5. 기록을 수락하거나 건너뛰기를 삭제합니다
체커(checker)는 제안된 CI 스크립트입니다. 이 기사에서는 실행되지 않았습니다. 이는 필드 존재 여부, 부모 blob ID, diff 제외, 불일치, 그리고 fixture 바이트를 확인합니다. 테스트를 수집하지 않으며, assert가 얼마나 강력한지 점수를 매기지도 않습니다.
import hashlib, json, subprocess, sys
from pathlib import Path
...
합성 기록은 형태를 보여줍니다. 사용하기 전에 모든 해시를 교체하십시오. 아래의 0과 1은 이 페이지가 실행 보고서를 작성하는 것처럼 보이지 않도록 하는 자리 표시자입니다.
{
"test_id": "tests/test_order.py::test_total",
"fixture_path": "fixtures/order_snapshot.json",
...
}
체커는 해당 파일이 존재하고 두 개의 로그가 이미 디스크에 있는 후에만 호출하십시오.
python freeze_gate.py .freeze/record.json
종료 코드 0만이 트리에서 건너뛰기 표시를 남길 수 있는 유일한 상태입니다. 다른 모든 상태는 해당 표시를 삭제하고 작업을 실패시킵니다. 에이전트에게는 우회 플래그가 없습니다. 비-제로 상태를 경고로 변환하는 래퍼는 게이트를 깨뜨립니다.
부모 오라클 작성하기
불변성을 작성하는 것이 느린 단계입니다. 누군가가 fixture가 지원할 수 있는 관계를 명시하고, 그 파일을 에이전트 패치가 건드리지 않을 커밋에 배치해야 합니다.
MonkeyCode의 무료 모델 접근은 해당 부모 파일에 대한 초안 작성 지원 용도로만 관련이 있습니다. 공개 고지: 본 문서는 MonkeyCode의 제품 홍보 활동의 일환으로 준비되었습니다. 함수 시그니처와 fixture 스키마에서 후보 속성을 요청한 다음, 커밋하기 전에 모든 assert를 검토하십시오. 무료 서버 옵션은 계정이 이미 이를 사용하도록 구성된 경우 해당 초안에 대한 호출 경로입니다.
여기에는 할당량(quota), 하드웨어 형태(hardware shape), 보존 기간(retention window) 또는 통과율(pass rate)이 명시되지 않았는데, 이는 아무것도 제공되지 않았기 때문입니다. 서버는 fixture 바이트를 저장소로 취급하지 않습니다. 해당 바이트는 freeze 기록 옆의 git worktree에 남아 있습니다.
모델 초안을 검토 스레드에 첨부하고, 오라클(oracle)을 별도의 커밋으로 만드십시오. 그래야 검토자들이 부모 블롭과 초안을 diff할 수 있습니다. 나중에 문구 수정 작업을 할 경우 동일한 무료 모델 접근을 사용할 수 있지만, 해당 편집 사항들 역시 자체 커밋이 필요합니다. 만약 이들이 에이전트 패치 내부에 포함되면 블롭 핀(blob pin)이 깨지고 검사기(checker)가 freeze를 거부합니다. 그 거부는 올바른 결과입니다.
제한 사항
두 개의 샘플로는 플레이크율(flake rate)을 추정할 수 없습니다. 이는 단지 두 가지 결과가 한 번 달랐다는 것만을 감지합니다. 비율이 필요하면 더 긴 캠페인을 사용하고, 이 기록을 해당 캠페인이라고 설명하지 마십시오.
부모 블롭 규칙은 assert의 강도를 점수화하지 않습니다. 이전에 커밋된 속성이라도 거의 동어반복적(tautological)일 수 있습니다. 의존하기 전에 부모 파일을 읽으십시오. 이 검사는 현재 패치가 자체 증인(witness)을 발행하는 것을 막는 것만을 합니다.
제안된 검사기는 pytest --collect-only를 실행하지 않습니다. 잘못 구성된 test_id는 형태 검사를 통과할 수 있습니다. 해당 보장이 필요하면 작업에 컬렉션(collection)을 추가하십시오. 깊이 1의 얕은 클론(Shallow clone)에는 해결할 HEAD^ 블롭이 없습니다. 부모를 먼저 가져오거나, 작업이 폐쇄적으로 실패합니다. 이는 의도된 기본값입니다.
이 게이트를 사용해서는 안 되는 경우
부모 오라클이 존재하지 않고 별도의 커밋으로 이를 발행할 계획이 없다면 워크플로우를 건너뛰십시오. 매번 실행될 때 재작성되는 스냅샷 스위트(Snapshot suites)는 oracle_blob이 핀 할 대상이 없습니다.
결제(payment), 인증(authentication), 데이터 손실(data-loss) 패치에서는 건너뜁니다(Skip it). 불일치가 진짜이더라도, 프리즈 기록(freeze record)은 잘못된 경로입니다. 테스트는 빨간색으로 남겨두고 제품 코드(product code)를 변경하세요.
테스트가 워커 간에 상태를 누설할 때도 건너뜁니다(Skip it). 공유 오염(Shared contamination)은 피처(fixture) 해시가 일정하게 유지되는 동안 결과를 뒤집을 수 있으며, 이는 불일치를 거짓 증인으로 만듭니다. 기록하기 전에 테스트를 격리하세요(Isolate the test).
검토자들이 record.json 파일을 열지 않을 때도 건너뜁니다(Skip it). 읽히지 않은 프리즈 파일은 추가 필드를 가진 건너뛰기 주석과 같습니다. 체커(checker)는 해당 읽기를 제공할 수 없습니다.
검토자들이 재계산하는 것
.freeze/order_snapshot.json에서 피처 다이제스트(fixture digest)를 재계산하고 두 불일치 항목 모두와 비교하세요. oracle_path가 git diff --name-only HEAD^ HEAD에 없는지 확인하세요. 부모 속성(parent property)을 열고, 프로덕션 디프(production diff)를 읽지 않고 설명할 수 있는 관계를 어설션이 지정하는지 확인하세요.
불일치와 부모 오라클 모두 유효하다면, 해당 단일 테스트 ID를 격리하고 프리즈를 삭제하는 후속 조치를 제출하세요. 둘 중 어느 하나라도 확인에 실패하면, 테스트는 빨간색으로 남겨두세요. 체커가 형태(shape)를 강제합니다. 하지만 인변치(invariant)가 피처와 일치하는지는 여전히 사람이 결정해야 합니다.