/** * @file error.c * @brief Unit tests for the libakgl status band: reservation, ownership and names. * * The libakerror registry is process-global, so these tests assert against * whatever akgl_error_init() left behind rather than building their own state. */ #include #include #include #include "testutil.h" /** * @brief akgl_error_init() must own the libakgl status band and name every code in it. * * A code whose name never registered degrades to "Unknown Error" in every stack * trace that carries it, and a band we never reserved is one another component * can name out from under us. Both stay silent until something has already gone * wrong, so assert them directly rather than waiting to read a useless trace. */ akerr_ErrorContext *test_error_init_owns_the_status_band(void) { PREPARE_ERROR(e); static const struct { int status; const char *name; } expected[] = { { AKGL_ERR_SDL, "SDL Error" }, { AKGL_ERR_REGISTRY, "Registry Error" }, { AKGL_ERR_HEAP, "Heap Error" }, { AKGL_ERR_BEHAVIOR, "Behavior Error" }, { AKGL_ERR_LOGICINTERRUPT, "Logic Interrupt" } }; bool named = true; int i = 0; ATTEMPT { CATCH(e, akgl_error_init()); TEST_ASSERT(e, (int)(sizeof(expected) / sizeof(expected[0])) == AKGL_ERR_COUNT, "the libakgl status band holds %d codes but %d are named here", AKGL_ERR_COUNT, (int)(sizeof(expected) / sizeof(expected[0]))); for ( i = 0; i < (int)(sizeof(expected) / sizeof(expected[0])); i++ ) { TEST_ASSERT_FLAG(named, strcmp(akerr_name_for_status(expected[i].status, NULL), expected[i].name) == 0); } TEST_ASSERT(e, named, "akgl_error_init did not register the expected name for every AKGL_ERR_* code"); // The reservation is what makes those names ours. Without it the // registrations above would still succeed for anyone who asked. TEST_EXPECT_STATUS(e, AKERR_STATUS_NAME_FOREIGN, akerr_register_status_name("not-libakgl", AKGL_ERR_HEAP, "Squatter"), "a foreign owner was allowed to rename a libakgl status"); TEST_EXPECT_STATUS(e, AKERR_STATUS_RANGE_OVERLAP, akerr_reserve_status_range(AKGL_ERR_BASE, AKGL_ERR_COUNT, "not-libakgl"), "a foreign owner was allowed to reserve the libakgl status band"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } /** * @brief Calling akgl_error_init() twice must be a no-op, not a self-collision. * * Nothing in libakgl orders initialization for an embedding program, so a second * call has to be harmless. libakerror only treats an *identical* reservation as * a repeat -- a subset or superset raises -- which makes this a real constraint * on AKGL_ERR_BASE and AKGL_ERR_COUNT, not a triviality. */ akerr_ErrorContext *test_error_init_is_idempotent(void) { PREPARE_ERROR(e); ATTEMPT { TEST_EXPECT_OK(e, akgl_error_init(), "the second akgl_error_init failed"); TEST_EXPECT_OK(e, akgl_error_init(), "the third akgl_error_init failed"); TEST_ASSERT(e, strcmp(akerr_name_for_status(AKGL_ERR_SDL, NULL), "SDL Error") == 0, "re-running akgl_error_init lost the name for AKGL_ERR_SDL"); } CLEANUP { } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } int main(void) { PREPARE_ERROR(errctx); ATTEMPT { // Unlike every other suite, this one has no akgl_error_init() in // main() -- the first test is what brings the subsystem up. The trap // therefore goes after it: libakerror installs its own default handler // from the lazy akerr_init() inside that first call, and would // overwrite anything set beforehand. CATCH(errctx, test_error_init_owns_the_status_band()); TEST_TRAP_UNHANDLED_ERRORS(); CATCH(errctx, test_error_init_is_idempotent()); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); }