C 如何在va_列表上应用非vararg函数? 背景故事
我正在将单元测试框架移植到C(请参阅第页的工作代码)。语法将是:C 如何在va_列表上应用非vararg函数? 背景故事,c,unit-testing,tuples,quickcheck,C,Unit Testing,Tuples,Quickcheck,我正在将单元测试框架移植到C(请参阅第页的工作代码)。语法将是: for_all(property, gen1, gen2, gen3 ...); 其中,property是一个要测试的函数,例如bool是奇数(int)gen1、gen2等是为属性生成输入值的函数。一些生成整数,一些生成字符,一些生成字符串,等等 for_all将接受具有任意输入(任意数量的参数、任意类型的参数)的函数for_all将运行生成器,创建要传递给属性函数的测试值。例如,属性是奇数的是类型为bool f(int)的函数
for_all(property, gen1, gen2, gen3 ...);
其中,property
是一个要测试的函数,例如bool是奇数(int)
gen1
、gen2
等是为属性生成输入值的函数。一些生成整数,一些生成字符,一些生成字符串,等等
for_all
将接受具有任意输入(任意数量的参数、任意类型的参数)的函数for_all
将运行生成器,创建要传递给属性函数的测试值。例如,属性是奇数的
是类型为bool f(int)
的函数for_all
将使用生成的代码创建100个测试用例。如果属性为其中任何一个返回false,for_all
将打印有问题的测试用例值。否则,for_all
将打印“SUCCESS”
因此,for_all
应使用va_列表
访问发电机。调用生成器函数后,如何将它们传递给属性函数
例子
如果的类型为bool f(int)
,我们将如何实现具有以下语法的函数apply()
:
apply(is_odd, generated_values);
次要问题
看
我们如何智能地打印失败测试用例的任意值?一个测试用例可以是一个整数,或者两个字符,或者一个字符串,或者上面的一些组合?我们无法提前知道是否使用:
printf(“%d%d%d\n”,一些int,一些int,一些int)代码>
printf(“%c\n”一个字符)代码>
printf(“%s%s\n”,一个字符串,一个需要自己的printf函数的结构)代码>
C语言是一种静态类型语言。它没有其他语言所具有的运行时反射能力。它也不提供从运行时提供的类型生成任意函数调用的方法。您需要知道
的函数签名是什么,它接受多少个参数,这些参数的类型是什么。它甚至不知道它什么时候到达终点。。。参数表;你需要一个明确的终结者
enum function_signature {
returns_bool_accepts_int,
returns_bool_accepts_float,
returns_bool_accepts_int_int,
};
typedef bool (*function_returning_bool_accepting_int)(int);
typedef int (*function_generates_int)();
void for_all(function_signature signature, ...)
{
va_list ap;
va_start(ap, signature);
switch (function_signature)
{
case returns_bool_accepts_int:
{
function_returning_bool_accepting_int fn = va_arg(ap, function_returning_bool_accepting_int);
function_generates_int generator;
do {
generator = va_arg(ap, function_generates_int);
if (generator) fn(generator());
} while (generator);
}
break;
... etc ...
}
}
您的问题是,QuickCheck是为了利用JavaScripts的高动态可编程性而设计的,这是C语言中缺少的
更新如果允许任意函数签名,则需要一种方法使其再次保持静态,例如,让调用方提供适当的适配器
typedef void (*function_pointer)();
typedef bool (*function_applicator)(function_pointer, function_pointer);
void for_all(function_applicator apply, ...)
{
va_list ap;
va_start(ap, apply);
function_pointer target = va_arg(ap, function_pointer);
function_pointer generator;
do {
generator = va_arg(ap, function_pointer);
if (generator) apply(target, generator);
} while (generator);
}
// sample caller
typedef bool (*function_returning_bool_accepting_int)(int);
typedef int (*function_returning_int)();
bool apply_one_int(function_pointer target_, function_pointer generator_)
{
function_returning_bool_accepting_int target = (function_returning_bool_accepting_int)target_;
function_returning_int generator = (function_returning_int)generator_;
return target(generator());
}
for_all(apply_one_int, is_odd, generated_values1, generated_values2, (function_pointer)0);
}
我不认为我遵循-你应该调用每个生成器一次并立即接收生成的值的整个集合,还是b)
在循环中调用每个生成器函数以获得后续的值进行测试?好的,我已经阅读了源代码,所以我知道它实际上是c)
每个生成器为特定函数参数提供后续值。示例:testme(int,char,char)
需要一个随机整数和两个随机字符。一旦我们弄清楚了如何做,我们将让for_all
在一个大循环中运行一个测试用例,而不是100个测试用例。对于您的第二个问题,您需要生成参数,并将它们放置在一个特殊的日志函数中,您也可以通过,或者启动一个va_列表
,然后将启动的列表传递给某种类型的打印或处理函数(您可以使用va_列表
类型作为函数的参数,允许您包装或使用vararg函数)。@peachykeen是的,我想我必须将API全部更改为(property,gen1,print1,gen2,print2,…)
来处理任意复杂的数据类型(想想红黑树)。是的,C不能动态地确定函数的类型签名。这就是为什么必须提供显式生成器函数的原因。而且我们不能硬编码enum
,因为用户可能想要在自定义类型上测试函数(结构、联合等)。然后你必须将键入的内容推入调用方。请参阅更新。(填补我留下的空白作为练习。我不能为你做所有事情…)嘿,不,我不希望你这么做。:)在您的代码中,似乎对生成的值1和生成的值2分别调用了is_odd。请记住,每个生成器表示属性输入的一部分。is_odd(int)
具有arity 1,因此它应该具有单个生成器(gen_int
)。等于(int a,int b)
有arity 2,因此它将有两个生成器(gen_int,gen_int
)。欢迎您扩展设计以支持arity大于1的函数。该设计要求为每个可能的函数签名创建辅助函数。我将把无限组合的枚举留给其他人作为练习。