我正在寻找一种更好的方法来保存模糊测试的有趣结果,以便以后重复。当前的计划是将失败的输入序列化并将其作为测试用例写出来。假设我们要测试:
int function_under_test(struct arbitrary *);
假设这个函数在任意结构中使用半随机数据运行了几千次并且失败了两次。我希望能够重现失败的案例,以确定错误修复是否成功。
我可以看到实现这一目标的两种策略:
- 强制确定性 - 存储随机种子,记下哪个测试编号失败
- 将导致失败的结构持久存储在某处
选项 1 需要充分注意如何生成模糊测试用例以实现可重复性,并在重新播种随机数生成器时出现一些混乱。选项 2 需要一些繁琐的代码生成,可能通过序列化库减少。我希望听到有我看不到的第三种选择。
我对选项 2 的计划本质上是将结构序列化为字符数组,将所述数组写入文本文件,然后将其读回并按需转换为结构。以下是 POD 的概念验证,更复杂的类型需要更复杂的序列化。示例是 C,但也欢迎基于 C++ 的答案。
#include <stdio.h> /* for printf in main() */
#include <stddef.h> /* size_t */
#define SIZEFOO 5
struct arbitrary
{
int foo[SIZEFOO];
double bar;
};
int equal_arbitrary(struct arbitrary *lhs, struct arbitrary *rhs)
{
/* return 1 if structs are equal, 0 if not */
for (int i=0; i < SIZEFOO; i++)
{
if (lhs->foo[i] != rhs->foo[i]) {return 0;}
}
if (lhs->bar != rhs->bar) {return 0;}
return 1;
}
void arbitrary_to_bytes(struct arbitrary *input, char *output)
{
union
{
struct arbitrary data;
unsigned char view[sizeof(struct arbitrary)];
} local;
size_t i;
local.data = *input;
for (i = 0; i < sizeof(struct arbitrary); i++)
{
output[i] = local.view[i];
}
return;
}
void bytes_to_arbitrary(char *input, struct arbitrary *output)
{
union
{
struct arbitrary data;
unsigned char view[sizeof(struct arbitrary)];
} local;
size_t i;
for (i = 0; i < sizeof(struct arbitrary); i++)
{
local.view[i] = input[i];
}
*output = local.data;
return;
}
int main(void)
{
struct arbitrary original;
struct arbitrary copied;
unsigned char working[sizeof (struct arbitrary)];
for (int i=0; i < SIZEFOO; i++) { original.foo[i] = 3 + i*i; }
original.bar = 3.14;
arbitrary_to_bytes(&original,working);
bytes_to_arbitrary(working,&copied);
if (equal_arbitrary(&original,&copied))
{
printf("PASS\n");
}
else
{
printf("FAIL\n");
}
return 0;
}
在执行期间,当(模糊)测试用例失败时,副作用之一是将输入结构转换为字节数组(如上)并将类似以下内容写入文件,该文件随后可以成为确定性的一部分测试套件:
result function_under_test_fuzz_123(void) /* 123 a unique number */
{
int rc;
struct arbitrary;
unsigned char test_data[] = "byte array stored as ascii string";
bytes_to_arbitrary(test_data, &arbitrary);
rc = function_under_test(&arbitrary);
/* Do whatever is needed to determine if the function failed */
if (rc) {return PASS;} else {return FAIL;}
}
我相信如果另一个值是 char 数组,则通过联合进行类型双关是有效的。当函数输入不是普通的旧数据时,(反)序列化步骤变得相当复杂,但一般策略仍然可用。将测试用例存储为 ascii 可能会产生一些相当大的文本文件。
在花时间设置基础设施以执行上述操作(主要是代码生成器,对测试框架进行一些修改)之前,似乎值得询问社区是否有已知的更好方法。我怀疑我是第一个认为可重复的模糊测试是个好主意的人!
更改为 C++ 意味着模板而不是外部代码生成器,并在 BOOST 中找到一个对 stl 友好的序列化库,但不会改变“如何最好地保存失败的模糊测试结果?”的基本查询。
谢谢