diff --git a/src/assertions.h b/src/assertions.h index 7fa4330..a39b463 100644 --- a/src/assertions.h +++ b/src/assertions.h @@ -4,6 +4,7 @@ #include "config.h" #include "evaluators.h" #include "formatters.h" +#include "fork.h" #ifndef CAUGHT_ASSERTIONS #define CAUGHT_ASSERTIONS @@ -72,4 +73,18 @@ bool caught_internal_handle_assertion_result(caught_internal_assertion_result as #define EXPECT_STR_PTR(lhs, op, rhs) \ CAUGHT_INTERNAL_EXPECT_HANDLE(STR_PTR, char **, lhs, op, rhs, caught_internal_evaluator_str_ptr, caught_internal_formatter_str_ptr) +#define CAUGHT_INTERNAL_EXPECT_TERMINATE_HANDLE(func_name, expected_status, execute_block) \ + do \ + { \ + CAUGHT_INTERNAL_FORK(execute_block) \ + CAUGHT_INTERNAL_EXPECT_HANDLE(func_name, caught_internal_process_status, caught_internal_fork_child_status, ==, expected_status, \ + caught_internal_evaluator_exit_status, caught_internal_formatter_exit_status); \ + } while (0) + +#define EXPECT_EXIT(expected_status, execute_block) \ + CAUGHT_INTERNAL_EXPECT_TERMINATE_HANDLE(EXIT, create_caught_internal_process_status(0, expected_status), execute_block) + +#define EXPECT_SIGNAL(expected_status, execute_block) \ + CAUGHT_INTERNAL_EXPECT_TERMINATE_HANDLE(EXIT, create_caught_internal_process_status(1, expected_status), execute_block) + #endif diff --git a/src/evaluators.c b/src/evaluators.c index c4227f9..cbb89fe 100644 --- a/src/evaluators.c +++ b/src/evaluators.c @@ -94,3 +94,14 @@ bool caught_internal_evaluator_str_ptr(char **lhs, enum caught_operator operator CAUGHT_GENERATE_GENERIC_EVALUATOR_NULL_GUARD(lhs, operator, rhs); return caught_internal_evaluator_str(*lhs, operator, * rhs); } + +bool caught_internal_evaluator_exit_status(caught_internal_process_status lhs, enum caught_operator operator, caught_internal_process_status rhs) +{ + if (operator!= CAUGHT_OP_EQUAL) + { + fprintf(stderr, "Cannot compare exit statuses with %s, only == is supported!", caught_operator_to_str(operator)); + exit(1); + } + + return lhs.type == rhs.type && lhs.status == rhs.status; +} diff --git a/src/evaluators.h b/src/evaluators.h index 4747bb5..745b5b0 100644 --- a/src/evaluators.h +++ b/src/evaluators.h @@ -1,5 +1,6 @@ #include #include +#include "fork.h" #ifndef CAUGHT_EVALUATORS #define CAUGHT_EVALUATORS @@ -52,6 +53,8 @@ bool caught_internal_evaluator_char_ptr(char *lhs, enum caught_operator operator bool caught_internal_evaluator_str(char *lhs, enum caught_operator operator, char * rhs); bool caught_internal_evaluator_str_ptr(char **lhs, enum caught_operator operator, char ** rhs); +bool caught_internal_evaluator_exit_status(caught_internal_process_status lhs, enum caught_operator operator, caught_internal_process_status rhs); + // Uses default operators (==, <=, >=, ...) to compare lhs to rhs #define CAUGHT_GENERATE_GENERIC_EVALUATOR(lhs, operator, rhs) \ switch (operator) \ diff --git a/src/fork.c b/src/fork.c new file mode 100644 index 0000000..0452494 --- /dev/null +++ b/src/fork.c @@ -0,0 +1,19 @@ +#include "fork.h" + +caught_internal_process_status create_caught_internal_process_status(int type, int status) +{ + caught_internal_process_status new = { + .type = type, + .status = status, + .status_str = NULL, + }; + if (type == 1 && status >= 1 && status <= 31) + { + new.status_str = signal_names[status - 1]; + } + else if (type == 0 && status >= 0 && status <= 1) + { + new.status_str = exit_status_names[status]; + } + return new; +} diff --git a/src/fork.h b/src/fork.h new file mode 100644 index 0000000..52276c2 --- /dev/null +++ b/src/fork.h @@ -0,0 +1,59 @@ +#ifndef CAUGHT_FORK +#define CAUGHT_FORK + +#include +#include +#include +#include +#include + +typedef struct caught_internal_process_status +{ + int type; // 0 for exit status, 1 for signal status + int status; // The exit status or signal, depending on above + const char *status_str; // The string signal, if there is one +} caught_internal_process_status; + +static const char *exit_status_names[] = {"EXIT_SUCCESS", "EXIT_FAILURE"}; + +static const char *signal_names[] = { + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", + "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", + "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", + "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", + "SIGIO", "SIGPWR", "SIGSYS"}; + +caught_internal_process_status create_caught_internal_process_status(int type, int status); + +#define CAUGHT_INTERNAL_FORK(child_execute_block) \ + caught_internal_process_status caught_internal_fork_child_status = {}; \ + pid_t caught_internal_pid = fork(); \ + if (caught_internal_pid == -1) \ + { \ + perror("Caught: failed to fork\n"); \ + exit(EXIT_FAILURE); \ + } \ + if (caught_internal_pid == 0) \ + { \ + child_execute_block \ + \ + perror("Caught: fork segment must call exit to prevent fork bombs\n"); \ + exit(EXIT_FAILURE); \ + } \ + else \ + { \ + int caught_internal_status = 0; \ + waitpid(caught_internal_pid, &caught_internal_status, 0); \ + if (WIFEXITED(caught_internal_status)) \ + { \ + caught_internal_fork_child_status = \ + create_caught_internal_process_status(0, WEXITSTATUS(caught_internal_status)); \ + } \ + else if (WIFSIGNALED(caught_internal_status)) \ + { \ + caught_internal_fork_child_status = \ + create_caught_internal_process_status(1, WTERMSIG(caught_internal_status)); \ + } \ + } + +#endif diff --git a/src/formatters.c b/src/formatters.c index 1f80c04..79b684d 100644 --- a/src/formatters.c +++ b/src/formatters.c @@ -53,3 +53,13 @@ char *caught_internal_formatter_str_ptr(char **value) CAUGHT_INTERNAL_FORMATTER_NULL_GUARD(value) return caught_internal_formatter_str(*value); } + +char *caught_internal_formatter_exit_status(caught_internal_process_status value) +{ + const char *type = value.type ? "Signal" : "Exit code"; + if (value.status_str) + { + CAUGHT_INTERNAL_FORMATTER_FORMAT("%s (%i)", value.status_str, value.status) + } + CAUGHT_INTERNAL_FORMATTER_FORMAT("%s %i", type, value.status) +} diff --git a/src/formatters.h b/src/formatters.h index a17b633..a35a138 100644 --- a/src/formatters.h +++ b/src/formatters.h @@ -1,6 +1,7 @@ #define _GNU_SOURCE #include #include +#include "fork.h" #ifndef CAUGHT_FORMATTERS #define CAUGHT_FORMATTERS @@ -23,9 +24,11 @@ char *caught_internal_formatter_char_ptr(char *value); char *caught_internal_formatter_str(char *value); char *caught_internal_formatter_str_ptr(char **value); -#define CAUGHT_INTERNAL_FORMATTER_FORMAT(fstr, value) \ - char *result; \ - asprintf(&result, fstr, value); \ +char *caught_internal_formatter_exit_status(caught_internal_process_status value); + +#define CAUGHT_INTERNAL_FORMATTER_FORMAT(fstr, ...) \ + char *result; \ + asprintf(&result, fstr, __VA_ARGS__); \ return result; #define CAUGHT_INTERNAL_FORMATTER_NULL_GUARD(value) \ if (value == NULL) \ diff --git a/tests/exit.c b/tests/exit.c new file mode 100644 index 0000000..ec1e307 --- /dev/null +++ b/tests/exit.c @@ -0,0 +1,21 @@ +// NOTE: The location of this include might differ in your code depending on location +// For example, it could be: #include "caught.h" +#include "../src/caught.h" + +TEST("exit - success") +{ + EXPECT_EXIT(EXIT_SUCCESS, { + exit(EXIT_SUCCESS); + }); + + EXPECT_INT(1 + 1, ==, 2); // This still runs +} + +TEST("exit - failure") +{ + EXPECT_EXIT(EXIT_FAILURE, { + exit(EXIT_FAILURE); + }); + + EXPECT_INT(1 + 1, ==, 2); // This still runs +} diff --git a/tests/signal.c b/tests/signal.c new file mode 100644 index 0000000..bc86560 --- /dev/null +++ b/tests/signal.c @@ -0,0 +1,22 @@ +// NOTE: The location of this include might differ in your code depending on location +// For example, it could be: #include "caught.h" +#include "../src/caught.h" + +TEST("signal - SIGABRT") +{ + EXPECT_SIGNAL(SIGABRT, { + raise(SIGABRT); + }); + + EXPECT_INT(1 + 1, ==, 2); // This still runs +} + +TEST("signal - SIGSEGV") +{ + EXPECT_SIGNAL(SIGSEGV, { + int *ptr = NULL; + ptr[1] = 123; // BAD! + }); + + EXPECT_INT(1 + 1, ==, 2); // This still runs +}