Expand test coverage for error-handling macros and error pool
All checks were successful
libakerror CI Build / cmake_build (push) Successful in 2m42s
All checks were successful
libakerror CI Build / cmake_build (push) Successful in 2m42s
Add 11 CTest programs and a shared test helper covering gaps left by the original four tests (which only exercised FAIL/CATCH/HANDLE and CLEANUP): - err_capture.h: capturing akerr_log_method + NDEBUG-proof AKERR_CHECK so tests can assert on message/status/stacktrace content, not just exit codes - err_success: clean nested return does not break/handle/leak - err_pool_refcount: 100k raise->catch->handle cycles leak 0 pool slots - err_handle_default / err_handle_group / err_handle_dispatch: handler routing - err_pass / err_ignore / err_swallow: PASS, IGNORE, FINISH(e,false) - err_break_variants: FAIL_*_BREAK and FAIL_*_RETURN - err_errno: system errno name lookup + "Unknown Error" boundary - err_custom_handler: override the unhandled-error hook, assert non-fatally Register tests via a foreach loop in CMakeLists.txt. Ignore build/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
include/akerror.h
|
||||
build/
|
||||
@@ -44,25 +44,43 @@ target_compile_definitions(akerror
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB}
|
||||
)
|
||||
|
||||
add_executable(test_err_catch tests/err_catch.c)
|
||||
add_executable(test_err_cleanup tests/err_cleanup.c)
|
||||
add_executable(test_err_trace tests/err_trace.c)
|
||||
add_executable(test_err_improper_closure tests/err_improper_closure.c)
|
||||
add_test(NAME err_catch COMMAND test_err_catch)
|
||||
add_test(NAME err_cleanup COMMAND test_err_cleanup)
|
||||
add_test(NAME err_trace COMMAND test_err_trace)
|
||||
add_test(NAME err_improper_closure COMMAND test_err_improper_closure)
|
||||
|
||||
set_tests_properties(
|
||||
err_trace
|
||||
err_improper_closure
|
||||
PROPERTIES WILL_FAIL TRUE
|
||||
# Each test is one source file in tests/ built into test_<name> and registered
|
||||
# as CTest <name>. Tests expected to abort (unhandled error / contract
|
||||
# violation) go in AKERR_WILL_FAIL_TESTS; all others must exit 0.
|
||||
set(AKERR_TESTS
|
||||
err_catch
|
||||
err_cleanup
|
||||
err_trace
|
||||
err_improper_closure
|
||||
err_success
|
||||
err_pool_refcount
|
||||
err_handle_default
|
||||
err_handle_group
|
||||
err_handle_dispatch
|
||||
err_pass
|
||||
err_ignore
|
||||
err_swallow
|
||||
err_errno
|
||||
err_break_variants
|
||||
err_custom_handler
|
||||
)
|
||||
|
||||
target_link_libraries(test_err_catch PRIVATE akerror)
|
||||
target_link_libraries(test_err_cleanup PRIVATE akerror)
|
||||
target_link_libraries(test_err_trace PRIVATE akerror)
|
||||
target_link_libraries(test_err_improper_closure PRIVATE akerror)
|
||||
set(AKERR_WILL_FAIL_TESTS
|
||||
err_trace
|
||||
err_improper_closure
|
||||
)
|
||||
|
||||
foreach(_test IN LISTS AKERR_TESTS)
|
||||
add_executable(test_${_test} tests/${_test}.c)
|
||||
target_include_directories(test_${_test} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
|
||||
target_link_libraries(test_${_test} PRIVATE akerror)
|
||||
add_test(NAME ${_test} COMMAND test_${_test})
|
||||
endforeach()
|
||||
|
||||
set_tests_properties(
|
||||
${AKERR_WILL_FAIL_TESTS}
|
||||
PROPERTIES WILL_FAIL TRUE
|
||||
)
|
||||
|
||||
set(main_lib_dest "lib/my_library-${MY_LIBRARY_VERSION}")
|
||||
install(TARGETS akerror
|
||||
|
||||
92
tests/err_break_variants.c
Normal file
92
tests/err_break_variants.c
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* Cover the conditional break/return failure macros:
|
||||
* FAIL_ZERO_BREAK / FAIL_NONZERO_BREAK / FAIL_BREAK (inside ATTEMPT)
|
||||
* FAIL_ZERO_RETURN / FAIL_NONZERO_RETURN / FAIL_RETURN (direct return)
|
||||
* Each is checked in both its failing and its non-failing (pass-through) state.
|
||||
*/
|
||||
|
||||
akerr_ErrorContext *zero_break(int x)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(e, x, AKERR_VALUE, "x was zero");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *nonzero_break(int x)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
FAIL_NONZERO_BREAK(e, x, AKERR_INDEX, "x was nonzero");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *always_break(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
FAIL_BREAK(e, AKERR_IO, "always");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *zero_return(int x)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, x, AKERR_KEY, "x was zero");
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *nonzero_return(int x)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_NONZERO_RETURN(e, x, AKERR_TYPE, "x was nonzero");
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
akerr_init();
|
||||
|
||||
akerr_ErrorContext *r;
|
||||
|
||||
r = zero_break(0);
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_VALUE);
|
||||
r = akerr_release_error(r);
|
||||
AKERR_CHECK(zero_break(7) == NULL);
|
||||
|
||||
r = nonzero_break(7);
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_INDEX);
|
||||
r = akerr_release_error(r);
|
||||
AKERR_CHECK(nonzero_break(0) == NULL);
|
||||
|
||||
r = always_break();
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_IO);
|
||||
r = akerr_release_error(r);
|
||||
|
||||
r = zero_return(0);
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_KEY);
|
||||
r = akerr_release_error(r);
|
||||
AKERR_CHECK(zero_return(7) == NULL);
|
||||
|
||||
r = nonzero_return(7);
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_TYPE);
|
||||
r = akerr_release_error(r);
|
||||
AKERR_CHECK(nonzero_return(0) == NULL);
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_break_variants ok\n");
|
||||
return 0;
|
||||
}
|
||||
84
tests/err_capture.h
Normal file
84
tests/err_capture.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef AKERR_TEST_CAPTURE_H
|
||||
#define AKERR_TEST_CAPTURE_H
|
||||
|
||||
/*
|
||||
* Shared test helpers for libakerror.
|
||||
*
|
||||
* Installs a capturing implementation of akerr_log_method so tests can assert
|
||||
* on the *content* of log/stacktrace output (messages, status codes, error
|
||||
* names) instead of relying solely on process exit codes.
|
||||
*
|
||||
* Also provides AKERR_CHECK(), a NDEBUG-proof assertion that fails the test by
|
||||
* returning non-zero from main() (unlike assert(), which is compiled out in
|
||||
* release builds and would silently turn a test into a no-op).
|
||||
*/
|
||||
|
||||
#include "akerror.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define AKERR_CAPTURE_BUFSZ 65536
|
||||
static char akerr_capture_buf[AKERR_CAPTURE_BUFSZ];
|
||||
static size_t akerr_capture_len = 0;
|
||||
|
||||
static void __attribute__((unused)) akerr_capture_logger(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(akerr_capture_buf + akerr_capture_len,
|
||||
AKERR_CAPTURE_BUFSZ - akerr_capture_len, fmt, ap);
|
||||
va_end(ap);
|
||||
if ( n > 0 ) {
|
||||
akerr_capture_len += (size_t)n;
|
||||
if ( akerr_capture_len >= AKERR_CAPTURE_BUFSZ ) {
|
||||
akerr_capture_len = AKERR_CAPTURE_BUFSZ - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) akerr_capture_reset(void)
|
||||
{
|
||||
akerr_capture_len = 0;
|
||||
akerr_capture_buf[0] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Install the capturing logger. akerr_init() only assigns a default logger when
|
||||
* akerr_log_method is NULL, and it is idempotent, so calling this either before
|
||||
* or after the first PREPARE_ERROR keeps our logger in place.
|
||||
*/
|
||||
static void __attribute__((unused)) akerr_capture_install(void)
|
||||
{
|
||||
akerr_capture_reset();
|
||||
akerr_log_method = &akerr_capture_logger;
|
||||
}
|
||||
|
||||
/* Count array slots currently checked out of the pool (refcount != 0). */
|
||||
static int __attribute__((unused)) akerr_slots_in_use(void)
|
||||
{
|
||||
int n = 0;
|
||||
for ( int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
||||
if ( AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
#define AKERR_CHECK(cond) \
|
||||
do { \
|
||||
if ( !(cond) ) { \
|
||||
fprintf(stderr, "CHECK FAILED: %s at %s:%d\n", \
|
||||
#cond, __FILE__, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while ( 0 )
|
||||
|
||||
#define AKERR_CHECK_CONTAINS(needle) \
|
||||
AKERR_CHECK(strstr(akerr_capture_buf, (needle)) != NULL)
|
||||
|
||||
#define AKERR_CHECK_NOT_CONTAINS(needle) \
|
||||
AKERR_CHECK(strstr(akerr_capture_buf, (needle)) == NULL)
|
||||
|
||||
#endif // AKERR_TEST_CAPTURE_H
|
||||
45
tests/err_custom_handler.c
Normal file
45
tests/err_custom_handler.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* The unhandled-error hook (akerr_handler_unhandled_error) is overridable.
|
||||
* Install a non-fatal handler so an unhandled error can be asserted directly
|
||||
* (status, invocation) instead of relying on process death / WILL_FAIL.
|
||||
*/
|
||||
|
||||
static int custom_fired = 0;
|
||||
static int custom_status = 0;
|
||||
|
||||
static void my_handler(akerr_ErrorContext *e)
|
||||
{
|
||||
custom_fired = 1;
|
||||
custom_status = (e != NULL) ? e->status : -1;
|
||||
/* deliberately does NOT exit() */
|
||||
}
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, AKERR_TYPE, "unhandled on purpose");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
akerr_init(); /* sets the default handler... */
|
||||
akerr_handler_unhandled_error = &my_handler; /* ...which we then override */
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
/* no HANDLE for AKERR_TYPE -> stays unhandled */
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(custom_fired == 1);
|
||||
AKERR_CHECK(custom_status == AKERR_TYPE);
|
||||
AKERR_CHECK_CONTAINS("Unhandled Error");
|
||||
fprintf(stderr, "err_custom_handler ok\n");
|
||||
return 0;
|
||||
}
|
||||
47
tests/err_errno.c
Normal file
47
tests/err_errno.c
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* The library imports system errno codes and their descriptions at build time
|
||||
* (scripts/generrno.sh -> akerr_init_errno). Verify:
|
||||
* - a system errno (EACCES) has a registered, non-empty name;
|
||||
* - an out-of-range status returns the "Unknown Error" sentinel;
|
||||
* - a system errno can be raised, propagated and handled like any AKERR_* code.
|
||||
*/
|
||||
|
||||
static int handled = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, EACCES, "permission denied");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
akerr_init();
|
||||
|
||||
char *nm = akerr_name_for_status(EACCES, NULL);
|
||||
AKERR_CHECK(nm != NULL);
|
||||
AKERR_CHECK(nm[0] != '\0');
|
||||
AKERR_CHECK(strcmp(nm, "Unknown Error") != 0);
|
||||
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(AKERR_MAX_ERR_VALUE + 5000, NULL),
|
||||
"Unknown Error") == 0);
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, EACCES) {
|
||||
handled = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(handled == 1);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_errno ok (EACCES name: \"%s\")\n", nm);
|
||||
return 0;
|
||||
}
|
||||
38
tests/err_handle_default.c
Normal file
38
tests/err_handle_default.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* An error whose status has no matching HANDLE block must fall through to
|
||||
* HANDLE_DEFAULT, and the non-matching HANDLE body must not run.
|
||||
*/
|
||||
|
||||
static int specific_fired = 0;
|
||||
static int default_fired = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, AKERR_TYPE, "wrong type");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_KEY) {
|
||||
specific_fired = 1; /* must NOT run: error is AKERR_TYPE */
|
||||
} HANDLE_DEFAULT(e) {
|
||||
default_fired = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(specific_fired == 0);
|
||||
AKERR_CHECK(default_fired == 1);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_handle_default ok\n");
|
||||
return 0;
|
||||
}
|
||||
42
tests/err_handle_dispatch.c
Normal file
42
tests/err_handle_dispatch.c
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* With several distinct HANDLE blocks, only the one matching the raised status
|
||||
* may run. Raise the middle code and confirm exact dispatch.
|
||||
*/
|
||||
|
||||
static int a_fired = 0;
|
||||
static int b_fired = 0;
|
||||
static int c_fired = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, AKERR_TYPE, "the middle one");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_KEY) {
|
||||
a_fired = 1;
|
||||
} HANDLE(e, AKERR_TYPE) {
|
||||
b_fired = 1;
|
||||
} HANDLE(e, AKERR_IO) {
|
||||
c_fired = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(a_fired == 0);
|
||||
AKERR_CHECK(b_fired == 1);
|
||||
AKERR_CHECK(c_fired == 0);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_handle_dispatch ok\n");
|
||||
return 0;
|
||||
}
|
||||
56
tests/err_handle_group.c
Normal file
56
tests/err_handle_group.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* HANDLE_GROUP lets several status codes share one handler body via
|
||||
* case-fallthrough. The first member of the group uses HANDLE (which emits the
|
||||
* leading break that terminates the previous case); each additional member
|
||||
* uses HANDLE_GROUP; the shared body follows the last member. Verify that two
|
||||
* different status codes both reach the shared body.
|
||||
*/
|
||||
|
||||
static int group_fired = 0;
|
||||
static int other_fired = 0;
|
||||
|
||||
akerr_ErrorContext *boom(int status)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, status, "boom %d", status);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *run(int status)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom(status));
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_KEY)
|
||||
HANDLE_GROUP(e, AKERR_INDEX) {
|
||||
group_fired++;
|
||||
} HANDLE(e, AKERR_IO) {
|
||||
other_fired++;
|
||||
} FINISH(e, false);
|
||||
return e;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
|
||||
run(AKERR_KEY);
|
||||
AKERR_CHECK(group_fired == 1);
|
||||
AKERR_CHECK(other_fired == 0);
|
||||
|
||||
run(AKERR_INDEX);
|
||||
AKERR_CHECK(group_fired == 2);
|
||||
AKERR_CHECK(other_fired == 0);
|
||||
|
||||
run(AKERR_IO);
|
||||
AKERR_CHECK(group_fired == 2);
|
||||
AKERR_CHECK(other_fired == 1);
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_handle_group ok\n");
|
||||
return 0;
|
||||
}
|
||||
34
tests/err_ignore.c
Normal file
34
tests/err_ignore.c
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* IGNORE deliberately swallows an error: it records the context in
|
||||
* __akerr_last_ignored, logs it with an "IGNORED ERROR" marker, and lets
|
||||
* execution continue.
|
||||
*/
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, AKERR_VALUE, "this error is ignored on purpose");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
|
||||
int reached_after_ignore = 0;
|
||||
PREPARE_ERROR(e);
|
||||
(void)e;
|
||||
|
||||
IGNORE(boom());
|
||||
reached_after_ignore = 1;
|
||||
|
||||
AKERR_CHECK(__akerr_last_ignored != NULL);
|
||||
AKERR_CHECK(__akerr_last_ignored->status == AKERR_VALUE);
|
||||
AKERR_CHECK(reached_after_ignore == 1);
|
||||
AKERR_CHECK_CONTAINS("IGNORED ERROR");
|
||||
AKERR_CHECK_CONTAINS("this error is ignored on purpose");
|
||||
fprintf(stderr, "err_ignore ok\n");
|
||||
return 0;
|
||||
}
|
||||
46
tests/err_pass.c
Normal file
46
tests/err_pass.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* PASS bubbles an error up to the caller without a local ATTEMPT/PROCESS block:
|
||||
* if the wrapped call fails, PASS returns the context from the current function.
|
||||
* Verify the error reaches main and that the code after PASS is skipped on
|
||||
* failure.
|
||||
*/
|
||||
|
||||
static int reached_after_pass = 0;
|
||||
|
||||
akerr_ErrorContext *inner(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, AKERR_IO, "inner failed");
|
||||
}
|
||||
|
||||
akerr_ErrorContext *outer(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
PASS(e, inner());
|
||||
reached_after_pass = 1; /* must NOT run: PASS returned already */
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
|
||||
int handled = 0;
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, outer());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_IO) {
|
||||
handled = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(handled == 1);
|
||||
AKERR_CHECK(reached_after_pass == 0);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_pass ok\n");
|
||||
return 0;
|
||||
}
|
||||
55
tests/err_pool_refcount.c
Normal file
55
tests/err_pool_refcount.c
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* Pool-hygiene regression test. The error pool is a fixed 128-slot static
|
||||
* array; a single leaked reference per error would silently exhaust it. Run a
|
||||
* large number of full raise -> catch -> handle cycles and confirm every slot
|
||||
* is returned to the pool (refcount 0) and the pool can still hand out
|
||||
* contexts afterwards.
|
||||
*/
|
||||
|
||||
#define ITERATIONS 100000
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
FAIL(e, AKERR_VALUE, "boom %d", 1);
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* One full raise -> catch -> handle cycle. Returns NULL (context released). */
|
||||
akerr_ErrorContext *one_cycle(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_VALUE) {
|
||||
} FINISH(e, false);
|
||||
return e;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
akerr_init();
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
for ( int iter = 0; iter < ITERATIONS; iter++ ) {
|
||||
(void)one_cycle();
|
||||
}
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
akerr_ErrorContext *probe = akerr_next_error();
|
||||
AKERR_CHECK(probe != NULL);
|
||||
|
||||
fprintf(stderr, "err_pool_refcount ok (%d cycles, 0 leaked)\n", ITERATIONS);
|
||||
return 0;
|
||||
}
|
||||
44
tests/err_success.c
Normal file
44
tests/err_success.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* The success path: a nested call that returns cleanly (NULL) must not break
|
||||
* out of the caller's ATTEMPT block, must not enter any handler, and must not
|
||||
* consume a slot from the error pool.
|
||||
*/
|
||||
|
||||
static int default_fired = 0;
|
||||
|
||||
akerr_ErrorContext *ok_func(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
akerr_init();
|
||||
|
||||
int reached_after_catch = 0;
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, ok_func());
|
||||
reached_after_catch = 1; /* must run: no break on success */
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE_DEFAULT(e) {
|
||||
default_fired = 1; /* must NOT run */
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(reached_after_catch == 1);
|
||||
AKERR_CHECK(default_fired == 0);
|
||||
AKERR_CHECK(e == NULL);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_success ok\n");
|
||||
return 0;
|
||||
}
|
||||
45
tests/err_swallow.c
Normal file
45
tests/err_swallow.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
/*
|
||||
* FINISH(ctx, false) closes an ATTEMPT block without propagating: an unhandled
|
||||
* error is dropped (not returned, not passed to the unhandled handler) and the
|
||||
* context is released back to the pool. Because we use FINISH (not
|
||||
* FINISH_NORETURN) the process must continue normally and exit 0.
|
||||
*/
|
||||
|
||||
static int wrong_handler_fired = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_RETURN(e, AKERR_VALUE, "unhandled but swallowed");
|
||||
}
|
||||
|
||||
/* FINISH(e, false) belongs in a context-returning function. On the swallow
|
||||
* path e is released to NULL, so this returns NULL to its caller. */
|
||||
akerr_ErrorContext *swallow_it(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_KEY) {
|
||||
wrong_handler_fired = 1; /* does not match AKERR_VALUE */
|
||||
} FINISH(e, false);
|
||||
return e; /* NULL: released even though unhandled */
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_capture_install();
|
||||
|
||||
akerr_ErrorContext *res = swallow_it();
|
||||
|
||||
AKERR_CHECK(wrong_handler_fired == 0);
|
||||
AKERR_CHECK(res == NULL); /* released even though unhandled */
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_swallow ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user