From 5c87e78d0fe8b48efe7f2ea145f06ad0f0b364b6 Mon Sep 17 00:00:00 2001 From: Randy Jordan Date: Tue, 16 Jun 2026 12:03:48 -0500 Subject: [PATCH] Exceptions --- include/ci2.h | 2 +- include/platform/error/ci2_exception.h | 188 +++++++++++++++++++++++++ src/linux_exception.c | 67 +++++++++ src/win32_exception.c | 101 +++++++++++++ tests/04_ci2_assert.c | 154 ++++++++++---------- tests/05_ci2_exception.c | 100 +++++++++++++ 6 files changed, 540 insertions(+), 72 deletions(-) create mode 100644 include/platform/error/ci2_exception.h create mode 100644 src/linux_exception.c create mode 100644 src/win32_exception.c create mode 100644 tests/05_ci2_exception.c diff --git a/include/ci2.h b/include/ci2.h index 4336444..dfa52b1 100644 --- a/include/ci2.h +++ b/include/ci2.h @@ -24,5 +24,5 @@ SOFTWARE. #define CI2_H #include "./platform/ci2_platform.h" - +#include "./platform/error/ci2_exception.h" #endif // ci2.h diff --git a/include/platform/error/ci2_exception.h b/include/platform/error/ci2_exception.h new file mode 100644 index 0000000..1c289c2 --- /dev/null +++ b/include/platform/error/ci2_exception.h @@ -0,0 +1,188 @@ +/* - | Copyright | ------------------------------------------------------------ + Copyright (c) 2026 Randy Jordan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + * --------------------------------------------------------------------------*/ +#ifndef CI2_EXCEPTION_H +#define CI2_EXCEPTION_H + +#include "../ci2_platform.h" +#include + +struct Exception +{ + const char* msg; +}; +typedef struct Exception Exception; + +struct Exception_Frame +{ + struct Exception_Frame* prev; + jmp_buf env; + const char* file; + const char* func; + int line; + const struct Exception* exception; +}; +typedef struct Exception_Frame Exception_Frame; + +enum +{ + EXCEPTION_ENTERED = 0, + EXCEPTION_RAISED, + EXCEPTION_HANDLED, + EXCEPTION_FINALIZED +}; + +CI2_DEF const struct Exception assertion_failed; +CI2_DEF void +ci2_rt_assert(int cond); + +CI2_DEF void +ci2_raise_exception(const struct Exception* e, + const char* file, + const char* func, + int line); + +#if defined(CI2_WINDOWS) +CI2_DEF DWORD exception_stack; +CI2_DEF void +ci2_exception_init(void); +CI2_DEF void +ci2_exception_push(struct Exception_Frame* fp); +CI2_DEF void +ci2_exception_pop(void); + + #define CI2_RAISE(e) ci2_raise_exception(&(e), __FILE__, CI2_FUNC, __LINE__) + + #define CI2_RERAISE \ + ci2_raise_exception(exception_frame.exception, \ + exception_frame.file, \ + exception_frame.func, \ + exception_frame.line) + + #define CI2_RETURN \ + switch (ci2_exception_pop(), 0) \ + default: \ + return + + #define CI2_TRY \ + do { \ + volatile int exception_flag; \ + Exception_Frame exception_frame; \ + if (exception_stack == TLS_OUT_OF_INDEXES) \ + ci2_exception_init(); \ + ci2_exception_push(&exception_frame); \ + exception_flag = setjmp(exception_frame.env); \ + if (exception_flag == EXCEPTION_ENTERED) { + + #define CI2_EXCEPT(e) \ + if (exception_flag == EXCEPTION_ENTERED) \ + ci2_exception_pop(); \ + } \ + else if (exception_frame.exception == &(e)) \ + { \ + exception_flag = EXCEPTION_HANDLED; + + #define CI2_ELSE \ + if (exception_flag == EXCEPTION_ENTERED) \ + ci2_exception_pop(); \ + } \ + else \ + { \ + exception_flag = EXCEPTION_HANDLED; + + #define CI2_FINALLY \ + if (exception_flag == EXCEPTION_ENTERED) \ + ci2_exception_pop(); \ + } \ + { \ + if (exception_flag == EXCEPTION_ENTERED) \ + exception_flag = EXCEPTION_FINALIZED; + + #define CI2_END_TRY \ + if (exception_flag == EXCEPTION_ENTERED) \ + ci2_exception_pop(); \ + } \ + if (exception_flag == EXCEPTION_RAISED) \ + CI2_RERAISE; \ + } \ + while (0) + +#else // UNIX +CI2_DEF struct Exception_Frame* exception_stack; + + #define CI2_RAISE(e) ci2_raise_exception(&(e), __FILE__, CI2_FUNC, __LINE__) + + #define CI2_RERAISE \ + ci2_raise_exception(exception_frame.exception, \ + exception_frame.file, \ + exception_frame.func, \ + exception_frame.line) + + #define CI2_RETURN \ + switch (exception_stack = exception_stack->prev, 0) \ + default: \ + return + + #define CI2_TRY \ + do { \ + volatile int exception_flag; \ + Exception_Frame exception_frame; \ + exception_frame.prev = exception_stack; \ + exception_stack = &exception_frame; \ + exception_flag = setjmp(exception_frame.env); \ + if (exception_flag == EXCEPTION_ENTERED) { + + #define CI2_EXCEPT(e) \ + if (exception_flag == EXCEPTION_ENTERED) \ + exception_stack = exception_stack->prev; \ + } \ + else if (exception_frame.exception == &(e)) \ + { \ + exception_flag = EXCEPTION_HANDLED; + + #define CI2_ELSE \ + if (exception_flag == EXCEPTION_ENTERED) \ + exception_stack = exception_stack->prev; \ + } \ + else \ + { \ + exception_flag = EXCEPTION_HANDLED; + + #define CI2_FINALLY \ + if (exception_flag == EXCEPTION_ENTERED) \ + exception_stack = exception_stack->prev; \ + } \ + { \ + if (exception_flag == EXCEPTION_ENTERED) \ + exception_flag = EXCEPTION_FINALIZED; + + #define CI2_END_TRY \ + if (exception_flag == EXCEPTION_ENTERED) \ + exception_stack = exception_stack->prev; \ + } \ + if (exception_flag == EXCEPTION_RAISED) \ + CI2_RERAISE; \ + } \ + while (0) +#endif + +#endif // ci2_exception.h diff --git a/src/linux_exception.c b/src/linux_exception.c new file mode 100644 index 0000000..f72d4e5 --- /dev/null +++ b/src/linux_exception.c @@ -0,0 +1,67 @@ +/* - | Copyright | ------------------------------------------------------------ + Copyright (c) 2026 Randy Jordan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + * --------------------------------------------------------------------------*/ +#include "../include/platform/error/ci2_exception.h" + +struct Exception_Frame* exception_stack = NULL; + +const struct Exception assertion_failed = { "CI2 Assertion Failed" }; + +void +ci2_rt_assert(int cond) +{ + if (!cond) { + CI2_RAISE(assertion_failed); + } +} + +void +ci2_raise_exception(const struct Exception* e, + const char* file, + const char* func, + int line) +{ + + struct Exception_Frame* p = exception_stack; + ci2_rt_assert(e != NULL); + if (p == NULL) { + fprintf(stderr, "Uncaught exception"); + if (e->msg) + fprintf(stderr, " %s", e->msg); + else + fprintf(stderr, " at 0x%p", (void*)e); + if (file && line > 0) + fprintf(stderr, " raised at %s:%d", file, line); + if (func != NULL) + fprintf(stderr, ":%s", func); + fprintf(stderr, "\ninitiating debug trap...\n"); + fflush(stderr); + CI2_DEBUG_TRAP(); + } + p->exception = e; + p->file = file; + p->func = func; + p->line = line; + + exception_stack = exception_stack->prev; + longjmp(p->env, EXCEPTION_RAISED); +} diff --git a/src/win32_exception.c b/src/win32_exception.c new file mode 100644 index 0000000..d29d033 --- /dev/null +++ b/src/win32_exception.c @@ -0,0 +1,101 @@ +/* - | Copyright | ------------------------------------------------------------ + Copyright (c) 2026 Randy Jordan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + * --------------------------------------------------------------------------*/ +#include "../include/platform/error/ci2_exception.h" + +DWORD exception_stack = -1; + +const struct Exception assertion_failed = { "CI2 Assertion Failed" }; +void +ci2_rt_assert(int cond) +{ + if (!cond) { + CI2_RAISE(assertion_failed); + } +} + +void +ci2_raise_exception(const struct Exception* e, + const char* file, + const char* func, + int line) +{ + struct Exception_Frame* p; + + if (exception_stack == TLS_OUT_OF_INDEXES) + ci2_exception_init(); + + p = TlsGetValue(exception_stack); + + ci2_rt_assert(e != NULL); + if (p == NULL) { + fprintf(stderr, "Uncaught exception"); + if (e->msg) + fprintf(stderr, " %s", e->msg); + else + fprintf(stderr, " at 0x%p", (void*)e); + if (file && line > 0) + fprintf(stderr, " raised at %s:%d", file, line); + if (func != NULL) + fprintf(stderr, ":%s", func); + fprintf(stderr, "\nInitiating debug trap/break...\n"); + fflush(stderr); + CI2_DEBUG_TRAP(); + } + p->exception = e; + p->file = file; + p->func = func; + p->line = line; + + ci2_exception_pop(); + + longjmp(p->env, EXCEPTION_RAISED); +} + +void +ci2_exception_init(void) +{ + BOOL cond; + + exception_stack = TlsAlloc(); + ci2_try_assert(exception_stack != TLS_OUT_OF_INDEXES); + cond = TlsSetValue(exception_stack, NULL); + ci2_try_assert(cond == TRUE); +} + +void +ci2_exception_push(struct Exception_Frame* fp) +{ + BOOL cond; + fp->prev = TlsGetValue(exception_stack); + cond = TlsSetValue(exception_stack, fp); + ci2_try_assert(cond == TRUE); +} + +void +ci2_exception_pop(void) +{ + BOOL cond; + struct Exception_Frame* tos = TlsGetValue(exception_stack); + cond = TlsSetValue(exception_stack, tos->prev); + ci2_try_assert(cond == TRUE); +} diff --git a/tests/04_ci2_assert.c b/tests/04_ci2_assert.c index 4e81afe..7095377 100644 --- a/tests/04_ci2_assert.c +++ b/tests/04_ci2_assert.c @@ -1,109 +1,121 @@ /* - | Copyright | ------------------------------------------------------------ Copyright (c) 2026 Randy Jordan -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * --------------------------------------------------------------------------*/ #define DEBUG #include "../include/ci2.h" +#include /* bool, true, false — C99 */ #include #include -#include /* bool, true, false — C99 */ -#include /* strcmp — C standard */ -static void test_assert_passing(void){ - /* Basic truthy values */ - CI2_ASSERT(1); - CI2_ASSERT(1 == 1); +#include /* strcmp — C standard */ +static void +test_assert_passing(void) +{ + /* Basic truthy values */ + CI2_ASSERT(1); + CI2_ASSERT(1 == 1); - /* Pointer non-null */ - const char *str = "hello"; - CI2_ASSERT(str != NULL); + /* Pointer non-null */ + const char* str = "hello"; + CI2_ASSERT(str != NULL); - /* Arithmetic */ - int x = 42; - CI2_ASSERT(x > 0); - CI2_ASSERT(x * 2 == 84); + /* Arithmetic */ + int x = 42; + CI2_ASSERT(x > 0); + CI2_ASSERT(x * 2 == 84); - /* CI2_ASSERT_MSG variants */ - CI2_ASSERT_MSG(x == 42, "x must be 42"); - CI2_ASSERT_MSG(sizeof(int) == 4, "int must be 4 bytes on this platform"); + /* CI2_ASSERT_MSG variants */ + CI2_ASSERT_MSG(x == 42, "x must be 42"); + CI2_ASSERT_MSG(sizeof(int) == 4, "int must be 4 bytes on this platform"); - /* Boolean */ - bool flag = true; - CI2_ASSERT(flag); + /* Boolean */ + bool flag = true; + CI2_ASSERT(flag); } - -static void test_assert_failing(void){ - printf(" Triggering CI2_ASSERT(0) failure now...\n"); - fflush(stdout); - CI2_ASSERT(0); /* <-- should break/trap here */ - /* Should never reach here */ - printf(" [FAIL] Execution continued past a failing CI2_ASSERT!\n"); +static void +test_assert_failing(void) +{ + printf(" Triggering CI2_ASSERT(0) failure now...\n"); + fflush(stdout); + CI2_ASSERT(0); /* <-- should break/trap here */ + /* Should never reach here */ + printf(" [FAIL] Execution continued past a failing CI2_ASSERT!\n"); } -static void test_assert_msg_failing(void){ - printf("CI2_ASSERT_MSG — Deliberate Failure (triggering a debug break...)\n"); - fflush(stdout); +static void +test_assert_msg_failing(void) +{ + printf("CI2_ASSERT_MSG — Deliberate Failure (triggering a debug break...)\n"); + fflush(stdout); - CI2_ASSERT_MSG(0, "This failure is intentional — testing the message path"); + CI2_ASSERT_MSG(0, "This failure is intentional — testing the message path"); - printf(" [FAIL] Execution continued past a failing CI2_ASSERT_MSG!\n"); + printf(" [FAIL] Execution continued past a failing CI2_ASSERT_MSG!\n"); } -static void test_ndebug_status(void){ - printf("NDEBUG / Release Mode"); +static void +test_ndebug_status(void) +{ + printf("NDEBUG / Release Mode"); #ifdef NDEBUG - printf(" NDEBUG is DEFINED — asserts are compiled out.\n"); - CI2_ASSERT(0); /* harmless in release */ - printf(" Confirmed: CI2_ASSERT(0) was a no-op.\n"); + printf(" NDEBUG is DEFINED — asserts are compiled out.\n"); + CI2_ASSERT(0); /* harmless in release */ + printf(" Confirmed: CI2_ASSERT(0) was a no-op.\n"); #else - printf(" NDEBUG is NOT defined — asserts are active (debug build).\n"); + printf(" NDEBUG is NOT defined — asserts are active (debug build).\n"); #endif } -int main(int argc, char *argv[]){ - (void) argc; - (void) argv; -/* Determine whether the user wants to trigger a deliberate failure */ - bool run_fail = false; - bool run_fail_msg = false; +int +main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + /* Determine whether the user wants to trigger a deliberate failure */ + bool run_fail = false; + bool run_fail_msg = false; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--fail") == 0) run_fail = true; - if (strcmp(argv[i], "--fail-msg") == 0) run_fail_msg = true; - } + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--fail") == 0) + run_fail = true; + if (strcmp(argv[i], "--fail-msg") == 0) + run_fail_msg = true; + } - /* --- Run the safe tests --- */ - test_assert_passing(); - test_ndebug_status(); + /* --- Run the safe tests --- */ + test_assert_passing(); + test_ndebug_status(); - /* --- Optional deliberate-failure tests --- */ - if (run_fail) test_assert_failing(); - if (run_fail_msg) test_assert_msg_failing(); + /* --- Optional deliberate-failure tests --- */ + if (run_fail) + test_assert_failing(); + if (run_fail_msg) + test_assert_msg_failing(); - - if (!run_fail && !run_fail_msg) { - printf("Tip: run with --fail or --fail-msg to test the"); - printf(" debug-break path (attach a debugger first).\n"); - } - return EXIT_SUCCESS; + if (!run_fail && !run_fail_msg) { + printf("Tip: run with --fail or --fail-msg to test the"); + printf(" debug-break path (attach a debugger first).\n"); + } + return EXIT_SUCCESS; } diff --git a/tests/05_ci2_exception.c b/tests/05_ci2_exception.c new file mode 100644 index 0000000..6370fca --- /dev/null +++ b/tests/05_ci2_exception.c @@ -0,0 +1,100 @@ +/* - | Copyright | ------------------------------------------------------------ + Copyright (c) 2026 Randy Jordan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + * --------------------------------------------------------------------------*/ +#define DEBUG +#include "../include/ci2.h" + +#include /* bool, true, false — C99 */ +#include +#include +#include /* strcmp — C standard */ +static void +test_assert_passing(void) +{ + /* Basic truthy values */ + ci2_rt_assert(1); + ci2_rt_assert(1 == 1); + + /* Pointer non-null */ + const char* str = "hello"; + ci2_rt_assert(str != NULL); + + /* Arithmetic */ + int x = 42; + ci2_rt_assert(x > 0); + ci2_rt_assert(x * 2 == 84); + + /* Boolean */ + bool flag = true; + ci2_rt_assert(flag); +} + +static void +test_assert_failing(void) +{ + printf(" Triggering ci2_rt_assert(0) failure now...\n"); + fflush(stdout); + ci2_rt_assert(0); /* <-- should break/trap here */ + /* Should never reach here */ + printf(" [FAIL] Execution continued past a failing ci2_rt_assert!\n"); +} + +static void +test_ndebug_status(void) +{ + printf("NDEBUG / Release Mode"); + +#ifdef NDEBUG + printf(" NDEBUG is DEFINED — asserts are compiled out.\n"); + ci2_rt_assert(0); /* harmless in release */ + printf(" Confirmed: ci2_rt_assert(0) was a no-op.\n"); +#else + printf(" NDEBUG is NOT defined — asserts are active (debug build).\n"); +#endif +} + +int +main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + /* Determine whether the user wants to trigger a deliberate failure */ + bool run_fail = false; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--fail") == 0) + run_fail = true; + } + + /* --- Run the safe tests --- */ + test_assert_passing(); + test_ndebug_status(); + + /* --- Optional deliberate-failure tests --- */ + if (run_fail) + test_assert_failing(); + + if (!run_fail) { + printf("Tip: run with --fail to test the exceptions\n"); + } + return EXIT_SUCCESS; +}