TT Lab
开始
学习 学习路径 课程

面对陌生系统

把 200 行和 12 个步骤缩到两行四步

在 TT Lab 中继续学习

目标

把客户发来的故障包(一个处理程序、200 行的 feed、12 步的复现步骤)精简成最小复现用例。先让机器判定“是否仍然是同一个失败”,只用这个判定器来精简,并且证明剩下的内容只要去掉任何一个,复现就会停止。

为什么重要

客户写给我们的复现步骤,通常就是那个人当天做过的全部事情。带着这一切去找原因,需要查看的组合会爆炸。但如果一上来就精简,风险更大:删掉一行,程序因为另一个错误崩溃了,而那也是错误,于是误以为仍然能复现。这样得到的用例,复现的是与我们追查的缺陷无关的另一个缺陷。 所以有一个顺序:先做判定器,只用这个判定器来精简。判定器要同时看退出码和只在该失败中才会出现的特征字符串。运行时间或临时文件名这类每次运行都会变的内容,不要放进判定。 精简由机器来做。一行一行地删,行数是多少就要调用多少次判定器;而把候选分成块来看的 delta debugging(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. 用判定器精简 feed,生成 /root/mre/min_input.txt。剩下的行是原文件中的行,并保持原文件的顺序。
  4. 在 /root/mre/minimality.json 中,为剩下的每一行写明去掉它之后是否仍能复现,以证明结果是 1-最小。
  5. 在 /root/mre/shrink.py 中实现 delta debugging(ddmin)。它接收 --input --oracle --out,并且调用判定器的次数不能与行数成正比。
  6. 精简 12 步的复现步骤,生成 /root/mre/min_steps.txt。剩下的步骤保持原文件中的行原样,并保持顺序。
  7. 确认改变取值后是否仍能复现,留下 /root/mre/generalize.json 和三份变形文件。
  8. 在 /root/mre/mre_report.md 中分四节报告。

参考

展开故障包

创建并运行 /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 结束。标准错误中的特征字符串也要一并查看。

精简 feed

用判定器精简 feed,生成 /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 中实现 delta debugging(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 中分 ## 무엇을 재현이라고 불렀나 ## 얼마나 줄였나 ## 남은 줄이 왜 다 필요한가 ## 남은 가정 四节书写(韩文标题,依次意为“把什么称为复现”“精简了多少”“剩下的行为什么都需要”“剩余的假设”)。原始用例与最小用例的行数和步骤数,以及判定器所看的退出码和特征字符串,都必须出现。

接收方只看到两行输入,会先问“这为什么是个问题”。先写明把什么视为同一个失败,再写精简了多少,以及为什么不能再精简。最后一节写明改变取值后仍能复现这一事实,以及改变什么会使复现停止。