/* * Version reporting -- TODO.md section 2.2.16. * * 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); }