The suite could not fail and did not run. test_linkedlist.c asserted nothing at all -- it printed node names and returned 0 -- so every confirmed list defect passed it. test_tree.c was red on every run because parms.steps was never reset between its three searches, and four libakerror tests registered but never built, reporting Not Run. CI, meanwhile, was not fetching the submodule, so configure failed before reaching any of it. - tests/aksl_capture.h: AKSL_CHECK (NDEBUG-proof), AKSL_CHECK_STATUS/_OK to run an akerror-returning call, assert its status and release the context, a capturing akerr_log_method, aksl_slots_in_use(), and an AKSL_RUN driver that fails any test leaking an error-pool slot. - test_linkedlist.c: rewritten as 15 assertion cases over the append, iterate and pop behaviour that is correct today. - test_tree.c: each search builds its own tree and params. - Registration driven by AKSL_TESTS, plus AKSL_WILL_FAIL_TESTS (aborts by design) and AKSL_KNOWN_FAILING_TESTS, which marks WILL_FAIL the three new tests asserting correct behaviour for the confirmed defects in TODO.md 2.1.1-2.1.3. Fixing a defect flips its test to "unexpectedly passed", which is the cue to promote it into AKSL_TESTS. - add_test/set_tests_properties are shadowed across the libakerror add_subdirectory call: CMake cannot un-register a test and set_tests_properties cannot cross directory scopes. The dependency has its own CI. - AKSL_SANITIZE=ON builds library, tests and dependency with ASan+UBSan. - scripts/mutation_test.py ported and retargeted at src/stdlib.c; wired to a manual `mutation` target, not a CI gate until 1.1-1.9 exist. - CI: checkout with submodules: recursive, and ctest --output-on-failure. ctest is now 5/5 green in both the default and sanitizer builds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
179 lines
6.7 KiB
CMake
179 lines
6.7 KiB
CMake
cmake_minimum_required(VERSION 3.10)
|
|
project(akstdlib LANGUAGES C)
|
|
|
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ggdb -pg")
|
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g -ggdb -pg")
|
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -g -ggdb -pg")
|
|
|
|
# Sanitizer build, off by default:
|
|
# cmake -S . -B build-asan -DAKSL_SANITIZE=ON && ctest --test-dir build-asan
|
|
# Set before the dependency is added so libakerror is instrumented too --
|
|
# several of the defects in TODO.md section 2 (the uninitialised %s in
|
|
# aksl_realpath, the unbounded vsprintf in aksl_sprintf, the missing va_end in
|
|
# the printf family) only show up under ASan/UBSan.
|
|
option(AKSL_SANITIZE "Build the library and its tests with ASan + UBSan" OFF)
|
|
if(AKSL_SANITIZE)
|
|
set(AKSL_SANITIZE_FLAGS "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all")
|
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AKSL_SANITIZE_FLAGS}")
|
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${AKSL_SANITIZE_FLAGS}")
|
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${AKSL_SANITIZE_FLAGS}")
|
|
message(STATUS "AKSL_SANITIZE=ON: building with ASan + UBSan")
|
|
endif()
|
|
|
|
if(TARGET akerror::akerror)
|
|
message(STATUS "FOUND akerror::akerror")
|
|
else()
|
|
message(STATUS "MISSING akerror::akerror")
|
|
endif()
|
|
|
|
include(CTest)
|
|
include(GNUInstallDirs)
|
|
include(CMakePackageConfigHelpers)
|
|
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
# libakerror registers its own CTest entries unconditionally, and because we
|
|
# pull it in EXCLUDE_FROM_ALL its test binaries are never built -- so every
|
|
# one of them used to show up in our suite as "Not Run" and fail. CMake has no
|
|
# way to un-register a test, and set_tests_properties cannot reach across
|
|
# directory scopes, so add_test() is shadowed for the duration of the
|
|
# add_subdirectory() call. The dependency has its own CI; this project's suite
|
|
# should only contain this project's tests.
|
|
set(AKSL_SUPPRESS_ADD_TEST TRUE)
|
|
function(add_test)
|
|
if(NOT AKSL_SUPPRESS_ADD_TEST)
|
|
_add_test(${ARGV})
|
|
endif()
|
|
endfunction()
|
|
# libakerror also marks two of those tests WILL_FAIL, which would now be
|
|
# setting properties on tests that no longer exist, so suppress that too.
|
|
function(set_tests_properties)
|
|
if(NOT AKSL_SUPPRESS_ADD_TEST)
|
|
_set_tests_properties(${ARGV})
|
|
endif()
|
|
endfunction()
|
|
|
|
add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL)
|
|
|
|
set(AKSL_SUPPRESS_ADD_TEST FALSE)
|
|
else()
|
|
if(NOT TARGET akerror::akerror)
|
|
find_package(PkgConfig REQUIRED)
|
|
find_package(akerror REQUIRED)
|
|
endif()
|
|
endif()
|
|
|
|
set(akstdlib_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/akstdlib")
|
|
set(prefix ${CMAKE_INSTALL_PREFIX})
|
|
set(exec_prefix "\${prefix}")
|
|
set(libdir "\${exec_prefix}/lib")
|
|
set(includedir "\${prefix}/include")
|
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/akstdlib.pc.in ${CMAKE_CURRENT_BINARY_DIR}/akstdlib.pc @ONLY)
|
|
|
|
add_library(akstdlib SHARED
|
|
src/stdlib.c
|
|
)
|
|
|
|
add_library(akstdlib::akstdlib ALIAS akstdlib)
|
|
|
|
# Specify include directories for the library's headers (if applicable)
|
|
target_include_directories(akstdlib PUBLIC
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
|
|
)
|
|
|
|
target_link_libraries(akstdlib PUBLIC akerror::akerror)
|
|
|
|
set(main_lib_dest "lib/my_library-${MY_LIBRARY_VERSION}")
|
|
install(TARGETS akstdlib
|
|
EXPORT akstdlibTargets
|
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
)
|
|
|
|
install(FILES "include/akstdlib.h" DESTINATION "include/")
|
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akstdlib.pc DESTINATION "lib/pkgconfig/")
|
|
|
|
|
|
install(EXPORT akstdlibTargets
|
|
FILE akstdlibTargets.cmake
|
|
NAMESPACE akstdlib::
|
|
DESTINATION ${akstdlib_install_cmakedir}
|
|
)
|
|
|
|
configure_package_config_file(
|
|
cmake/akstdlib.cmake.in
|
|
"${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfig.cmake"
|
|
INSTALL_DESTINATION ${akstdlib_install_cmakedir}
|
|
)
|
|
|
|
install(FILES
|
|
"${CMAKE_CURRENT_BINARY_DIR}/akstdlibConfig.cmake"
|
|
DESTINATION ${akstdlib_install_cmakedir}
|
|
)
|
|
|
|
# Each test is one source file tests/test_<name>.c, built into the executable
|
|
# test_<name> and registered as the CTest test <name>. Shared helpers live in
|
|
# tests/aksl_capture.h.
|
|
#
|
|
# AKSL_TESTS must exit 0.
|
|
# AKSL_WILL_FAIL_TESTS expected to abort by design (an unhandled error
|
|
# reaching FINISH_NORETURN, a deliberate contract
|
|
# violation), so a non-zero exit is a pass.
|
|
# AKSL_KNOWN_FAILING_TESTS assert the *correct* behaviour of a confirmed
|
|
# defect from TODO.md section 2.1. They fail until
|
|
# the defect is fixed, and are marked WILL_FAIL so
|
|
# the suite stays green and the gap stays visible.
|
|
# When one is fixed CTest reports it as failed with
|
|
# "unexpectedly passed" -- that is the cue to move
|
|
# it up into AKSL_TESTS.
|
|
set(AKSL_TESTS
|
|
linkedlist
|
|
tree
|
|
)
|
|
|
|
set(AKSL_WILL_FAIL_TESTS
|
|
)
|
|
|
|
set(AKSL_KNOWN_FAILING_TESTS
|
|
list_append_chain # TODO.md 2.1.1 -- append truncates lists of 2+ nodes
|
|
list_iterate_head # TODO.md 2.1.2 -- iterate starts at the midpoint
|
|
tree_iterate_break # TODO.md 2.1.3 -- ITERATOR_BREAK does not stop a walk
|
|
)
|
|
|
|
foreach(_test IN LISTS AKSL_TESTS AKSL_WILL_FAIL_TESTS AKSL_KNOWN_FAILING_TESTS)
|
|
add_executable(test_${_test} tests/test_${_test}.c)
|
|
target_include_directories(test_${_test} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
|
|
target_link_libraries(test_${_test} PRIVATE akstdlib)
|
|
add_test(NAME ${_test} COMMAND test_${_test})
|
|
endforeach()
|
|
|
|
if(AKSL_WILL_FAIL_TESTS OR AKSL_KNOWN_FAILING_TESTS)
|
|
set_tests_properties(
|
|
${AKSL_WILL_FAIL_TESTS} ${AKSL_KNOWN_FAILING_TESTS}
|
|
PROPERTIES WILL_FAIL TRUE
|
|
)
|
|
endif()
|
|
|
|
# Mutation testing: break the library in small ways and confirm the test suite
|
|
# notices. This is a meta-check on the tests themselves, so it is a manual
|
|
# target (it rebuilds and re-runs the whole suite once per mutant), not a CTest
|
|
# test and not yet a CI gate -- the per-function tests in TODO.md sections
|
|
# 1.1-1.9 need to exist before a threshold would mean anything.
|
|
# cmake --build build --target mutation
|
|
find_package(Python3 COMPONENTS Interpreter)
|
|
if(Python3_FOUND)
|
|
add_custom_target(mutation
|
|
COMMAND ${Python3_EXECUTABLE}
|
|
${CMAKE_CURRENT_SOURCE_DIR}/scripts/mutation_test.py
|
|
--source-root ${CMAKE_CURRENT_SOURCE_DIR}
|
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
USES_TERMINAL
|
|
COMMENT "Running mutation tests (breaks the library, expects tests to fail)"
|
|
)
|
|
endif()
|
|
|
|
|
|
# pkgconfig
|