From 582fa1838900113d3d6b4aded81fcdbb7a86864b Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 17 Sep 2015 17:44:47 +0200 Subject: [PATCH 01/14] Added parameterized tests --- include/criterion/criterion.h | 2 ++ include/criterion/parameterized.h | 48 +++++++++++++++++++++++++++++++ include/criterion/types.h | 21 ++++++++++++++ samples/CMakeLists.txt | 1 + samples/parameterized.c | 13 +++++++++ src/compat/process.c | 26 ++++++++++++++--- src/compat/process.h | 1 + src/core/runner.c | 46 ++++++++++++++++++++++++++--- src/core/worker.c | 6 ++-- src/core/worker.h | 8 +++++- 10 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 include/criterion/parameterized.h create mode 100644 samples/parameterized.c 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_ */ From 1d908327d8f0f2329aa1a1e38ffe91a059d6a849 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Fri, 18 Sep 2015 00:33:13 +0200 Subject: [PATCH 02/14] Fixed erroneous indirection logic for passed data --- include/criterion/parameterized.h | 2 +- include/criterion/types.h | 4 ++-- samples/parameterized.c | 4 ++-- src/core/runner.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/criterion/parameterized.h b/include/criterion/parameterized.h index 56dc3b6..a3c199f 100644 --- a/include/criterion/parameterized.h +++ b/include/criterion/parameterized.h @@ -42,7 +42,7 @@ 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) } + (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 ab46247..bbb11aa 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -40,11 +40,11 @@ enum criterion_test_kind { struct criterion_test_params { size_t size; - void **params; + void *params; size_t length; # ifdef __cplusplus - constexpr criterion_test_params(size_t size, void **params, size_t length) + constexpr criterion_test_params(size_t size, void *params, size_t length) : size(size) , params(params) , length(length) diff --git a/samples/parameterized.c b/samples/parameterized.c index 637f158..0d9ab2e 100644 --- a/samples/parameterized.c +++ b/samples/parameterized.c @@ -8,6 +8,6 @@ ParameterizedTestParameters(params, str) { 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); +ParameterizedTest(const char **str, params, str) { + cr_assert(0, "Parameter: %s", *str); } diff --git a/src/core/runner.c b/src/core/runner.c index 775033e..0b0e03f 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -386,7 +386,7 @@ static void run_test_param(struct criterion_global_stats *stats, 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] }; + struct test_single_param param = { params.size, (char *) params.params + i * params.size }; run_test(stats, suite_stats, test, suite, ¶m); } From c5e6d0c770a2d6cd7a341060ebafbe14c1296608 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 17 Sep 2015 14:04:01 -0700 Subject: [PATCH 03/14] Handle safely winfork failures --- src/compat/process.c | 12 +++++++++++- src/core/worker.c | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/compat/process.c b/src/compat/process.c index 41990fa..c2d7f47 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -290,7 +290,17 @@ s_proc_handle *fork_process() { ResumeThread(info.hThread); CloseHandle(info.hThread); - while (!ctx->resumed); // wait until the child has initialized itself + // wait until the child has initialized itself + while (!ctx->resumed) { + DWORD exit; + GetExitCodeProcess(info.hProcess, &exit); + if (exit != STILL_ACTIVE) { + UnmapViewOfFile(ctx); + CloseHandle(sharedMem); + CloseHandle(info.hProcess); + return (void *) -1; + } + } UnmapViewOfFile(ctx); CloseHandle(sharedMem); diff --git a/src/core/worker.c b/src/core/worker.c index 93ff121..85a945a 100644 --- a/src/core/worker.c +++ b/src/core/worker.c @@ -91,7 +91,7 @@ struct process *spawn_test_worker(struct criterion_test *test, s_proc_handle *proc = fork_process(); if (proc == (void *) -1) { - return NULL; + abort(); } else if (proc == NULL) { run_worker(&g_worker_context); return NULL; From c30e70da59ad49c6efbc09461cdb700684152a4d Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 17 Sep 2015 14:19:38 -0700 Subject: [PATCH 04/14] Fixed windows compilation errors --- src/compat/process.c | 15 ++++++++++----- src/core/runner.c | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/compat/process.c b/src/compat/process.c index c2d7f47..8a4ed82 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -183,18 +183,20 @@ int resume_child(void) { 0, MAPPING_SIZE); - if (ctx == NULL) + if (ctx == NULL) { + CloseHandle(sharedMem); 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 = malloc(sizeof (struct test_single_param) + local_ctx.param.size); *param = (struct test_single_param) { - .size = local_ctx.param->size, + .size = local_ctx.param.size, .ptr = param + 1, }; - memcpy(param + 1, local_ctx.param->ptr, param->size); + memcpy(param + 1, local_ctx.param.ptr, param->size); } g_worker_context = (struct worker_context) { @@ -282,6 +284,7 @@ s_proc_handle *fork_process() { if (g_worker_context.param) { ctx->param = *g_worker_context.param, memcpy(ctx + 1, g_worker_context.param->ptr, g_worker_context.param->size); + ctx->param.ptr = ctx + 1; } if (g_worker_context.suite->data) @@ -293,7 +296,9 @@ s_proc_handle *fork_process() { // wait until the child has initialized itself while (!ctx->resumed) { DWORD exit; - GetExitCodeProcess(info.hProcess, &exit); + if (!GetExitCodeProcess(info.hProcess, &exit)); + continue; + if (exit != STILL_ACTIVE) { UnmapViewOfFile(ctx); CloseHandle(sharedMem); diff --git a/src/core/runner.c b/src/core/runner.c index 0b0e03f..5380f07 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -389,6 +389,10 @@ static void run_test_param(struct criterion_global_stats *stats, struct test_single_param param = { params.size, (char *) params.params + i * params.size }; run_test(stats, suite_stats, test, suite, ¶m); + if (criterion_options.fail_fast && stats->tests_failed > 0) + break; + if (!is_runner()) + break; } } From f09dff7d2814d717602949054634e155c7985a3f Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 17 Sep 2015 15:58:05 -0700 Subject: [PATCH 05/14] Fixed context passing error with parameterized tests --- src/compat/process.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/compat/process.c b/src/compat/process.c index 8a4ed82..7ac7727 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -34,6 +34,7 @@ #include #ifdef VANILLA_WIN32 +# include # define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \ CreateProcessW(Filename, \ CmdLine, \ @@ -103,8 +104,8 @@ struct full_context { volatile int resumed; }; -static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker"); -#define MAPPING_SIZE sizeof (struct full_context) +static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker_%lu"); +#define MAPPING_SIZE 128 static struct full_context local_ctx; #endif @@ -169,10 +170,13 @@ static void CALLBACK handle_child_terminated(PVOID lpParameter, int resume_child(void) { #ifdef VANILLA_WIN32 + TCHAR mapping_name[128]; + _sntprintf(mapping_name, 128, g_mapping_name, GetCurrentProcessId()); + HANDLE sharedMem = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, - g_mapping_name); + mapping_name); if (sharedMem == NULL) return 0; @@ -249,6 +253,9 @@ s_proc_handle *fork_process() { if (!CREATE_SUSPENDED_(filename, GetCommandLineW(), si, info)) return (void *) -1; + TCHAR mapping_name[128]; + _sntprintf(mapping_name, 128, g_mapping_name, info.dwProcessId); + // Copy context over HANDLE sharedMem = CreateFileMapping( INVALID_HANDLE_VALUE, @@ -256,9 +263,9 @@ s_proc_handle *fork_process() { PAGE_READWRITE, 0, MAPPING_SIZE, - g_mapping_name); + mapping_name); - if (sharedMem == NULL) + if (sharedMem == NULL || GetLastError() == ERROR_ALREADY_EXISTS) return (void *) -1; struct full_context *ctx = (struct full_context *) MapViewOfFile(sharedMem, @@ -290,8 +297,8 @@ s_proc_handle *fork_process() { if (g_worker_context.suite->data) ctx->suite_data = *g_worker_context.suite->data; - ResumeThread(info.hThread); - CloseHandle(info.hThread); + if (ResumeThread(info.hThread) == -1) + goto failure; // wait until the child has initialized itself while (!ctx->resumed) { @@ -299,13 +306,10 @@ s_proc_handle *fork_process() { if (!GetExitCodeProcess(info.hProcess, &exit)); continue; - if (exit != STILL_ACTIVE) { - UnmapViewOfFile(ctx); - CloseHandle(sharedMem); - CloseHandle(info.hProcess); - return (void *) -1; - } + if (exit != STILL_ACTIVE) + goto failure; } + CloseHandle(info.hThread); UnmapViewOfFile(ctx); CloseHandle(sharedMem); @@ -326,6 +330,14 @@ s_proc_handle *fork_process() { s_proc_handle *handle = smalloc(sizeof (s_proc_handle)); *handle = (s_proc_handle) { info.hProcess }; return handle; + +failure: + UnmapViewOfFile(ctx); + CloseHandle(sharedMem); + CloseHandle(info.hThread); + CloseHandle(info.hProcess); + return (void *) -1; + #else pid_t pid = fork(); if (pid == -1) From dd41497476454436435800f4cf0a1daafff0627a Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 17 Sep 2015 16:07:49 -0700 Subject: [PATCH 06/14] Removed stray semicolon and fixed signed/unsigned comparison warning on windows --- src/compat/process.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compat/process.c b/src/compat/process.c index 7ac7727..168a4b8 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -297,13 +297,13 @@ s_proc_handle *fork_process() { if (g_worker_context.suite->data) ctx->suite_data = *g_worker_context.suite->data; - if (ResumeThread(info.hThread) == -1) + if (ResumeThread(info.hThread) == (DWORD) -1) goto failure; // wait until the child has initialized itself while (!ctx->resumed) { DWORD exit; - if (!GetExitCodeProcess(info.hProcess, &exit)); + if (!GetExitCodeProcess(info.hProcess, &exit)) continue; if (exit != STILL_ACTIVE) From b10d6fb165778401bb57798bd432656a6af2a18b Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 17 Sep 2015 19:31:28 -0700 Subject: [PATCH 07/14] Fixed invalid context passing in windows fork code --- src/compat/process.c | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/compat/process.c b/src/compat/process.c index 168a4b8..d5d6476 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -88,8 +88,6 @@ static int get_win_status(HANDLE handle) { } #endif - - struct worker_context g_worker_context = {.test = NULL}; #ifdef VANILLA_WIN32 @@ -101,11 +99,11 @@ struct full_context { f_worker_func func; struct pipe_handle pipe; struct test_single_param param; - volatile int resumed; + HANDLE sync; }; static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker_%lu"); -#define MAPPING_SIZE 128 +#define MAPPING_SIZE 1024 static struct full_context local_ctx; #endif @@ -195,6 +193,8 @@ int resume_child(void) { local_ctx = *ctx; struct test_single_param *param = NULL; if (local_ctx.param.size != 0) { + local_ctx.param.ptr = ctx + 1; + param = malloc(sizeof (struct test_single_param) + local_ctx.param.size); *param = (struct test_single_param) { .size = local_ctx.param.size, @@ -214,11 +214,13 @@ int resume_child(void) { local_ctx.test.data = &local_ctx.test_data; local_ctx.suite.data = &local_ctx.suite_data; - ctx->resumed = 1; + HANDLE sync = ctx->sync; UnmapViewOfFile(ctx); CloseHandle(sharedMem); + SetEvent(sync); + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); run_worker(&g_worker_context); @@ -246,6 +248,16 @@ s_proc_handle *fork_process() { ZeroMemory(&info, sizeof (info)); + SECURITY_ATTRIBUTES inherit_handle = { + .nLength = sizeof (SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE + }; + + // Create the synchronization event + HANDLE sync = CreateEvent(&inherit_handle, FALSE, FALSE, NULL); + if (sync == NULL) + return (void *) -1; + // Create the suspended child process wchar_t filename[MAX_PATH]; GetModuleFileNameW(NULL, filename, MAX_PATH); @@ -253,10 +265,10 @@ s_proc_handle *fork_process() { if (!CREATE_SUSPENDED_(filename, GetCommandLineW(), si, info)) return (void *) -1; + // Copy context over TCHAR mapping_name[128]; _sntprintf(mapping_name, 128, g_mapping_name, info.dwProcessId); - // Copy context over HANDLE sharedMem = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, @@ -285,13 +297,12 @@ s_proc_handle *fork_process() { .suite = *g_worker_context.suite, .func = g_worker_context.func, .pipe = *g_worker_context.pipe, - .resumed = 0, + .sync = sync, }; if (g_worker_context.param) { ctx->param = *g_worker_context.param, memcpy(ctx + 1, g_worker_context.param->ptr, g_worker_context.param->size); - ctx->param.ptr = ctx + 1; } if (g_worker_context.suite->data) @@ -301,14 +312,11 @@ s_proc_handle *fork_process() { goto failure; // wait until the child has initialized itself - while (!ctx->resumed) { - DWORD exit; - if (!GetExitCodeProcess(info.hProcess, &exit)) - continue; + HANDLE handles[] = { info.hProcess, sync }; + DWORD wres = WaitForMultipleObjects(sizeof (handles) / sizeof (HANDLE), handles, FALSE, INFINITE); + if (wres == WAIT_OBJECT_0) + goto failure; - if (exit != STILL_ACTIVE) - goto failure; - } CloseHandle(info.hThread); UnmapViewOfFile(ctx); From 3d6bd319058e9cb2c23f36c2da44fd26b55bc69d Mon Sep 17 00:00:00 2001 From: Snaipe Date: Fri, 18 Sep 2015 04:47:49 -0700 Subject: [PATCH 08/14] Removed the hard 1024-byte limit on passed user data for parameterized tests --- src/compat/process.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/compat/process.c b/src/compat/process.c index d5d6476..8a67623 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -100,10 +100,10 @@ struct full_context { struct pipe_handle pipe; struct test_single_param param; HANDLE sync; + DWORD extra_size; }; static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker_%lu"); -#define MAPPING_SIZE 1024 static struct full_context local_ctx; #endif @@ -183,7 +183,7 @@ int resume_child(void) { FILE_MAP_ALL_ACCESS, 0, 0, - MAPPING_SIZE); + sizeof (struct full_context)); if (ctx == NULL) { CloseHandle(sharedMem); @@ -191,18 +191,32 @@ int resume_child(void) { } local_ctx = *ctx; + UnmapViewOfFile(ctx); + struct test_single_param *param = NULL; if (local_ctx.param.size != 0) { - local_ctx.param.ptr = ctx + 1; + ctx = (struct full_context*) MapViewOfFile(sharedMem, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof (struct full_context) + local_ctx.extra_size); + + if (ctx == NULL) { + CloseHandle(sharedMem); + exit(-1); + } 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); + memcpy(param + 1, ctx + 1, param->size); + UnmapViewOfFile(ctx); } + CloseHandle(sharedMem); + g_worker_context = (struct worker_context) { .test = &local_ctx.test, .suite = &local_ctx.suite, @@ -214,12 +228,7 @@ int resume_child(void) { local_ctx.test.data = &local_ctx.test_data; local_ctx.suite.data = &local_ctx.suite_data; - HANDLE sync = ctx->sync; - - UnmapViewOfFile(ctx); - CloseHandle(sharedMem); - - SetEvent(sync); + SetEvent(local_ctx.sync); SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); @@ -269,12 +278,16 @@ s_proc_handle *fork_process() { TCHAR mapping_name[128]; _sntprintf(mapping_name, 128, g_mapping_name, info.dwProcessId); + DWORD mapping_size = sizeof (struct full_context); + if (g_worker_context.param) + mapping_size += g_worker_context.param->size; + HANDLE sharedMem = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, - MAPPING_SIZE, + mapping_size, mapping_name); if (sharedMem == NULL || GetLastError() == ERROR_ALREADY_EXISTS) @@ -284,7 +297,7 @@ s_proc_handle *fork_process() { FILE_MAP_ALL_ACCESS, 0, 0, - MAPPING_SIZE); + mapping_size); if (ctx == NULL) { CloseHandle(sharedMem); @@ -301,7 +314,8 @@ s_proc_handle *fork_process() { }; if (g_worker_context.param) { - ctx->param = *g_worker_context.param, + ctx->extra_size = g_worker_context.param->size; + ctx->param = *g_worker_context.param; memcpy(ctx + 1, g_worker_context.param->ptr, g_worker_context.param->size); } From 12a0d6c190d519deb946e81da6a74d7af5dcaa80 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Fri, 18 Sep 2015 05:15:17 -0700 Subject: [PATCH 09/14] Fixed invalid setjmp usage --- src/core/runner.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/runner.c b/src/core/runner.c index 5380f07..cd3f13b 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -59,7 +59,6 @@ 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; @@ -187,11 +186,14 @@ static void run_test_child(struct criterion_test *test, struct timespec_compat ts; if (!setjmp(g_pre_test)) { timer_start(&ts); - if (!test->data->param_) - (test->test ? test->test : nothing)(); - else - (test->test ? (void(*)(void*)) test->test - : nothing_ptr)(g_worker_context.param->ptr); + if (test->test) { + if (!test->data->param_) { + test->test(); + } else { + void(*param_test_func)(void *) = (void(*)(void*)) test->test; + param_test_func(g_worker_context.param->ptr); + } + } } double elapsed_time; From d66df33a5c1d0b3b6bef57ccd2230e225e6f40db Mon Sep 17 00:00:00 2001 From: Snaipe Date: Fri, 18 Sep 2015 08:56:21 -0700 Subject: [PATCH 10/14] Updated samples and added cleanup procedure --- include/criterion/parameterized.h | 8 ++-- include/criterion/types.h | 9 +++++ samples/parameterized.c | 63 ++++++++++++++++++++++++++++++- src/core/runner.c | 3 ++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/include/criterion/parameterized.h b/include/criterion/parameterized.h index a3c199f..135fbe4 100644 --- a/include/criterion/parameterized.h +++ b/include/criterion/parameterized.h @@ -38,11 +38,11 @@ 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)) +# define cr_make_param_array(Type, Array, ...) \ + criterion_test_params(sizeof (Type), (Array), __VA_ARGS__) # else -# define cr_make_param_array(Type, Array, Length) \ - (struct criterion_test_params) { sizeof (Type), (void*)(Array), (Length) } +# define cr_make_param_array(Type, Array, ...) \ + (struct criterion_test_params) { sizeof (Type), (void*)(Array), __VA_ARGS__ } # endif #endif /* !CRITERION_PARAMETERIZED_H_ */ diff --git a/include/criterion/types.h b/include/criterion/types.h index bbb11aa..93a54c9 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -42,6 +42,7 @@ struct criterion_test_params { size_t size; void *params; size_t length; + void (*cleanup)(struct criterion_test_params *); # ifdef __cplusplus constexpr criterion_test_params(size_t size, void *params, size_t length) @@ -49,6 +50,14 @@ struct criterion_test_params { , params(params) , length(length) {} + + constexpr criterion_test_params(size_t size, void *params, size_t length, + void (*cleanup)(struct criterion_test_params *)) + : size(size) + , params(params) + , length(length) + , cleanup(cleanup) + {} # endif }; diff --git a/samples/parameterized.c b/samples/parameterized.c index 0d9ab2e..9c6c863 100644 --- a/samples/parameterized.c +++ b/samples/parameterized.c @@ -1,4 +1,7 @@ #include +#include + +// Basic usage ParameterizedTestParameters(params, str) { static const char *strings[] = { @@ -9,5 +12,63 @@ ParameterizedTestParameters(params, str) { } ParameterizedTest(const char **str, params, str) { - cr_assert(0, "Parameter: %s", *str); + cr_assert_fail("Parameter: %s", *str); +} + +// Multiple parameters must be coalesced in a single parameter + +struct parameter_tuple { + int i; + double d; +}; + +ParameterizedTestParameters(params, multiple) { + static struct parameter_tuple params[] = { + {1, 2}, + {3, 4}, + {5, 6}, + }; + + return cr_make_param_array(struct parameter_tuple, params, sizeof (params) / sizeof (struct parameter_tuple)); +} + +ParameterizedTest(struct parameter_tuple *tup, params, multiple) { + cr_assert_fail("Parameters: (%d, %f)", tup->i, tup->d); +} + +// Cleaning up dynamically generated parameters + +// Note: Do **NOT** embed dynamically allocated pointers inside of structures +// or this will fail on windows + +void free_params(struct criterion_test_params *crp) { + struct parameter_tuple_dyn *tuples = crp->params; + for (size_t i = 0; i < crp->length; ++i) { + struct parameter_tuple_dyn *tup = &tuples[i]; + free(tup->d); + } + free(crp->params); +} + +double *gen_double(double d) { + double *ptr = malloc(sizeof(double)); + printf("%p\n", ptr); + fflush(NULL); + *ptr = d; + return ptr; +} + +ParameterizedTestParameters(params, cleanup) { + const size_t nb_tuples = 3; + + struct parameter_tuple_dyn *params = malloc(sizeof(struct parameter_tuple_dyn) * nb_tuples); + params[0] = (struct parameter_tuple_dyn) { 1, gen_double(2) }; + params[1] = (struct parameter_tuple_dyn) { 3, gen_double(4) }; + params[2] = (struct parameter_tuple_dyn) { 5, gen_double(6) }; + + return cr_make_param_array(struct parameter_tuple_dyn, params, nb_tuples, free_params); +} + +ParameterizedTest(struct parameter_tuple_dyn *tup, params, cleanup) { + cr_assert_fail("Parameters: (%d, %p)", tup->i, tup->d); } diff --git a/src/core/runner.c b/src/core/runner.c index cd3f13b..a998843 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -396,6 +396,9 @@ static void run_test_param(struct criterion_global_stats *stats, if (!is_runner()) break; } + + if (params.cleanup) + params.cleanup(¶ms); } static void run_test_switch(struct criterion_global_stats *stats, From d4472a1fdcf3814ca9a3b2487d9f0db4cae52b8a Mon Sep 17 00:00:00 2001 From: Snaipe Date: Sat, 19 Sep 2015 18:45:48 +0200 Subject: [PATCH 11/14] Fixed header warnings and removed dynamically-embeded data in parameterized sample --- include/criterion/parameterized.h | 2 +- include/criterion/types.h | 1 + samples/parameterized.c | 27 +++++++-------------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/include/criterion/parameterized.h b/include/criterion/parameterized.h index 135fbe4..5005e3e 100644 --- a/include/criterion/parameterized.h +++ b/include/criterion/parameterized.h @@ -42,7 +42,7 @@ criterion_test_params(sizeof (Type), (Array), __VA_ARGS__) # else # define cr_make_param_array(Type, Array, ...) \ - (struct criterion_test_params) { sizeof (Type), (void*)(Array), __VA_ARGS__ } + (struct criterion_test_params) { .size = sizeof (Type), (void*)(Array), __VA_ARGS__ } # endif #endif /* !CRITERION_PARAMETERIZED_H_ */ diff --git a/include/criterion/types.h b/include/criterion/types.h index 93a54c9..7e98bc8 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -49,6 +49,7 @@ struct criterion_test_params { : size(size) , params(params) , length(length) + , cleanup(nullptr) {} constexpr criterion_test_params(size_t size, void *params, size_t length, diff --git a/samples/parameterized.c b/samples/parameterized.c index 9c6c863..6c58b9a 100644 --- a/samples/parameterized.c +++ b/samples/parameterized.c @@ -42,33 +42,20 @@ ParameterizedTest(struct parameter_tuple *tup, params, multiple) { // or this will fail on windows void free_params(struct criterion_test_params *crp) { - struct parameter_tuple_dyn *tuples = crp->params; - for (size_t i = 0; i < crp->length; ++i) { - struct parameter_tuple_dyn *tup = &tuples[i]; - free(tup->d); - } free(crp->params); } -double *gen_double(double d) { - double *ptr = malloc(sizeof(double)); - printf("%p\n", ptr); - fflush(NULL); - *ptr = d; - return ptr; -} - ParameterizedTestParameters(params, cleanup) { const size_t nb_tuples = 3; - struct parameter_tuple_dyn *params = malloc(sizeof(struct parameter_tuple_dyn) * nb_tuples); - params[0] = (struct parameter_tuple_dyn) { 1, gen_double(2) }; - params[1] = (struct parameter_tuple_dyn) { 3, gen_double(4) }; - params[2] = (struct parameter_tuple_dyn) { 5, gen_double(6) }; + struct parameter_tuple *params = malloc(sizeof(struct parameter_tuple) * nb_tuples); + params[0] = (struct parameter_tuple) { 1, 2 }; + params[1] = (struct parameter_tuple) { 3, 4 }; + params[2] = (struct parameter_tuple) { 5, 6 }; - return cr_make_param_array(struct parameter_tuple_dyn, params, nb_tuples, free_params); + return cr_make_param_array(struct parameter_tuple, params, nb_tuples, free_params); } -ParameterizedTest(struct parameter_tuple_dyn *tup, params, cleanup) { - cr_assert_fail("Parameters: (%d, %p)", tup->i, tup->d); +ParameterizedTest(struct parameter_tuple *tup, params, cleanup) { + cr_assert_fail("Parameters: (%d, %f)", tup->i, tup->d); } From ef0d88326cf3934ad44ed08899c7a87d2afb6e81 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Sat, 19 Sep 2015 18:47:19 +0200 Subject: [PATCH 12/14] Added regression test for parameterized sample --- .../outputs/parameterized.c.bin.err.expected | 19 +++++++++++++++++++ .../outputs/parameterized.c.bin.out.expected | 0 2 files changed, 19 insertions(+) create mode 100644 samples/outputs/parameterized.c.bin.err.expected create mode 100644 samples/outputs/parameterized.c.bin.out.expected diff --git a/samples/outputs/parameterized.c.bin.err.expected b/samples/outputs/parameterized.c.bin.err.expected new file mode 100644 index 0000000..f4b3134 --- /dev/null +++ b/samples/outputs/parameterized.c.bin.err.expected @@ -0,0 +1,19 @@ +[----] parameterized.c:60: Assertion failed: Parameters: (1, 2.000000) +[FAIL] params::cleanup: (0.00s) +[----] parameterized.c:60: Assertion failed: Parameters: (3, 4.000000) +[FAIL] params::cleanup: (0.00s) +[----] parameterized.c:60: Assertion failed: Parameters: (5, 6.000000) +[FAIL] params::cleanup: (0.00s) +[----] parameterized.c:36: Assertion failed: Parameters: (1, 2.000000) +[FAIL] params::multiple: (0.00s) +[----] parameterized.c:36: Assertion failed: Parameters: (3, 4.000000) +[FAIL] params::multiple: (0.00s) +[----] parameterized.c:36: Assertion failed: Parameters: (5, 6.000000) +[FAIL] params::multiple: (0.00s) +[----] parameterized.c:15: Assertion failed: Parameter: foo +[FAIL] params::str: (0.00s) +[----] parameterized.c:15: Assertion failed: Parameter: bar +[FAIL] params::str: (0.00s) +[----] parameterized.c:15: Assertion failed: Parameter: baz +[FAIL] params::str: (0.00s) +[====] Synthesis: Tested: 9 | Passing: 0 | Failing: 9 | Crashing: 0  diff --git a/samples/outputs/parameterized.c.bin.out.expected b/samples/outputs/parameterized.c.bin.out.expected new file mode 100644 index 0000000..e69de29 From 8fab533cad8948347ece43afabb5d2f6c57fdaa5 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Sun, 20 Sep 2015 13:53:33 +0200 Subject: [PATCH 13/14] Added documentation for parameterized tests --- doc/index.rst | 1 + doc/parameterized.rst | 110 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 doc/parameterized.rst diff --git a/doc/index.rst b/doc/index.rst index 56fbdf4..1b2183b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -10,6 +10,7 @@ Criterion assert hooks env + parameterized theories internal faq diff --git a/doc/parameterized.rst b/doc/parameterized.rst new file mode 100644 index 0000000..8a7fc04 --- /dev/null +++ b/doc/parameterized.rst @@ -0,0 +1,110 @@ +Using parameterized tests +========================= + +Parameterized tests are useful to repeat a specific test logic over a finite +set of parameters. + +Due to limitations on how generated parameters are passed, parameterized tests +can only accept one pointer parameter; however, this is not that much of a +problem since you can just pass a structure containing the context you need. + +Adding parameterized tests +-------------------------- + +Adding parameterized tests is done by defining the parameterized test function, +and the parameter generator function: + +.. code-block:: c + + #include + + ParameterizedTestParameter(suite_name, test_name) = { + void *params; + size_t nb_params; + + // generate parameter set + return cr_make_param_array(Type, params, nb_params); + } + + ParameterizedTest(Type *param, suite_name, test_name) { + // contents of the test + } + +``suite_name`` and ``test_name`` are the identifiers of the test suite and +the test, respectively. These identifiers must follow the language +identifier format. + +``Type`` is the compound type of the generated array. ``params`` and ``nb_params`` +are the pointer and the length of the generated array, respectively. + +Passing multiple parameters +--------------------------- + +As said earlier, parameterized tests only take one parameter, so passing +multiple parameters is, in the strict sense, not possible. However, one can +easily use a struct to hold the context as a workaround: + +.. code-block:: c + + #include + + struct my_params { + int param0; + double param1; + ... + }; + + ParameterizedTestParameter(suite_name, test_name) = { + size_t nb_params = 32; + struct my_params *params = malloc(sizeof (struct my_params) * nb_params); + + // generate parameter set + + params[0] = ... + params[1] = ... + + ... + + return cr_make_param_array(struct my_params, params, nb_params); + } + + ParameterizedTest(struct my_params *param, suite_name, test_name) { + // access param.param0, param.param1, ... + } + +There is, however, one absolute rule that must be respected, unless you don't +want your tests to run on windows, ever: parameters must not contain any +pointer to dynamically allocated data. + +Hence, this is not permitted: + +.. code-block:: c + + #include + + struct my_params { + int *some_int_ptr; + }; + + ParameterizedTestParameter(suite_name, test_name) = { + static my_params param = { + .some_int_ptr = malloc(sizeof (int)); // Don't do this. + }; + *param.some_int_ptr = 42; + + return cr_make_param_array(struct my_params, ¶m, 1); + } + +and **will crash the test** on Windows. + +Configuring parameterized tests +------------------------------- + +Parameterized tests can optionally recieve configuration parameters to alter +their own behaviour, and are applied to each iteration of the parameterized +test individually (this means that the initialization and finalization runs once +per iteration). +Those parameters are the same ones as the ones of the ``Test`` macro function +(c.f. :ref:`test-config-ref`). + + From 0da61ed2c5b54cd21034cc273111aa9106b7c22d Mon Sep 17 00:00:00 2001 From: Snaipe Date: Sun, 20 Sep 2015 14:01:31 +0200 Subject: [PATCH 14/14] Changed parameterized tests to mirror the fix to #50 --- include/criterion/parameterized.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/criterion/parameterized.h b/include/criterion/parameterized.h index 5005e3e..6bfec4e 100644 --- a/include/criterion/parameterized.h +++ b/include/criterion/parameterized.h @@ -25,13 +25,15 @@ .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_; \ + }; \ + SECTION_("cr_tst") \ + struct criterion_test *IDENTIFIER_(Category, Name, ptr) \ + = &IDENTIFIER_(Category, Name, meta) SECTION_SUFFIX_; \ CR_PARAM_TEST_PROTOTYPE_(Param, Category, Name) # define ParameterizedTestParameters(Category, Name) \