把 200 行和 12 个步骤缩到两行四步
한국어 원문으로 표시합니다.
목표
고객이 보내 온 장애 꾸러미(처리기 하나, 피드 200줄, 재현 절차 12단계)를 최소 재현 사례로 줄인다. 먼저 "아직도 같은 실패인가" 를 기계가 판정하게 만들고, 그 판정기로만 줄이고, 남은 것 하나를 빼면 재현이 멈춘다는 것까지 증명한다.
왜 중요한가
고객이 적어 준 재현 절차는 대개 그 사람이 그날 한 일 전부다. 그 전부를 안고 원인을 찾으면 봐야 할 조합이 폭발한다. 그렇다고 줄이기부터 시작하면 더 위험하다 — 한 줄을 뺐더니 프로그램이 다른 오류로 죽고, 그것도 오류니까 아직 재현된다고 착각하게 된다. 그렇게 나온 사례는 우리가 쫓던 결함과 상관없는 다른 결함을 재현한다. 그래서 순서가 있다. 판정기를 먼저 만들고, 그 판정기로만 줄인다. 판정기는 종료 코드와 그 실패에만 나오는 서명 문자열을 함께 본다. 실행 시각이나 임시 파일 이름처럼 실행마다 달라지는 것은 판정에 넣지 않는다. 줄이는 일은 기계가 한다. 한 줄씩 빼 보면 줄 수만큼 판정기를 부르지만, 후보를 덩어리로 갈라 보는 델타 디버깅(ddmin)은 그 횟수를 줄 수의 로그에 가깝게 떨어뜨린다. 채점기는 여러분이 적어 낸 숫자를 믿지 않는다. 오라클에는 채점기가 만든 여러 후보를 넣어 보고, 축소기에는 매번 다른 자리에 답을 숨긴 256줄짜리 입력을 주어 결과와 판정기 호출 횟수를 함께 잰다.
단계
- /root/mre/gen_case.py 를 만들어 실행해 /root/mre/app/ingest.py, /root/mre/input.txt(200줄), /root/mre/repro/steps.txt(12단계)를 펼치세요.
- /root/mre/oracle.py 를 만드세요.
--input으로 받은 후보가 우리가 쫓는 실패를 내면 종료 코드 0, 아니면 1 입니다. 다른 오류는 재현이 아닙니다. - 오라클로 피드를 줄여 /root/mre/min_input.txt 를 만드세요. 남은 줄은 원본의 줄이고 원본 차례를 지킵니다.
- /root/mre/minimality.json 에 남은 줄마다 그것을 뺐을 때 재현되는지를 적어 1-최소임을 증명하세요.
- /root/mre/shrink.py 에 델타 디버깅(ddmin)을 구현하세요.
--input --oracle --out을 받고, 판정기를 부르는 횟수가 줄 수에 비례하지 않아야 합니다. - 재현 절차 12단계를 줄여 /root/mre/min_steps.txt 를 만드세요. 남은 단계는 원본 줄 그대로이고 차례를 지킵니다.
- 값을 바꿔도 재현되는지 확인해 /root/mre/generalize.json 과 변형 파일 셋을 남기세요.
- /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 원 논문 페이지와 Debugging Book 의 델타 디버깅 장이 알고리즘을, 파이썬 subprocess 문서가 판정기를 부르는 방법을 설명합니다.
장애 꾸러미 펼치기
/root/mre/gen_case.py 를 만들어 실행해 /root/mre/app/ingest.py, /root/mre/input.txt(200줄), /root/mre/repro/steps.txt(12단계)를 만드세요.
현장에서 받는 것은 프로그램과 데이터와 절차 셋뿐입니다. 펼친 뒤에는 절차를 한 번 그대로 돌려 실패를 눈으로 보세요 — MRE_WORK 를 아직 없는 디렉터리로 잡고 돌립니다.
같은 실패인지 기계가 판정하게 하기
/root/mre/oracle.py 를 만드세요. --input 으로 받은 후보가 우리가 쫓는 실패를 내면 종료 코드 0, 아니면 1 입니다. 설정 파일은 오라클이 스스로 만들고, 종료 코드와 서명 문자열을 함께 봅니다.
0 이 아닌 종료 코드를 전부 재현으로 보면, 줄이다가 전혀 다른 오류로 넘어간 것을 눈치채지 못합니다. 이 처리기는 설정을 못 읽으면 2, 우리가 쫓는 결함이면 3, qty 줄이 하나도 없으면 4 로 끝납니다. 표준오류의 서명 문자열도 함께 보세요.
피드를 줄이기
오라클로 피드를 줄여 /root/mre/min_input.txt 를 만드세요. 남은 줄은 전부 원본 input.txt 의 줄이어야 하고 원본 차례를 지켜야 하며, 어느 한 줄을 빼면 재현이 멈춰야 합니다.
이 단계는 손으로 해도 됩니다. 한 줄씩 빼 보면서 재현이 유지되는 줄만 남기는 방법이 가장 단순합니다. 200번쯤 판정기를 부르게 되는데, 그 느림이 5단계에서 ddmin 을 만드는 이유입니다.
남은 줄이 왜 다 필요한지 증명하기
/root/mre/minimality.json 에 min_input.txt 의 줄마다 그 줄을 뺐을 때 재현되는지를 적으세요. 모든 줄에서 재현이 멈추면 minimal 은 true 입니다. lines 는 min_input.txt 의 차례 그대로입니다.
1-최소는 '남은 것 중 어느 하나를 빼도 재현이 멈춘다' 는 뜻입니다. 이 증명이 없으면 '왜 이 줄이 필요한가' 라는 질문에 답할 수 없고, 받는 쪽도 그냥 믿어야 합니다. 판정은 오라클에게 맡기고 결과만 적으세요.
줄이기를 자동화하기
/root/mre/shrink.py 에 델타 디버깅(ddmin)을 구현하세요. --input --oracle --out 을 받고, 원본이 재현되는지 먼저 확인한 뒤 줄이며, 판정기 호출 횟수가 줄 수에 비례하지 않아야 합니다.
한 줄씩 빼는 방법은 줄 수만큼 판정기를 부릅니다. ddmin 은 후보를 n 조각으로 갈라 조각 하나만으로 재현되는지 먼저 보고, 안 되면 조각 하나를 뺀 나머지로 재현되는지 봅니다. 둘 다 아니면 더 잘게 갈라 반복합니다. 채점기는 256줄짜리 입력에 여러분의 축소기를 돌리고 판정기를 몇 번 불렀는지 셉니다.
재현 절차도 줄이기
재현 절차 12단계를 줄여 /root/mre/min_steps.txt 를 만드세요. 남은 단계는 steps.txt 의 줄 그대로이고 원본 차례를 지켜야 하며, 어느 한 단계를 빼면 재현이 멈춰야 합니다.
절차를 시험할 때마다 MRE_WORK 를 아직 없는 디렉터리로 새로 잡으세요. 앞 실행이 남긴 설정 파일이 있으면 '설정을 만드는 단계' 를 빼도 재현되어, 그 단계가 필요 없다는 잘못된 결론이 나옵니다. 재현 판정은 표준오류에 서명 문자열이 나오는지로 합니다.
값이 아니라 구조인지 확인하기
최소 사례에서 값을 바꾼 판과 구조를 바꾼 판을 만들어 /root/mre/generalize.json 에 적으세요. 변형은 셋이고, 값만 바꾼 /root/mre/min_input_generic.txt 는 재현되어야 하며 나머지 둘은 재현되지 않아야 합니다. 세 파일 모두 실제로 남기세요.
남은 두 줄이 우연한 값 때문인지, 구조 때문인지를 가르는 단계입니다. 숫자를 다른 숫자로 바꿔도 재현되면 원인은 그 숫자가 아닙니다. 반대로 쉼표를 마침표로 바꾸거나 로케일을 바꾸면 멈추는 것을 보이면, 원인이 두 조건의 조합이라는 것이 증명됩니다.
무엇을 재현이라고 불렀는지 적기
/root/mre/mre_report.md 에 ## 무엇을 재현이라고 불렀나 ## 얼마나 줄였나 ## 남은 줄이 왜 다 필요한가 ## 남은 가정 네 절로 적으세요. 원본과 최소 사례의 줄 수와 단계 수, 오라클이 보는 종료 코드와 서명 문자열이 모두 나와야 합니다.
받는 쪽은 입력 두 줄만으로는 '이게 왜 문제인가' 부터 묻습니다. 무엇을 같은 실패로 보았는지 먼저 적고, 그다음 얼마나 줄였는지와 왜 더 못 줄이는지를 적으세요. 마지막 절에는 값을 바꿔도 재현된다는 사실과 무엇을 바꾸면 멈추는지를 적습니다.