Backstory
I'm porting the QuickCheck unit test framework to C (see the working code at GitHub). The syntax will be:
for_all(property, gen1, gen2, gen3 ...);
Where property
is a function to test, for example bool is_odd(int)
. gen1
, gen2
, etc. are functions that generate input values for property
. Some generate integers, some generate chars, some generate strings, and so on.
for_all
will accept a function with arbitrary inputs (any number of arguments, any types of arguments). for_all
will run the generators, creating test values to pass to the property function. For example, the property is_odd
is a function with type bool f(int)
. for_all
will use the generates to create 100 test cases. If the property returns false for any of them, for_all
will print the offending test case values. Otherwise, for_all
will print "SUCCESS"
.
Thus for_all
should use a va_list
to access the generators. Once we call the generator functions, how do we pass them to the property function?
Example
If is_odd
has the type bool f(int)
, how would we implement a function a开发者_如何学运维pply()
that has this syntax:
apply(is_odd, generated_values);
Secondary Issue
See SO.
How can we intelligently print the arbitrary values of a failing test case? A test case may be a single integer, or two characters, or a string, or some combination of the above? We won't know ahead of time whether to use:
printf("%d %d %d\n", some_int, some_int, some_int);
printf("%c\n" a_character);
printf("%s%s\n", a_string, a_struct_requiring_its_own_printf_function);
The C language is a statically-typed language. It does not have the powers of runtime reflection that other languages do. It also does not provide ways to build arbitrary function calls from runtime-provided types. You need to have some way of knowing what the function signature of is_odd
is and how many parameter it accepts and what the types of those parameters is. It doesn't even know when it has reached the end of the ... argument list; you need an explicit terminator.
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 ...
}
}
Your problem is that QuickCheck was designed to take advantage of JavaScripts high dynamic programmability, something missing from C.
Update If you allow arbitrary function signatures, then you need a way to make it static again, say, by making the caller provide the appropriate adapters.
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);
}
精彩评论