#include "akerror.h" #include "err_capture.h" #include #include /* * The default unhandled-error handler is the library's last stop: it exits the * process. Both of its exits were untested -- exit(1) for a NULL context (a * handler invoked with no error at all) and, for a real one, the status handed * to akerr_exit(). tests/err_exit_status.c covers what akerr_exit() does with a * status; this covers that the handler reaches it, and the NULL case, which * never gets that far. * * The handler never returns, so each case runs in a forked child and the test * asserts the exact exit status. That is stricter than a WILL_FAIL test, which * would pass on any non-zero exit, including one caused by an unrelated bug. */ /* * Run the default handler on ctx in a child process and return the child's exit * status, or -1 if it did not exit normally. The _exit() sentinel catches a * handler that returns instead of terminating. */ static int handler_exit_status(akerr_ErrorContext *ctx) { pid_t pid = fork(); if ( pid == 0 ) { akerr_default_handler_unhandled_error(ctx); _exit(99); } int status = 0; if ( pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status) ) { return -1; } return WEXITSTATUS(status); } int main(void) { akerr_capture_install(); akerr_init(); /* No context to report: the handler has nothing to exit with but failure. */ AKERR_CHECK(handler_exit_status(NULL) == 1); /* * With a context, the status becomes the exit code. waitpid only reports * its low 8 bits, which is where AKERR_VALUE (144) lands, and it is neither * 0 nor the 1 used for the NULL case, so the two exits stay distinguishable. */ akerr_ErrorContext *slot = akerr_next_error(); AKERR_CHECK(slot != NULL); slot->refcount = 1; slot->status = AKERR_VALUE; AKERR_CHECK((AKERR_VALUE & 0xff) != 0 && (AKERR_VALUE & 0xff) != 1); AKERR_CHECK(handler_exit_status(slot) == (AKERR_VALUE & 0xff)); slot = akerr_release_error(slot); AKERR_CHECK(slot == NULL); AKERR_CHECK(akerr_slots_in_use() == 0); fprintf(stderr, "err_unhandled_null ok\n"); return 0; }