diff --git a/include/criterion/logging.h b/include/criterion/logging.h index c8c0205..17baf4b 100644 --- a/include/criterion/logging.h +++ b/include/criterion/logging.h @@ -104,6 +104,7 @@ struct criterion_output_provider { void (*log_pre_test )(struct criterion_test *test); void (*log_assert )(struct criterion_assert_stats *stats); void (*log_theory_fail )(struct criterion_theory_stats *stats); + void (*log_test_timeout )(struct criterion_test_stats *stats); void (*log_test_crash )(struct criterion_test_stats *stats); void (*log_other_crash )(struct criterion_test_stats *stats); void (*log_abnormal_exit)(struct criterion_test_stats *stats); diff --git a/include/criterion/stats.h b/include/criterion/stats.h index 8d6d4ca..ec80c2f 100644 --- a/include/criterion/stats.h +++ b/include/criterion/stats.h @@ -44,6 +44,7 @@ struct criterion_test_stats { int signal; int exit_code; float elapsed_time; + bool timed_out; unsigned progress; const char *file; diff --git a/include/criterion/types.h b/include/criterion/types.h index aa05e03..e9e948f 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -44,6 +44,7 @@ struct criterion_test_extra_data { int exit_code; bool disabled; const char *description; + float timeout; void *data; }; diff --git a/po/fr.po b/po/fr.po index 79c850d..0ca50cf 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: criterion 1.0.0\n" "Report-Msgid-Bugs-To: franklinmathieu+criterion@gmail.com\n" -"POT-Creation-Date: 2015-09-10 03:35+0200\n" +"POT-Creation-Date: 2015-09-11 16:47+0200\n" "PO-Revision-Date: 2015-04-03 17:58+0200\n" "Last-Translator: \n" "Language-Team: French\n" @@ -60,18 +60,23 @@ msgstr "" " La théorie %1$s::%2$s a échoué avec les paramètres suivants: (%3$s)\n" #: src/log/normal.c:61 +#, fuzzy, c-format +msgid "%1$s::%2$s: Timed out.\n" +msgstr "%1$s::%2$s: Délai expiré.\n" + +#: src/log/normal.c:62 #, c-format msgid "%1$s%2$s%3$s:%4$s%5$u%6$s: Unexpected signal caught below this line!\n" msgstr "" "%1$s%2$s%3$s:%4$s%5$u%6$s: Un signal inattendu a été reçu après cette " "ligne!\n" -#: src/log/normal.c:62 +#: src/log/normal.c:63 #, c-format msgid "%1$s::%2$s: CRASH!\n" msgstr "%1$s::%2$s: PLANTAGE!\n" -#: src/log/normal.c:63 +#: src/log/normal.c:64 #, fuzzy, c-format msgid "" "%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown." @@ -80,7 +85,7 @@ msgstr "" "%1$sAttention! Le test `%2$s::%3$s` a planté pendant son initialisation ou " "sa finalisation.%4$s\n" -#: src/log/normal.c:64 +#: src/log/normal.c:65 #, fuzzy, c-format msgid "" "%1$sWarning! The test `%2$s::%3$s` exited during its setup or teardown.%4$s\n" @@ -88,14 +93,14 @@ msgstr "" "%1$sAttention! Le test `%2$s::%3$s` a quitté pendant son initialisation ou " "sa finalisation.%4$s\n" -#: src/log/normal.c:65 +#: src/log/normal.c:66 #, c-format msgid "Running %1$s%2$lu%3$s test from %4$s%5$s%6$s:\n" msgid_plural "Running %1$s%2$lu%3$s tests from %4$s%5$s%6$s:\n" msgstr[0] "Lancement de %1$s%2$lu%3$s test dans %4$s%5$s%6$s:\n" msgstr[1] "Lancement de %1$s%2$lu%3$s tests dans %4$s%5$s%6$s:\n" -#: src/log/normal.c:67 +#: src/log/normal.c:68 #, c-format msgid "" "%1$sSynthesis: Tested: %2$s%3$lu%4$s | Passing: %5$s%6$lu%7$s | Failing: %8$s" diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 038f8c2..00b2e54 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -15,6 +15,7 @@ set(SAMPLES description.c simple.c theories.c + timeout.c signal.cc report.cc diff --git a/samples/timeout.c b/samples/timeout.c new file mode 100644 index 0000000..db37bf0 --- /dev/null +++ b/samples/timeout.c @@ -0,0 +1,12 @@ +#include + +#ifdef _WIN32 +# include +# define sleep(x) Sleep(x * 1000) +#else +# include +#endif + +Test(timeout, simple, .timeout = 1.) { + sleep(10); +} diff --git a/src/log/normal.c b/src/log/normal.c index fc5c7f0..5bae2ef 100644 --- a/src/log/normal.c +++ b/src/log/normal.c @@ -58,6 +58,7 @@ static msg_t msg_post_suite_test = N_("%1$s::%2$s: Test is disabled\n"); static msg_t msg_post_suite_suite = N_("%1$s::%2$s: Suite is disabled\n"); static msg_t msg_assert_fail = N_("%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n"); static msg_t msg_theory_fail = N_(" Theory %1$s::%2$s failed with the following parameters: (%3$s)\n"); +static msg_t msg_test_timeout = N_("%1$s::%2$s: Timed out.\n"); static msg_t msg_test_crash_line = N_("%1$s%2$s%3$s:%4$s%5$u%6$s: Unexpected signal caught below this line!\n"); static msg_t msg_test_crash = N_("%1$s::%2$s: CRASH!\n"); static msg_t msg_test_other_crash = N_("%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown.%4$s\n"); @@ -77,6 +78,7 @@ static msg_t msg_post_suite_test = "%s::%s: Test is disabled\n"; static msg_t msg_post_suite_suite = "%s::%s: Suite is disabled\n"; static msg_t msg_assert_fail = "%s%s%s:%s%d%s: Assertion failed: %s\n"; static msg_t msg_theory_fail = " Theory %s::%s failed with the following parameters: %s\n"; +static msg_t msg_test_timeout = "%s::%s: timed out.\n"; static msg_t msg_test_crash_line = "%s%s%s:%s%u%s: Unexpected signal caught below this line!\n"; static msg_t msg_test_crash = "%s::%s: CRASH!\n"; static msg_t msg_test_other_crash = "%sWarning! The test `%s::%s` crashed during its setup or teardown.%s\n"; @@ -220,12 +222,20 @@ void normal_log_theory_fail(struct criterion_theory_stats *stats) { stats->formatted_args); } +void normal_log_test_timeout(UNUSED struct criterion_test_stats *stats) { + criterion_pimportant(CRITERION_PREFIX_FAIL, + _(msg_test_timeout), + stats->test->category, + stats->test->name); +} + struct criterion_output_provider normal_logging = { .log_pre_all = normal_log_pre_all, .log_pre_init = normal_log_pre_init, .log_pre_suite = normal_log_pre_suite, .log_assert = normal_log_assert, .log_theory_fail = normal_log_theory_fail, + .log_test_timeout = normal_log_test_timeout, .log_test_crash = normal_log_test_crash, .log_other_crash = normal_log_other_crash, .log_abnormal_exit = normal_log_abnormal_exit, diff --git a/src/log/tap.c b/src/log/tap.c index 76937a0..b3180b5 100644 --- a/src/log/tap.c +++ b/src/log/tap.c @@ -118,10 +118,17 @@ void tap_log_test_crash(struct criterion_test_stats *stats) { stats->progress); } +void tap_log_test_timeout(struct criterion_test_stats *stats) { + criterion_important("not ok - %s::%s timed out\n", + stats->test->category, + stats->test->name); +} + struct criterion_output_provider tap_logging = { - .log_pre_all = tap_log_pre_all, - .log_pre_suite = tap_log_pre_suite, - .log_test_crash = tap_log_test_crash, - .log_post_test = tap_log_post_test, - .log_post_suite = tap_log_post_suite, + .log_pre_all = tap_log_pre_all, + .log_pre_suite = tap_log_pre_suite, + .log_test_crash = tap_log_test_crash, + .log_test_timeout = tap_log_test_timeout, + .log_post_test = tap_log_post_test, + .log_post_suite = tap_log_post_suite, }; diff --git a/src/posix-compat.c b/src/posix-compat.c index 291a727..2808916 100644 --- a/src/posix-compat.c +++ b/src/posix-compat.c @@ -52,9 +52,6 @@ WriteProcessMemory(Proc, &What, &What, Size, NULL); # include -# ifndef SIGALRM -# define SIGALRM 14 -# endif #else # include @@ -240,7 +237,7 @@ void wait_process(s_proc_handle *handle, int *status) { case STATUS_PRIVILEGED_INSTRUCTION: case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break; - case STATUS_TIMEOUT: sig = SIGALRM; break; + case CR_EXCEPTION_TIMEOUT: sig = SIGPROF; break; case STATUS_ACCESS_VIOLATION: case STATUS_DATATYPE_MISALIGNMENT: diff --git a/src/posix-compat.h b/src/posix-compat.h index 9f938db..c51ab1b 100644 --- a/src/posix-compat.h +++ b/src/posix-compat.h @@ -43,6 +43,9 @@ # define WTERMSIG(Status) ((Status) & 0x7F) # define WIFEXITED(Status) (WTERMSIG(Status) == 0) # define WIFSIGNALED(Status) (((signed char) (WTERMSIG(Status) + 1) >> 1) > 0) + +# define SIGPROF 27 +# define CR_EXCEPTION_TIMEOUT 0xC0001042 # else # include # endif diff --git a/src/runner.c b/src/runner.c index 5e078ea..c752d86 100644 --- a/src/runner.c +++ b/src/runner.c @@ -172,6 +172,11 @@ static void map_tests(struct criterion_test_set *set, static void run_test_child(struct criterion_test *test, struct criterion_suite *suite) { + if (suite->data && suite->data->timeout != 0 && test->data->timeout == 0) + setup_timeout((uint64_t) (suite->data->timeout * 1e9)); + else if (test->data->timeout != 0) + setup_timeout((uint64_t) (test->data->timeout * 1e9)); + send_event(PRE_INIT, NULL, 0); if (suite->data) (suite->data->init ? suite->data->init : nothing)(); @@ -274,6 +279,17 @@ static void run_test(struct criterion_global_stats *stats, struct process_status status = wait_proc(proc); if (status.kind == SIGNAL) { + if (status.status == SIGPROF) { + test_stats->timed_out = true; + double elapsed_time = test->data->timeout; + if (elapsed_time == 0 && suite->data) + elapsed_time = suite->data->timeout; + push_event(POST_TEST, .data = &elapsed_time); + push_event(POST_FINI); + log(test_timeout, test_stats); + goto cleanup; + } + if (normal_finish || !test_started) { log(other_crash, test_stats); if (!test_started) { diff --git a/src/stats.c b/src/stats.c index 04723ab..a9dbe95 100644 --- a/src/stats.c +++ b/src/stats.c @@ -209,15 +209,20 @@ static void push_post_test(s_glob_stats *stats, test->elapsed_time = (float) *data; if (test->failed_asserts > 0 + || test->timed_out || test->signal != test->test->data->signal || test->exit_code != test->test->data->exit_code) { test->failed = 1; + } + + if (test->failed) { ++stats->tests_failed; ++suite->tests_failed; } else { ++stats->tests_passed; ++suite->tests_passed; } + } static void push_test_crash(s_glob_stats *stats, diff --git a/src/timer.c b/src/timer.c index edb181e..7efb70c 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,6 +1,9 @@ #include #include +#include #include "timer.h" +#include "criterion/common.h" +#include "posix-compat.h" #define GIGA 1000000000 @@ -17,6 +20,7 @@ extern __attribute__ ((weak)) int clock_gettime(clockid_t, struct timespec *); #elif defined(__APPLE__) # include # include +# include #elif defined(_WIN32) || defined(__CYGWIN__) # define VC_EXTRALEAN # define WIN32_LEAN_AND_MEAN @@ -81,3 +85,63 @@ int timer_end(double *time, struct timespec_compat *state) { *time = (last.tv_sec - state->tv_sec) + (last.tv_nsec - state->tv_nsec) / (double) GIGA; return 1; } + +#if defined(_WIN32) || defined(__CYGWIN__) +void win_raise_timeout(UNUSED HWND hwnd, + UNUSED UINT uMsg, + UNUSED UINT_PTR idEvent, + UNUSED DWORD dwTime) { + RaiseException(CR_EXCEPTION_TIMEOUT, EXCEPTION_NONCONTINUABLE, 0, NULL); +} +#endif + +#if defined(__APPLE__) +void *wait_and_raise(void *ptr) { + uint64_t *nanos = ptr; + struct timespec elapsed = { + .tv_sec = *nanos / GIGA, + .tv_nsec = *nanos % GIGA, + }; + free(nanos); + if (!nanosleep(&elapsed, NULL)) + raise(SIGPROF); + return NULL; +} +#endif + +int setup_timeout(uint64_t nanos) { +#if defined(__APPLE__) + uint64_t *nanos_copy = malloc(sizeof (uint64_t)); + *nanos_copy = nanos; + + pthread_t thread; + int res = pthread_create(&thread, NULL, wait_and_raise, nanos_copy); + + return res ? -1 : 0; +#elif defined(_WIN32) || defined(__CYGWIN__) + return SetTimer(NULL, 0, nanos / 1000000, (TIMERPROC) win_raise_timeout) ? 0 : -1; +#elif defined(__unix__) + if (!can_measure_time()) { + errno = ENOTSUP; + return -1; + } + + timer_t timer; + int res = timer_create(CLOCK_MONOTONIC, &(struct sigevent) { + .sigev_notify = SIGEV_SIGNAL, + .sigev_signo = SIGPROF, + }, &timer); + + if (res == -1) + return res; + + struct itimerspec schedule = { + .it_value = { .tv_sec = nanos / GIGA, .tv_nsec = (nanos % GIGA) } + }; + + return timer_settime(timer, 0, &schedule, NULL); +#else + errno = ENOTSUP; + return -1; +#endif +} diff --git a/src/timer.h b/src/timer.h index 2d334cb..14f301d 100644 --- a/src/timer.h +++ b/src/timer.h @@ -1,6 +1,7 @@ #ifndef TIMER_H_ # define TIMER_H_ +# include # include # include # include @@ -13,5 +14,6 @@ struct timespec_compat { bool can_measure_time(void); int timer_start(struct timespec_compat *state); int timer_end(double *time, struct timespec_compat *state); +int setup_timeout(uint64_t nanos); #endif /* !TIMER_H_ */