The version was written down twice and agreed with itself by luck. AKGL_VERSION was hand-maintained in include/akgl/game.h, and nothing connected it to the build, which had no version at all: - project() declared no VERSION, so the @PROJECT_VERSION@ substitution in akgl.pc.in expanded to nothing and every installed akgl.pc shipped an empty Version field. pkg-config --modversion returned "" and --atleast-version could not work at all. - libakgl.so had no VERSION or SOVERSION, so there was no soname and no symlink chain. A stale libakgl could be paired with new headers silently, which is the failure libakerror added its own soname to prevent. - MY_LIBRARY_VERSION at CMakeLists.txt:288 was never set anywhere. It built main_lib_dest as "lib/akgl-", and main_lib_dest was then never read. Dead line, removed. project(akgl VERSION 0.1.0) is now the only place the number appears. It drives the generated include/akgl/version.h, the shared library VERSION and SOVERSION, and the Version field in akgl.pc. Held at 0.1.0 to match the constant it replaces, so akgl_game_load_versioncmp still compares savegames against the same value. include/akgl/version.h is generated by configure_file from version.h.in and publishes AKGL_VERSION, AKGL_VERSION_MAJOR/MINOR/PATCH and AKGL_VERSION_AT_LEAST(major, minor, patch). That macro is the point: libakstdlib could not write that test against libakerror, which publishes no version macro, and had to feature-test on AKERR_FIRST_CONSUMER_STATUS instead. game.h now includes the generated header rather than defining the version itself, so consumers see AKGL_VERSION exactly where they saw it before. While the major version is 0 the soname carries major.minor, giving libakgl.so.0.1. A plain SOVERSION 0 would assert that 0.1.0 and 0.2.0 are ABI-interchangeable, and they are not -- the preceding commit alone added akgl_error_init. At 1.0.0 the soname becomes the major alone, matching libakerror. akgl_version() in src/version.c reports the version of the libakgl that is actually linked, where AKGL_VERSION reports what the caller compiled against. Comparing the two is the only way to catch a stale shared library on the loader path; the macro alone cannot see it. It returns a string rather than an akerr_ErrorContext * because it cannot fail, for the same reason akerr_name_for_status does. Add tests/version.c: the string and the numeric components must describe one version, the linked library must agree with the headers, and AKGL_VERSION_AT_LEAST must order correctly at the major, minor and patch boundaries. Verified out of band that bumping project() to 1.2.3 moves version.h, akgl.pc and the soname together, and that an install produces the libakgl.so -> .so.0.1 -> .so.0.1.0 chain with SONAME libakgl.so.0.1. Also ignore include/akgl/version.h. include/ precedes the build tree on the include path, so a stray copy there would shadow the generated one and pin every consumer to whatever version it was generated at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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);
|
|
}
|