diff options
| author | JP Appel <jeanpierre.appel01@gmail.com> | 2024-07-22 14:32:29 -0400 |
|---|---|---|
| committer | JP Appel <jeanpierre.appel01@gmail.com> | 2024-07-22 14:32:29 -0400 |
| commit | 337ffc577ad20a457b086dee8b3af8bcde8c2881 (patch) | |
| tree | 5104d9d1edde117667608e0e1c924a6368786e92 /eztester.c | |
| parent | da6877b8be4ecd6a27e076766bf0b6b6f02959cd (diff) | |
run tests in a separate process
Diffstat (limited to 'eztester.c')
| -rw-r--r-- | eztester.c | 199 |
1 files changed, 141 insertions, 58 deletions
@@ -1,48 +1,28 @@ #include "eztester.h" -#include <assert.h> +#include <signal.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> -eztester_list *eztester_create_list(const size_t capacity) { - eztester_list *list = malloc(sizeof(eztester_list)); - if (!list) { - return NULL; - } +#include <string.h> - list->length = 0; +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/wait.h> - if (capacity == 0) { - list->tests = NULL; - list->capacity = 0; - return list; - } +#include <stdbool.h> - eztester_test *tests = malloc(capacity * sizeof(eztester_test)); - if (!tests) { - free(list); - return NULL; - } - list->tests = tests; +struct shared_mem { + int work_in_queue : 1; + eztester_status status : 2; + eztester_behavior behavior : 2; + size_t index; +}; - list->capacity = capacity; - return list; -} -void eztester_register(eztester_list *test_list, const eztester_test new_test) { - if (test_list->capacity == 0) { - test_list->tests = realloc(test_list->tests, 2 * sizeof(eztester_test)); - test_list->capacity = 2; - } - if (test_list->capacity <= test_list->length + 1) { - test_list->capacity *= 2; - test_list->tests = - realloc(test_list->tests, test_list->capacity * sizeof(eztester_test)); - assert(test_list->tests); - } - - test_list->tests[test_list->length++] = new_test; -} +volatile sig_atomic_t child_premature_exit = 0; +volatile sig_atomic_t child_premature_exit_signal; +volatile sig_atomic_t child_premature_exit_status = 0; void print_test_results(const size_t tests_run, const size_t tests_passed, const size_t num_tests) { @@ -56,17 +36,122 @@ void print_test_results(const size_t tests_run, const size_t tests_passed, } } +void premature_exit(const char *message, const pid_t worker, + const size_t current_test, const size_t tests_passed, + const size_t num_tests) { + fprintf(stderr, "%s\n", message); + if (child_premature_exit) { + if (child_premature_exit_status != 0) { + fprintf(stderr, "Worker Process exited with status: %d\n", + child_premature_exit_status); + } + if (child_premature_exit_signal > 0) { + fprintf(stderr, "Worker Process exited because of signal: %s", + strsignal(child_premature_exit_signal)); + } + } + kill(worker, SIGTERM); + wait(NULL); + print_test_results(current_test, tests_passed, num_tests); + exit(1); +} + +void worker(volatile struct shared_mem *mem, const eztester_list *list) { + while (mem->index < list->length) { + // wait for work + while (!mem->work_in_queue) { + usleep(1000 * 50); + } + mem->status = list->tests[mem->index].runner(); + + // check if worker should die + if (mem->status == TEST_ERROR || + (mem->status == TEST_FAIL && mem->behavior != CONTINUE_ALL) || + (mem->status == TEST_WARNING && mem->behavior == EXIT_ON_FAIL)) { + exit(1); + } + + mem->work_in_queue = false; + } + exit(0); +} + +void chld_handler(int signum) { + int status; + pid_t pid; + child_premature_exit = 1; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + if (WIFEXITED(status)) { + child_premature_exit_status = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + child_premature_exit_signal = WTERMSIG(status); + } + } +} + void eztester_run(eztester_list *test_list, eztester_behavior behavior) { + const size_t shm_size = sizeof(struct shared_mem); + int shm_fd = shm_open("/test_queue", O_RDWR | O_CREAT, 0666); + if (shm_fd == -1) { + perror("shm_open"); + exit(1); + } + + if (ftruncate(shm_fd, shm_size) == -1) { + perror("ftruncate"); + exit(1); + } + + void *shm_ptr = + mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + if (shm_ptr == MAP_FAILED) { + perror("mmap"); + exit(1); + } + + struct shared_mem *mem = shm_ptr; + mem->index = 0; + mem->work_in_queue = false; + mem->behavior = behavior; + + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + if (pid == 0) { + worker(mem, test_list); + } + eztester_status status; eztester_test test; const size_t length = test_list->length; size_t pass_count = 0; + // set child signal handler + signal(SIGCHLD, chld_handler); + for (size_t i = 0; i < test_list->length; i++) { test = test_list->tests[i]; + + mem->index = i; + mem->work_in_queue = true; + printf("[%03zu/%03zu] Testing: %s\n", i + 1, length, test.name); fflush(stdout); - status = test.runner(); + if (child_premature_exit) { + premature_exit("Worker Process ended prematurely!", pid, i + 1, + pass_count, length); + } + while (mem->work_in_queue) { + usleep(1000 * 50); + if (child_premature_exit) { + premature_exit("Worker Process ended prematurely!", pid, i + 1, + pass_count, length); + } + } + status = mem->status; switch (status) { case TEST_PASS: @@ -77,33 +162,42 @@ void eztester_run(eztester_list *test_list, eztester_behavior behavior) { case TEST_WARNING: printf("[%03zu/%03zu] %s Result: Warning\n", i + 1, length, test.name); if (behavior == EXIT_ON_WARNING) { - printf("Warning occured, Exitting\n"); - print_test_results(i + 1, pass_count, length); - exit(1); + premature_exit("Warning occured, Exitting", pid, i + 1, pass_count, + length); } break; case TEST_FAIL: printf("[%03zu/%03zu] %s Result: Fail\n", i + 1, length, test.name); if (behavior != CONTINUE_ALL) { - printf("Failure occured, Exitting\n"); - print_test_results(i + 1, pass_count, length); - exit(1); + premature_exit("Failure occured, Exitting", pid, i + 1, pass_count, + length); } break; case TEST_ERROR: - printf("[%03zu/%03zu] %s Result: Error\nFatal Error occured! Exiting\n", - i + 1, length, test.name); - print_test_results(i + 1, pass_count, length); - exit(1); + printf("[%03zu/%03zu] %s Result: Error\n", i + 1, length, test.name); + premature_exit("Fatal Error occured! Exiting", pid, i + 1, pass_count, + length); break; } } + signal(SIGCHLD, SIG_DFL); print_test_results(length, pass_count, length); + + // unmap memory + if (munmap(shm_ptr, shm_size) == -1) { + perror("munmap"); + exit(1); + } + // unlink shared memory + if (shm_unlink("/test_queue") == -1) { + perror("shm_unlink"); + exit(1); + } } -void eztester_test_print(const char *format, ...) { +void eztester_test_print(const char *restrict format, ...) { va_list args; va_start(args, format); printf("> "); @@ -112,17 +206,6 @@ void eztester_test_print(const char *format, ...) { va_end(args); } -void eztester_clear_list(eztester_list *test_list) { - free(test_list->tests); - test_list->length = 0; - test_list->capacity = 0; -} - -void eztester_destroy_list(eztester_list *test_list) { - eztester_clear_list(test_list); - free(test_list); -} - eztester_status eztester_always_pass_test() { return TEST_PASS; } eztester_status eztester_always_warn_test() { return TEST_WARNING; } eztester_status eztester_always_fail_test() { return TEST_FAIL; } |
