Cover deterministic TODO 1.1 memory-wrapper behavior with a new CTest target. Exercise malloc/free round trips, NULL argument handling, memset fill/no-op semantics, and memcpy copy/no-op/null-pointer paths. Harden AKSL_CHECK_OK so success requires no returned error context, catching wrappers that accidentally raise status-0 errors from stale errno. Co-authored-by: Codex GPT-5.5 Default <codex-gpt-5.5-default@openai.com>
193 lines
7.3 KiB
CMake
193 lines
7.3 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
|
|
memory
|
|
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()
|
|
|
|
# Cap every test. The list and tree code is full of loops whose termination
|
|
# depends on a single condition, so a plausible bug -- or a mutant from the
|
|
# target below -- turns a test into an infinite loop. Without this, ctest waits
|
|
# forever; the mutation harness then kills ctest at its own timeout and the
|
|
# spinning test binary is left orphaned, holding the pipe open and burning a
|
|
# core. The whole suite runs in well under a second, so 30s is pure headroom.
|
|
set_tests_properties(
|
|
${AKSL_TESTS} ${AKSL_WILL_FAIL_TESTS} ${AKSL_KNOWN_FAILING_TESTS}
|
|
PROPERTIES TIMEOUT 30
|
|
)
|
|
|
|
# Mutation testing: break the library in small ways and confirm the test suite
|
|
# notices. This is a meta-check on the tests themselves, and it rebuilds and
|
|
# re-runs the whole suite once per mutant, so it is a manual target rather than
|
|
# a CTest test:
|
|
# cmake --build build --target mutation
|
|
# This target covers both src/stdlib.c and include/akstdlib.h. CI runs the
|
|
# narrower, faster src/stdlib.c set with a --threshold gate; see
|
|
# .gitea/workflows/ci.yaml.
|
|
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
|