¿Cómo podemos aplicar una función no vararg sobre una va_list?
unit-testing tuples (1)
Trasfondo
Estoy portando el marco de prueba de la unidad QuickCheck a C (ver el código de trabajo en GitHub ). La sintaxis será:
for_all(property, gen1, gen2, gen3 ...);
Donde la property
es una función para probar, por ejemplo, bool is_odd(int)
. gen1
, gen2
, etc. son funciones que generan valores de entrada para la property
. Algunos generan enteros, otros generan caracteres, algunos generan cadenas, etc.
for_all
aceptará una función con entradas arbitrarias (cualquier número de argumentos, cualquier tipo de argumentos). for_all
ejecutará los generadores, creando valores de prueba para pasar a la función de propiedad. Por ejemplo, la propiedad is_odd
es una función con tipo bool f(int)
. for_all
usará el generador para crear 100 casos de prueba. Si la propiedad devuelve false para cualquiera de ellos, for_all
imprimirá los valores del caso de prueba ofensivo. De lo contrario, for_all
imprimirá "SUCCESS"
.
Por for_all
tanto, for_all
debería usar una va_list
para acceder a los generadores. Una vez que llamamos a las funciones del generador, ¿cómo las pasamos a la función de propiedad?
Ejemplo
Si is_odd
tiene el tipo bool f(int)
, ¿cómo implementaríamos una función apply()
que tiene esta sintaxis?
apply(is_odd, generated_values);
Problema secundario
Ver SO .
¿Cómo podemos imprimir inteligentemente los valores arbitrarios de un caso de prueba fallido? Un caso de prueba puede ser un solo entero, o dos caracteres, o una cadena, o alguna combinación de los anteriores? No sabremos por adelantado si debemos usar:
-
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);
El lenguaje C es un lenguaje de tipo estático. No tiene los poderes de reflexión en tiempo de ejecución que otros lenguajes tienen. Tampoco proporciona formas de crear llamadas a funciones arbitrarias a partir de tipos proporcionados por el tiempo de ejecución. Necesita tener alguna forma de saber cuál es la firma de función de is_odd
y cuántos parámetros acepta y cuáles son los tipos de esos parámetros. Ni siquiera sabe cuándo ha llegado al final de la ... lista de argumentos; necesitas un terminador explícito.
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 ...
}
}
Su problema es que QuickCheck fue diseñado para aprovechar la alta programación dinámica de JavaScripts, algo que falta en C.
Actualización Si permite firmas de funciones arbitrarias, entonces necesita una forma de hacerlo estático de nuevo, por ejemplo, haciendo que la persona que llama proporcione los adaptadores apropiados.
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);
}