Version the library at 0.1.0
project() now carries VERSION 0.1.0, and is the single place a version number is spelled. It flows into generated version macros, the shared library's VERSION/SOVERSION, the Version: field in akstdlib.pc, and a new akstdlibConfigVersion.cmake. Before this @PROJECT_VERSION@ expanded to nothing, so akstdlib.pc shipped an empty Version: and libakstdlib.so carried no soname at all. 0.x on purpose: TODO.md section 2.1 still records four confirmed defects whose fixes change documented behaviour, so the API is not being promised yet. While the major version is 0 the soname carries MAJOR.MINOR -- 0.1 and 0.2 are different ABIs -- and becomes MAJOR alone at 1.0. The if() in CMakeLists.txt and the #if in tests/test_version.c encode that rule and are tested against each other. include/akstdlib_version.h.in is configured into the build tree as akstdlib_version.h and installed beside akstdlib.h. It defines AKSL_VERSION_MAJOR/MINOR/PATCH/STRING/NUMBER and AKSL_VERSION_SONAME. AKSL_VERSION_NUMBER is computed rather than written as a literal, because a literal 000100 is octal in C and would make 0.1.0 compare as 64; test_version.c asserts it against the runtime components, so a rewrite to a literal fails. Those macros record what a caller was compiled against. aksl_version(), aksl_version_string() and aksl_version_soname() report what actually loaded, and AKSL_VERSION_CHECK() compares the two, raising AKERR_VALUE naming both. It is a macro so that it expands at the caller's site and captures the caller's numbers; the function compares them against the ones baked into the library. Compatibility is "same soname", so patch is ignored -- a caller built against 0.1.0 keeps working against 0.1.7. Normally the soname catches a mismatch at load time and the check never fires. It earns its keep when the soname is bypassed: a 0.2.0 build dropped in under the 0.1 filename loads happily, and only the check notices. write_basic_package_version_file() uses SameMinorVersion to mirror the soname, falling back to ExactVersion below CMake 3.11 where that mode does not exist. The fallback is stricter than the soname rule -- it pins the patch level too -- but never laxer, and wrongly refusing a good pairing beats wrongly accepting a bad one. Coverage of src/stdlib.c rose to 99.1% of lines (217/219), 45.1% of branches and 25/25 functions. That puts branch coverage back over the old 45 gate, but the gate stays at 40: 0.1 points of headroom is not a ratchet. ctest 14/14, ASan+UBSan 14/14, coverage 16/16 at 90/40. Also verified out of tree: SONAME libakstdlib.so.0.1 recorded in consumers, pkg-config --modversion reporting 0.1.0, find_package(akstdlib 0.1) accepted with 0.2 and 1.0 refused, a patch-bumped 0.1.1 loading and passing the check, a 0.2.0 dropped in under the 0.1 filename caught by it, and an embedded add_subdirectory build keeping its own version rather than the parent's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,12 +45,17 @@ jobs:
|
|||||||
# scripts/coverage.py needs nothing but python3 and gcc's own gcov, so
|
# scripts/coverage.py needs nothing but python3 and gcc's own gcov, so
|
||||||
# there is no lcov/gcovr to install here.
|
# there is no lcov/gcovr to install here.
|
||||||
#
|
#
|
||||||
# The gate is a ratchet, not a target: src/stdlib.c is at 99.0% of lines
|
# The gate is a ratchet, not a target: src/stdlib.c is at 99.1% of lines
|
||||||
# and 44.3% of branches, so 90/40 fails on a real regression (a test
|
# and 45.1% of branches, so 90/40 fails on a real regression (a test
|
||||||
# deleted, or new untested code added) without tripping over rounding.
|
# deleted, or new untested code added) without tripping over rounding.
|
||||||
# The report is printed either way -- the uncovered lines it lists are the
|
# The report is printed either way -- the uncovered lines it lists are the
|
||||||
# missing tests.
|
# missing tests.
|
||||||
#
|
#
|
||||||
|
# Branch coverage is back over 45 (the aksl_version_* functions are fully
|
||||||
|
# covered, which lifted it from 44.3%), but the gate stays at 40: 0.1
|
||||||
|
# points of headroom is not a ratchet, it is a coin flip on the next
|
||||||
|
# rounding change.
|
||||||
|
#
|
||||||
# The branch gate was 45 against 51.0% until the libakerror 1.0.0 bump.
|
# The branch gate was 45 against 51.0% until the libakerror 1.0.0 bump.
|
||||||
# Nothing about this library's tests changed: line coverage held at
|
# Nothing about this library's tests changed: line coverage held at
|
||||||
# 99.0% (200/202) and function coverage at 100% (21/21), but the branch
|
# 99.0% (200/202) and function coverage at 100% (21/21), but the branch
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ right list in `CMakeLists.txt`. `AKSL_TESTS` must exit zero.
|
|||||||
`AKSL_KNOWN_FAILING_TESTS` assert documented defects from `TODO.md`; when one
|
`AKSL_KNOWN_FAILING_TESTS` assert documented defects from `TODO.md`; when one
|
||||||
starts unexpectedly passing, move it into `AKSL_TESTS` with the fix.
|
starts unexpectedly passing, move it into `AKSL_TESTS` with the fix.
|
||||||
|
|
||||||
`src/stdlib.c` is at 99.0% line coverage and CI gates it at 90 (line) / 40
|
`src/stdlib.c` is at 99.1% line coverage and CI gates it at 90 (line) / 40
|
||||||
(branch), so new code needs tests in the same commit. Run
|
(branch), so new code needs tests in the same commit. Run
|
||||||
`cmake --build build-coverage --target coverage` and check the uncovered-line
|
`cmake --build build-coverage --target coverage` and check the uncovered-line
|
||||||
listing before proposing a change. Tests for behaviour that `TODO.md` records as
|
listing before proposing a change. Tests for behaviour that `TODO.md` records as
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(akstdlib LANGUAGES C)
|
# The single source of truth for the version. It flows from here into
|
||||||
|
# include/akstdlib_version.h (generated from the .in template next to it), the
|
||||||
|
# shared library's VERSION/SOVERSION, the Version: field in akstdlib.pc, and
|
||||||
|
# akstdlibConfigVersion.cmake. Nothing else should spell a version number.
|
||||||
|
#
|
||||||
|
# 0.x on purpose: TODO.md section 2.1 still records four confirmed defects whose
|
||||||
|
# fixes change documented behaviour (aksl_atoi's contract, aksl_realpath's
|
||||||
|
# signature), so this library is not promising a stable API yet.
|
||||||
|
project(akstdlib VERSION 0.1.0 LANGUAGES C)
|
||||||
|
|
||||||
|
# The granularity at which the ABI is allowed to break, and therefore the
|
||||||
|
# soname: libakstdlib.so.0.1. Pre-1.0 that is MAJOR.MINOR, because a 0.x library
|
||||||
|
# makes no compatibility promise across a minor bump. At 1.0 this becomes
|
||||||
|
# ${PROJECT_VERSION_MAJOR} alone -- change it here, and AKSL_VERSION_SONAME in
|
||||||
|
# the header template follows automatically because it is configured from this.
|
||||||
|
if(PROJECT_VERSION_MAJOR EQUAL 0)
|
||||||
|
set(AKSL_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||||
|
else()
|
||||||
|
set(AKSL_SOVERSION "${PROJECT_VERSION_MAJOR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(AKSL_GENERATED_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/include")
|
||||||
|
set(AKSL_VERSION_HEADER "${AKSL_GENERATED_INCLUDE_DIR}/akstdlib_version.h")
|
||||||
|
configure_file(
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/include/akstdlib_version.h.in"
|
||||||
|
"${AKSL_VERSION_HEADER}"
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ggdb -pg")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ggdb -pg")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g -ggdb -pg")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g -ggdb -pg")
|
||||||
@@ -135,12 +162,25 @@ add_library(akstdlib SHARED
|
|||||||
|
|
||||||
add_library(akstdlib::akstdlib ALIAS akstdlib)
|
add_library(akstdlib::akstdlib ALIAS akstdlib)
|
||||||
|
|
||||||
# Specify include directories for the library's headers (if applicable)
|
# Specify include directories for the library's headers (if applicable).
|
||||||
|
# The generated directory carries akstdlib_version.h, which akstdlib.h includes;
|
||||||
|
# it is PUBLIC because consumers building against the build tree need it too. On
|
||||||
|
# install both headers land side by side in ${CMAKE_INSTALL_INCLUDEDIR}, so the
|
||||||
|
# install interface needs no second entry.
|
||||||
target_include_directories(akstdlib PUBLIC
|
target_include_directories(akstdlib PUBLIC
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<BUILD_INTERFACE:${AKSL_GENERATED_INCLUDE_DIR}>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# VERSION gives the real file (libakstdlib.so.0.1.0); SOVERSION gives the symlink
|
||||||
|
# and the ELF soname recorded in every consumer (libakstdlib.so.0.1), so a
|
||||||
|
# consumer built against 0.1 will not silently load an ABI-incompatible 0.2.
|
||||||
|
set_target_properties(akstdlib PROPERTIES
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
SOVERSION ${AKSL_SOVERSION}
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(akstdlib PUBLIC akerror::akerror)
|
target_link_libraries(akstdlib PUBLIC akerror::akerror)
|
||||||
|
|
||||||
aksl_target_coverage(akstdlib)
|
aksl_target_coverage(akstdlib)
|
||||||
@@ -155,6 +195,7 @@ install(TARGETS akstdlib
|
|||||||
)
|
)
|
||||||
|
|
||||||
install(FILES "include/akstdlib.h" DESTINATION "include/")
|
install(FILES "include/akstdlib.h" DESTINATION "include/")
|
||||||
|
install(FILES "${AKSL_VERSION_HEADER}" DESTINATION "include/")
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akstdlib.pc DESTINATION "lib/pkgconfig/")
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akstdlib.pc DESTINATION "lib/pkgconfig/")
|
||||||
|
|
||||||
|
|
||||||
@@ -170,8 +211,31 @@ configure_package_config_file(
|
|||||||
INSTALL_DESTINATION ${akstdlib_install_cmakedir}
|
INSTALL_DESTINATION ${akstdlib_install_cmakedir}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Without this, find_package(akstdlib 0.1 REQUIRED) is refused for want of a
|
||||||
|
# version file no matter what is installed -- which is exactly the gap that
|
||||||
|
# stops cmake/akstdlib.cmake.in from requesting a version of akerror.
|
||||||
|
#
|
||||||
|
# SameMinorVersion mirrors the soname: pre-1.0, 0.1 and 0.2 are different ABIs.
|
||||||
|
# It arrived in CMake 3.11 and this project declares 3.10, so fall back to
|
||||||
|
# ExactVersion on older CMake. That is stricter than the soname rule -- it pins
|
||||||
|
# the patch level too, so a 0.1.0 request refuses a compatible 0.1.1 -- but it is
|
||||||
|
# never laxer, and wrongly refusing a good pairing beats wrongly accepting a bad
|
||||||
|
# one. Drop the branch when the minimum moves past 3.11.
|
||||||
|
if(CMAKE_VERSION VERSION_LESS 3.11)
|
||||||
|
set(AKSL_VERSION_COMPATIBILITY ExactVersion)
|
||||||
|
else()
|
||||||
|
set(AKSL_VERSION_COMPATIBILITY SameMinorVersion)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
write_basic_package_version_file(
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfigVersion.cmake"
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY ${AKSL_VERSION_COMPATIBILITY}
|
||||||
|
)
|
||||||
|
|
||||||
install(FILES
|
install(FILES
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfig.cmake"
|
"${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfig.cmake"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfigVersion.cmake"
|
||||||
DESTINATION ${akstdlib_install_cmakedir}
|
DESTINATION ${akstdlib_install_cmakedir}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -200,6 +264,7 @@ set(AKSL_TESTS
|
|||||||
stream
|
stream
|
||||||
strhash
|
strhash
|
||||||
tree
|
tree
|
||||||
|
version
|
||||||
)
|
)
|
||||||
|
|
||||||
set(AKSL_WILL_FAIL_TESTS
|
set(AKSL_WILL_FAIL_TESTS
|
||||||
|
|||||||
74
README.md
74
README.md
@@ -25,6 +25,70 @@ A top-level build compiles the vendored `deps/libakerror`. When `libakstdlib` is
|
|||||||
consumed as a subproject, it uses whatever `akerror::akerror` target or installed
|
consumed as a subproject, it uses whatever `akerror::akerror` target or installed
|
||||||
package the parent provides instead.
|
package the parent provides instead.
|
||||||
|
|
||||||
|
### This library's own version
|
||||||
|
|
||||||
|
`libakstdlib` is at **0.1.0**. The version lives in exactly one place — the
|
||||||
|
`project()` call in `CMakeLists.txt` — and flows from there into everything
|
||||||
|
else, so a bump is a one-line edit:
|
||||||
|
|
||||||
|
| Artifact | Value at 0.1.0 | From |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `AKSL_VERSION_MAJOR` / `_MINOR` / `_PATCH` | `0` / `1` / `0` | `include/akstdlib_version.h.in` |
|
||||||
|
| `AKSL_VERSION_STRING` | `"0.1.0"` | same |
|
||||||
|
| `AKSL_VERSION_NUMBER` | `100` | same |
|
||||||
|
| `AKSL_VERSION_SONAME` | `"0.1"` | same |
|
||||||
|
| shared library | `libakstdlib.so.0.1.0`, soname `libakstdlib.so.0.1` | `VERSION` / `SOVERSION` |
|
||||||
|
| `pkg-config --modversion akstdlib` | `0.1.0` | `akstdlib.pc` |
|
||||||
|
| `find_package(akstdlib 0.1)` | accepted; `0.2` and `1.0` refused | `akstdlibConfigVersion.cmake` |
|
||||||
|
|
||||||
|
`akstdlib_version.h` is **generated** — that is why there is no such file in the
|
||||||
|
source tree, only the `.in` template beside `akstdlib.h`. Don't hand-edit the
|
||||||
|
copy in your build directory; change the template or `project()`.
|
||||||
|
|
||||||
|
It is `0.x` deliberately. `TODO.md` §2.1 still records four confirmed defects
|
||||||
|
whose fixes change documented behaviour, so the API is not being promised yet.
|
||||||
|
While the major version is `0`, **the soname carries `MAJOR.MINOR`**: 0.1 and 0.2
|
||||||
|
are different ABIs and the loader will not substitute one for the other. At 1.0
|
||||||
|
the soname becomes `MAJOR` alone — the `if(PROJECT_VERSION_MAJOR EQUAL 0)` in
|
||||||
|
`CMakeLists.txt` and the matching `#if` in `tests/test_version.c` are the two
|
||||||
|
places that encode this, and they are tested against each other.
|
||||||
|
|
||||||
|
`AKSL_VERSION_NUMBER` is computed rather than written as a literal, because a
|
||||||
|
literal `000100` is *octal* in C and would make 0.1.0 compare as 64.
|
||||||
|
|
||||||
|
#### Compiled-against vs. loaded
|
||||||
|
|
||||||
|
The macros above record what a caller was **compiled** against. What it actually
|
||||||
|
**loaded** is a different question, and the two can disagree:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int major, minor, patch;
|
||||||
|
akerr_ErrorContext *e = aksl_version(&major, &minor, &patch); /* the loaded .so */
|
||||||
|
const char *v = aksl_version_string(); /* likewise */
|
||||||
|
|
||||||
|
e = AKSL_VERSION_CHECK(); /* compares the two; AKERR_VALUE on a mismatch */
|
||||||
|
```
|
||||||
|
|
||||||
|
`AKSL_VERSION_CHECK()` is a macro on purpose: it expands at *your* call site, so
|
||||||
|
it captures the `AKSL_VERSION_*` you were built with and passes them to a
|
||||||
|
function that compares against the values baked into the library. Calling
|
||||||
|
`aksl_version_check()` with hand-written numbers defeats the whole mechanism.
|
||||||
|
|
||||||
|
Compatibility is defined as "same soname", so pre-1.0 both major and minor must
|
||||||
|
match and the patch level is ignored — a caller built against 0.1.0 keeps working
|
||||||
|
against 0.1.7, which is exactly the promise the shared soname makes.
|
||||||
|
|
||||||
|
In normal use the soname catches the mismatch first, at load time, and the check
|
||||||
|
never fires. It earns its keep when the soname is bypassed: a hand-install that
|
||||||
|
drops a 0.2.0 build in under the 0.1 filename, or a package that strips
|
||||||
|
versioning. Then the loader is happy and only the check notices:
|
||||||
|
|
||||||
|
```
|
||||||
|
compiled against : 0.1.0 (soname 0.1)
|
||||||
|
loaded : 0.2.0 (0.2.0)
|
||||||
|
MISMATCH DETECTED: compiled against libakstdlib 0.1.0, loaded 0.2.0 (soname 0.2)
|
||||||
|
```
|
||||||
|
|
||||||
### The libakerror version floor
|
### The libakerror version floor
|
||||||
|
|
||||||
**libakerror 1.0.0 or newer is required.** That release made the status-name
|
**libakerror 1.0.0 or newer is required.** That release made the status-name
|
||||||
@@ -182,8 +246,8 @@ cmake -S . -B build-coverage -DAKSL_COVERAGE=ON \
|
|||||||
-DAKSL_COVERAGE_THRESHOLD=90 -DAKSL_COVERAGE_BRANCH_THRESHOLD=40
|
-DAKSL_COVERAGE_THRESHOLD=90 -DAKSL_COVERAGE_BRANCH_THRESHOLD=40
|
||||||
```
|
```
|
||||||
|
|
||||||
**Where it stands.** `src/stdlib.c` is at **99.0% of lines (200/202)**, **44.3% of
|
**Where it stands.** `src/stdlib.c` is at **99.1% of lines (217/219)**, **45.1% of
|
||||||
branches (481/1087)** and **21/21 functions**, so 90/40 above is a ratchet with
|
branches (519/1151)** and **25/25 functions**, so 90/40 above is a ratchet with
|
||||||
headroom rather than a target. The two uncovered lines are both
|
headroom rather than a target. The two uncovered lines are both
|
||||||
`} HANDLE(e, AKERR_ITERATOR_BREAK) {` — in libakerror that macro begins with the
|
`} HANDLE(e, AKERR_ITERATOR_BREAK) {` — in libakerror that macro begins with the
|
||||||
`break;` belonging to `PROCESS`'s `case 0:` arm, which is only reachable when a
|
`break;` belonging to `PROCESS`'s `case 0:` arm, which is only reachable when a
|
||||||
@@ -197,8 +261,10 @@ stack-trace buffer limits, `akerr_valid_error_address` failures — and belong t
|
|||||||
libakerror's own suite rather than to this one. The libakerror 1.0.0 bump made
|
libakerror's own suite rather than to this one. The libakerror 1.0.0 bump made
|
||||||
that gap wider without changing a line of this library: the branch denominator
|
that gap wider without changing a line of this library: the branch denominator
|
||||||
went from 661 to 1087 as those macros grew, so the same tests that scored 51.0%
|
went from 661 to 1087 as those macros grew, so the same tests that scored 51.0%
|
||||||
(337/661) now score 44.3% (481/1087). The gate moved 45 → 40 to match; line and
|
(337/661) dropped to 44.3% (481/1087). The gate moved 45 → 40 to match; line and
|
||||||
function coverage did not move at all.
|
function coverage did not move at all. Adding the `aksl_version_*` family and its
|
||||||
|
tests brought the figure back to 45.1% (519/1151), but the gate stays at 40 —
|
||||||
|
0.1 points of headroom is not a ratchet.
|
||||||
|
|
||||||
Two caveats. Coverage is measured at `-O0`, because the optimizer reorders lines
|
Two caveats. Coverage is measured at `-O0`, because the optimizer reorders lines
|
||||||
until per-line counts stop matching the source — so a coverage build is not the
|
until per-line counts stop matching the source — so a coverage build is not the
|
||||||
|
|||||||
44
TODO.md
44
TODO.md
@@ -406,14 +406,19 @@ cue to move it into `AKSL_TESTS`.
|
|||||||
|
|
||||||
15. **`aksl_TreeNode.parent` is declared but never set or read** by any library function.
|
15. **`aksl_TreeNode.parent` is declared but never set or read** by any library function.
|
||||||
|
|
||||||
16. **Header hygiene**: no `extern "C" { }` guard for C++ consumers; no version macros
|
16. **Header hygiene**: no `extern "C" { }` guard for C++ consumers; `akstdlib.h` pulls in
|
||||||
(`AKSL_VERSION_MAJOR`…); `akstdlib.h` pulls in `stdio.h`/`stdlib.h`/`string.h`/`stdint.h`
|
`stdio.h`/`stdlib.h`/`string.h`/`stdint.h` into every consumer's namespace. The version
|
||||||
into every consumer's namespace.
|
macros this item also asked for are **done**: `AKSL_VERSION_MAJOR`/`_MINOR`/`_PATCH`/
|
||||||
|
`_STRING`/`_NUMBER`/`_SONAME` are generated into `akstdlib_version.h` from
|
||||||
|
`project(akstdlib VERSION …)`, with `aksl_version()`/`aksl_version_string()`/
|
||||||
|
`aksl_version_soname()` and `AKSL_VERSION_CHECK()` reporting the *loaded* library so a
|
||||||
|
header/`.so` mismatch is nameable. See `tests/test_version.c` and `README.md`.
|
||||||
|
|
||||||
17. **Doxygen coverage is 2 functions out of 20.** A `Doxyfile` exists but only
|
17. **Doxygen coverage is 2 functions out of 24.** A `Doxyfile` exists but only
|
||||||
`aksl_tree_iterate` and `aksl_list_iterate` have doc comments. Every public function
|
`aksl_tree_iterate` and `aksl_list_iterate` have doc comments. Every public function
|
||||||
needs `@param`/`@throws`/`@return`, especially the ones whose contract deviates from libc
|
needs `@param`/`@throws`/`@return`, especially the ones whose contract deviates from libc
|
||||||
(`aksl_free(NULL)` is an error; `aksl_atoi` will become strict).
|
(`aksl_free(NULL)` is an error; `aksl_atoi` will become strict). The four
|
||||||
|
`aksl_version_*` entry points have prose comments in the header but no Doxygen tags.
|
||||||
|
|
||||||
### 2.3 Build, CI and repository
|
### 2.3 Build, CI and repository
|
||||||
|
|
||||||
@@ -456,15 +461,26 @@ cue to move it into `AKSL_TESTS`.
|
|||||||
would mean testing libakerror's macros rather than this library; that belongs to
|
would mean testing libakerror's macros rather than this library; that belongs to
|
||||||
libakerror's mutation suite, since macros expand at the call site and coverage cannot
|
libakerror's mutation suite, since macros expand at the call site and coverage cannot
|
||||||
see them properly from either side. Revisit if the gap ever hides a real regression.
|
see them properly from either side. Revisit if the gap ever hides a real regression.
|
||||||
- [ ] **`project()` declares no `VERSION`**, so `@PROJECT_VERSION@` expands to nothing and
|
- [x] **`project()` declared no `VERSION`**, so `@PROJECT_VERSION@` expanded to nothing, the
|
||||||
the installed `akstdlib.pc` ships an empty `Version:` field — a consumer cannot pin
|
installed `akstdlib.pc` shipped an empty `Version:` field, and `libakstdlib.so` carried
|
||||||
this library the way `akstdlib.pc` now pins `akerror >= 1.0.0`. libakstdlib also has
|
no soname. Now `project(akstdlib VERSION 0.1.0)`, with `SOVERSION` `0.1`
|
||||||
no `SOVERSION`, so `libakstdlib.so` carries no soname even though its public header
|
(`MAJOR.MINOR` while major is 0, `MAJOR` from 1.0 — the `if()` in `CMakeLists.txt` and
|
||||||
re-exports libakerror's ABI and therefore breaks whenever libakerror's does. Give the
|
the matching `#if` in `tests/test_version.c` encode the rule and are tested against
|
||||||
project a version and a `SOVERSION`, and install an `akstdlibConfigVersion.cmake` via
|
each other), an `akstdlibConfigVersion.cmake` at `SameMinorVersion`, and version macros
|
||||||
`write_basic_package_version_file()` so `find_package(akstdlib 1.2 REQUIRED)` can work.
|
generated into `akstdlib_version.h`. Verified: `find_package(akstdlib 0.1)` is accepted
|
||||||
(libakerror has the same `ConfigVersion` gap, which is why
|
while `0.2` and `1.0` are refused, and a 0.2.0 build dropped in under the 0.1 filename
|
||||||
`cmake/akstdlib.cmake.in` calls `find_dependency(akerror)` with no version.)
|
is caught by `AKSL_VERSION_CHECK()`.
|
||||||
|
- [ ] **libakerror still installs no `akerrorConfigVersion.cmake`**, so
|
||||||
|
`cmake/akstdlib.cmake.in` has to call `find_dependency(akerror)` with no version — a
|
||||||
|
request for one would be refused for want of a version file regardless of what is
|
||||||
|
installed. libakstdlib now does this correctly and libakerror does not; fix it there
|
||||||
|
with the same `write_basic_package_version_file()` call, then add the `1.0.0` floor to
|
||||||
|
the `find_dependency` here. Until then the floor rests on `akstdlib.pc`'s `Requires:`
|
||||||
|
and the `#error` guard in `akstdlib.h`.
|
||||||
|
- [ ] **`aksl_version_check()` ignores its `patch` argument** (`src/stdlib.c`, the `(void)patch`),
|
||||||
|
which is correct for the current "same soname" rule but means the parameter exists only
|
||||||
|
so the error message can name the caller's full version. If a future rule needs patch
|
||||||
|
to participate, that is the line to change.
|
||||||
- [ ] **CI does not build against the submodule it pins.** `.gitea/workflows/ci.yaml` clones
|
- [ ] **CI does not build against the submodule it pins.** `.gitea/workflows/ci.yaml` clones
|
||||||
`libakerror@main` and installs it, while the build it then runs is top-level and so
|
`libakerror@main` and installs it, while the build it then runs is top-level and so
|
||||||
compiles `deps/libakerror` at the pinned commit — two different libakerror versions
|
compiles `deps/libakerror` at the pinned commit — two different libakerror versions
|
||||||
|
|||||||
@@ -21,6 +21,14 @@
|
|||||||
#error "libakstdlib requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
|
#error "libakstdlib requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AKSL_VERSION_MAJOR/MINOR/PATCH/STRING/NUMBER and AKSL_VERSION_SONAME.
|
||||||
|
* Generated by CMake from include/akstdlib_version.h.in, which is why there is
|
||||||
|
* no such file in the source tree -- it is configured into the build tree and
|
||||||
|
* installed alongside this header.
|
||||||
|
*/
|
||||||
|
#include <akstdlib_version.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -52,6 +60,42 @@ typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_TreeNodeIterator)(aksl_TreeNod
|
|||||||
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_AllocFunc)(size_t size, void **dest);
|
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_AllocFunc)(size_t size, void **dest);
|
||||||
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_FreeFunc)(void *ptr);
|
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_FreeFunc)(void *ptr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Version of the shared library actually loaded, as opposed to the
|
||||||
|
* AKSL_VERSION_* macros above, which record what the caller was *compiled*
|
||||||
|
* against. The two differ exactly when a stale libakstdlib.so is on the loader
|
||||||
|
* path, which is the failure these entry points exist to name.
|
||||||
|
*
|
||||||
|
* All three pointers are required; this library treats a NULL out-param as a
|
||||||
|
* caller error rather than as "don't care" (so does aksl_free).
|
||||||
|
*/
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_version(int *major, int *minor, int *patch);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The loaded library's version as "MAJOR.MINOR.PATCH", and its soname. Neither
|
||||||
|
* can fail -- they return pointers to string literals in the library's own
|
||||||
|
* .rodata -- so they return the string directly rather than an error context,
|
||||||
|
* the same exception akerr_name_for_status() makes for the same reason.
|
||||||
|
*/
|
||||||
|
const char *aksl_version_string(void);
|
||||||
|
const char *aksl_version_soname(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare the caller's compiled-in version against the loaded library's.
|
||||||
|
* Compatibility is defined as "same soname", so pre-1.0 both major and minor
|
||||||
|
* must match and patch is ignored; from 1.0 only major will matter. Raises
|
||||||
|
* AKERR_VALUE naming both versions on a mismatch.
|
||||||
|
*
|
||||||
|
* Call it through AKSL_VERSION_CHECK() below rather than directly. The macro
|
||||||
|
* expands at *your* call site, so it captures the AKSL_VERSION_* the caller was
|
||||||
|
* built with; the function compares them against the values baked into the
|
||||||
|
* library. Passing the numbers by hand defeats the entire mechanism.
|
||||||
|
*/
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_version_check(int major, int minor, int patch);
|
||||||
|
|
||||||
|
#define AKSL_VERSION_CHECK() \
|
||||||
|
aksl_version_check(AKSL_VERSION_MAJOR, AKSL_VERSION_MINOR, AKSL_VERSION_PATCH)
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(char *pathname, char *mode, FILE **fp);
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(char *pathname, char *mode, FILE **fp);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fwrite(void *ptr, size_t size, size_t nmemb, FILE *fp);
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_fwrite(void *ptr, size_t size, size_t nmemb, FILE *fp);
|
||||||
|
|||||||
37
include/akstdlib_version.h.in
Normal file
37
include/akstdlib_version.h.in
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef _AKSTDLIB_VERSION_H_
|
||||||
|
#define _AKSTDLIB_VERSION_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GENERATED FILE -- do not edit.
|
||||||
|
*
|
||||||
|
* Configured from include/akstdlib_version.h.in by CMake. The version itself
|
||||||
|
* lives in exactly one place, the project() call in CMakeLists.txt, and flows
|
||||||
|
* from there into these macros, the shared library's SOVERSION, the Version:
|
||||||
|
* field in akstdlib.pc, and akstdlibConfigVersion.cmake. To bump the version,
|
||||||
|
* edit project(); to change what the macros look like, edit this template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define AKSL_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||||
|
#define AKSL_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||||
|
#define AKSL_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||||
|
#define AKSL_VERSION_STRING "@PROJECT_VERSION@"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A single ordered integer, for `#if AKSL_VERSION_NUMBER >= ...` compares.
|
||||||
|
* Computed rather than written out as a literal on purpose: a literal like
|
||||||
|
* 000100 is octal in C, which would silently make version 0.1.0 compare as 64.
|
||||||
|
* Each component gets two decimal digits, so this is correct up to x.99.99.
|
||||||
|
*/
|
||||||
|
#define AKSL_VERSION_NUMBER \
|
||||||
|
((AKSL_VERSION_MAJOR * 10000) + (AKSL_VERSION_MINOR * 100) + AKSL_VERSION_PATCH)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The soname of the library these headers describe -- "0.1" for libakstdlib.so.0.1.
|
||||||
|
* This is the granularity at which the ABI is allowed to break, and it is what
|
||||||
|
* aksl_version_check() compares: pre-1.0 a minor bump is a break, so the soname
|
||||||
|
* carries MAJOR.MINOR. From 1.0 it becomes MAJOR alone, and the SOVERSION
|
||||||
|
* expression in CMakeLists.txt and this macro change together.
|
||||||
|
*/
|
||||||
|
#define AKSL_VERSION_SONAME "@AKSL_SOVERSION@"
|
||||||
|
|
||||||
|
#endif // _AKSTDLIB_VERSION_H_
|
||||||
51
src/stdlib.c
51
src/stdlib.c
@@ -6,6 +6,57 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Version of the library itself. These read the AKSL_VERSION_* macros as they
|
||||||
|
* were when *this translation unit* was compiled, which is what makes them the
|
||||||
|
* loaded library's version rather than the caller's: the caller's copy of the
|
||||||
|
* same macros comes from whatever akstdlib_version.h it compiled against.
|
||||||
|
*/
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_version(int *major, int *minor, int *patch)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(e);
|
||||||
|
FAIL_ZERO_RETURN(e, major, AKERR_NULLPOINTER, "major=%p, minor=%p, patch=%p",
|
||||||
|
(void *)major, (void *)minor, (void *)patch);
|
||||||
|
FAIL_ZERO_RETURN(e, minor, AKERR_NULLPOINTER, "major=%p, minor=%p, patch=%p",
|
||||||
|
(void *)major, (void *)minor, (void *)patch);
|
||||||
|
FAIL_ZERO_RETURN(e, patch, AKERR_NULLPOINTER, "major=%p, minor=%p, patch=%p",
|
||||||
|
(void *)major, (void *)minor, (void *)patch);
|
||||||
|
*major = AKSL_VERSION_MAJOR;
|
||||||
|
*minor = AKSL_VERSION_MINOR;
|
||||||
|
*patch = AKSL_VERSION_PATCH;
|
||||||
|
SUCCEED_RETURN(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *aksl_version_string(void)
|
||||||
|
{
|
||||||
|
return AKSL_VERSION_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *aksl_version_soname(void)
|
||||||
|
{
|
||||||
|
return AKSL_VERSION_SONAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compatibility is "same soname". Pre-1.0 that is MAJOR.MINOR, so both are
|
||||||
|
* compared and patch is deliberately ignored; from 1.0 the minor comparison
|
||||||
|
* comes out, in step with the SOVERSION expression in CMakeLists.txt. The
|
||||||
|
* caller's numbers arrive as arguments because AKSL_VERSION_CHECK() expanded
|
||||||
|
* them at the caller's site -- see the macro in akstdlib.h.
|
||||||
|
*/
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_version_check(int major, int minor, int patch)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(e);
|
||||||
|
(void)patch;
|
||||||
|
FAIL_NONZERO_RETURN(e,
|
||||||
|
(major != AKSL_VERSION_MAJOR || minor != AKSL_VERSION_MINOR),
|
||||||
|
AKERR_VALUE,
|
||||||
|
"compiled against libakstdlib %d.%d.%d, loaded %s (soname %s)",
|
||||||
|
major, minor, patch,
|
||||||
|
AKSL_VERSION_STRING, AKSL_VERSION_SONAME);
|
||||||
|
SUCCEED_RETURN(e);
|
||||||
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_malloc(size_t size, void **dst)
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_malloc(size_t size, void **dst)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
|
|||||||
177
tests/test_version.c
Normal file
177
tests/test_version.c
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user