diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ef516ad --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: -ferror-limit=0 diff --git a/CMakeLists.txt b/CMakeLists.txt index b53d656..9c0343c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,13 @@ project(uppaal_libs) include(cmake/stdcpp.cmake) include(cmake/sanitizers.cmake) -option(UPPAALLIBS_WITH_TESTS "UPPAAL LIBS Unit Tests" ON) +option(UPPAALLIBS_WITH_TESTS "UPPAAL LIBS Unit Tests" ${PROJECT_IS_TOP_LEVEL}) + +include_directories(include) +add_subdirectory(src) if (UPPAALLIBS_WITH_TESTS) include(cmake/doctest.cmake) enable_testing() + add_subdirectory(tests) endif (UPPAALLIBS_WITH_TESTS) - -add_subdirectory(src) diff --git a/cmake/doctest.cmake b/cmake/doctest.cmake index 6c410c1..83c8e58 100644 --- a/cmake/doctest.cmake +++ b/cmake/doctest.cmake @@ -1,17 +1,46 @@ # Downloads and compiles DocTest unit testing framework include(FetchContent) -set(FETCHCONTENT_QUIET ON) -set(FETCHCONTENT_UPDATES_DISCONNECTED ON) +#set(FETCHCONTENT_QUIET ON) +#set(FETCHCONTENT_UPDATES_DISCONNECTED ON) -set(DOCTEST_WITH_TESTS OFF CACHE BOOL "doctest tests and examples") -set(DOCTEST_WITH_MAIN_IN_STATIC_LIB ON CACHE BOOL "enable doctest_with_main") -set(DOCTEST_NO_INSTALL OFF CACHE BOOL "Skip the installation process") -#set(DOCTEST_USE_STD_HEADERS OFF CACHE BOOL "Use std headers") +set(CMAKE_POLICY_VERSION_MINIMUM 3.10) # turn off the CMake warnings FetchContent_Declare(doctest GIT_REPOSITORY https://github.com/doctest/doctest.git - GIT_TAG v2.4.11 # "main" for latest + GIT_TAG v2.4.12 # "main" for latest GIT_SHALLOW TRUE # download specific revision only (git clone --depth 1) GIT_PROGRESS TRUE # show download progress in Ninja - USES_TERMINAL_DOWNLOAD TRUE) + EXCLUDE_FROM_ALL ON # don't build if not used + FIND_PACKAGE_ARGS 2.4.12) + +set(DOCTEST_WITH_TESTS OFF CACHE BOOL "Build tests/examples") +set(DOCTEST_WITH_MAIN_IN_STATIC_LIB ON CACHE BOOL "Build a static lib for doctest::doctest_with_main") +set(DOCTEST_NO_INSTALL OFF CACHE BOOL "Skip the installation process") +set(DOCTEST_USE_STD_HEADERS OFF CACHE BOOL "Use std headers") + FetchContent_MakeAvailable(doctest) + +if(doctest_FOUND) # find_package + message(STATUS "Found doctest: ${doctest_DIR}") +else(doctest_FOUND) # FetchContent + message(STATUS "Fetched doctest: ${doctest_SOURCE_DIR}") +endif(doctest_FOUND) + +if (TARGET doctest::doctest) + message(STATUS " Available target: doctest::doctest") +endif () + +if (TARGET doctest::doctest_with_main) + message(STATUS " Available target: doctest::doctest_with_main") +endif () + +macro(add_debug_test TEST_NAME) + get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(isMultiConfig) + add_test(NAME ${TEST_NAME} COMMAND ${ARGN} CONFIGURATIONS Debug RelWithDebInfo) + else() + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + add_test(NAME ${TEST_NAME} COMMAND ${ARGN}) + endif() + endif() +endmacro() diff --git a/cmake/stdcpp.cmake b/cmake/stdcpp.cmake index f05c045..ab6a54d 100644 --- a/cmake/stdcpp.cmake +++ b/cmake/stdcpp.cmake @@ -5,17 +5,14 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # -fPIC set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # for clang-tidy -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - message(STATUS "Extra warnings for ${CMAKE_CXX_COMPILER_ID} compiler") - add_compile_options(-Wpedantic -Wall -Wextra -Wno-cast-function-type) -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - message(STATUS "Extra warnings for ${CMAKE_CXX_COMPILER_ID} compiler") - add_compile_options(-Wpedantic -Wall -Wextra -Wno-cast-function-type) +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(STATUS "Enabled extra warnings for ${CMAKE_CXX_COMPILER}") + add_compile_options(-Wpedantic -Wall -Wextra -Wconversion -Wno-cast-function-type) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - message(STATUS "Extra warnings for ${CMAKE_CXX_COMPILER_ID} compiler") + message(STATUS "Enabled extra warnings for ${CMAKE_CXX_COMPILER}") add_compile_options( /permissive- # strict standards - /Wall -wd4191 -wd4668 -wd4710 -wd4711 -wd5045) + /W4) add_compile_definitions(__STDC_LIB_EXT1__ NOMINMAX) # enable fopen_s, kill min/max macros else() message(STATUS "No extra warnings for ${CMAKE_CXX_COMPILER_ID} compiler") diff --git a/toolchain/i686-w64-mingw32.cmake b/cmake/toolchain/i686-w64-mingw32.cmake similarity index 100% rename from toolchain/i686-w64-mingw32.cmake rename to cmake/toolchain/i686-w64-mingw32.cmake diff --git a/toolchain/linux32-gcc10.cmake b/cmake/toolchain/linux32-gcc10.cmake similarity index 100% rename from toolchain/linux32-gcc10.cmake rename to cmake/toolchain/linux32-gcc10.cmake diff --git a/toolchain/linux32-gcc11.cmake b/cmake/toolchain/linux32-gcc11.cmake similarity index 100% rename from toolchain/linux32-gcc11.cmake rename to cmake/toolchain/linux32-gcc11.cmake diff --git a/toolchain/linux32-gcc12.cmake b/cmake/toolchain/linux32-gcc12.cmake similarity index 100% rename from toolchain/linux32-gcc12.cmake rename to cmake/toolchain/linux32-gcc12.cmake diff --git a/toolchain/linux32-gcc13.cmake b/cmake/toolchain/linux32-gcc13.cmake similarity index 100% rename from toolchain/linux32-gcc13.cmake rename to cmake/toolchain/linux32-gcc13.cmake diff --git a/cmake/toolchain/linux32-gcc14.cmake b/cmake/toolchain/linux32-gcc14.cmake new file mode 100644 index 0000000..4bbeccc --- /dev/null +++ b/cmake/toolchain/linux32-gcc14.cmake @@ -0,0 +1,18 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-14) +set(CMAKE_C_FLAGS -m32) +set(CMAKE_CXX_COMPILER g++-14) +set(CMAKE_CXX_FLAGS -m32) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both target and host environments +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchain/linux32-gcc15.cmake b/cmake/toolchain/linux32-gcc15.cmake new file mode 100644 index 0000000..328bf9f --- /dev/null +++ b/cmake/toolchain/linux32-gcc15.cmake @@ -0,0 +1,18 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-15) +set(CMAKE_C_FLAGS -m32) +set(CMAKE_CXX_COMPILER g++-15) +set(CMAKE_CXX_FLAGS -m32) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both target and host environments +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchain/linux32-gcc16.cmake b/cmake/toolchain/linux32-gcc16.cmake new file mode 100644 index 0000000..8dd4a0b --- /dev/null +++ b/cmake/toolchain/linux32-gcc16.cmake @@ -0,0 +1,18 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-16) +set(CMAKE_C_FLAGS -m32) +set(CMAKE_CXX_COMPILER g++-16) +set(CMAKE_CXX_FLAGS -m32) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both target and host environments +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/toolchain/linux32.cmake b/cmake/toolchain/linux32.cmake similarity index 100% rename from toolchain/linux32.cmake rename to cmake/toolchain/linux32.cmake diff --git a/toolchain/linux64-gcc10.cmake b/cmake/toolchain/linux64-gcc10.cmake similarity index 100% rename from toolchain/linux64-gcc10.cmake rename to cmake/toolchain/linux64-gcc10.cmake diff --git a/toolchain/linux64-gcc11.cmake b/cmake/toolchain/linux64-gcc11.cmake similarity index 100% rename from toolchain/linux64-gcc11.cmake rename to cmake/toolchain/linux64-gcc11.cmake diff --git a/toolchain/linux64-gcc12.cmake b/cmake/toolchain/linux64-gcc12.cmake similarity index 100% rename from toolchain/linux64-gcc12.cmake rename to cmake/toolchain/linux64-gcc12.cmake diff --git a/toolchain/linux64-gcc13.cmake b/cmake/toolchain/linux64-gcc13.cmake similarity index 100% rename from toolchain/linux64-gcc13.cmake rename to cmake/toolchain/linux64-gcc13.cmake diff --git a/cmake/toolchain/linux64-gcc14.cmake b/cmake/toolchain/linux64-gcc14.cmake new file mode 100644 index 0000000..3f36e43 --- /dev/null +++ b/cmake/toolchain/linux64-gcc14.cmake @@ -0,0 +1,18 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-14) +set(CMAKE_C_FLAGS -m64) +set(CMAKE_CXX_COMPILER g++-14) +set(CMAKE_CXX_FLAGS -m64) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both target and host environments +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchain/linux64-gcc15.cmake b/cmake/toolchain/linux64-gcc15.cmake new file mode 100644 index 0000000..c6f9c7e --- /dev/null +++ b/cmake/toolchain/linux64-gcc15.cmake @@ -0,0 +1,18 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-15) +set(CMAKE_C_FLAGS -m64) +set(CMAKE_CXX_COMPILER g++-15) +set(CMAKE_CXX_FLAGS -m64) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both target and host environments +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchain/linux64-gcc16.cmake b/cmake/toolchain/linux64-gcc16.cmake new file mode 100644 index 0000000..5120f1a --- /dev/null +++ b/cmake/toolchain/linux64-gcc16.cmake @@ -0,0 +1,18 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-16) +set(CMAKE_C_FLAGS -m64) +set(CMAKE_CXX_COMPILER g++-16) +set(CMAKE_CXX_FLAGS -m64) + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both target and host environments +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/toolchain/linux64.cmake b/cmake/toolchain/linux64.cmake similarity index 100% rename from toolchain/linux64.cmake rename to cmake/toolchain/linux64.cmake diff --git a/toolchain/macos64-brew-gcc10.cmake b/cmake/toolchain/macos64-brew-gcc10.cmake similarity index 100% rename from toolchain/macos64-brew-gcc10.cmake rename to cmake/toolchain/macos64-brew-gcc10.cmake diff --git a/toolchain/macos64-brew-gcc11.cmake b/cmake/toolchain/macos64-brew-gcc11.cmake similarity index 100% rename from toolchain/macos64-brew-gcc11.cmake rename to cmake/toolchain/macos64-brew-gcc11.cmake diff --git a/toolchain/macos64-brew-gcc12.cmake b/cmake/toolchain/macos64-brew-gcc12.cmake similarity index 100% rename from toolchain/macos64-brew-gcc12.cmake rename to cmake/toolchain/macos64-brew-gcc12.cmake diff --git a/toolchain/macos64-brew-gcc13.cmake b/cmake/toolchain/macos64-brew-gcc13.cmake similarity index 100% rename from toolchain/macos64-brew-gcc13.cmake rename to cmake/toolchain/macos64-brew-gcc13.cmake diff --git a/cmake/toolchain/macos64-brew-gcc14.cmake b/cmake/toolchain/macos64-brew-gcc14.cmake new file mode 100644 index 0000000..f55de03 --- /dev/null +++ b/cmake/toolchain/macos64-brew-gcc14.cmake @@ -0,0 +1,28 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-14) +set(CMAKE_CXX_COMPILER g++-14) +set(CMAKE_AR gcc-ar-14) +set(CMAKE_NM gcc-nm-14) +set(CMAKE_RANLIB gcc-ranlib-14) # https://stackoverflow.com/questions/53128049/ld-archive-has-no-table-of-contents-file-error-with-homebrew +set(RANLIB gcc-ranlib-14) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH FALSE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchain/macos64-brew-gcc15.cmake b/cmake/toolchain/macos64-brew-gcc15.cmake new file mode 100644 index 0000000..1334271 --- /dev/null +++ b/cmake/toolchain/macos64-brew-gcc15.cmake @@ -0,0 +1,28 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-15) +set(CMAKE_CXX_COMPILER g++-15) +set(CMAKE_AR gcc-ar-15) +set(CMAKE_NM gcc-nm-15) +set(CMAKE_RANLIB gcc-ranlib-15) # https://stackoverflow.com/questions/53128049/ld-archive-has-no-table-of-contents-file-error-with-homebrew +set(RANLIB gcc-ranlib-15) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH FALSE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/toolchain/macos64-brew-gcc16.cmake b/cmake/toolchain/macos64-brew-gcc16.cmake new file mode 100644 index 0000000..c4f7e7a --- /dev/null +++ b/cmake/toolchain/macos64-brew-gcc16.cmake @@ -0,0 +1,28 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-16) +set(CMAKE_CXX_COMPILER g++-16) +set(CMAKE_AR gcc-ar-16) +set(CMAKE_NM gcc-nm-16) +set(CMAKE_RANLIB gcc-ranlib-16) # https://stackoverflow.com/questions/53128049/ld-archive-has-no-table-of-contents-file-error-with-homebrew +set(RANLIB gcc-ranlib-16) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH FALSE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/toolchain/macos64-ports-gcc10.cmake b/cmake/toolchain/macos64-ports-gcc10.cmake similarity index 100% rename from toolchain/macos64-ports-gcc10.cmake rename to cmake/toolchain/macos64-ports-gcc10.cmake diff --git a/toolchain/macos64-ports-gcc11.cmake b/cmake/toolchain/macos64-ports-gcc11.cmake similarity index 100% rename from toolchain/macos64-ports-gcc11.cmake rename to cmake/toolchain/macos64-ports-gcc11.cmake diff --git a/toolchain/macos64-ports-gcc12.cmake b/cmake/toolchain/macos64-ports-gcc12.cmake similarity index 100% rename from toolchain/macos64-ports-gcc12.cmake rename to cmake/toolchain/macos64-ports-gcc12.cmake diff --git a/cmake/toolchain/macos64-ports-gcc13.cmake b/cmake/toolchain/macos64-ports-gcc13.cmake new file mode 100644 index 0000000..abaf23b --- /dev/null +++ b/cmake/toolchain/macos64-ports-gcc13.cmake @@ -0,0 +1,29 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-mp-13) +set(CMAKE_CXX_COMPILER g++-mp-13) +set(CMAKE_AR gcc-ar-mp-13) +set(CMAKE_NM gcc-nm-mp-13) +set(CMAKE_RANLIB gcc-ranlib-mp-13) +set(RANLIB gcc-ranlib-mp-13) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH TRUE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + diff --git a/cmake/toolchain/macos64-ports-gcc14.cmake b/cmake/toolchain/macos64-ports-gcc14.cmake new file mode 100644 index 0000000..adf22a6 --- /dev/null +++ b/cmake/toolchain/macos64-ports-gcc14.cmake @@ -0,0 +1,29 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-mp-14) +set(CMAKE_CXX_COMPILER g++-mp-14) +set(CMAKE_AR gcc-ar-mp-14) +set(CMAKE_NM gcc-nm-mp-14) +set(CMAKE_RANLIB gcc-ranlib-mp-14) +set(RANLIB gcc-ranlib-mp-14) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH TRUE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + diff --git a/cmake/toolchain/macos64-ports-gcc15.cmake b/cmake/toolchain/macos64-ports-gcc15.cmake new file mode 100644 index 0000000..13dd9e7 --- /dev/null +++ b/cmake/toolchain/macos64-ports-gcc15.cmake @@ -0,0 +1,29 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-mp-15) +set(CMAKE_CXX_COMPILER g++-mp-15) +set(CMAKE_AR gcc-ar-mp-15) +set(CMAKE_NM gcc-nm-mp-15) +set(CMAKE_RANLIB gcc-ranlib-mp-15) +set(RANLIB gcc-ranlib-mp-15) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH TRUE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + diff --git a/cmake/toolchain/macos64-ports-gcc16.cmake b/cmake/toolchain/macos64-ports-gcc16.cmake new file mode 100644 index 0000000..743bb71 --- /dev/null +++ b/cmake/toolchain/macos64-ports-gcc16.cmake @@ -0,0 +1,29 @@ +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Darwin) + +# which compilers to use for C and C++ +set(CMAKE_C_COMPILER gcc-mp-16) +set(CMAKE_CXX_COMPILER g++-mp-16) +set(CMAKE_AR gcc-ar-mp-16) +set(CMAKE_NM gcc-nm-mp-16) +set(CMAKE_RANLIB gcc-ranlib-mp-16) +set(RANLIB gcc-ranlib-mp-16) +# silence superfluous "has no symbols" warnings (does not help): +# set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +# set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + +# here is the target environment located +set(CMAKE_FIND_ROOT_PATH "${CMAKE_PREFIX_PATH}") +# Do not use RPATH: +#set(CMAKE_MACOSX_RPATH FALSE) +#set(MACOSX_RPATH TRUE) +# set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +# set(CMAKE_SKIP_RPATH TRUE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, +# search programs in both the target and host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + diff --git a/toolchain/macos64.cmake b/cmake/toolchain/macos64.cmake similarity index 100% rename from toolchain/macos64.cmake rename to cmake/toolchain/macos64.cmake diff --git a/toolchain/x86_64-w64-mingw32.cmake b/cmake/toolchain/x86_64-w64-mingw32.cmake similarity index 100% rename from toolchain/x86_64-w64-mingw32.cmake rename to cmake/toolchain/x86_64-w64-mingw32.cmake diff --git a/compile.bat b/compile.bat index c7fd9a1..ab1a20f 100644 --- a/compile.bat +++ b/compile.bat @@ -2,36 +2,35 @@ erase /F /S /Q libtable.dll libtable-dbg.dll > NUL -set BUILD_TYPE=Release -set BUILD_DIR=build-release - -echo Compiling %BUILD_TYPE% in %BUILD_DIR% -cmake -B %BUILD_DIR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% . +set CMAKE_BUILD_PARALLEL_LEVEL=%NUMBER_OF_PROCESSORS% +set CTEST_PARALLEL_LEVEL=%NUMBER_OF_PROCESSORS% +set CTEST_TEST_LOAD=%NUMBER_OF_PROCESSORS% +set CTEST_OUTPUT_ON_FAILURE=1 + +set CMAKE_BUILD_TYPE=Debug +set BUILD_DIR=build-%CMAKE_BUILD_TYPE% +echo Compiling %CMAKE_BUILD_TYPE% in %BUILD_DIR% +cmake -B %BUILD_DIR% -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% . if %ERRORLEVEL% NEQ 0 goto Failure -cmake --build %BUILD_DIR% --config %BUILD_TYPE% +cmake --build %BUILD_DIR% --config %CMAKE_BUILD_TYPE% if %ERRORLEVEL% NEQ 0 goto Failure -cd %BUILD_DIR% -ctest --build-config %BUILD_TYPE% --output-on-failure +ctest --test-dir %BUILD_DIR% --build-config %CMAKE_BUILD_TYPE% if %ERRORLEVEL% NEQ 0 goto Failure -cd .. -copy %cd%\%BUILD_DIR%\src\%BUILD_TYPE%\table.dll %cd%\libtable.dll -echo SUCCESS building release into libtable.dll - - -set BUILD_TYPE=Debug -set BUILD_DIR=build-debug +copy %cd%\%BUILD_DIR%\src\%CMAKE_BUILD_TYPE%\table.dll %cd%\libtable-dbg.dll +echo SUCCESS building debug into libtable-dbg.dll -echo Compiling %BUILD_TYPE% in %BUILD_DIR% -cmake -B %BUILD_DIR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% . +set CMAKE_BUILD_TYPE=Release +set BUILD_DIR=build-%CMAKE_BUILD_TYPE% +echo Compiling %CMAKE_BUILD_TYPE% in %BUILD_DIR% +cmake -B %BUILD_DIR% -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% . if %ERRORLEVEL% NEQ 0 goto Failure -cmake --build %BUILD_DIR% --config %BUILD_TYPE% +cmake --build %BUILD_DIR% --config %CMAKE_BUILD_TYPE% if %ERRORLEVEL% NEQ 0 goto Failure -cd %BUILD_DIR% -ctest --build-config %BUILD_TYPE% --output-on-failure +ctest --test-dir %BUILD_DIR% --build-config %CMAKE_BUILD_TYPE% --output-on-failure if %ERRORLEVEL% NEQ 0 goto Failure -cd .. -copy %cd%\%BUILD_DIR%\src\%BUILD_TYPE%\table.dll %cd%\libtable-dbg.dll -echo SUCCESS building debug into libtable-dbg.dll +copy %cd%\%BUILD_DIR%\src\%CMAKE_BUILD_TYPE%\table.dll %cd%\libtable.dll +echo SUCCESS building release into libtable.dll + start "" "%cd%" pause diff --git a/compile.sh b/compile.sh index a52f34e..115b0d5 100755 --- a/compile.sh +++ b/compile.sh @@ -20,7 +20,7 @@ targets="$@" if [ -z "$targets" ]; then echo "Expected a target platform as an argument. The following are supported:" - for target in $(ls toolchain) ; do + for target in $(ls cmake/toolchain) ; do t=${target%%.cmake} echo -e "\t$t" done @@ -30,28 +30,18 @@ if [ -z "$targets" ]; then targets="" if [ -n "$(command -v c++)" ]; then targets="$targets linux64" - if [ -n "$(c++ -m32 --print-file-name=libstdc++.so)" ]; then + if echo 'int main(){}' | c++ -m32 -xc++ - -o /dev/null 2>&1 ; then targets="$targets linux32" fi fi - if [ -n "$(command -v g++-10)" ]; then - targets="$targets linux64-gcc10" - if [ -n "$(g++-10 -m32 --print-file-name=libstdc++.so)" ]; then - targets="$targets linux32-gcc10" + for COMPILER in g++-16 g++-15 g++-14 g++-13 g++-12 g++-11 g++-10 ; do + if [ -n "$(command -v "$COMPILER")" ]; then + targets="$targets linux64-${COMPILER/++-/cc}" + if echo 'int main(){}' | ${COMPILER} -m32 -xc++ - -o /dev/null 2>&1 ; then + targets="$targets linux32-${COMPILER/++-/cc}" + fi fi - fi - if [ -n "$(command -v g++-11)" ]; then - targets="$targets linux64-gcc11" - if [ -n "$(g++-11 -m32 --print-file-name=libstdc++.so)" ]; then - targets="$targets linux32-gcc11" - fi - fi - if [ -n "$(command -v g++-12)" ]; then - targets="$targets linux64-gcc12" - if [ -n "$(g++-12 -m32 --print-file-name=libstdc++.so)" ]; then - targets="$targets linux32-gcc12" - fi - fi + done if [ -n "$(command -v x86_64-w64-mingw32-g++)" ]; then targets="$targets x86_64-w64-mingw32" fi @@ -64,24 +54,16 @@ if [ -z "$targets" ]; then if [ -n "$(command -v c++)" ]; then targets="$targets macos64" fi - if [ -n "$(command -v g++-10)" ]; then - targets="$targets macos64-brew-gcc10" - fi - if [ -n "$(command -v g++-11)" ]; then - targets="$targets macos64-brew-gcc11" - fi - if [ -n "$(command -v g++-12)" ]; then - targets="$targets macos64-brew-gcc12" - fi - if [ -n "$(command -v g++-mp-10)" ]; then - targets="$targets macos64-ports-gcc10" - fi - if [ -n "$(command -v g++-mp-11)" ]; then - targets="$targets macos64-ports-gcc11" - fi - if [ -n "$(command -v g++-mp-12)" ]; then - targets="$targets macos64-ports-gcc12" - fi + for COMPILER in g++-16 g++-15 g++-14 g++-13 g++-12 g++-11 g++-10 ; do + if [ -n "$(command -v "$COMPILER")" ]; then + targets="$targets macos64-brew-${COMPILER}" + fi + done + for COMPILER in g++-mp-16 g++-mp-15 g++-mp-14 g++-mp-13 g++-mp-12 g++-mp-11 g++-mp-10 ; do + if [ -n "$(command -v "$COMPILER")" ]; then + targets="$targets macos64-ports-${COMPILER}" + fi + done ;; *) echo "Unknown hosting platform" @@ -106,30 +88,25 @@ fi if [ -z "$CTEST_TEST_LOAD" ]; then export CTEST_TEST_LOAD=$CORES fi +if [ -z "$CTEST_OUTPUT_ON_FAILURE" ]; then + export CTEST_OUTPUT_ON_FAILURE=1 +fi for target in $targets ; do unset CMAKE_TOOLCHAIN_FILE unset SANITIZE - if [ ! -r $PWD/toolchain/${target}.cmake ]; then - echo "The toolchain file does not exist: $PWD/toolchain/${target}.cmake" + if [ ! -r $PWD/cmake/toolchain/${target}.cmake ]; then + echo "The toolchain file does not exist: $PWD/cmake/toolchain/${target}.cmake" exit 1 else - export CMAKE_TOOLCHAIN_FILE="$PWD/toolchain/${target}.cmake" + export CMAKE_TOOLCHAIN_FILE="$PWD/cmake/toolchain/${target}.cmake" fi case $target in linux*) extension=so SANITIZE="-DSSP=ON -DUBSAN=ON -DASAN=ON" ;; - macos64-brew-gcc10) - extension=dylib - SANITIZE="-DSSP=ON" - ;; - macos64-brew-gcc11) - extension=dylib - SANITIZE="-DSSP=ON" - ;; - macos64-brew-gcc12) + macos64-brew-gcc*) extension=dylib SANITIZE="-DSSP=ON" ;; @@ -140,17 +117,36 @@ for target in $targets ; do i686*mingw32) extension=dll SANITIZE="-DSSP=ON" + export WINEARCH=win32 + export WINEPREFIX="${HOME}/.wine32" + if [ ! -d "$WINEPREFIX" ]; then wine cmd /c echo "Prepared WINEPREFIX"; fi libgcc_path=$($target-g++ --print-file-name=libgcc_s_dw2-1.dll) libgcc_path=$(realpath "$libgcc_path") libgcc_path=$(dirname "$libgcc_path") libwinpthread_path=$($target-g++ --print-file-name=libwinpthread-1.dll) libwinpthread_path=$(realpath "$libwinpthread_path") libwinpthread_path=$(dirname "$libwinpthread_path") - export WINEPATH="$libwinpthread_path;$libgcc_path" + export WINEPATH="${libwinpthread_path};${libgcc_path}" + echo "WINEARCH=$WINEARCH" + echo "WINEPATH=$WINEPATH" + echo "WINEPREFIX=$WINEPREFIX" ;; x86_64*mingw32) extension=dll SANITIZE="-DSSP=ON" + export WINEARCH=win64 + export WINEPREFIX="${HOME}/.wine64" + if [ ! -d "$WINEPREFIX" ]; then wine cmd /c echo "Prepared WINEPREFIX"; fi + libgcc_path=$($target-g++ --print-file-name=libgcc_s_seh-1.dll) + libgcc_path=$(realpath "$libgcc_path") + libgcc_path=$(dirname "$libgcc_path") + libwinpthread_path=$($target-g++ --print-file-name=libwinpthread-1.dll) + libwinpthread_path=$(realpath "$libwinpthread_path") + libwinpthread_path=$(dirname "$libwinpthread_path") + export WINEPATH="${libwinpthread_path};${libgcc_path}" + echo "WINEPREFIX=$WINEPREFIX" + echo "WINEPATH=$WINEPATH" + echo "WINEARCH=$WINEARCH" ;; *) echo "Unknown target platform: $target" @@ -164,11 +160,9 @@ for target in $targets ; do echo "Building debug configuration for $target" cmake --build "$BUILD_DIR" --config $CMAKE_BUILD_TYPE echo "Testing debug configuration for $target" - (cd "$BUILD_DIR" ; ctest -C $CMAKE_BUILD_TYPE --output-on-failure) + ctest --test-dir "$BUILD_DIR" -C $CMAKE_BUILD_TYPE ## Create a link to it: - if [ ! -e libtable-dbg.${extension} ]; then - ln -s "$BUILD_DIR"/src/libtable.${extension} libtable-dbg.${extension} - fi + ln -snf "${BUILD_DIR}/src/libtable.${extension}" "libtable-dbg.${extension}" export CMAKE_BUILD_TYPE=Release BUILD_DIR=build-$target-$CMAKE_BUILD_TYPE @@ -177,9 +171,7 @@ for target in $targets ; do echo "Building optimized release configuration for $target" cmake --build "$BUILD_DIR" --config $CMAKE_BUILD_TYPE echo "Testing optimized release configuration for $target" - (cd "$BUILD_DIR" ; ctest -C $CMAKE_BUILD_TYPE --output-on-failure) + ctest --test-dir "$BUILD_DIR" -C $CMAKE_BUILD_TYPE ## Create a link to it: - if [ ! -e libtable.${extension} ]; then - ln -s "$BUILD_DIR"/src/libtable.${extension} libtable.${extension} - fi + ln -snf "${BUILD_DIR}/src/libtable.${extension}" "libtable.${extension}" done diff --git a/table_input.csv b/examples/table_input.csv similarity index 100% rename from table_input.csv rename to examples/table_input.csv diff --git a/src/dynlib.h b/include/dynlib.h similarity index 100% rename from src/dynlib.h rename to include/dynlib.h diff --git a/src/library.hpp b/include/library.hpp similarity index 74% rename from src/library.hpp rename to include/library.hpp index b5c9c1e..e39400f 100644 --- a/src/library.hpp +++ b/include/library.hpp @@ -2,8 +2,8 @@ * C++ wrapper for opening dynamically linked libraries * Author: Marius Mikucionis */ -#ifndef _LIBRARY_HPP_ -#define _LIBRARY_HPP_ +#ifndef INCLUDE_LIBRARY_HPP +#define INCLUDE_LIBRARY_HPP #include // to_string #include // runtime_error @@ -13,11 +13,8 @@ /** Wrapper for opening Library files. * Methods may throw runtime_error upon errors. */ -class Library +struct Library { - void* handle; // library handle - -public: Library(const char* filepath): handle{dlopen(filepath, RTLD_LAZY | RTLD_LOCAL)} { if (!handle) @@ -35,26 +32,25 @@ class Library * auto fn = lib.lookup(fn_name); * fn(arg1, arg2); * Where fn_type ::= return_type (*)(arg1_type, arg2_type); - * */ + */ template FnType lookup(const char* fn_name) { - auto res = (FnType)(dlsym(handle, fn_name)); - if (res == nullptr) - throw std::runtime_error(dlerror()); - return res; + if (auto res = reinterpret_cast(dlsym(handle, fn_name)); res) + return res; + throw std::runtime_error{dlerror()}; } + +private: + void* handle{}; ///< library handle }; #elif defined(_WIN32) || defined(__MINGW32__) #include #include -class Library +struct Library { - HMODULE handle; // library handle - -public: Library(const char* filepath): handle{LoadLibrary(TEXT(filepath))} { if (!handle) { @@ -78,16 +74,17 @@ class Library template FnType lookup(const char* fn_name) { - auto res = reinterpret_cast(GetProcAddress(handle, fn_name)); - if (res == nullptr) - throw std::runtime_error{"Failed symbol lookup with error " + - std::to_string(GetLastError())}; - return res; + if (auto res = reinterpret_cast(GetProcAddress(handle, fn_name)); res != nullptr) + return res; + throw std::runtime_error{"Failed symbol lookup: " + std::to_string(GetLastError())}; } + +private: + HMODULE handle{}; ///< library handle }; #else #error "unsupported platform" #endif -#endif /* _LIBRARY_HPP_ */ +#endif /* INCLUDE_LIBRARY_HPP */ diff --git a/src/errors.hpp b/include/logging.h similarity index 56% rename from src/errors.hpp rename to include/logging.h index 99c4ca6..1ed8609 100644 --- a/src/errors.hpp +++ b/include/logging.h @@ -1,28 +1,29 @@ /** - * Error reporting + * Error logging * Author: Marius Mikucionis */ -#ifndef _ERRORS_HPP_ -#define _ERRORS_HPP_ +#ifndef INCLUDE_LOGGING_H +#define INCLUDE_LOGGING_H #include "dynlib.h" -#ifndef NDEBUG +#ifdef LOGGING #ifdef _WIN32 #define log_err(format, ...) log_error(__FUNCTION__, __FILE__, __LINE__, format, __VA_ARGS__) #else // not _WIN32 #define log_err(format, ...) \ log_error(__FUNCTION__, __FILE__, __LINE__, format __VA_OPT__(, ) __VA_ARGS__) #endif // _WIN32 -#else // with NDEBUG +#else // no LOGGING #define log_err(format, ...) -#endif // NDEBUG +#endif // LOGGING -/** Set the file path for errors, returns 0 always */ -C_PUBLIC int set_error_path(const char* err_path); -C_PUBLIC const char* get_error_path(); +/// Set the file path for errors, returns 0 always +C_PUBLIC int set_log_path(const char* err_path); +/// Returns the path to the log file, "error.log" by default +C_PUBLIC const char* get_log_path(); void log_error(const char* function, const char* path, int line, const char* format, ...); -#endif /* _ERRORS_HPP_ */ +#endif // INCLUDE_LOGGING_H \ No newline at end of file diff --git a/src/csvtable.hpp b/include/table.hpp similarity index 69% rename from src/csvtable.hpp rename to include/table.hpp index 8b4b3b4..129b198 100644 --- a/src/csvtable.hpp +++ b/include/table.hpp @@ -2,18 +2,18 @@ * CSV table representation and its input and output. * Author: Marius Mikucionis */ -#ifndef _CSVTABLE_HPP_ -#define _CSVTABLE_HPP_ +#ifndef INCLUDE_TABLE_HPP +#define INCLUDE_TABLE_HPP #include #include #include #include -using elem_t = double; -using row_t = std::vector; -using table_t = std::vector; -using dictionary_t = std::unordered_map>; +using Elem = double; +using Row = std::vector; +using Table = std::vector; +using Dictionary = std::unordered_map>; inline void skip_line(std::istream& is) { @@ -28,14 +28,14 @@ inline void skip_comments(std::istream& is) skip_line(is); } -[[nodiscard]] table_t table_read_csv(std::istream& is, int skip_lines) +[[nodiscard]] Table table_read_csv(std::istream& is, int skip_lines) { auto sep = char{}; while (is && skip_lines-- > 0) skip_line(is); skip_comments(is); - auto table = table_t{}; - auto elem = elem_t{}; + auto table = Table{}; + auto elem = Elem{}; while (is >> elem) { auto& row = table.emplace_back(); row.push_back(elem); @@ -49,10 +49,10 @@ inline void skip_comments(std::istream& is) return table; } -[[nodiscard]] dictionary_t dictionary_read_csv(std::istream& is) +[[nodiscard]] Dictionary dictionary_read_csv(std::istream& is) { - auto dictionary = dictionary_t{}; - auto elem = elem_t{}; + auto dictionary = Dictionary{}; + auto elem = Elem{}; auto sep = char{}; while (is >> elem) { auto& row = dictionary[elem]; @@ -63,20 +63,19 @@ inline void skip_comments(std::istream& is) return dictionary; } -[[nodiscard]] double interpolate(const table_t& table, const elem_t key, int key_column, - int value_column) +[[nodiscard]] Elem interpolate(const Table& table, const Elem key, int key_column, int value_column) { using namespace std::string_literals; if (key_column < 0) - throw std::runtime_error("negative key column"); + throw std::logic_error{"negative key column"}; if (key_column >= static_cast(table.front().size())) - throw std::runtime_error("key column overflow"); + throw std::logic_error{"key column overflow"}; if (value_column < 0) - throw std::runtime_error("negative value column"); + throw std::logic_error{"negative value column"}; if (value_column >= static_cast(table.front().size())) - throw std::runtime_error("value column overflow"); + throw std::logic_error{"value column overflow"}; auto it2 = std::lower_bound(std::begin(table), std::end(table), key, - [=](const row_t& row, const elem_t& key) { + [=](const Row& row, const Elem& key) { return row[static_cast(key_column)] < key; }); if (it2 == std::end(table)) @@ -96,7 +95,7 @@ inline void skip_comments(std::istream& is) return y1 + (y2 - y1) / (x2 - x1) * (key - x1); // linear interpolation } -std::ostream& table_write_csv(std::ostream& os, const table_t& table, const char sep = ',') +std::ostream& table_write_csv(std::ostream& os, const Table& table, const char sep = ',') { for (auto& row : table) { auto b = std::begin(row), e = std::end(row); @@ -110,7 +109,7 @@ std::ostream& table_write_csv(std::ostream& os, const table_t& table, const char return os; } -std::ostream& dictionary_write_csv(std::ostream& os, const dictionary_t& dictionary, +std::ostream& dictionary_write_csv(std::ostream& os, const Dictionary& dictionary, const char sep = ',') { for (auto& row : dictionary) { @@ -121,4 +120,4 @@ std::ostream& dictionary_write_csv(std::ostream& os, const dictionary_t& diction return os; } -#endif /* _CSVTABLE_HPP_ */ +#endif /* INCLUDE_TABLE_HPP */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 76e6d0c..24a6116 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,23 +1,10 @@ -add_custom_target(data - COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/table_input.csv ${CMAKE_CURRENT_BINARY_DIR}/table_input.csv - DEPENDS ${PROJECT_SOURCE_DIR}/table_input.csv - BYPRODUCTS table_input.csv) +add_library(logging OBJECT logging.cpp) +if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + target_compile_definitions(logging PUBLIC LOGGING=1) +else() # assuming multi-config generator: + target_compile_definitions(logging PUBLIC $<$:LOGGING=1> ) +endif () -add_library(errors OBJECT errors.cpp) add_library(table SHARED table.cpp) -target_link_libraries(table PRIVATE errors) -add_dependencies(table data) - -if (UPPAALLIBS_WITH_TESTS) - add_executable(test_table test_table.cpp) - target_link_libraries(test_table PRIVATE doctest::doctest_with_main) - add_dependencies(test_table table) - add_test(NAME test_table COMMAND test_table) - - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - add_executable(test_errors test_errors.cpp) - target_link_libraries(test_errors PRIVATE errors doctest::doctest_with_main) - add_test(NAME test_errors COMMAND test_errors) - endif() -endif (UPPAALLIBS_WITH_TESTS) \ No newline at end of file +target_link_libraries(table PRIVATE logging) diff --git a/src/errors.cpp b/src/logging.cpp similarity index 78% rename from src/errors.cpp rename to src/logging.cpp index 513de6e..3996f4b 100644 --- a/src/errors.cpp +++ b/src/logging.cpp @@ -2,7 +2,7 @@ * Error reporting * Author: Marius Mikucionis */ -#include "errors.hpp" +#include "logging.h" #include #include @@ -13,18 +13,18 @@ static auto error_own = false; // do we own the error file? static auto error_path = std::string{"error.log"}; -C_PUBLIC int set_error_path(const char* path) +C_PUBLIC int set_log_path(const char* path) { error_path = path; error_own = false; return 0; } -C_PUBLIC const char* get_error_path() { return error_path.c_str(); } +C_PUBLIC const char* get_log_path() { return error_path.c_str(); } -FILE* open_error_file() +static FILE* open_log_file() { - const auto path = get_error_path(); + const auto path = get_log_path(); FILE* file = nullptr; #ifdef __STDC_LIB_EXT1__ auto err = errno_t{}; @@ -40,9 +40,9 @@ FILE* open_error_file() } #else if (error_own) { - file = std::fopen(get_error_path(), "a"); + file = std::fopen(get_log_path(), "a"); } else { - file = std::fopen(get_error_path(), "w"); + file = std::fopen(get_log_path(), "w"); error_own = true; } if (file == nullptr) { @@ -54,8 +54,9 @@ FILE* open_error_file() void log_error(const char* function, const char* path, int line, const char* format, ...) { - auto file = open_error_file(); - const auto time = std::chrono::system_clock::now().time_since_epoch(); + auto file = open_log_file(); + using Clock = std::chrono::system_clock; + const auto time = Clock::now().time_since_epoch(); const auto sec = std::chrono::duration_cast(time); const auto usec = std::chrono::duration_cast(time - sec); #if (defined(__APPLE__) && defined(__MACH__)) || defined(_WIN32) diff --git a/src/table.cpp b/src/table.cpp index a367b25..d413a72 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -2,38 +2,61 @@ * Implements libtable functions. * Author: Marius Mikucionis */ -#include "csvtable.hpp" -#include "errors.hpp" +#include "table.hpp" +#include "logging.h" #include "dynlib.h" #include #include // to_string/MSVC -#include // nan +#include +#include // nan + +/// creates a new table rows x cols and populates with zeros +C_PUBLIC int table_new(int rows, int cols); +/// creates a new table rows x cols and populates with integer value C_PUBLIC int table_new_int(int rows, int cols, int value); +/// creates a new table rows x cols and populates with floating point value C_PUBLIC int table_new_double(int rows, int cols, double value); +/// Resizes the table to a given rows x cols size. Returns id on success +C_PUBLIC int table_resize(int id, int rows, int cols); +/// Resizes the table to a given rows x cols size. Returns id on success C_PUBLIC int table_resize_double(int id, int rows, int cols, double value); +/// Resizes the table to a given rows x cols size. Returns id on success C_PUBLIC int table_resize_int(int id, int rows, int cols, int value); +/// Loads the table from CSV file, returns the table id, or -1 on error C_PUBLIC int table_read_csv(const char* csv_path, int skip_lines); +/// Writes the table to CSV file, returns the number of rows, or -1 on error C_PUBLIC int table_write_csv(int id, const char* csv_path); +/// Creates an independent copy of table with id and returns the ID of the new table C_PUBLIC int table_copy(int id); +/// Clears selected table to 0x0 and releases associated resources C_PUBLIC int table_clear(int id); +/// Returns the number of rows in the table with given id C_PUBLIC int table_rows(int id); +/// Returns the number of columns in the table with given id C_PUBLIC int table_cols(int id); +/// Returns an integer value stored at given row and column of table with given id C_PUBLIC int read_int(int id, int row, int col); +/// Returns a floating point value stored at given row and column of table with given id C_PUBLIC double read_double(int id, int row, int col); -C_PUBLIC void write_int(int id, int row, int col, int value); -C_PUBLIC void write_double(int id, int row, int col, double value); -C_PUBLIC double interpolate(int id, double key, int key_col, int valu_col); -C_PUBLIC void read_int_col(int id, int row, int col, int* items, int offset, int count); -C_PUBLIC void read_int_row(int id, int row, int col, int* items, int offset, int count); +/// Writes an integer value into given table at given row and column +C_PUBLIC int write_int(int id, int row, int col, int value); +/// Writes a floating point value into given table at given row and column +C_PUBLIC int write_double(int id, int row, int col, double value); +/// Maps key from key column to value column by using interpolation +C_PUBLIC double interpolate(int id, double key, int key_col, int value_col); +/// Copies column of count integers starting with given row and col into items starting with offset. +C_PUBLIC int read_int_col(int id, int row, int col, int* items, int offset, int count); +/// Copies row of count integers starting with given row and col into items starting with offset. +C_PUBLIC int read_int_row(int id, int row, int col, int* items, int offset, int count); using namespace std::string_literals; -static std::vector tables{}; +static std::vector tables{}; -C_PUBLIC int table_new_int(int rows, int cols, int value) +C_PUBLIC int table_new_double(int rows, int cols, double value) { - log_err("table_new(%d, %d, %d)", rows, cols, value); + log_err("table_new(%d, %d, %f)", rows, cols, value); auto& t = tables.emplace_back(); t.resize(static_cast(rows)); for (auto& row : t) @@ -43,19 +66,14 @@ C_PUBLIC int table_new_int(int rows, int cols, int value) return res; } -C_PUBLIC int table_new_double(int rows, int cols, double value) +C_PUBLIC int table_new(int rows, int cols) { return table_new_double(rows, cols, 0.0); } + +C_PUBLIC int table_new_int(int rows, int cols, int value) { - log_err("table_new(%d, %d, %f)", rows, cols, value); - auto& t = tables.emplace_back(); - t.resize(static_cast(rows)); - for (auto& row : t) - row.resize(static_cast(cols), value); - const auto res = static_cast(tables.size()) - 1; - log_err("table_new: ", res); - return res; + return table_new_double(rows, cols, value); } -static table_t load(const std::string& path, int skip_lines) +static Table load(const std::string& path, int skip_lines) { #ifdef ENABLE_CSV_CACHE static auto cache = std::unordered_map{}; @@ -83,7 +101,6 @@ static table_t load(const std::string& path, int skip_lines) #endif } -/** loads the table from CSV file, returns the table id, or -1 on error */ C_PUBLIC int table_read_csv(const char* csv_path, int skip_lines) { log_err("table_read_csv(%s, %d)", csv_path, skip_lines); @@ -93,109 +110,110 @@ C_PUBLIC int table_read_csv(const char* csv_path, int skip_lines) return res; } -/** writes the table to CSV file, returns the number of rows, or -1 on error */ +static void validate_table_id(int id) +{ + if (id < 0) + throw std::underflow_error{std::format("table id is too low: {}", id)}; + if (id >= static_cast(tables.size())) + throw std::overflow_error{std::format("table id is too high: {}", id)}; +} + +static Table& get_table(int id) +{ + validate_table_id(id); + return tables[static_cast(id)]; +} + C_PUBLIC int table_write_csv(const int id, const char* csv_path) { - log_err("table_write_csv(%d, %s)", id, csv_path); - if (id < 0) { - log_err("table id is too low: %d", id); - return -1; - } - if (id >= static_cast(tables.size())) { - log_err("table id is too high: %d", id); - return -1; - } - auto os = std::ofstream{csv_path}; - if (!os) { - log_err("failed to write: %s", csv_path); - return -1; + try { + log_err("table_write_csv(%d, %s)", id, csv_path); + const auto& table = get_table(id); + auto os = std::ofstream{csv_path}; + if (!table_write_csv(os, table, ',')) + throw std::runtime_error{std::format("failed to write file: {}", csv_path)}; + const auto rows = table.size(); + log_err("table_write_csv: %zu rows", rows); + return static_cast(rows); + } catch (const std::exception& e) { + log_err("%s", e.what()); } - table_write_csv(os, tables[static_cast(id)], ','); - auto res = static_cast(tables[static_cast(id)].size()); - log_err("table_write_csv: %d (rows)", res); - return res; + return -1; } C_PUBLIC int table_copy(const int id) { - log_err("table_copy(%d)", id); - if (id < 0) { - log_err("table id is too low: %d", id); - return -1; - } - if (id >= static_cast(tables.size())) { - log_err("table id is too high: %d", id); - return -1; + try { + log_err("table_copy(%d)", id); + const auto& table = get_table(id); + const auto res = static_cast(tables.size()); + tables.push_back(table); + log_err("table_copy: %d (id)", res); + return res; + } catch (const std::exception& e) { + log_err("%s", e.what()); } - tables.push_back(tables[static_cast(id)]); - auto res = static_cast(tables.size()) - 1; - log_err("table_copy: %d (id)", res); - return res; + return -1; } C_PUBLIC int table_clear(int id) { - log_err("table_clear(%d)", id); - if (id < 0) { - log_err("table id is too low: %d", id); - return -1; - } - if (id >= static_cast(tables.size())) { - log_err("table id is too high: %d", id); - return -1; + try { + log_err("table_clear(%d)", id); + auto& table = get_table(id); + table.clear(); + table.shrink_to_fit(); + log_err("table_clear: %d (id)", id); + return id; + } catch (const std::exception& e) { + log_err("%s", e.what()); } - tables[static_cast(id)].clear(); - tables[static_cast(id)].shrink_to_fit(); - log_err("table_clear: %d (id)", id); - return id; + return -1; } /** User function: get the number of rows in the table */ C_PUBLIC int table_rows(const int id) { - log_err("table_rows(%d)", id); - if (id < 0) { - log_err("table id is too low: %d", id); - return -1; - } - if (id >= static_cast(tables.size())) { - log_err("table id is too high: %d", id); - return -1; + try { + log_err("table_rows(%d)", id); + const auto& table = get_table(id); + const auto res = table.size(); + log_err("table_rows: %zu (rows)", res); + return static_cast(res); + } catch (const std::exception& e) { + log_err("%s", e.what()); } - auto res = static_cast(tables[static_cast(id)].size()); - log_err("table_rows: %d (rows)", res); - return res; + return -1; } /** User function: get the number of columns in the first table row. * Note that some rows may have fewer or more columns (depends on the source of data). */ C_PUBLIC int table_cols(const int id) { - log_err("table_cols(%d)", id); - if (id < 0) { - log_err("table id is too low: %d", id); - return -1; - } - if (id >= static_cast(tables.size())) { - log_err("table id is too high: %d", id); - return -1; - } - if (tables[static_cast(id)].empty()) { - log_err("%s", "table is empty"); - return 0; + try { + log_err("table_cols(%d)", id); + const auto& table = get_table(id); + if (table.empty()) { + log_err("%s", "table is empty"); + return 0; + } + const auto res = table.front().size(); + log_err("table_rows: %zu (cols)", res); + return static_cast(res); + } catch (const std::exception& e) { + log_err("%s", e.what()); } - const auto res = static_cast(tables[static_cast(id)].front().size()); - log_err("table_rows: %d (cols)", res); - return res; + return -1; } -static table_t& get_table(int id) +static auto& get_table_row(int id, int row) { - if (id < 0) - throw std::runtime_error("table id too low: "s + std::to_string(id)); - if (id >= static_cast(tables.size())) - throw std::runtime_error("table id too high: "s + std::to_string(id)); - return tables[static_cast(id)]; + auto& table = get_table(id); + if (row < 0) + throw std::underflow_error{std::format("negative row: {}", row)}; + if (static_cast(table.size()) <= row) + throw std::overflow_error{std::format("row overflow: {}", row)}; + return table[static_cast(row)]; } /** @@ -204,19 +222,14 @@ static table_t& get_table(int id) * @param col the column number * @return the element reference at row:col */ -static elem_t& access(int id, int row, int col) +static Elem& access(int id, int row, int col) { using namespace std::string_literals; - auto& table = get_table(id); - if (row < 0) - throw std::runtime_error("negative row: "s + std::to_string(row)); - if (static_cast(table.size()) <= row) - throw std::runtime_error("row overflow: "s + std::to_string(row)); + auto& table_row = get_table_row(id, row); if (col < 0) - throw std::runtime_error("negative column: "s + std::to_string(col)); - auto& table_row = table[static_cast(row)]; + throw std::underflow_error{std::format("negative column: {}", col)}; if (static_cast(table_row.size()) <= col) - throw std::runtime_error("column overflow: "s + std::to_string(col)); + throw std::overflow_error{std::format("column overflow: {}", col)}; return table_row[static_cast(col)]; } @@ -225,111 +238,128 @@ C_PUBLIC double read_double(int id, int row, int col) { try { return access(id, row, col); - } catch (std::runtime_error& e [[maybe_unused]]) { + } catch (const std::exception& e [[maybe_unused]]) { log_err("%s", e.what()); } return std::nan(""); } -C_PUBLIC int read_int(int id, int row, int col) { return (int)read_double(id, row, col); } +constexpr auto BAD_INT = std::numeric_limits::lowest(); + +C_PUBLIC int read_int(int id, int row, int col) +{ + const auto res = read_double(id, row, col); + if (std::isnan(res)) + return BAD_INT; + return static_cast(res); +} -/** User function: resize the entire table to a given rectangular size. Return id on success */ C_PUBLIC int table_resize_double(int id, int rows, int cols, double value) { - auto& table = get_table(id); - if (rows < 0) { - log_err("negative row number: %d", rows); - return -1; - } - if (cols < 0) { - log_err("negative column number: %d", cols); - return -1; + try { + auto& table = get_table(id); + if (rows < 0) + throw std::underflow_error{std::format("negative number of rows: {}", rows)}; + if (cols < 0) + throw std::underflow_error{std::format("negative number of columns: {}", cols)}; + table.resize(static_cast(rows)); + for (auto& row : table) + row.resize(static_cast(cols), value); + return id; + } catch (const std::exception& e) { + log_err("%s", e.what()); } - table.resize(static_cast(rows)); - for (auto& row : table) - row.resize(static_cast(cols), value); - return id; + return -1; } -/** User function: resize the entire table to a given rectangular size. Return id on success */ C_PUBLIC int table_resize_int(int id, int rows, int cols, int value) { - auto& table = get_table(id); - if (rows < 0) { - log_err("negative row number: %d", rows); - return -1; - } - if (cols < 0) { - log_err("negative column number: %d", cols); - return -1; - } - table.resize(static_cast(rows)); - for (auto& row : table) - row.resize(static_cast(cols), value); - return id; + return table_resize_double(id, rows, cols, value); } -C_PUBLIC void write_double(int id, int row, int col, double value) +C_PUBLIC int table_resize(int id, int rows, int cols) +{ + return table_resize_double(id, rows, cols, 0); +} + +C_PUBLIC int write_double(int id, int row, int col, double value) { try { access(id, row, col) = value; - } catch (std::runtime_error& e [[maybe_unused]]) { + return 0; + } catch (const std::exception& e [[maybe_unused]]) { log_err("%s", e.what()); } + return -1; } -C_PUBLIC void write_int(int id, int row, int col, int value) { write_double(id, row, col, value); } +C_PUBLIC int write_int(int id, int row, int col, int value) +{ + return write_double(id, row, col, value); +} -C_PUBLIC double interpolate(int id, double key, int key_col, int valu_col) +C_PUBLIC double interpolate(int id, double key, int key_col, int value_col) { - auto res = 0.0; try { - auto& table = get_table(id); - res = interpolate(table, key, key_col, valu_col); - } catch (std::runtime_error& e [[maybe_unused]]) { + const auto& table = get_table(id); + const auto res = interpolate(table, key, key_col, value_col); + return res; + } catch (const std::exception& e [[maybe_unused]]) { log_err("%s", e.what()); } - return res; + return std::nan(""); } -C_PUBLIC void read_int_col(int id, int row, int col, int* items, int offset, int count) +C_PUBLIC int read_int_col(int id, int row, int col, int* items, int offset, int count) { try { log_err("read_int_col(%d, %d, %d, %p, %d %d)", id, row, col, items, offset, count); - auto& table = get_table(id); + const auto& table = get_table(id); if (row < 0) - throw std::runtime_error("negative row"); - if (col < 0) - throw std::runtime_error("negative column"); + throw std::underflow_error{std::format("negative row: {}", row)}; if (row + count > static_cast(table.size())) - throw std::runtime_error("row range is beyond table size"); + throw std::overflow_error{ + std::format("row+count {} is beyond number of rows", row + count)}; + if (col < 0) + throw std::underflow_error{std::format("negative column {}", col)}; if (col >= static_cast(table[static_cast(row)].size())) - throw std::runtime_error("column is beyond table size"); - auto rb = std::next(std::begin(table), row), re = std::end(table); + throw std::overflow_error{std::format("column {} is beyond table size", col)}; + const auto re = std::end(table); + auto rb = std::next(std::begin(table), row); for (auto i = 0; i < count && rb != re; ++i, ++rb) items[offset + i] = static_cast((*rb)[static_cast(col)]); - } catch (std::runtime_error& e [[maybe_unused]]) { + return 0; + } catch (const std::exception& e [[maybe_unused]]) { log_err("%s", e.what()); } + return -1; } -C_PUBLIC void read_int_row(int id, int row, int col, int* items, int offset, int count) +C_PUBLIC int read_int_row(int id, int row, int col, int* items, int offset, int count) { try { log_err("read_int_row(%d, %d, %d, %p, %d %d)", id, row, col, items, offset, count); - auto& table = get_table(id); + const auto& table = get_table(id); if (row < 0) - throw std::runtime_error("negative row"); - if (col < 0) - throw std::runtime_error("negative column"); + throw std::underflow_error{std::format("negative row {}", row)}; if (row >= static_cast(table.size())) - throw std::runtime_error("row is beyond table size"); + throw std::overflow_error{std::format("row {} is beyond table size", row)}; + if (offset < 0) + throw std::underflow_error{std::format("negative offset {}", offset)}; + if (count < 0) + throw std::underflow_error{std::format("negative count {}", count)}; + if (col < 0) + throw std::underflow_error{std::format("negative column {}", col)}; if (col + count > static_cast(table[static_cast(row)].size())) - throw std::runtime_error("column range is beyond table size"); + throw std::overflow_error{ + std::format("col+count {} is beyond table size", col + count)}; auto rb = std::next(std::begin(table), row); - for (auto i = 0; i < count; ++i) - items[offset + i] = static_cast((*rb)[static_cast(col + i)]); - } catch (std::runtime_error& e [[maybe_unused]]) { + for (auto i = size_t{0}; i < static_cast(count); ++i) + items[static_cast(offset) + i] = + static_cast((*rb)[static_cast(col) + i]); + return 0; + } catch (const std::exception& e [[maybe_unused]]) { log_err("%s", e.what()); } + return -1; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b2846b8 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(table_test table_test.cpp) +target_compile_definitions(table_test PUBLIC + LIBTABLE_PATH=$ + CSV_PATH=${CMAKE_SOURCE_DIR}/examples/table_input.csv) +target_link_libraries(table_test PRIVATE doctest::doctest_with_main) +add_dependencies(table_test table) +add_test(NAME table_test COMMAND table_test) + +add_executable(logging_test logging_test.cpp) +target_link_libraries(logging_test PRIVATE logging doctest::doctest_with_main) +if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + add_test(NAME logging_test COMMAND logging_test) +else() # assuming multi-config generator: + add_test(NAME logging_test COMMAND logging_test CONFIGURATION Debug RelWithDebInfo) +endif () diff --git a/src/test_errors.cpp b/tests/logging_test.cpp similarity index 57% rename from src/test_errors.cpp rename to tests/logging_test.cpp index fa204ee..68ee947 100644 --- a/src/test_errors.cpp +++ b/tests/logging_test.cpp @@ -1,4 +1,4 @@ -#include "errors.hpp" +#include "logging.h" #include // hack to fix doctest for MSVC #include @@ -8,15 +8,17 @@ TEST_CASE("Error message") { - set_error_path("test_errors.log"); + CHECK(get_log_path() == std::string_view{"error.log"}); + set_log_path("logging_test.log"); + const auto error_path = get_log_path(); + REQUIRE_MESSAGE(error_path != nullptr, "Failed to get log path"); +#ifdef LOGGING log_err("Testing: %s %d %f", "errors", 42, 3.141); - const auto error_path = get_error_path(); - REQUIRE(error_path != nullptr); auto is = std::ifstream{error_path}; - REQUIRE(static_cast(is)); - const auto buffer = - std::string{std::istreambuf_iterator{is}, std::istreambuf_iterator{}}; - auto content = std::string_view{buffer}; + REQUIRE_MESSAGE(static_cast(is), (std::string{"Failed to read "}+error_path)); + using isbit = std::istreambuf_iterator; + const auto buffer = std::string{isbit{is}, isbit{}}; + const auto content = std::string_view{buffer}; REQUIRE(static_cast(is)); const auto message_pos = content.find(" "); REQUIRE(message_pos != std::string_view::npos); @@ -26,8 +28,9 @@ TEST_CASE("Error message") CHECK(message == "Testing: errors 42 3.141000"); const auto at_pos = content.find(" at ", in_pos + 1); REQUIRE(at_pos != std::string_view::npos); - const auto test_errors_pos = content.find("test_errors.cpp", at_pos + 4); + const auto test_errors_pos = content.find("logging_test.cpp", at_pos + 4); REQUIRE(test_errors_pos != std::string_view::npos); const auto location = content.substr(test_errors_pos); - CHECK(location == "test_errors.cpp:12\n"); + CHECK(location == "logging_test.cpp:16\n"); +#endif // LOGGING } \ No newline at end of file diff --git a/src/test_table.cpp b/tests/table_test.cpp similarity index 57% rename from src/test_table.cpp rename to tests/table_test.cpp index cdb04fb..49501a8 100644 --- a/src/test_table.cpp +++ b/tests/table_test.cpp @@ -4,38 +4,36 @@ */ #include "library.hpp" +#include #include #include #include #include +#include -#if defined(__linux__) -const auto table_path = std::filesystem::current_path() / "libtable.so"; -#elif defined(__APPLE__) -const auto table_path = std::filesystem::current_path() / "libtable.dylib"; -#elif defined(__MINGW32__) -const auto table_path = std::filesystem::current_path() / "libtable.dll"; -#elif defined(_WIN32) -const auto table_path = [] { - // CMake on Windows puts Release binaries into CMAKE_CURRENT_BINARY_DIR/Release - // otherwise binaries are in CMAKE_CURRENT_BINARY_DIR - auto buffer = std::string(1024, '\0'); - auto size = GetModuleFileNameA( - NULL, buffer.data(), static_cast(buffer.size())); // path to current executable - while (size >= buffer.size()) { - buffer.resize(buffer.size() * 2, '\0'); - size = GetModuleFileNameA(NULL, buffer.data(), static_cast(buffer.size())); - } - buffer.resize(size); // truncate the path - return std::filesystem::path{buffer}.parent_path() / "table.dll"; -}(); -#else -#error("Unknown platform") +TEST_SUITE_BEGIN("libtable"); + +#ifndef LIBTABLE_PATH +#error "Please define LIBTABLE_PATH to path to the dynamic library of table" #endif +#ifndef CSV_PATH +#error "Please define CSV_PATH to path to the example csv file" +#endif + +#define STRINGIFY(x) #x +#define TO_STRING(x) STRINGIFY(x) + +using std::filesystem::path; +using std::filesystem::exists; // MSVC fails to do ADL +const path libtable_path = TO_STRING(LIBTABLE_PATH); +const path csv_path = TO_STRING(CSV_PATH); TEST_CASE("load libtable") { + REQUIRE_MESSAGE(exists(libtable_path), ("Failed to find " + libtable_path.string())); + REQUIRE_MESSAGE(exists(csv_path), ("Failed to find " + csv_path.string())); + using fn_str_int_to_int = int (*)(const char*, int); using fn_int_str_to_int = int (*)(int, const char*); using fn_int_to_int = int (*)(int); @@ -45,13 +43,12 @@ TEST_CASE("load libtable") using fn_int_double_int_int_to_double = double (*)(int, double, int, int); using fn_int_int_int_int = int (*)(int, int, int, int); using fn_int_int_int_double = int (*)(int, int, int, double); - using fn_int_int_int_intp_int_int = void (*)(int, int, int, int*, int, int); + using fn_int_int_int_intp_int_int = int (*)(int, int, int, int*, int, int); auto approx = doctest::Approx{0}.epsilon(0.00001); try { - auto lib_path_str = table_path.string(); - std::cout << "Loading " << lib_path_str << std::endl; + auto lib_path_str = libtable_path.string(); auto lib = Library{lib_path_str.c_str()}; // may throw upon errors auto table_new_int [[maybe_unused]] = lib.lookup("table_new_int"); auto table_new_double = lib.lookup("table_new_double"); @@ -72,19 +69,40 @@ TEST_CASE("load libtable") auto interpolate = lib.lookup("interpolate"); // read from file: - const auto id = table_read_csv("table_input.csv", 0); - auto rows = table_rows(id); - auto cols = table_cols(id); - REQUIRE(rows != 0); // table should not be empty - REQUIRE(cols != 0); + const auto id = table_read_csv(csv_path.string().c_str(), 0); + REQUIRE(id >= 0); // success with loading table + const auto rows = table_rows(id); + REQUIRE(rows >= 0); // table should be non-empty + CHECK(table_rows(id + 1) == -1); // non-existing table + const auto cols = table_cols(id); + REQUIRE(cols >= 0); // should be some columns + CHECK(table_cols(id + 1) == -1); // non-existing table // read access: - for (int i = 0; i < rows; ++i) { - for (int j = 0; j < cols; ++j) - std::cout << read_double(id, i, j) << " "; - std::cout << '\n'; + auto os = std::ostringstream{}; + for (int row = 0; row < rows; ++row) { + os << read_double(id, row, 0); + for (int col = 1; col < cols; ++col) + os << " " << read_double(id, row, col); + os << '\n'; } + CHECK(os.str() == "1 5 9 1\n2 6 10 4\n3 7 11 9\n4 8 12 16\n"); CHECK(6 == read_double(id, 1, 1)); + // bad arguments: + CHECK(std::isnan(read_double(-1, 1, 1))); // negative table id + CHECK(std::isnan(read_double(id + 1, 1, 1))); // non-existing table + CHECK(std::isnan(read_double(id, -1, 1))); // negative row + CHECK(std::isnan(read_double(id, rows, 1))); // row overflow + CHECK(std::isnan(read_double(id, 1, -1))); // negative column + CHECK(std::isnan(read_double(id, 1, cols))); // column overflow + constexpr auto bad_int = std::numeric_limits::lowest(); + CHECK(read_int(-1, 1, 1) == bad_int); // negative table id + CHECK(read_int(id + 1, 1, 1) == bad_int); // non-existing table + CHECK(read_int(id, -1, 1) == bad_int); // negative row + CHECK(read_int(id, rows, 1) == bad_int); // row overflow + CHECK(read_int(id, 1, -1) == bad_int); // negative column + CHECK(read_int(id, 1, cols) == bad_int); // column overflow + const auto v1_2 = interpolate(id, 1.2, 0, 1); CHECK(v1_2 == approx(5.2)); const auto v0 = interpolate(id, 0.0, 0, 1); @@ -93,7 +111,7 @@ TEST_CASE("load libtable") CHECK(8.0 == v5_5); // read in bulk: auto column1 = std::vector(static_cast(rows), 0); - read_int_col(id, 0, 1, column1.data(), 0, rows); + REQUIRE(read_int_col(id, 0, 1, column1.data(), 0, rows) == 0); CHECK(column1[0] == 5); CHECK(column1[1] == 6); CHECK(column1[2] == 7); @@ -122,11 +140,11 @@ TEST_CASE("load libtable") CHECK(table_rows(id3) == 3); CHECK(table_cols(id3) == 4); CHECK(read_double(id3, 2, 2) == 3.14); - } catch (std::exception& err) { - std::cerr << "Failed: " << err.what() << std::endl; - CHECK(false); + } catch (const std::exception& err) { + FAIL(err.what()); } catch (...) { - std::cerr << "Failed with unknown exception" << std::endl; - CHECK(false); + FAIL("Failed with unknown exception"); } } + +TEST_SUITE_END(); \ No newline at end of file