2.0.0 makes the error pool and the status registry thread safe, and it is an ABI break carrying the soname to libakerror.so.2. The break is a quiet one: __akerr_last_ignored became thread-local and akerr_next_error() now returns a context that already holds a reference, so objects compiled against a 1.x header and linked against 2.x count every reference twice and never give a slot back. Nothing about that fails to link, which is exactly what a guard is for -- include/akbasic/error.h feature-tests AKERR_THREAD_SAFE instead of AKERR_FIRST_CONSUMER_STATUS, which 2.0.0 also still defines and which therefore no longer distinguishes anything. 2.0.1 is the release this band needed most. The default unhandled-error handler ended in exit(errctx->status), and a process exit status is one byte: AKBASIC_ERR_BASE is 512, and 512 truncates to 0, so an unhandled AKBASIC_ERR_SYNTAX reported success to anything watching $?. Every other code in the band came out as some unrelated error's number. akerr_exit() substitutes 125 for anything a byte cannot carry, and a probe raising AKBASIC_ERR_DEVICE through FINISH_NORETURN now exits 125 rather than 7. It was latent here rather than live -- src/main.c handles the context and returns EXIT_FAILURE, and every test with a top-level ATTEMPT carries a HANDLE_DEFAULT -- but "no caller relies on it today" is not a property a header can keep true. tests/version_check.c asserts the mapping and fails if AKBASIC_ERR_BASE ever stops truncating to zero, because that is the day this stops being about our base. Chapter 10 gains a threading section: libakerror is safe from any thread now, and this interpreter is not and has no lock anywhere in it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
2.8 KiB
C
78 lines
2.8 KiB
C
/**
|
|
* @file version_check.c
|
|
* @brief Tests that the loaded dependencies match the headers we compiled against.
|
|
*
|
|
* The soname normally catches a mismatch at load time. This earns its keep when
|
|
* the soname is bypassed -- a 0.2.0 dropped in under the 0.1 filename loads
|
|
* happily, and only the check notices.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <akstdlib.h>
|
|
|
|
#include <akbasic/error.h>
|
|
|
|
#include "testutil.h"
|
|
|
|
int main(void)
|
|
{
|
|
int major = 0;
|
|
int minor = 0;
|
|
int patch = 0;
|
|
|
|
TEST_REQUIRE_OK(akbasic_error_register());
|
|
|
|
/*
|
|
* AKSL_VERSION_CHECK() is a macro on purpose: it expands here so it captures
|
|
* the numbers *this* translation unit was built with, and compares them to
|
|
* the ones baked into the library that actually loaded.
|
|
*/
|
|
TEST_REQUIRE_OK(AKSL_VERSION_CHECK());
|
|
|
|
TEST_REQUIRE_OK(aksl_version(&major, &minor, &patch));
|
|
TEST_REQUIRE_INT(major, AKSL_VERSION_MAJOR);
|
|
TEST_REQUIRE_INT(minor, AKSL_VERSION_MINOR);
|
|
TEST_REQUIRE_STR(aksl_version_string(), AKSL_VERSION_STRING);
|
|
TEST_REQUIRE_STR(aksl_version_soname(), AKSL_VERSION_SONAME);
|
|
|
|
/*
|
|
* While libakstdlib's major is 0 the soname carries MAJOR.MINOR, so 0.1 and
|
|
* 0.2 are different ABIs. Asserting the shape here means a future 1.0 bump
|
|
* that forgets to change the soname rule fails a test rather than shipping.
|
|
*/
|
|
if ( major == 0 ) {
|
|
char expected[32];
|
|
snprintf(expected, sizeof(expected), "%d.%d", major, minor);
|
|
TEST_REQUIRE_STR(aksl_version_soname(), expected);
|
|
}
|
|
|
|
/*
|
|
* libakerror publishes no version macro at all, so there is nothing to
|
|
* compare. Its floor is the two #errors akbasic/error.h carries -- if this
|
|
* file compiled, both guards passed. What is asserted here is the *value* of
|
|
* what they test, because a guard that only checks a macro is defined passes
|
|
* against a header that defines it to something else entirely.
|
|
*/
|
|
TEST_REQUIRE_INT(AKERR_FIRST_CONSUMER_STATUS, 256);
|
|
TEST_REQUIRE_INT(AKERR_THREAD_SAFE, 1);
|
|
|
|
/*
|
|
* The one that matters most to this band. AKBASIC_ERR_BASE is 512, and a
|
|
* process exit status is one byte: 512 truncates to 0, so before libakerror
|
|
* 2.0.1 an unhandled AKBASIC_ERR_SYNTAX exited *success*. akerr_exit()
|
|
* substitutes 125 for anything a byte cannot carry.
|
|
*
|
|
* Asserted as a number rather than trusted as a macro because this is the
|
|
* value a supervisor watching $? actually sees, and 125 is chosen to sit
|
|
* clear of the 1-124 range a program is likely to use for itself.
|
|
*/
|
|
TEST_REQUIRE_INT(AKERR_EXIT_STATUS_UNREPRESENTABLE, 125);
|
|
TEST_REQUIRE((AKBASIC_ERR_BASE & 0xff) == 0,
|
|
"AKBASIC_ERR_BASE %d no longer truncates to zero -- if the band moved, "
|
|
"the exit-status hazard this guards moved with it",
|
|
AKBASIC_ERR_BASE);
|
|
|
|
return akbasic_test_failures;
|
|
}
|