All checks were successful
Eighty-five comments cited section numbers -- 1.1, 2.2.6, 3.6 -- from a numbering the file had already abandoned before the move to the tracker. They label completed work, so the pointer was the only wrong part. The citation is removed and the sentence kept, which is what issue #27 recommended: these are labels, not references, and a label carrying a version-dependent pointer goes stale again at the next reorganisation. Where a pointer earns its place it names what actually holds the content now -- UPGRADING.md for the confirmed defects, libakerror #15 for the target namespacing, issue #7 for the mutation survivors. README.md and akstdlib.h sent readers to TODO.md for 'what is still open'; they name the tracker. Verified: cmake --build build && ctest --test-dir build, 19/19. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
178 lines
5.9 KiB
C
178 lines
5.9 KiB
C
/*
|
|
* Version reporting.
|
|
*
|
|
* There are two versions in play and the whole point of this API is that they
|
|
* are allowed to differ:
|
|
*
|
|
* the AKSL_VERSION_* macros what the caller was COMPILED against, baked
|
|
* into the caller's object file from whatever
|
|
* akstdlib_version.h was on its include path
|
|
* aksl_version() and friends what is actually LOADED, baked into
|
|
* libakstdlib.so when the library was built
|
|
*
|
|
* In this test they necessarily agree, because the test binary and the library
|
|
* are built from one tree in one configure. So the assertions below split into
|
|
* two kinds: the ones that check the two sides agree (which would catch a build
|
|
* that compiled the library and its tests against different generated headers),
|
|
* and the ones that drive aksl_version_check() with deliberately wrong numbers
|
|
* to prove it actually refuses a mismatch rather than always returning success.
|
|
* The second kind is what makes the first kind worth anything.
|
|
*/
|
|
|
|
#include "aksl_capture.h"
|
|
|
|
static int test_version_string_matches_its_components(void)
|
|
{
|
|
char expected[64];
|
|
|
|
snprintf(expected, sizeof(expected), "%d.%d.%d",
|
|
AKSL_VERSION_MAJOR, AKSL_VERSION_MINOR, AKSL_VERSION_PATCH);
|
|
AKSL_CHECK(strcmp(AKSL_VERSION_STRING, expected) == 0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* AKSL_VERSION_NUMBER must be ordinary decimal arithmetic. Written out as a
|
|
* literal it would be tempting to spell 0.1.0 as 000100, which C reads as
|
|
* octal 64 -- so this compares the compile-time macro against the same figure
|
|
* computed at runtime from the loaded library's own components. A literal that
|
|
* went octal fails here; the current computed form cannot.
|
|
*/
|
|
static int test_version_number_is_decimal_and_ordered(void)
|
|
{
|
|
int major = -1;
|
|
int minor = -1;
|
|
int patch = -1;
|
|
|
|
AKSL_CHECK_OK(aksl_version(&major, &minor, &patch));
|
|
AKSL_CHECK(AKSL_VERSION_NUMBER == (major * 10000) + (minor * 100) + patch);
|
|
|
|
/* Usable in the preprocessor, which is the reason it exists at all. */
|
|
#if AKSL_VERSION_NUMBER < 0
|
|
AKSL_CHECK(0 && "AKSL_VERSION_NUMBER is negative");
|
|
#endif
|
|
AKSL_CHECK(AKSL_VERSION_NUMBER >= 0);
|
|
return 0;
|
|
}
|
|
|
|
/* Header and shared library came out of the same configure. */
|
|
static int test_loaded_version_matches_the_header(void)
|
|
{
|
|
int major = -1;
|
|
int minor = -1;
|
|
int patch = -1;
|
|
|
|
AKSL_CHECK_OK(aksl_version(&major, &minor, &patch));
|
|
AKSL_CHECK(major == AKSL_VERSION_MAJOR);
|
|
AKSL_CHECK(minor == AKSL_VERSION_MINOR);
|
|
AKSL_CHECK(patch == AKSL_VERSION_PATCH);
|
|
|
|
AKSL_CHECK(strcmp(aksl_version_string(), AKSL_VERSION_STRING) == 0);
|
|
AKSL_CHECK(strcmp(aksl_version_soname(), AKSL_VERSION_SONAME) == 0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The soname is the ABI-break granularity, and it mirrors the SOVERSION
|
|
* expression in CMakeLists.txt: MAJOR.MINOR while major is 0, MAJOR alone after
|
|
* that. If one of the two ever changes without the other, this fails.
|
|
*/
|
|
static int test_soname_matches_the_documented_rule(void)
|
|
{
|
|
char expected[64];
|
|
|
|
#if AKSL_VERSION_MAJOR == 0
|
|
snprintf(expected, sizeof(expected), "%d.%d",
|
|
AKSL_VERSION_MAJOR, AKSL_VERSION_MINOR);
|
|
#else
|
|
snprintf(expected, sizeof(expected), "%d", AKSL_VERSION_MAJOR);
|
|
#endif
|
|
AKSL_CHECK(strcmp(aksl_version_soname(), expected) == 0);
|
|
return 0;
|
|
}
|
|
|
|
static int test_version_check_accepts_a_matching_build(void)
|
|
{
|
|
AKSL_CHECK_OK(AKSL_VERSION_CHECK());
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The refusals. Without these the success above proves nothing -- a
|
|
* aksl_version_check() that returned NULL unconditionally would pass it.
|
|
*/
|
|
static int test_version_check_refuses_a_mismatch(void)
|
|
{
|
|
AKSL_CHECK_STATUS(aksl_version_check(AKSL_VERSION_MAJOR + 1,
|
|
AKSL_VERSION_MINOR,
|
|
AKSL_VERSION_PATCH),
|
|
AKERR_VALUE);
|
|
AKSL_CHECK_STATUS(aksl_version_check(AKSL_VERSION_MAJOR,
|
|
AKSL_VERSION_MINOR + 1,
|
|
AKSL_VERSION_PATCH),
|
|
AKERR_VALUE);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Patch is deliberately not part of the comparison: a patch bump is an ABI
|
|
* promise, so a caller built against 0.1.0 must keep working against 0.1.7.
|
|
*/
|
|
static int test_version_check_ignores_the_patch_level(void)
|
|
{
|
|
AKSL_CHECK_OK(aksl_version_check(AKSL_VERSION_MAJOR,
|
|
AKSL_VERSION_MINOR,
|
|
AKSL_VERSION_PATCH + 9));
|
|
return 0;
|
|
}
|
|
|
|
/* The error has to say which two versions disagree, or it is not actionable. */
|
|
static int test_mismatch_message_names_both_versions(void)
|
|
{
|
|
char compiled[64];
|
|
|
|
snprintf(compiled, sizeof(compiled), "%d.%d.%d",
|
|
AKSL_VERSION_MAJOR + 1, AKSL_VERSION_MINOR, AKSL_VERSION_PATCH);
|
|
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_version_check(AKSL_VERSION_MAJOR + 1,
|
|
AKSL_VERSION_MINOR,
|
|
AKSL_VERSION_PATCH),
|
|
AKERR_VALUE, compiled);
|
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_version_check(AKSL_VERSION_MAJOR + 1,
|
|
AKSL_VERSION_MINOR,
|
|
AKSL_VERSION_PATCH),
|
|
AKERR_VALUE, AKSL_VERSION_STRING);
|
|
return 0;
|
|
}
|
|
|
|
static int test_rejects_null_arguments(void)
|
|
{
|
|
int major = 0;
|
|
int minor = 0;
|
|
int patch = 0;
|
|
|
|
AKSL_CHECK_STATUS(aksl_version(NULL, &minor, &patch), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_version(&major, NULL, &patch), AKERR_NULLPOINTER);
|
|
AKSL_CHECK_STATUS(aksl_version(&major, &minor, NULL), AKERR_NULLPOINTER);
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int failures = 0;
|
|
|
|
akerr_init();
|
|
|
|
AKSL_RUN(failures, test_version_string_matches_its_components);
|
|
AKSL_RUN(failures, test_version_number_is_decimal_and_ordered);
|
|
AKSL_RUN(failures, test_loaded_version_matches_the_header);
|
|
AKSL_RUN(failures, test_soname_matches_the_documented_rule);
|
|
AKSL_RUN(failures, test_version_check_accepts_a_matching_build);
|
|
AKSL_RUN(failures, test_version_check_refuses_a_mismatch);
|
|
AKSL_RUN(failures, test_version_check_ignores_the_patch_level);
|
|
AKSL_RUN(failures, test_mismatch_message_names_both_versions);
|
|
AKSL_RUN(failures, test_rejects_null_arguments);
|
|
|
|
AKSL_REPORT(failures);
|
|
}
|