Every libakgl test suite could report success while failing. libakerror's default unhandled-error handler ended in exit(errctx->status), an exit status is one byte wide, and libakgl's band starts at 256 -- so AKGL_ERR_SDL, the most common failure a library built on SDL can have, exited 0 and CTest recorded a pass. tests/character.c aborted at its second of four tests on a bad renderer and was green for months. 0.5.0 worked around that here with TEST_TRAP_UNHANDLED_ERRORS() in tests/testutil.h, and TODO.md ended the entry saying any consumer's suites have the same problem and it was worth raising upstream. It was. 2.0.1 fixes it at the source: akerr_exit() owns the mapping and the default handler calls it, so 0 exits 0, 1 through 255 exit themselves, and anything else exits AKERR_EXIT_STATUS_UNREPRESENTABLE (125). The trap and its 21 call sites are gone. Verified by putting the original failure back rather than by reading the release notes: a FAIL_BREAK(AKGL_ERR_SDL) in tests/character.c's main exits 125 and CTest reports a failure. A standalone consumer raising the same status unhandled exits 125 where it exited 0 before. tests/actor.c installs its own handler and called exit(errctx->status) from it, which is the same defect one layer up. It calls akerr_exit() now. 2.0.0 also makes the error pool and the status registry thread safe, which libakgl needs more than it knew: audio_stream_callback raises error contexts on SDL's audio thread. With an unlocked pool that callback and the main thread could scan AKERR_ARRAY_ERROR at the same time and be handed the same slot. The comment there says so. This is a hard dependency floor, not a preference. 2.0.0 moved __akerr_last_ignored to thread-local storage and made akerr_next_error() return a context that already holds its reference, and both expand at libakgl's call sites -- and at a consumer's, because akerror.h is part of libakgl's public interface. Mixing headers and libraries across that line double-counts every reference and never returns a pool slot. The soname moved to libakerror.so.2; include/akgl/error.h now also feature- tests AKERR_EXIT_STATUS_UNREPRESENTABLE, which is the narrowest probe for 2.0.1 since libakerror publishes no version macro. 0.7.0 for that reason: libakgl's own ABI is unchanged, but the one it re-exports through its headers is not. TODO.md records the pkg-config gap this makes sharper -- akgl.pc names no dependencies at all, so nothing tells a pkg-config consumer which libakerror it needs. Clean build, 26/26 ctest, memcheck clean, warning-clean at -Wall -Werror. libakgl.so.0.7 links libakerror.so.2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8T5FAYXE8HEJqFLCYwNNc
109 lines
3.4 KiB
C
109 lines
3.4 KiB
C
/**
|
|
* @file version.c
|
|
* @brief Unit tests for the version macros and the linked-library accessor.
|
|
*
|
|
* These assert that the several places the version appears cannot drift: the
|
|
* string, the numeric components, and what the shared library reports at
|
|
* runtime all come from one project() call, and this is what proves it.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <akerror.h>
|
|
|
|
#include <akgl/error.h>
|
|
#include <akgl/version.h>
|
|
|
|
#include "testutil.h"
|
|
|
|
/**
|
|
* @brief The version string and the numeric components must describe one version.
|
|
*
|
|
* They are separate substitutions in version.h.in, so a mangled template can
|
|
* leave them disagreeing and nothing else would notice.
|
|
*/
|
|
akerr_ErrorContext *test_version_string_matches_components(void)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
char assembled[64];
|
|
|
|
ATTEMPT {
|
|
snprintf(assembled, sizeof(assembled), "%d.%d.%d",
|
|
AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, AKGL_VERSION_PATCH);
|
|
TEST_ASSERT(e, strcmp(assembled, AKGL_VERSION) == 0,
|
|
"AKGL_VERSION is \"%s\" but the components assemble to \"%s\"",
|
|
AKGL_VERSION, assembled);
|
|
} CLEANUP {
|
|
} PROCESS(e) {
|
|
} FINISH(e, true);
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
/**
|
|
* @brief The linked library must report the version its headers were generated from.
|
|
*
|
|
* In this build tree they are the same tree, so this can only fail if the test
|
|
* picked up an installed libakgl off LD_LIBRARY_PATH instead of the one just
|
|
* built -- which is precisely the mispairing the accessor exists to catch.
|
|
*/
|
|
akerr_ErrorContext *test_version_linked_matches_compiled(void)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
|
|
ATTEMPT {
|
|
TEST_ASSERT(e, akgl_version() != NULL, "akgl_version returned NULL");
|
|
TEST_ASSERT(e, strcmp(akgl_version(), AKGL_VERSION) == 0,
|
|
"linked libakgl reports \"%s\" but the headers say \"%s\"",
|
|
akgl_version(), AKGL_VERSION);
|
|
} CLEANUP {
|
|
} PROCESS(e) {
|
|
} FINISH(e, true);
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
/**
|
|
* @brief AKGL_VERSION_AT_LEAST must order versions correctly at the boundaries.
|
|
*
|
|
* Checked against the current version rather than fixed literals, so the test
|
|
* does not have to be rewritten every time the version is bumped.
|
|
*/
|
|
akerr_ErrorContext *test_version_at_least_boundaries(void)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
|
|
ATTEMPT {
|
|
TEST_ASSERT(e,
|
|
AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, AKGL_VERSION_PATCH),
|
|
"AKGL_VERSION_AT_LEAST rejected the current version");
|
|
TEST_ASSERT(e,
|
|
!AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, AKGL_VERSION_PATCH + 1),
|
|
"AKGL_VERSION_AT_LEAST accepted a later patch");
|
|
TEST_ASSERT(e,
|
|
!AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR + 1, 0),
|
|
"AKGL_VERSION_AT_LEAST accepted a later minor");
|
|
TEST_ASSERT(e,
|
|
!AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR + 1, 0, 0),
|
|
"AKGL_VERSION_AT_LEAST accepted a later major");
|
|
TEST_ASSERT(e,
|
|
AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, 0),
|
|
"AKGL_VERSION_AT_LEAST rejected an earlier patch");
|
|
} CLEANUP {
|
|
} PROCESS(e) {
|
|
} FINISH(e, true);
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
|
|
ATTEMPT {
|
|
CATCH(errctx, akgl_error_init());
|
|
CATCH(errctx, test_version_string_matches_components());
|
|
CATCH(errctx, test_version_linked_matches_compiled());
|
|
CATCH(errctx, test_version_at_least_boundaries());
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH_NORETURN(errctx);
|
|
}
|