두 번째 호스트가 드리프트를 측정할 수 있지만, 프리즈를 열 수는 없다
요약
이 글은 소프트웨어 테스트와 CI/CD 파이프라인에서의 '불변성(invariant)'과 '드리프트 측정'에 대한 심층적인 연구 내용을 다룹니다. 특히, 플래키한 테스트 결과나 패치 실패를 정확하게 기록하고 추적하기 위한 새로운 워크플로우와 데이터 구조를 제안합니다.
핵심 포인트
- 테스트 결과를 이름이 아닌 동등성 클래스로 취급해야 합니다.
- 실패의 원인을 명확히 하기 위해 여러 '핀(pin)'을 조합하여 고유한 실행 기록을 생성해야 합니다.
- 피처 에포크는 mtime 대신 바이트 기반으로 구축되어야 신뢰성이 높아집니다.
- 오직 소유 호스트에서 안정적인 빨간색 결과만이 유효한 '프리즈' 입력값입니다.
flake 프리즈는 오직 하나의 호스트에서, 하나의 피처 에포크(fixture epoch) 하에, 그리고 패치가 수정하지 않은 오라클을 기준으로만 유효합니다. 두 번째 머신은 드리프트(drift)를 측정할 수 있지만, 그 프리즈를 열 수는 없습니다.
Agent의 패치 실패는 빨간 CI 라인에서 동일하게 보이는 방식으로 발생할 수 있습니다. 불변성(invariant)이 거짓일 수 있고, 피처 잠금(fixture lock)이 오래되었을 수 있습니다. 소유 호스트가 스스로와 의견이 다를 수도 있고, 두 번째 호스트가 첫 번째 호스트와 의견이 다를 수도 있습니다.
이 네 가지 결과는 서로 다른 기록을 필요로 합니다. 이들을 하나의 프리즈로 통합하면 다음 패치가 발견할 결함을 숨기게 됩니다. 아래 워크플로우는 제안입니다. 여기에 제시된 코드 조각들은 라이브 스위트의 보고서가 아니라 실행되지 않은 명세 코드로, 어떤 통과율, 할당량 또는 하드웨어 수치도 주장하지 않습니다.
클래스가 존재하기 위해 일치해야 하는 것들
실행을 테스트 이름이 아닌 동등성 클래스(equivalence class)로 취급하세요. 이름은 같지만 핀(pin)에서 차이가 나는 두 실패는 서로 다른 실험입니다. 첫 리플레이 전에 핀들을 기록하고, 그중 어느 것도 누락된 실행은 분류하는 것을 거부해야 합니다.
핀 세트는 작습니다.
patch_id: 브랜치 이름이 아닌 diff의 해시 값입니다.oracle_hash: 속성 모듈(property module)의 해시 값입니다. 패치는 이를 이동할 수 없습니다.fixture_epoch: 모든 피처 파일과 해당 파일 자체의 해시 값을 나열하는 매니페스트(manifest)의 해시 값입니다.seed: 속성 검사가 실제로 사용한 정수입니다.host_id: 이후 프리즈를 소유할 수 있는 머신 이름을 지정합니다.replay_budget: 실행 전에 선택됩니다. 빨간 결과가 이를 증가시키지는 않습니다.
핀 중 하나라도 변경되면 새로운 행을 시작해야 합니다. 이전 것을 수정해서는 안 됩니다.
에포크를 디렉터리 mtime(수정 시간)이 아닌 바이트에서 구축하세요. 러너는 동일한 파일을 언패킹하면서도 타임스탬프를 변경할 수 있습니다. 상대 POSIX 경로를 정렬하고, path digest 라인을 작성하여 그 텍스트를 해시하고 다이제스트를 저장해야 합니다. 만약 시계, 임시 경로 또는 디렉터리 순서가 결과에 영향을 줄 수 있다면, 해당 입력을 매니페스트에 넣거나 실패라고 부르는 것을 중단해야 합니다.
여섯 개의 클래스, 그리고 오직 하나만 프리즈할 수 있다
아래 표를 사용하여 관측치(observation)에 점수를 매기세요. 'Freeze' 열이 정책(policy)이며, 나머지 열들은 장부 기록(bookkeeping)입니다.
| Observation | Class | Freeze on owner host | Next action |
| :--- | :--- | :--- |
| Patch changes the oracle hash | oracle_tamper | No | Reject the diff. Restore the oracle file. |
| ... |
A property failure는 나중에 실행이 녹색(green)으로 진행되더라도 여전히 속성 실패(property failure)입니다. 그 녹색 실행은 다른 질문이며, 카운터 예시(counterexample)를 플레이크(flake)로 세탁하지 않습니다. 오너 호스트에서 안정적인 빨간색(red) 결과는 오라클이 유지되었을 때의 회귀(regression)입니다. 예산(budget) 이후에 발생하는 혼합된 결과만이 유일한 프리즈 입력값입니다.
호스트 B는 측정치이며, 호스트 B가 다른 CI 러너가 아니라 무료 서버인 경우에도 마찬가지입니다. 이진 값은 오너 원장(owner ledger)에서 투표를 받지 않습니다.
공개 정보: 본 문서는 MonkeyCode의 제품 홍보 활동의 일환으로 작성되었습니다.
MonkeyCode의 무료 모델 접근성은 여기서 후보 diff의 출처로서만 중요합니다. 무료 서버 옵션은 오직 가능한 호스트 B로서만 중요합니다. 어느 주장도 모델 이름, 할당량(quota), 또는 성능 수치를 표에 추가하지 않으며, 이들 중 어떤 것도 필수적이지 않습니다.
무료 서버에서의 녹색 프로브는 오너 호스트의 속성 실패를 취소하지 못합니다. 빨간색 프로브가 오너 호스트의 프리즈를 열지 못합니다.
7단계
- 오라클 경로(oracle path)를 해시합니다. diff가 이를 건드리면
oracle_tamper를 방출하고 중단합니다. - 피처 루트(fixture root)에서 피처 매니페스트(fixture manifest)를 빌드하고 잠긴 에포크(locked epoch)와 비교합니다.
- 저장된 시드(seed)로 오너 호스트에서 속성 검사(property checks)를 실행합니다. 실패하면
property_fail을 방출하고 중단합니다. - 이름이 지정된 회귀(named regression)를 한 번 오너 호스트에서 실행합니다. 통과하면 프리즈 경로를 건너뜁니다.
- 실패 시, 예산이 소진되고 모든 결과가 기록될 때까지 오너 호스트에서 재실행합니다.
- 그 후에만, 클래스가 이미
property_fail또는fixture_drift가 아니라면 동일한patch_id, 시드, 및 피처 에포크를 호스트 B에 제출합니다. - 표에서 하나의 클래스를 방출합니다. 이를 영구 저장(persist)합니다. 원장 쓰기 후에는 해당 클래스를 수정하지 마십시오.
Step 6은 의도적으로 지연됩니다. 미완성된 소유자-호스트 실행(owner-host run)으로는 테이블을 변경할 수 없으므로, 호스트 B를 일찍 호출하는 것은 단순히 좌석만 소비합니다. 만약 그 좌석이 무료 서버라면, 프로브는 Step 5가 완전한 벡터를 가질 때까지 비활성화 상태로 유지하세요.
이미 존재하는 프리즈(freeze)의 만료는 별도의 정책입니다. 이 게이트는 새로운 행이 삽입될 수 있는지 여부와 어느 호스트에서 가능한지만 결정합니다.
분류기 (Classifier)
이 모듈은 검토 가능한 제안서입니다. 표준 라이브러리 외에는 아무것도 가져오지 않습니다. 테이블을 인코딩합니다. 새로운 불변량(invariant)을 발견하지는 못합니다.
import hashlib
from pathlib import Path
...
freeze_allowed는 한 줄의 정책입니다. 환경 드리프트(environment drift)가 거짓을 반환합니다. 더 저렴한 호스트가 더 저렴하다는 이유로 소유자가 되지는 않습니다. 호스트 B는 혼합 결과 분기(mixed-result branch) 내부에서 참조되지 않습니다. 소유자 호스트에서의 불안정성만으로도 flake_candidate가 충분하며, 호스트 B는 해당 행을 거부하거나 상속받을 수 없습니다.
세 가지 예시는 플레이스홀더 ID를 사용합니다. 이는 측정 실행(measured runs)이 아닙니다.
소유자 결과값 [False, False, False], 예산 3, 속성 통과, 호스트 B True. 클래스는 environment입니다. 소유자 호스트는 안정적입니다. 테스트 이름을 프리징하면 다른 호스트에서는 재현되지 않은 실패에 대한 나중 패치를 막고, 드리프트를 숨기게 됩니다.
소유자 결과값 [False, True, False], 예산 3, 호스트 B False. 클래스는 flake_candidate입니다. 프리즈가 열릴 수 있으며, 오직 소유자 host_id에 대해서만 가능합니다. 호스트 B의 실패는 해당 행을 확장하지 않습니다.
소유자 결과값 [False], 예산 3. 클래스는 replay_incomplete입니다. 하나의 빨간색 결과가 프리징할 수 있는 클래스는 아닙니다. 먼저 벡터를 완성하고, 이 행에 대해 아직 호스트 B를 호출하지 마세요.
명세 검사 (Specification checks)
함수를 테이블을 재진술하는 단언(assertions)과 쌍으로 만드세요. 이는 제안서의 일부입니다. 나중에 수정하여 두 번째 호스트가 소유자 원장(owner ledger)을 열도록 허용하면 실패합니다.
def test_cross_host_split_is_not_a_freeze():
kind = classify({
호스트 체크(Host check)는 검토자들이 건너뛰는 단언(assertion)입니다. `ci-a`에서 발생한 플레이크(flake)는 두 호스트가 모두 실패를 목격했더라도 호스트 B에 의해 쓰여질 수 없어야 합니다. 호스트의 신원(identity)은 키의 일부입니다.
임시 값(placeholder values)을 사용한 최소한의 영속 기록(persisted record)은 다음과 같습니다.
{
"patch_id": "example",
"oracle_hash": "aa",
...
## 명령어 순서 (Command order)
테스트 러너가 시작되기 전에 오라클 경로(oracle path)를 확인하세요. 테스트 파일 내의 단언은 너무 늦습니다. 왜냐하면 해당 패치(patch)가 그 단언을 약화시켰을 수 있기 때문입니다.
git diff --name-only "$BASE"..HEAD -- tests/properties/invariants.py
python -m gate.classify --record runs/patch.json --owner-host ci-a
첫 번째 명령어는 아무것도 출력해서는 안 됩니다. 만약 오라클 경로를 출력하면 `oracle_tamper`를 발생시키고 스위트(suite)를 시작하지 마십시오. 두 번째 명령어는 관측 파일(observation file)을 읽어 하나의 클래스를 출력합니다. `pass` 외의 모든 클래스에 대해서는 0이 아닌 값으로 종료해야 합니다.
호스트 B는 동일한 핀과 빈 원장(ledger)을 받습니다. 아래 플래그들은 벤더 CLI가 아니라 제안된 로컬 프로브(local probe)에 속합니다.
python -m gate.probe
--patch-id "$PATCH_ID"
--fixture-epoch "$EPOCH"
...
`--ledger none`이 유용한 제약 조건입니다. 호스트 B는 true 또는 false를 반환할 수 있습니다. 하지만 프리즈(freeze) 행을 추가해서는 안 됩니다. `--host-id` 플래그는 이미 운영하고 있는 두 번째 머신에 지정하십시오. 무료 서버 좌석이 그 역할을 채울 수 있습니다. 그것이 병합 게이트(merge gate)가 되는 것은 아닙니다.
에이전트(agent)를 오라클 경로에서 두 곳 모두 멀리 떨어뜨려 놓으세요. 작업 지침(task instruction)에 그렇게 명시하고, 해시 비교로 이를 강제하십시오. 지침 자체가 게이트는 아닙니다. 해시가 게이트입니다.
## 제한 사항 (Limitations)
클래시파이어(classifier)는 패치가 정확하다는 것을 증명하지 않습니다. 단지 진술된 속성(stated properties)이 유지되었고, 명시된 체크가 한 호스트에서 안정적이었다는 것만을 보여줄 수 있습니다. 언급되지 않은 동작은 여전히 잘못일 수 있습니다. 그것은 커버리지 격차(coverage gap)이며, 빨간 이름(red name)을 프리즈할 이유가 아닙니다.
동일한 호스트 간의 불일치도 아직 나열되지 않은 피처(fixture)일 수 있습니다. 벽시계(Wall clocks), 임시 경로(temp paths), 디렉토리 순서 등은 매니페스트(manifest) 밖에 위치하면 잘못된 `flake_candidate` 행을 생성할 것입니다. 예산을 넓히기 전에 매니페스트를 넓히십시오.
예제에서 예산(budget)은 보정된 플레이크율이 아니라 정책 정수값입니다. 이 문서는 에이전트 패치가 얼마나 자주 플레이크하는지 추정하지 않습니다. 측정된 기준선(baseline)이 있다면 그것을 사용하십시오. 없다면, 해당 정수값을 작게 유지하고 안정적인 실패가 `회귀(regression)`로 간주되기까지 그 값만큼의 소유자-호스트 실행 횟수를 비용으로 지불할 것임을 받아들이십시오.
여기서 크로스-호스트 비교는 부울(boolean)입니다. 두 호스트가 서로 다른 단언(assertion)에서 실패하여 일치하는 것처럼 보일 수 있습니다. 더 엄격한 프로브가 필요하다면 예외 유형과 단언 ID를 저장하십시오. 그 확장 기능 역시 스스로 프리즈(freeze)를 열어서는 안 됩니다.
## 누가 건너뛰어야 하는가
소유자 호스트를 하나 지정할 수 없다면 워크플로우를 건너뛰십시오. 안정적인 ID가 없는 임시 실행기 풀은 거의 모든 분할을 `환경(environment)`으로 표시할 것이며, 프리즈 경로는 절대 열리지 않을 것입니다. 그것이 올바른 정책일 수 있습니다. 풀 자체가 하나의 기계인 척하는 대신 '풀된 호스트에서는 프리즈 없음'이라고 작성하십시오.
패치가 수정할 수 없는 속성 모듈(property module)을 스위트가 가지고 있지 않다면 건너뛰십시오. 이름만 지정된 테스트만으로는 변조와 회귀를 분리할 수 없습니다. 호스트 B를 추가하기 전에 오라클(oracle)을 추가하십시오.
두 번째 호스트가 실행되도록 허용되기 전에 공급업체 할당량, 하드웨어 사양 또는 공개 벤치마크가 필요하다면 건너뛰십시오. 그것들은 테이블의 입력값이 아니며, 이 메모는 그것들을 제공하지 않습니다.
관찰(observation) JSON을 풀 리퀘스트에 넣으십시오. 클래스가 `environment`라면 두 호스트 ID를 연결하고 프리즈 원장(freeze ledger)은 그대로 두십시오. 만약 무료 MonkeyCode 서버가 이미 작업대에 있다면, 그 좌석을 빈 원장을 가진 호스트 B로 사용하십시오. 추가된 좌석이 아니라 소유자 호스트가 여전히 프리즈의 존재 여부를 결정합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기