낯선 시스템 앞에서 · 최소 재현 사례로 줄이기 · 실습
200줄과 12단계를 두 줄과 네 단계로
목표
고객이 보내 온 장애 꾸러미(처리기 하나, 피드 200줄, 재현 절차 12단계)를 최소 재현 사례로 줄인다. 먼저 "아직도 같은 실패인가" 를 기계가 판정하게 만들고, 그 판정기로만 줄이고, 남은 것 하나를 빼면 재현이 멈춘다는 것까지 증명한다.
왜 중요한가
고객이 적어 준 재현 절차는 대개 그 사람이 그날 한 일 전부다. 그 전부를 안고 원인을 찾으면 봐야 할 조합이 폭발한다. 그렇다고 줄이기부터 시작하면 더 위험하다 — 한 줄을 뺐더니 프로그램이 다른 오류로 죽고, 그것도 오류니까 아직 재현된다고 착각하게 된다. 그렇게 나온 사례는 우리가 쫓던 결함과 상관없는 다른 결함을 재현한다.
그래서 순서가 있다. 판정기를 먼저 만들고, 그 판정기로만 줄인다. 판정기는 종료 코드와 그 실패에만 나오는 서명 문자열을 함께 본다. 실행 시각이나 임시 파일 이름처럼 실행마다 달라지는 것은 판정에 넣지 않는다.
줄이는 일은 기계가 한다. 한 줄씩 빼 보면 줄 수만큼 판정기를 부르지만, 후보를 덩어리로 갈라 보는 델타 디버깅(ddmin)은 그 횟수를 줄 수의 로그에 가깝게 떨어뜨린다.
채점기는 여러분이 적어 낸 숫자를 믿지 않는다. 오라클에는 채점기가 만든 여러 후보를 넣어 보고, 축소기에는 매번 다른 자리에 답을 숨긴 256줄짜리 입력을 주어 결과와 판정기 호출 횟수를 함께 잰다.
단계
1. /root/mre/gen_case.py 를 만들어 실행해 /root/mre/app/ingest.py, /root/mre/input.txt(200줄), /root/mre/repro/steps.txt(12단계)를 펼치세요.
2. /root/mre/oracle.py 를 만드세요. --input 으로 받은 후보가 우리가 쫓는 실패를 내면 종료 코드 0, 아니면 1 입니다. 다른 오류는 재현이 아닙니다.
3. 오라클로 피드를 줄여 /root/mre/min_input.txt 를 만드세요. 남은 줄은 원본의 줄이고 원본 차례를 지킵니다.
4. /root/mre/minimality.json 에 남은 줄마다 그것을 뺐을 때 재현되는지를 적어 1-최소임을 증명하세요.
5. /root/mre/shrink.py 에 델타 디버깅(ddmin)을 구현하세요. --input --oracle --out 을 받고, 판정기를 부르는 횟수가 줄 수에 비례하지 않아야 합니다.
6. 재현 절차 12단계를 줄여 /root/mre/min_steps.txt 를 만드세요. 남은 단계는 원본 줄 그대로이고 차례를 지킵니다.
7. 값을 바꿔도 재현되는지 확인해 /root/mre/generalize.json 과 변형 파일 셋을 남기세요.
8. /root/mre/mre_report.md 에 네 절로 보고하세요.
참고
- 처리기 실행 계약:
python3 /root/mre/app/ingest.py --input <피드> --config <설정 JSON>. 정상은 0, 설정을 읽을 수 없으면 2, 우리가 쫓는 결함은 3, qty 줄이 하나도 없으면 4 입니다. 설정은{"strict": true}꼴입니다. - 오라클 실행 계약:
python3 /root/mre/oracle.py --input <후보 파일>은 재현되면 0, 아니면 1 로 끝나고 한 줄을 표준출력에 냅니다. 설정 파일은 오라클이 스스로 만듭니다. - 축소기 실행 계약:
python3 /root/mre/shrink.py --input <원본> --oracle <오라클 경로> --out <결과 파일>은 줄인 결과를 out 에 쓰고 한 줄을 표준출력에 냅니다. 오라클은python3 <오라클> --input <후보>꼴로 부릅니다. - 절차 시험:
repro/steps.txt의 줄들은 셸 명령이고, 환경 변수 MRE_WORK 가 가리키는 아직 없는 디렉터리를 작업 자리로 씁니다. 고른 단계들을 한 셸에서 차례로 돌리고, 표준오류에 서명이 나오면 재현입니다. 시험할 때마다 MRE_WORK 를 새로 잡으세요. - minimality.json:
{"minimal": true|false, "lines": [{"line": 원본 줄, "removed_reproduces": true|false}]}. lines 는 min_input.txt 의 차례 그대로입니다. - generalize.json:
{"variants": [{"file": 절대 경로, "change": 무엇을 바꿨는지, "reproduces": true|false}]}. 변형은 셋이고 그중 재현되는 것은 하나입니다. - 흔한 실수: 종료 코드가 0 이 아니면 재현이라고 보기, 앞 실행이 남긴 파일이 있는 자리에서 절차를 시험하기, 축소기가 원본이 재현되는지부터 확인하지 않기.
- 1-최소는 "남은 것 중 어느 하나를 빼도 재현이 멈춘다" 는 뜻이지 "가장 작다" 는 뜻이 아닙니다. 전혀 다른 더 작은 조합이 있을 수도 있습니다.
- 판정기 호출 상한 160회(입력 256줄 기준)와 변형 세 개라는 숫자는 이 실습의 가정입니다. 알고리즘이 정해 주는 값이 아니라 채점을 위해 정한 값입니다.
- 참고 문서: [ddmin 원 논문 페이지](https://www.st.cs.uni-saarland.de/papers/tse2002/)와 [Debugging Book 의 델타 디버깅 장](https://www.debuggingbook.org/html/DeltaDebugger.html)이 알고리즘을, [파이썬 subprocess 문서](https://docs.python.org/3/library/subprocess.html)가 판정기를 부르는 방법을 설명합니다.
단계 8개
- 장애 꾸러미 펼치기
- 같은 실패인지 기계가 판정하게 하기
- 피드를 줄이기
- 남은 줄이 왜 다 필요한지 증명하기
- 줄이기를 자동화하기
- 재현 절차도 줄이기
- 값이 아니라 구조인지 확인하기
- 무엇을 재현이라고 불렀는지 적기