200行と12手順を、2行と4手順に
目標
顧客が送ってきた障害資料一式(処理プログラム1つ、フィード200行、再現手順12ステップ)を、最小再現例に縮めます。まず「まだ同じ失敗なのか」を機械に判定させる判定ツールを作り、その判定ツールだけで縮め、残ったもののどれか1つを抜くと再現が止まることまで証明します。
なぜ重要なのか
顧客が書いてくれた再現手順は、たいていその人がその日にしたことのすべてです。そのすべてを抱えて原因を探すと、見るべき組み合わせが爆発します。かといって、縮めることから始めると、もっと危険です。1行を抜いたらプログラムが別のエラーで落ち、それもエラーなので、まだ再現されていると勘違いしてしまいます。そうしてできた例は、私たちが追っていた欠陥とは無関係な別の欠陥を再現します。 そのため、順序があります。判定ツールを先に作り、その判定ツールだけで縮めます。判定ツールは、終了コードと、その失敗にだけ出るシグネチャ文字列を一緒に見ます。実行時刻や一時ファイル名のように実行ごとに変わるものは、判定に入れません。 縮める作業は機械が行います。1行ずつ抜いてみると、行数と同じ回数だけ判定ツールを呼びますが、候補を塊に分けて見るデルタデバッギング(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と変形ファイル3つを残してください。
- /root/mre/mre_report.mdに4つの節で報告してください。
参考
- 処理プログラムの実行契約:
python3 /root/mre/app/ingest.py --input <피드> --config <설정 JSON>(プレースホルダーはフィードと設定JSONのパスです)。正常は0、設定を読み込めなければ2、私たちが追っている欠陥は3、qtyの行が1つもなければ4です。設定は{"strict": true}の形です。 - オラクルの実行契約:
python3 /root/mre/oracle.py --input <후보 파일>(プレースホルダーは候補ファイルのパスです)は、再現されれば0、そうでなければ1で終わり、1行を標準出力に出します。設定ファイルはオラクルが自分で作ります。 - 縮小ツールの実行契約:
python3 /root/mre/shrink.py --input <원본> --oracle <오라클 경로> --out <결과 파일>(プレースホルダーは原本、オラクル、結果ファイルのパスです)は、縮めた結果をoutに書いて、1行を標準出力に出します。オラクルはpython3 <오라클> --input <후보>の形で呼びます(プレースホルダーはオラクルと候補のパスです)。 - 手順の試行:
repro/steps.txtの行はシェルコマンドで、環境変数MRE_WORKが指すまだ存在しないディレクトリを作業場所として使います。選んだステップを1つのシェルで順に実行し、標準エラー出力にシグネチャが出れば再現です。試すたびに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}]}(プレースホルダーは順に、絶対パスと、何を変えたかという説明です)。変形は3つで、そのうち再現されるのは1つです。 - よくあるミス: 終了コードが0でなければ再現とみなすこと、前の実行が残したファイルがある場所で手順を試すこと、縮小ツールが原本が再現されるかをまず確認しないこと。
- 1-最小は「残ったもののうちどれか1つを抜くと再現が止まる」という意味であって、「最も小さい」という意味ではありません。まったく別のもっと小さい組み合わせがあるかもしれません。
- 判定ツールの呼び出し上限160回(入力256行の場合)と、変形3つという数字は、このラボの前提です。アルゴリズムが決める値ではなく、採点のために決めた値です。
- 参考ドキュメント: ddminの元論文のページとDebugging Bookのデルタデバッギングの章がアルゴリズムを、Python subprocessドキュメントが判定ツールを呼ぶ方法を説明しています。
障害資料一式を広げる
/root/mre/gen_case.pyを作成して実行し、/root/mre/app/ingest.py、/root/mre/input.txt(200行)、/root/mre/repro/steps.txt(12ステップ)を作成してください。
現場で受け取るのは、プログラムとデータと手順の3つだけです。広げたら、手順を一度そのまま実行して、失敗を目で見てください。MRE_WORKをまだ存在しないディレクトリに設定して実行します。
同じ失敗かどうかを機械に判定させる
/root/mre/oracle.pyを作成してください。--inputで受け取った候補が追っている失敗を起こせば終了コード0、そうでなければ1です。設定ファイルはオラクルが自分で作り、終了コードとシグネチャ文字列を一緒に見ます。
0以外の終了コードをすべて再現とみなすと、縮めている途中でまったく別のエラーに移ったことに気づけません。このプログラムは、設定を読み込めなければ2、私たちが追っている欠陥なら3、qtyの行が1つもなければ4で終わります。標準エラー出力のシグネチャ文字列も一緒に見てください。
フィードを縮める
オラクルでフィードを縮めて、/root/mre/min_input.txtを作成してください。残る行はすべて原本のinput.txtの行で、原本の順序を守り、どれか1行を抜くと再現が止まる必要があります。
このステップは手作業でもかまいません。1行ずつ抜いてみて、再現が保たれる行だけを残す方法が最も単純です。判定ツールを200回ほど呼ぶことになりますが、その遅さが、ステップ5でddminを作る理由です。
残った行がなぜすべて必要なのかを証明する
/root/mre/minimality.jsonに、min_input.txtの行ごとに、その行を抜いたときに再現されるかどうかを書いてください。すべての行で再現が止まれば、minimalはtrueです。linesはmin_input.txtの順序そのままです。
1-最小とは、「残ったもののうちどれか1つを抜くと再現が止まる」という意味です。この証明がなければ、「なぜこの行が必要なのか」という質問に答えられず、受け取る側もただ信じるしかありません。判定はオラクルに任せ、結果だけを書いてください。
縮める作業を自動化する
/root/mre/shrink.pyにデルタデバッギング(ddmin)を実装してください。--input --oracle --outを受け取り、原本が再現されるかをまず確認してから縮め、判定ツールの呼び出し回数が行数に比例しないようにします。
1行ずつ抜く方法は、行数と同じ回数だけ判定ツールを呼びます。ddminは、候補をn個に分けて、1つの断片だけで再現されるかをまず見て、だめなら1つの断片を抜いた残りで再現されるかを見ます。どちらもだめなら、さらに細かく分けて繰り返します。採点ツールは、256行の入力で作成した縮小ツールを動かし、判定ツールを何回呼んだかを数えます。
再現手順も縮める
再現手順12ステップを縮めて、/root/mre/min_steps.txtを作成してください。残るステップはsteps.txtの行そのままで、原本の順序を守り、どれか1つのステップを抜くと再現が止まる必要があります。
手順を試すたびに、MRE_WORKをまだ存在しないディレクトリとして新しく用意してください。前の実行が残した設定ファイルがあると、「設定を作るステップ」を抜いても再現されてしまい、そのステップは不要だという誤った結論が出ます。再現の判定は、標準エラー出力にシグネチャ文字列が出るかどうかで行います。
値ではなく構造かを確認する
最小例から、値を変えたバージョンと構造を変えたバージョンを作り、/root/mre/generalize.jsonに書いてください。変形は3つで、値だけを変えたもの(/root/mre/min_input_generic.txt)は再現される必要があり、残りの2つは再現されない必要があります。3つのファイルをすべて、実際に残してください。
残った2行が偶然の値のせいなのか、構造のせいなのかを分けるステップです。数字を別の数字に変えても再現されるなら、原因はその数字ではありません。逆に、カンマをピリオドに変えたりロケールを変えたりすると止まることを示せば、原因が2つの条件の組み合わせだと証明できます。
何を再現と呼んだかを書く
/root/mre/mre_report.mdに、## 무엇을 재현이라고 불렀나、## 얼마나 줄였나、## 남은 줄이 왜 다 필요한가、## 남은 가정の4つの節で書いてください(韓国語の見出しは順に「何を再現と呼んだか」「どれだけ縮めたか」「残った行がなぜすべて必要なのか」「残る仮定」という意味です)。原本と最小例の行数とステップ数、オラクルが見る終了コードとシグネチャ文字列が、すべて出ている必要があります。
受け取る側は、入力2行だけでは「これがなぜ問題なのですか」とまず尋ねます。何を同じ失敗とみなしたかを先に書き、そのあとにどれだけ縮めたかと、なぜこれ以上縮められないかを書いてください。最後の節には、値を変えても再現されるという事実と、何を変えると止まるかを書きます。