diff --git a/include/criterion/criterion.h b/include/criterion/criterion.h index f72eba0..0beb926 100644 --- a/include/criterion/criterion.h +++ b/include/criterion/criterion.h @@ -48,6 +48,8 @@ TEST_PROTOTYPE_(Category, Name); \ struct criterion_test_extra_data IDENTIFIER_(Category, Name, extra) = \ CR_EXPAND(CRITERION_MAKE_STRUCT(struct criterion_test_extra_data, \ + .kind_ = CR_TEST_NORMAL, \ + .param_ = (struct criterion_test_params(*)(void)) NULL, \ .identifier_ = #Category "/" #Name, \ .file_ = __FILE__, \ .line_ = __LINE__, \ diff --git a/include/criterion/parameterized.h b/include/criterion/parameterized.h new file mode 100644 index 0000000..56dc3b6 --- /dev/null +++ b/include/criterion/parameterized.h @@ -0,0 +1,48 @@ +#ifndef CRITERION_PARAMETERIZED_H_ +# define CRITERION_PARAMETERIZED_H_ + +# include "criterion.h" + +# ifdef __cplusplus +# define CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name) \ + extern "C" void IDENTIFIER_(Category, Name, impl)(Param) +# else +# define CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name) \ + void IDENTIFIER_(Category, Name, impl)(Param) +# endif + +# define ParameterizedTest(...) \ + CR_EXPAND(ParameterizedTest_(__VA_ARGS__, .sentinel_ = 0)) + +# define ParameterizedTest_(Param, Category, Name, ...) \ + CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name); \ + struct criterion_test_extra_data IDENTIFIER_(Category, Name, extra) = \ + CR_EXPAND(CRITERION_MAKE_STRUCT(struct criterion_test_extra_data, \ + .kind_ = CR_TEST_PARAMETERIZED, \ + .param_ = IDENTIFIER_(Category, Name, param), \ + .identifier_ = #Category "/" #Name, \ + .file_ = __FILE__, \ + .line_ = __LINE__, \ + __VA_ARGS__ \ + )); \ + SECTION_("cr_tst") \ + struct criterion_test IDENTIFIER_(Category, Name, meta) = { \ + #Name, \ + #Category, \ + (void(*)(void)) IDENTIFIER_(Category, Name, impl), \ + &IDENTIFIER_(Category, Name, extra) \ + } SECTION_SUFFIX_; \ + CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name) + +# define ParameterizedTestParameters(Category, Name) \ + static struct criterion_test_params IDENTIFIER_(Category, Name, param)(void) + +# ifdef __cplusplus +# define cr_make_param_array(Type, Array, Length) \ + criterion_test_params(sizeof (Type), (Array), (Length)) +# else +# define cr_make_param_array(Type, Array, Length) \ + (struct criterion_test_params) { sizeof (Type), (void**)(Array), (Length) } +# endif + +#endif /* !CRITERION_PARAMETERIZED_H_ */ diff --git a/include/criterion/types.h b/include/criterion/types.h index df2c3bc..ab46247 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -33,8 +33,29 @@ using std::size_t; # endif # include "common.h" +enum criterion_test_kind { + CR_TEST_NORMAL, + CR_TEST_PARAMETERIZED, +}; + +struct criterion_test_params { + size_t size; + void **params; + size_t length; + +# ifdef __cplusplus + constexpr criterion_test_params(size_t size, void **params, size_t length) + : size(size) + , params(params) + , length(length) + {} +# endif +}; + struct criterion_test_extra_data { int sentinel_; + enum criterion_test_kind kind_; + struct criterion_test_params (*param_)(void); const char *identifier_; const char *file_; unsigned line_; diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 6ae8e1a..a80f1af 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -17,6 +17,7 @@ set(SAMPLES theories.c timeout.c redirect.c + parameterized.c signal.cc report.cc diff --git a/samples/parameterized.c b/samples/parameterized.c new file mode 100644 index 0000000..637f158 --- /dev/null +++ b/samples/parameterized.c @@ -0,0 +1,13 @@ +#include + +ParameterizedTestParameters(params, str) { + static const char *strings[] = { + "foo", "bar", "baz" + }; + + return cr_make_param_array(const char *, strings, sizeof (strings) / sizeof (const char *)); +} + +ParameterizedTest(const char *str, params, str) { + cr_assert(0, "Parameter: %s", str); +} diff --git a/src/compat/process.c b/src/compat/process.c index f5c03bc..41990fa 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -99,6 +99,7 @@ struct full_context { struct criterion_test_extra_data suite_data; f_worker_func func; struct pipe_handle pipe; + struct test_single_param param; volatile int resumed; }; @@ -186,11 +187,22 @@ int resume_child(void) { exit(-1); local_ctx = *ctx; + struct test_single_param *param = NULL; + if (local_ctx.param.size != 0) { + param = malloc(sizeof (struct test_single_param) + local_ctx.param->size); + *param = (struct test_single_param) { + .size = local_ctx.param->size, + .ptr = param + 1, + }; + memcpy(param + 1, local_ctx.param->ptr, param->size); + } + g_worker_context = (struct worker_context) { - &local_ctx.test, - &local_ctx.suite, - local_ctx.func, - &local_ctx.pipe + .test = &local_ctx.test, + .suite = &local_ctx.suite, + .func = local_ctx.func, + .pipe = &local_ctx.pipe, + .param = param, }; local_ctx.test.data = &local_ctx.test_data; @@ -204,6 +216,7 @@ int resume_child(void) { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); run_worker(&g_worker_context); + free(param); return 1; #else # if defined(__unix__) || defined(__APPLE__) @@ -266,6 +279,11 @@ s_proc_handle *fork_process() { .resumed = 0, }; + if (g_worker_context.param) { + ctx->param = *g_worker_context.param, + memcpy(ctx + 1, g_worker_context.param->ptr, g_worker_context.param->size); + } + if (g_worker_context.suite->data) ctx->suite_data = *g_worker_context.suite->data; diff --git a/src/compat/process.h b/src/compat/process.h index 2e26db4..0d70770 100644 --- a/src/compat/process.h +++ b/src/compat/process.h @@ -42,6 +42,7 @@ struct worker_context { struct criterion_suite *suite; f_worker_func func; struct pipe_handle *pipe; + struct test_single_param *param; }; extern struct worker_context g_worker_context; diff --git a/src/core/runner.c b/src/core/runner.c index 4333ebd..775033e 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -59,6 +59,7 @@ TestSuite(); Test(,) {}; static INLINE void nothing(void) {} +static INLINE void nothing_ptr(UNUSED void* ptr) {} int cmp_suite(void *a, void *b) { struct criterion_suite *s1 = a, *s2 = b; @@ -186,7 +187,11 @@ static void run_test_child(struct criterion_test *test, struct timespec_compat ts; if (!setjmp(g_pre_test)) { timer_start(&ts); - (test->test ? test->test : nothing)(); + if (!test->data->param_) + (test->test ? test->test : nothing)(); + else + (test->test ? (void(*)(void*)) test->test + : nothing_ptr)(g_worker_context.param->ptr); } double elapsed_time; @@ -297,7 +302,8 @@ static void handle_worker_terminated(struct event *ev, static void run_test(struct criterion_global_stats *stats, struct criterion_suite_stats *suite_stats, struct criterion_test *test, - struct criterion_suite *suite) { + struct criterion_suite *suite, + struct test_single_param *param) { struct criterion_test_stats *test_stats = test_stats_init(test); struct process *proc = NULL; @@ -310,7 +316,7 @@ static void run_test(struct criterion_global_stats *stats, goto cleanup; } - proc = spawn_test_worker(test, suite, run_test_child, g_worker_pipe); + proc = spawn_test_worker(test, suite, run_test_child, g_worker_pipe, param); if (proc == NULL && !is_runner()) goto cleanup; @@ -370,6 +376,38 @@ cleanup: sfree(proc); } +static void run_test_param(struct criterion_global_stats *stats, + struct criterion_suite_stats *suite_stats, + struct criterion_test *test, + struct criterion_suite *suite) { + + if (!test->data->param_) + return; + + struct criterion_test_params params = test->data->param_(); + for (size_t i = 0; i < params.length; ++i) { + struct test_single_param param = { params.size, params.params[i] }; + + run_test(stats, suite_stats, test, suite, ¶m); + } +} + +static void run_test_switch(struct criterion_global_stats *stats, + struct criterion_suite_stats *suite_stats, + struct criterion_test *test, + struct criterion_suite *suite) { + + switch (test->data->kind_) { + case CR_TEST_NORMAL: + run_test(stats, suite_stats, test, suite, NULL); + break; + case CR_TEST_PARAMETERIZED: + run_test_param(stats, suite_stats, test, suite); + break; + default: break; + } +} + #ifdef HAVE_PCRE void disable_unmatching(struct criterion_test_set *set) { FOREACH_SET(struct criterion_suite_set *s, set->suites) { @@ -414,7 +452,7 @@ static int criterion_run_all_tests_impl(struct criterion_test_set *set) { abort(); struct criterion_global_stats *stats = stats_init(); - map_tests(set, stats, run_test); + map_tests(set, stats, run_test_switch); int result = is_runner() ? stats->tests_failed == 0 : -1; diff --git a/src/core/worker.c b/src/core/worker.c index 7f53279..93ff121 100644 --- a/src/core/worker.c +++ b/src/core/worker.c @@ -77,12 +77,14 @@ void run_worker(struct worker_context *ctx) { struct process *spawn_test_worker(struct criterion_test *test, struct criterion_suite *suite, f_worker_func func, - s_pipe_handle *pipe) { + s_pipe_handle *pipe, + struct test_single_param *param) { g_worker_context = (struct worker_context) { .test = test, .suite = suite, .func = func, - .pipe = pipe + .pipe = pipe, + .param = param, }; struct process *ptr = NULL; diff --git a/src/core/worker.h b/src/core/worker.h index 5cc7af7..ab8bff9 100644 --- a/src/core/worker.h +++ b/src/core/worker.h @@ -47,6 +47,11 @@ struct worker_status { struct process_status status; }; +struct test_single_param { + size_t size; + void *ptr; +}; + void run_worker(struct worker_context *ctx); void set_runner_process(void); void unset_runner_process(void); @@ -56,7 +61,8 @@ struct process_status get_status(int status); struct process *spawn_test_worker(struct criterion_test *test, struct criterion_suite *suite, f_worker_func func, - s_pipe_handle *pipe); + s_pipe_handle *pipe, + struct test_single_param *param); struct event *worker_read_event(struct process *proc); #endif /* !PROCESS_H_ */