Expand error status test coverage
- Added explicit status validation across error handling tests. - Added lifecycle and slot-leak checks to older tests. - Improved unhandled-error propagation coverage. - Added repository guidance and a test runner script. - Verified all 23 CTest tests pass. Co-Authored by Codex GPT 5.4
This commit is contained in:
66
AGENTS.md
Normal file
66
AGENTS.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
This is a small C library built with CMake. Core implementation lives in
|
||||
`src/error.c`. The public header is generated at build time from
|
||||
`include/akerror.tmpl.h` by `scripts/generrno.sh`, which also generates
|
||||
`src/errno.c` under the build directory. CMake package and pkg-config templates
|
||||
are in `cmake/` and `akerror.pc.in`. Tests are one-file C programs in `tests/`;
|
||||
shared test helpers live beside them, such as `tests/err_capture.h`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
Use an out-of-tree build:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
`cmake -S . -B build` configures the build and generates `akerror.h`.
|
||||
`cmake --build build` compiles `libakerror` and test executables. `ctest`
|
||||
runs the registered unit tests. To emit CI-style results, use:
|
||||
|
||||
```sh
|
||||
ctest --test-dir build --output-on-failure --output-junit "$(pwd)/ctest-junit.xml"
|
||||
```
|
||||
|
||||
Mutation testing is available through:
|
||||
|
||||
```sh
|
||||
cmake --build build --target mutation
|
||||
scripts/mutation_test.py --target src/error.c --threshold 65
|
||||
```
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
Use C99-compatible C and follow the surrounding style. Functions and types use
|
||||
the `akerr_` prefix; macros and constants use `AKERR_` or all-caps macro names
|
||||
such as `PREPARE_ERROR`. Keep generated-code changes in templates or generator
|
||||
scripts, not in build outputs. Preserve concise comments for invariants,
|
||||
macro constraints, and non-obvious error lifecycle behavior.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
Add tests as `tests/err_<behavior>.c`. Register each new test in the
|
||||
`AKERR_TESTS` list in `CMakeLists.txt`; tests that intentionally abort must
|
||||
also be listed in `AKERR_WILL_FAIL_TESTS`. Prefer focused executable tests that
|
||||
return zero on success and use existing helpers such as `AKERR_CHECK`. Run the
|
||||
CTest suite before submitting changes, and run mutation testing when changing
|
||||
core control-flow, reference counting, stack-trace, or handler behavior.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
Recent commits use short, imperative, sentence-case subjects, for example
|
||||
`Fix refcount leak and stack-trace buffer overflow`. Keep commits focused and
|
||||
describe the observable behavior changed. Pull requests should include a brief
|
||||
summary, tests run, and any compatibility impact for public macros, generated
|
||||
headers, installation paths, or CMake/pkg-config consumers.
|
||||
|
||||
## Agent-Specific Instructions
|
||||
|
||||
Do not overwrite uncommitted user changes. Avoid editing generated files in
|
||||
`build/`; update `include/akerror.tmpl.h`, `src/error.c`, CMake files, tests,
|
||||
or scripts instead.
|
||||
4
test.sh
Normal file
4
test.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure --output-junit "$(pwd)/ctest-junit.xml"
|
||||
python3 scripts/mutation_test.py --target src/error.c --junit mutation-junit.xml --threshold 65
|
||||
@@ -63,26 +63,26 @@ int main(void)
|
||||
akerr_ErrorContext *r;
|
||||
|
||||
r = zero_break(0);
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_VALUE);
|
||||
AKERR_CHECK_STATUS(r, 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);
|
||||
AKERR_CHECK_STATUS(r, AKERR_INDEX);
|
||||
r = akerr_release_error(r);
|
||||
AKERR_CHECK(nonzero_break(0) == NULL);
|
||||
|
||||
r = always_break();
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_IO);
|
||||
AKERR_CHECK_STATUS(r, AKERR_IO);
|
||||
r = akerr_release_error(r);
|
||||
|
||||
r = zero_return(0);
|
||||
AKERR_CHECK(r != NULL && r->status == AKERR_KEY);
|
||||
AKERR_CHECK_STATUS(r, 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);
|
||||
AKERR_CHECK_STATUS(r, AKERR_TYPE);
|
||||
r = akerr_release_error(r);
|
||||
AKERR_CHECK(nonzero_return(0) == NULL);
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@ static int __attribute__((unused)) akerr_slots_in_use(void)
|
||||
} \
|
||||
} while ( 0 )
|
||||
|
||||
#define AKERR_CHECK_STATUS(errctx, expected_status) \
|
||||
do { \
|
||||
AKERR_CHECK((errctx) != NULL); \
|
||||
AKERR_CHECK((errctx)->status == (expected_status)); \
|
||||
} while ( 0 )
|
||||
|
||||
#define AKERR_CHECK_CONTAINS(needle) \
|
||||
AKERR_CHECK(strstr(akerr_capture_buf, (needle)) != NULL)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
akerr_ErrorContext *func2(void)
|
||||
{
|
||||
@@ -31,6 +32,11 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
akerr_log_method("Caught exception");
|
||||
AKERR_CHECK_STATUS(errctx, AKERR_NULLPOINTER);
|
||||
akerr_log_method("Caught exception");
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_catch ok\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
|
||||
int x;
|
||||
|
||||
@@ -34,10 +35,15 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
AKERR_CHECK_STATUS(errctx, AKERR_NULLPOINTER);
|
||||
if ( x == 0 ) {
|
||||
fprintf(stderr, "Cleanup works\n");
|
||||
return 0;
|
||||
akerr_log_method("Cleanup works\n");
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_cleanup ok\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, EACCES) {
|
||||
AKERR_CHECK_STATUS(e, EACCES);
|
||||
handled = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
static int specific_fired = 0;
|
||||
static int default_fired = 0;
|
||||
static int default_status = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
@@ -28,10 +29,12 @@ int main(void)
|
||||
specific_fired = 1; /* must NOT run: error is AKERR_TYPE */
|
||||
} HANDLE_DEFAULT(e) {
|
||||
default_fired = 1;
|
||||
default_status = e->status;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(specific_fired == 0);
|
||||
AKERR_CHECK(default_fired == 1);
|
||||
AKERR_CHECK(default_status == AKERR_TYPE);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_handle_default ok\n");
|
||||
return 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
static int a_fired = 0;
|
||||
static int b_fired = 0;
|
||||
static int c_fired = 0;
|
||||
static int b_status = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
@@ -29,12 +30,14 @@ int main(void)
|
||||
a_fired = 1;
|
||||
} HANDLE(e, AKERR_TYPE) {
|
||||
b_fired = 1;
|
||||
b_status = e->status;
|
||||
} HANDLE(e, AKERR_IO) {
|
||||
c_fired = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(a_fired == 0);
|
||||
AKERR_CHECK(b_fired == 1);
|
||||
AKERR_CHECK(b_status == AKERR_TYPE);
|
||||
AKERR_CHECK(c_fired == 0);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_handle_dispatch ok\n");
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
static int group_fired = 0;
|
||||
static int other_fired = 0;
|
||||
static int group_status = 0;
|
||||
static int other_status = 0;
|
||||
|
||||
akerr_ErrorContext *boom(int status)
|
||||
{
|
||||
@@ -28,8 +30,10 @@ akerr_ErrorContext *run(int status)
|
||||
} HANDLE(e, AKERR_KEY)
|
||||
HANDLE_GROUP(e, AKERR_INDEX) {
|
||||
group_fired++;
|
||||
group_status = e->status;
|
||||
} HANDLE(e, AKERR_IO) {
|
||||
other_fired++;
|
||||
other_status = e->status;
|
||||
} FINISH(e, false);
|
||||
return e;
|
||||
}
|
||||
@@ -40,15 +44,18 @@ int main(void)
|
||||
|
||||
run(AKERR_KEY);
|
||||
AKERR_CHECK(group_fired == 1);
|
||||
AKERR_CHECK(group_status == AKERR_KEY);
|
||||
AKERR_CHECK(other_fired == 0);
|
||||
|
||||
run(AKERR_INDEX);
|
||||
AKERR_CHECK(group_fired == 2);
|
||||
AKERR_CHECK(group_status == AKERR_INDEX);
|
||||
AKERR_CHECK(other_fired == 0);
|
||||
|
||||
run(AKERR_IO);
|
||||
AKERR_CHECK(group_fired == 2);
|
||||
AKERR_CHECK(other_fired == 1);
|
||||
AKERR_CHECK(other_status == AKERR_IO);
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_handle_group ok\n");
|
||||
|
||||
@@ -35,6 +35,7 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_IO) {
|
||||
AKERR_CHECK_STATUS(e, AKERR_IO);
|
||||
handled = 1;
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#define ITERATIONS 100000
|
||||
|
||||
static int handled_status = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -31,6 +33,7 @@ akerr_ErrorContext *one_cycle(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_VALUE) {
|
||||
handled_status = e->status;
|
||||
} FINISH(e, false);
|
||||
return e;
|
||||
}
|
||||
@@ -42,7 +45,9 @@ int main(void)
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
for ( int iter = 0; iter < ITERATIONS; iter++ ) {
|
||||
handled_status = 0;
|
||||
(void)one_cycle();
|
||||
AKERR_CHECK(handled_status == AKERR_VALUE);
|
||||
}
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* library exit(1)s.
|
||||
*/
|
||||
|
||||
static int handled_status = 0;
|
||||
|
||||
akerr_ErrorContext *validate(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -31,6 +33,7 @@ akerr_ErrorContext *one_cycle(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_KEY) {
|
||||
handled_status = e->status;
|
||||
} HANDLE(e, AKERR_VALUE) {
|
||||
} FINISH(e, false);
|
||||
return e;
|
||||
@@ -42,7 +45,9 @@ int main(void)
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
for ( int i = 0; i < 32; i++ ) {
|
||||
handled_status = 0;
|
||||
(void)one_cycle();
|
||||
AKERR_CHECK(handled_status == AKERR_KEY);
|
||||
}
|
||||
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
@@ -27,6 +27,7 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_VALUE) {
|
||||
AKERR_CHECK_STATUS(e, AKERR_VALUE);
|
||||
} FINISH_NORETURN(e);
|
||||
|
||||
AKERR_CHECK(e == NULL);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
static int wrong_handler_fired = 0;
|
||||
static int swallowed_status = 0;
|
||||
|
||||
akerr_ErrorContext *boom(void)
|
||||
{
|
||||
@@ -24,6 +25,7 @@ akerr_ErrorContext *swallow_it(void)
|
||||
ATTEMPT {
|
||||
CATCH(e, boom());
|
||||
} CLEANUP {
|
||||
swallowed_status = (e != NULL) ? e->status : 0;
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKERR_KEY) {
|
||||
wrong_handler_fired = 1; /* does not match AKERR_VALUE */
|
||||
@@ -38,6 +40,7 @@ int main(void)
|
||||
akerr_ErrorContext *res = swallow_it();
|
||||
|
||||
AKERR_CHECK(wrong_handler_fired == 0);
|
||||
AKERR_CHECK(swallowed_status == AKERR_VALUE);
|
||||
AKERR_CHECK(res == NULL); /* released even though unhandled */
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
fprintf(stderr, "err_swallow ok\n");
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#include "akerror.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static void expect_unhandled_nullpointer(akerr_ErrorContext *errctx)
|
||||
{
|
||||
exit((errctx != NULL && errctx->status == AKERR_NULLPOINTER) ? 1 : 0);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *func2(void)
|
||||
{
|
||||
@@ -25,6 +31,9 @@ akerr_ErrorContext *func1(void)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_init();
|
||||
akerr_handler_unhandled_error = &expect_unhandled_nullpointer;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, func1());
|
||||
|
||||
Reference in New Issue
Block a user