diff --git a/.github/workflows/unittests_cmake.yml b/.github/workflows/unittests_cmake.yml new file mode 100644 index 0000000..5315c29 --- /dev/null +++ b/.github/workflows/unittests_cmake.yml @@ -0,0 +1,28 @@ +name: Unit tests (CMake) + +on: + push: + branches: + - '*' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Install compiler + run: sudo apt-get install gfortran + - name: Install build tools + run: | + sudo apt-get install cmake + - name: Show versions + run: | + gfortran --version + cmake --version + - name: Unit tests + run: | + mkdir build + cd build + cmake .. + make + make test diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d79af0a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,413 @@ +# cmake_minimum_required(VERSION 3.6) #maybe ok? +cmake_minimum_required(VERSION 3.6) + +# Append local CMake module directory +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# Set package metadata +set(CPACK_PACKAGE_NAME "zofu") +set(CPACK_PACKAGE_VERSION_MAJOR "1") +set(CPACK_PACKAGE_VERSION_MINOR "0") +set(CPACK_PACKAGE_VERSION_PATCH "1") +set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Zofu is a unit testing framework for modern Fortran") +set(CPACK_PACKAGE_VENDOR "Acorvid Technical Services Corporation") +set(CPACK_PACKAGE_CONTACT "Bob Apthorpe ") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/apthorpe/zofu") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}") + +# Save compilation commands to compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Set project name and language +project(zofu + LANGUAGES Fortran + VERSION "${CPACK_PACKAGE_VERSION}") + +############################################################################### +## Options #################################################################### +############################################################################### + +# Enable MPI support; disabled by default +option(ENABLE_MPI "Enable MPI" OFF) + +# Set additional search path for FORD +set(FORD_PATH "" CACHE PATH "Directory path of FORD documentation tool") + +# Set relative path to executables under installation root directory +# Default is ""; if not set, defaults to CMAKE_INSTALL_BINDIR for the +# platform. See https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html +set(ZOFU_BINARY_INSTALL_DIR "" + CACHE PATH "Relative path to Zofu executables for installation") + +# Set relative path to libraries under installation root directory +# Default is ""; if not set, defaults to CMAKE_INSTALL_LIBDIR for the +# platform. See https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html +set(ZOFU_LIBRARY_INSTALL_DIR "" + CACHE PATH "Relative path to Zofu libraries for installation") + +# Set relative path to Fortran `.mod` files under installation root directory +# Default is "finstall/zofu" +set(ZOFU_FORTRAN_MODULE_INSTALL_DIR "finstall/zofu" + CACHE PATH "Relative path to Zofu Fortran `.mod` files for installation") + +# Set relative path to HTML documentation under installation root directory +# Default is "doc/html" +set(ZOFU_DOCUMENTATION_INSTALL_DIR "doc/html" + CACHE PATH "Relative path to Zofu documentation for installation") + +############################################################################### +## Dependencies and CMake Modules ############################################ +############################################################################### + +# Defined cmake_parse_arguments(). Needed only for CMake 3.4 and earlier +include(CMakeParseArguments) + +# Set default installation paths; should be invoked after setting project +# language(s) +include(GNUInstallDirs) +if(NOT ZOFU_BINARY_INSTALL_DIR) + set(ZOFU_BINARY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") +endif() +if(NOT ZOFU_LIBRARY_INSTALL_DIR) + set(ZOFU_LIBRARY_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}") +endif() + +# CTest setup +# Needed for valgrind (usually only enable_testing() is needed) +include(CTest) +enable_testing() + +# Query MPI support +if(ENABLE_MPI) + find_package(MPI COMPONENTS Fortran OPTIONAL_COMPONENTS) +endif() + +# Add convenience function for creating and running acceptance tests +include(ZofuAcceptanceHelper) + +# Fortran Documenter +find_package(FORD) + +############################################################################### +## Build ###################################################################### +############################################################################### + +# Interrogates Fortran compiler to see which options are supported +# (vs configuring by looking at compiler name or ID) +include(DefineFortranOptions) + +list(APPEND FCOPTS + ${FCOPT_NO_OPTIMIZATION} + ${FCOPT_WALL} + ${FCOPT_FCHECKALL} + ${FCOPT_DEBUG} + ${FCOPT_BACKTRACE} +) + +if(${FC_ALLOWS_NO_MAYBE_UNINITIALIZED}) + list(APPEND FCOPTS ${FCOPT_NO_MAYBE_UNINITIALIZED}) +endif() + +# Set recent language standard if available +if(${FC_ALLOWS_STD_F2018}) + list(APPEND FCOPTS ${FCOPT_STD_F2018}) +elseif(${FC_ALLOWS_STD_F2008}) + list(APPEND FCOPTS ${FCOPT_STD_F2008}) +endif() + +message(STATUS "General Fortran compiler options set to ${FCOPTS}") + +### Zofu library + +set(ZOFU_LIB_NAME zofu) + +set(ZOFU_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") + +# Define sources for zofu library (no MPI) +set(ZOFU_SERIAL_LIB_SOURCES + "${ZOFU_SOURCE_DIR}/zofu.F90" + "${ZOFU_SOURCE_DIR}/zofu_kinds.F90" + "${ZOFU_SOURCE_DIR}/zofu_scan.F90" + "${ZOFU_SOURCE_DIR}/zofu_str_utils.F90" +) + +# Define sources for MPI-enabled zofu library (no MPI) +set(ZOFU_MPI_LIB_SOURCES "${ZOFU_SOURCE_DIR}/zofu_mpi.F90") + +set(ZOFU_LIB_SOURCES ${ZOFU_SERIAL_LIB_SOURCES}) +if(MPI_Fortran_FOUND) + list(APPEND ZOFU_LIB_SOURCES ${ZOFU_MPI_LIB_SOURCES}) +endif() + +set(ZOFU_FORTRAN_MODULE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ZOFU_LIB_NAME}_include") +file(MAKE_DIRECTORY "${ZOFU_FORTRAN_MODULE_DIR}") +set(ZOFU_MODULE_FILE "${ZOFU_FORTRAN_MODULE_DIR}/zofu.mod") +add_library(${ZOFU_LIB_NAME} ${ZOFU_LIB_SOURCES}) +set_target_properties( + ${ZOFU_LIB_NAME} + PROPERTIES + Fortran_MODULE_DIRECTORY ${ZOFU_FORTRAN_MODULE_DIR} +) + +if(MPI_Fortran_FOUND) + target_compile_options(${ZOFU_LIB_NAME} PUBLIC ${FCOPTS} ${MPI_Fortran_COMPILE_OPTIONS}) + set_target_properties( + ${ZOFU_LIB_NAME} + PROPERTIES + INCLUDE_DIRECTORIES ${MPI_Fortran_INCLUDE_DIRS} + ) +else() + target_compile_options(${ZOFU_LIB_NAME} PUBLIC ${FCOPTS}) +endif() + +install(DIRECTORY "${ZOFU_FORTRAN_MODULE_DIR}/" + DESTINATION "${ZOFU_FORTRAN_MODULE_INSTALL_DIR}") + +### Zofu-driver executable + +# Define sources for zofu-driver executable +set(ZOFUDRIVER_SOURCES "${ZOFU_SOURCE_DIR}/zofu_driver.F90") + +# Define zofu-driver executable artifact +set(ZOFUDRIVER_FORTRAN_MODULE_DIR "${CMAKE_CURRENT_BINARY_DIR}/zofu-driver_include") +file(MAKE_DIRECTORY "${ZOFUDRIVER_FORTRAN_MODULE_DIR}") +add_executable(ZOFUDRIVER ${ZOFUDRIVER_SOURCES}) +set_target_properties( + ZOFUDRIVER + PROPERTIES + OUTPUT_NAME zofu-driver + DEPENDS ${ZOFU_LIB_NAME} + Fortran_MODULE_DIRECTORY ${ZOFUDRIVER_FORTRAN_MODULE_DIR} + INCLUDE_DIRECTORIES ${ZOFU_FORTRAN_MODULE_DIR} + LINK_LIBRARIES ${ZOFU_LIB_NAME} +) + +if(MPI_Fortran_FOUND) + target_compile_options(ZOFUDRIVER PUBLIC ${FCOPTS} ${MPI_Fortran_COMPILE_OPTIONS}) +else() + target_compile_options(ZOFUDRIVER PUBLIC ${FCOPTS}) +endif() + +install( + TARGETS ZOFUDRIVER ${ZOFU_LIB_NAME} + RUNTIME DESTINATION ${ZOFU_BINARY_INSTALL_DIR} + LIBRARY DESTINATION ${ZOFU_LIBRARY_INSTALL_DIR} + ARCHIVE DESTINATION ${ZOFU_LIBRARY_INSTALL_DIR} +) + +############################################################################### +## Testing #################################################################### +############################################################################### + +# Directory containing unit test source files +set(TEST_SRC_BASE "${CMAKE_CURRENT_SOURCE_DIR}/test") + +# Simple unit tests + +set(SIMPLE_TEST + test_complex_asserts + test_double_asserts + test_integer_asserts + test_logical_asserts + test_real_asserts + test_string_asserts + test_str_utils +) + +foreach(TEST_NAME IN LISTS SIMPLE_TEST) + set(TEST_SOURCES ${TEST_SRC_BASE}/check.F90 ${TEST_SRC_BASE}/${TEST_NAME}.F90) + add_zofu_acceptance_test( + TARGET ${TEST_NAME} + SOURCES ${TEST_SOURCES} + ) +endforeach() + +# test_zofu_scan - needs environment variable set + +set(TEST_NAME test_zofu_scan) +set(TEST_SOURCES ${TEST_SRC_BASE}/test_zofu_scan.F90) +# set(TEST_ENV "ZOFU_TEST_DATA_PATH=${TEST_SRC_BASE}/data/") +add_zofu_acceptance_test( + TARGET ${TEST_NAME} + SOURCES ${TEST_SOURCES} +) +# Note: Set environment variable ZOFU_TEST_DATA_PATH +# to ${CMAKE_CURRENT_SOURCE_DIR}/test/data/ <-- TRAILING SLASH +set_tests_properties(${TEST_NAME} + PROPERTIES + ENVIRONMENT ZOFU_TEST_DATA_PATH=${TEST_SRC_BASE}/data/ +) + +# test_mpi - needs MPI compilation options and .mod files + +if(MPI_Fortran_FOUND) + # test_mpi + set(TEST_NAME test_mpi) + set(TEST_SOURCES + ${TEST_SRC_BASE}/check_mpi.F90 + ${TEST_SRC_BASE}/test_mpi.F90 + ) + add_zofu_acceptance_test( + TARGET ${TEST_NAME} + SOURCES ${TEST_SOURCES} + ) + set_target_properties( + ${TEST_NAME} + PROPERTIES + INCLUDE_DIRECTORIES ${MPI_Fortran_INCLUDE_DIRS} + ) + target_compile_options(${TEST_NAME} PUBLIC ${MPI_Fortran_COMPILE_OPTIONS}) +endif() + +############################################################################### +## Documentation ############################################################## +############################################################################### + +if(FORD_FOUND) + set(FORD_BUILD_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc") + set(FORD_CONFIG_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/doc.md.in") + set(FORD_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/doc.md") + file(MAKE_DIRECTORY "${FORD_BUILD_DIRECTORY}") + configure_file("${FORD_CONFIG_TEMPLATE}" "${FORD_CONFIG}" @ONLY) + + add_custom_target(doc + ALL + COMMAND ${CMAKE_COMMAND} -E echo "Generating documentation with FORD" + COMMAND ${FORD_BINARY} ${FORD_CONFIG} + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + VERBATIM + USES_TERMINAL + ) + # Installs contents of ./build/doc under installer path ./doc/html (note + # trailing slash in source directory) + install(DIRECTORY "${FORD_BUILD_DIRECTORY}/" + DESTINATION "${ZOFU_DOCUMENTATION_INSTALL_DIR}") +endif() + +############################################################################### +## Packaging ################################################################## +############################################################################### + +# Documentation files +install(FILES LICENSE README.md + DESTINATION doc) + +list(APPEND CPACK_GENERATOR ZIP) + +if(WIN32) + # Set up NSIS + find_package(NSIS) + if(NSIS_FOUND) + # set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/img/ZOFU.ico") + # set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/img/ZOFU.ico") + set(CPACK_NSIS_INSTALLED_ICON_NAME "Uninstall.exe") + set(CPACK_NSIS_HELP_LINK "${CPACK_PACKAGE_HOMEPAGE_URL}") + set(CPACK_NSIS_URL_INFO_ABOUT "${CPACK_PACKAGE_HOMEPAGE_URL}") + set(CPACK_NSIS_MODIFY_PATH ON) + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) + + list(APPEND CPACK_GENERATOR NSIS) + endif() + + # NuGet + # TODO: Find a more robust means of detecting whether NuGet is available + find_program(NUGET_EXECUTABLE nuget) + if(NUGET_EXECUTABLE) + message(STATUS "NuGet found at ${NUGET_EXECUTABLE}; needs configuration") + + # list(APPEND CPACK_GENERATOR NuGet) + endif() + + # Set up WIX + # TODO: Make this user-configurable or otherwise deal with version and architecture in WIX path + set(WIX_ROOT_DIR "/Program Files (x86)/WiX Toolset v3.11") + set(WIX_FIND_REQUIRED OFF) + set(WIX_FIND_QUIETLY ON) + find_package(WIX) + # message("WIX found? ${WIX_FOUND}") + if(WIX_FOUND) + message(STATUS "WIX was found: WIX_FOUND = ${WIX_FOUND}") + # The variable holding the WIX root directory used by CPack is different from the + # variable populated by find_package(WIX) i.e. cmake/FindWix.cmake + # Manually tell CPack where find_package() found WIX... + set(CPACK_WIX_ROOT "${WIX_ROOT_DIR}") + # Manually convert LICENSE to RTF format because WIX/CPack is stupid + set(WIX_LICENSE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rtf") + set(CPACK_WIX_LICENSE_RTF "${WIX_LICENSE_FILE}") + install(FILES "${WIX_LICENSE_FILE}" DESTINATION doc) + # set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/img/ZOFU.ico") + # Note: This should not change for the life of the product + # Generated from guidgen.exe + # set(CPACK_WIX_UPGRADE_GUID "") + + if(EXISTS "${WIX_CANDLE}") + list(APPEND CPACK_GENERATOR WIX) + else() + message(STATUS "Why is WIX_FOUND = ${WIX_FOUND} if WIX_CANDLE (${WIX_CANDLE}) is not found?") + endif() + else() + message(STATUS "WIX was not found: WIX_FOUND = ${WIX_FOUND}") + endif() +else() + list(APPEND CPACK_GENERATOR TGZ TBZ2) + if(APPLE) + # Set up DRAGNDROP + # Add DragNDrop properties + set(CPACK_DMG_VOLUME_NAME "${CPACK_PACKAGE_NAME} v${CPACK_PACKAGE_VERSION}") +# set(CPACK_DMG_FORMAT "UDZO") + set(CPACK_DMG_FORMAT "UDBZ") +#* CPACK_DMG_DS_STORE +#* CPACK_DMG_DS_STORE_SETUP_SCRIPT +#* CPACK_DMG_BACKGROUND_IMAGE +# CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK +# CPACK_DMG_SLA_DIR +# CPACK_DMG_SLA_LANGUAGES +# CPACK_DMG__FILE_NAME +# CPACK_COMMAND_HDIUTIL +# CPACK_COMMAND_SETFILE +# CPACK_COMMAND_REZ + list(APPEND CPACK_GENERATOR DragNDrop) + else() + # Set up DEB + # TODO: Find a more robust means of detecting whether debian packaging + # should be enabled + # Note that readelf is not strictly necessary but platform is assumed + # Debian-ish if it's present + find_program(READELF_EXECUTABLE readelf) + if(READELF_EXECUTABLE) + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION +"${CPACK_PACKAGE_DESCRIPTION_SUMMARY} +Zofu (Zofu is Object-oriented Fortran Unit-testing) is a framework for +carrying out unit testing of Fortran code modules. As the name +suggests, it makes use of the object-oriented features of Fortran 2003.") + # set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "any") + # Auto-detect dependencies + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + # Build multiple packages + set(CPACK_DEB_COMPONENT_INSTALL ON) + set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON) + # set(CPACK_DEBIAN_PACKAGE_DEBUG ON) + # Turn off for executable-only; only needed for packaging libraries + set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON) + + list(APPEND CPACK_GENERATOR DEB) + endif() + + # Set up RPM + # TODO: Find a more robust means of detecting whether RPM + # generator is available + find_program(RPMBUILD_EXECUTABLE rpmbuild) + if(RPMBUILD_EXECUTABLE) + # Needs additional work (maybe?) + set(CPACK_RPM_PACKAGE_LICENSE "LGPL3") + set(CPACK_RPM_FILE_NAME RPM-DEFAULT) + list(APPEND CPACK_GENERATOR RPM) + endif() + endif() +endif() + +# This must be last +include(CPack) diff --git a/README.md b/README.md index 7bcb5b0..a1b8f21 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ At the end of the test two further dictionaries are output, "cases" and "asserti # Running multiple tests -If a number of modules are to be tested, `zofu-driver` can build a separate driver program for each module. The tests can be run using a utility such as [meson test](https://mesonbuild.com/Unit-tests.html), which will run all the test driver programs and produce summary output for the whole suite of tests. +If a number of modules are to be tested, `zofu-driver` can build a separate driver program for each module. The tests can be run using a utility such as [meson test](https://mesonbuild.com/Unit-tests.html) or [CTest](https://gitlab.kitware.com/cmake/community/-/wikis/doc/ctest/Testing-With-CTest), which will run all the test driver programs and produce summary output for the whole suite of tests. Some unit testing systems create a single driver program which runs all tests in multiple modules. While this is perhaps simpler, it has the disadvantage that if one test crashes, the entire suite of tests stops and no other tests can be run. By contrast, if there is a separate driver program for each test module, the suite of tests can continue to run in the event of one module crashing. This approach also allows individual modules to be tested without recompiling the test driver program. @@ -175,7 +175,13 @@ This works because `unit_test_mpi_type` extends `unit_test_type`, so is still of The setup and teardown routines in each test module should include commands for initializing and finalizing MPI, e.g. `mpi_init()` in the `setup()` routine, and `mpi_finalize()` in the `teardown()` routine. -# Building Zofu +# Building and installing Zofu + +Zofu includes scripts for building and installing using either [Meson](https://mesonbuild.com/) or [CMake](https://cmake.org/). + +## Using Meson + +### Building with Meson A script (`meson.build`) is included for building Zofu using the [Meson](https://mesonbuild.com/) build system. This in turn uses the [Ninja](https://ninja-build.org/) tool to run the build. Meson and Ninja can be installed using your package manager or via [pip](https://packaging.python.org/tutorials/installing-packages/). Zofu can be configured by running: @@ -183,7 +189,7 @@ A script (`meson.build`) is included for building Zofu using the [Meson](https:/ meson build ``` -This will create and configure a build subdirectory called `build`. By default, a debug build is performed. If you want an optimized release build, you can specify the build type at configuration time, e.g.: +in the Zofu root directory. This will create and configure a build subdirectory called `build` (you can substitute a different name if you prefer). By default, a debug build is performed. If you want an optimized release build, you can specify the build type at configuration time, e.g.: ``` meson build --buildtype=release @@ -205,7 +211,7 @@ FC=ftn meson build -Dmpi_wrapper_compiler=true will configure the Zofu build to use an MPI wrapper compiler called `ftn`. -# Installing Zofu +### Installing with Meson Zofu can be installed as follows: @@ -229,14 +235,100 @@ meson build --prefix=/home/bob/ --libdir=lib --includedir=finclude/zofu Meson will also write a [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) file to make it easier for other software (e.g. your test driver programs) to link to Zofu. The pkg-config file is installed to the `pkgconfig` subdirectory under the directory where the Zofu library is installed. +## Using CMake + +### Building with CMake + +A script (`CMakeLists.txt`) is included for building Zofu using [CMake](https://cmake.org/). CMake can be installed using your package manager. CMake will by default use the `make` tool to run the build, but can also use Ninja instead if you configure it to do so. + +The Zofu CMake build can be configured by running: + +``` +mkdir build +cd build +cmake .. +``` + +in the Zofu root directory. Zofu can then be built by executing `make` in the build directory. + +### Installing with CMake + +Zofu can be installed by executing: + +``` +make package +make install +``` +in the build directory. + +### Customizing a CMake build + +To specify a release type (`Release` or `Debug` for example), +pass `-D CMAKE_BUILD_TYPE=Debug` or `-D CMAKE_BUILD_TYPE=Release` +to `cmake`. If not specified, `Release` is assumed. + +The installation path for can be specified for each component of Zofu - the +`zofu-driver` executable, the library, Fortran `/.mod` files, and HTML +documentation. The following parameters are optional: + +* `ZOFU_BINARY_INSTALL_DIR` sets the relative path to `zofu-driver` + under the root install directory. If not specified, it typically + defaults to `./bin`. +* `ZOFU_LIBRARY_INSTALL_DIR` sets the relative path to the library + (`.a`, `.dll`, `.dylib`). If not specified, it typically defaults to + `./lib`; see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html + for details. +* `ZOFU_FORTRAN_MODULE_INSTALL_DIR` sets the relative path to the Fortran +`.mod` files; the default is `./finstall/zofu` +* `ZOFU_DOCUMENTATION_INSTALL_DIR` sets the relative path to the HTML +documentation generated by FORD; the default is `./doc/html` + +These variables are passed to CMake using the `-D` option. For example: + +~~~sh +cd build +cmake -D CMAKE_BUILD_TYPE=Debug \ + -D ZOFU_BINARY_INSTALL_DIR:PATH=debug/bin \ + -D ZOFU_LIBRARY_INSTALL_DIR:PATH=debug/lib \ + -D ZOFU_FORTRAN_MODULE_INSTALL_DIR:PATH=debug/finclude \ + -D ZOFU_DOCUMENTATION_INSTALL_DIR:PATH=debug/html \ + .. +make +make test +make package +~~~ + +Similarly for Windows: + +~~~bat +cd build +cmake.exe -G Ninja -D CMAKE_BUILD_TYPE=Debug ^ + -D ZOFU_BINARY_INSTALL_DIR:PATH=debug/bin ^ + -D ZOFU_LIBRARY_INSTALL_DIR:PATH=debug/lib ^ + -D ZOFU_FORTRAN_MODULE_INSTALL_DIR:PATH=debug/finclude ^ + -D ZOFU_DOCUMENTATION_INSTALL_DIR:PATH=debug/html ^ + .. +ninja +ninja test +ninja package +~~~ + +More information can be found in `contrib/cmake/README.md` + # Testing Zofu -Zofu has its own unit tests. These can be run from a command line in the `build` directory using the command: +Zofu has its own unit tests. If you are using Meson, these can be run from a command line in the `build` directory using the command: ``` meson test ``` +If you are using CMake with `make`, the tests can be run using the command: + +``` +make test +``` + # Running tests with Meson If you are using Meson to build your code, you can add the tests to your build and have Meson run them. Because the test driver source code files do not exist at configure time, but only after they have been created using `zofu-driver`, they can be declared in your `meson.build` file using the `configure_file()` function. diff --git a/cmake/DefineFortranOptions.cmake b/cmake/DefineFortranOptions.cmake new file mode 100644 index 0000000..887c89f --- /dev/null +++ b/cmake/DefineFortranOptions.cmake @@ -0,0 +1,30 @@ +# Detect available compiler options +include(CheckFortranCompilerFlag) + +# Set variable name fcopt_name to $fc_flag and fcopt_allowed to 1 (True) +# if $fc_flag is a legal, quiet option to the Fortran compiler +function(set_fcopt fcopt_allowed fcopt_name fc_flag) + check_fortran_compiler_flag("${fc_flag}" ${fcopt_allowed}) + if(${${fcopt_allowed}}) + set(${fcopt_name} "${fc_flag}" PARENT_SCOPE) + else() + set(${fcopt_name} "" PARENT_SCOPE) + endif() +endfunction() + +# Set option flag visibility and values +set_fcopt(FC_ALLOWS_NO_OPTIMIZATION FCOPT_NO_OPTIMIZATION "-O0") +set_fcopt(FC_ALLOWS_STD_LEGACY FCOPT_STD_LEGACY "--std=legacy") +set_fcopt(FC_ALLOWS_WALL FCOPT_WALL "-Wall") +set_fcopt(FC_ALLOWS_DEBUG FCOPT_DEBUG "-g") +set_fcopt(FC_ALLOWS_SAVE FCOPT_SAVE "-fno-automatic") +set_fcopt(FC_ALLOWS_FCHECKALL FCOPT_FCHECKALL "-fcheck=all") +set_fcopt(FC_ALLOWS_NO_MAYBE_UNINITIALIZED FCOPT_NO_MAYBE_UNINITIALIZED "-Wno-maybe-uninitialized") + +set_fcopt(FC_ALLOWS_STD_F2008 FCOPT_STD_F2008 "--std=f2008") +set_fcopt(FC_ALLOWS_STD_F2018 FCOPT_STD_F2018 "--std=f2018") + +# Code coverage options - experimental +# set_fcopt(FC_ALLOWS_COVERAGE FCOPT_COVERAGE "--coverage") +# set_fcopt(FC_ALLOWS_PROFILE_ARCS FCOPT_PROFILE_ARCS "-fprofile-arcs") +# set_fcopt(FC_ALLOWS_TEST_COVERAGE FCOPT_TEST_COVERAGE "-ftest-coverage") \ No newline at end of file diff --git a/cmake/FindFORD.cmake b/cmake/FindFORD.cmake new file mode 100644 index 0000000..bde1333 --- /dev/null +++ b/cmake/FindFORD.cmake @@ -0,0 +1,13 @@ +# Set FORD_FOUND if FORD documentation tool can be found; see +# https://github.com/Fortran-FOSS-Programmers/ford +if(IS_DIRECTORY "${FORD_PATH}") + find_program(FORD_BINARY + NAMES ford + PATHS "${FORD_PATH}" + ) +else() + find_program(FORD_BINARY NAMES ford) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FORD DEFAULT_MSG FORD_BINARY) \ No newline at end of file diff --git a/cmake/ZofuAcceptanceHelper.cmake b/cmake/ZofuAcceptanceHelper.cmake new file mode 100644 index 0000000..e027db9 --- /dev/null +++ b/cmake/ZofuAcceptanceHelper.cmake @@ -0,0 +1,84 @@ +# Synopsis: +# +# add_zofu_acceptance_test() is a convenience function to build +# acceptance test binaries for Zofu and schedule them in CTest. +# +# Usage: +# +# add_zofu_acceptance_test( +# TARGET testname +# SOURCES list_of_source_files +# [WILL_FAIL] +# [DEBUG] +# ) +# +# Before calling this function, ZOFU_LIB_NAME and +# ZOFU_FORTRAN_MODULE_DIR should be set in the calling environment +# +# Arguments: +# +# - TARGET specifies the test target and executable name. Single value, +# required. +# - SOURCES indicates a list of source files required to build the test +# application. Multi-value, required. +# - WILL_FAIL - if present as an argument, WILL_FAIL will be set to +# TRUE for the test (i.e. test is expected to fail). No value. +# Optional; default is WILL_FAIL FALSE (test expected to pass) +# - DEBUG - if present, this function will print out the parsed argument +# values for troubleshooting. No value. Optional. +# +function(add_zofu_acceptance_test) + # Define the supported set of keywords + set(prefix ARG) + set(noValues WILL_FAIL DEBUG) + set(singleValues TARGET ENVIRONMENT) + set(multiValues SOURCES) + + cmake_parse_arguments( + ${prefix} + "${noValues}" "${singleValues}" "${multiValues}" + ${ARGN} + ) + + if(${ARG_DEBUG}) + # Log details for each supported keyword + message(STATUS "Option summary:") + foreach(arg IN LISTS noValues) + if(${prefix}_${arg}) + message(" ${arg} enabled") + else() + message(" ${arg} disabled") + endif() + endforeach() + + foreach(arg IN LISTS singleValues multiValues) + # Single argument values will print as a string + # Multiple argument values will print as a list + message(" ${arg} = ${${prefix}_${arg}}") + endforeach() + endif() + + set(TEST_FORTRAN_MODULE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_include") + file(MAKE_DIRECTORY "${TEST_FORTRAN_MODULE_DIR}") + add_executable(${ARG_TARGET} ${ARG_SOURCES}) + set_target_properties( + ${ARG_TARGET} + PROPERTIES + OUTPUT_NAME ${ARG_TARGET} + DEPENDS ${ZOFU_LIB_NAME} + Fortran_MODULE_DIRECTORY ${TEST_FORTRAN_MODULE_DIR} + INCLUDE_DIRECTORIES ${ZOFU_FORTRAN_MODULE_DIR} + LINK_LIBRARIES ${ZOFU_LIB_NAME} + ) + + add_test(NAME ${ARG_TARGET} + COMMAND $ + CONFIGURATIONS Debug Release "" + ) + + if(${ARG_WILL_FAIL}) + set_tests_properties(${ARG_TARGET} PROPERTIES + WILL_FAIL TRUE) + endif() + +endfunction() \ No newline at end of file diff --git a/contrib/cmake/FindZOFU.cmake b/contrib/cmake/FindZOFU.cmake new file mode 100644 index 0000000..c1444e6 --- /dev/null +++ b/contrib/cmake/FindZOFU.cmake @@ -0,0 +1,100 @@ +# Summary: +# Implementation of find_package() for zofu Fortran unit testing +# library from https://github.com/acroucher/zofu +# +# Input: +# These variables are unconfirmed, set by the user +# ZOFU_BINARY_PATH - path containing zofu-driver. Windows also contains libzofu.dll +# ZOFU_LIBRARY_PATH - path containing libzofu.so, libzofu.dll.a +# ZOFU_MODULE_PATH - path containing zofu.mod, zofu_kinds.mod, zofu_scan.mod, zofu_str_utils.mod +# +# Output: +# These variables are confirmed or set by CMake +# ZOFU_FOUND - boolean; status indicating output variables are set +# ZOFU_DRIVER - full path to zofu-driver +# ZOFU_LIBRARY_NAME - common name of library ("zofu"; as passed to -l) +# ZOFU_LIBRARY - full path to libzofu.so (or .a or .dll.a) +# ZOFU_LIBRARY_DIR - confirmed library path (should equal ZOFU_LIBRARY_PATH) as passed to -L +# ZOFU_MODULE_DIR - confirmed Fortran module path (should equal ZOFU_MODULE_PATH) as passed to -I + +### Initial conditions ### + +set(ZOFU_FOUND OFF) + +set(ZOFU_LIBRARY_NAME zofu) + +set(ZOFU_BINARY_PATH "" CACHE PATH "zofu binary directory path") +set(ZOFU_LIBRARY_PATH "" CACHE PATH "zofu library directory path") +set(ZOFU_MODULE_PATH "" CACHE PATH "zofu Fortran module directory path") + +### Search for artifacts ### + +# Find zofu-driver in bindir +find_program(ZOFU_DRIVER zofu-driver + PATHS "${ZOFU_BINARY_PATH}" ~/.local/bin +) + +# Find zofu library in libdir (let CMake guess at actual file name +# based on common library name) +find_library(ZOFU_LIBRARY ${ZOFU_LIBRARY_NAME} + PATHS "${ZOFU_LIBRARY_PATH}" ~/.local/lib +) + +if(ZOFU_LIBRARY) + get_filename_component(ZOFU_LIBRARY_DIR "${ZOFU_LIBRARY}" DIRECTORY) + set(ZOFU_FOUND ON) +# else() +# # ZOFU_FOUND is already OFF - just PASS +endif() + +# Find zofu module directory based on single check for zofu.mod +find_file(ZOFU_MODULE ${ZOFU_LIBRARY_NAME}.mod + PATHS "${ZOFU_MODULE_PATH}" ~/.local/finclude/zofu ~/Documents/Projects/git-proj/zofu/build/libzofu.so.p +) + +if(ZOFU_MODULE) + get_filename_component(ZOFU_MODULE_DIR "${ZOFU_MODULE}" DIRECTORY) + # If ZOFU_FOUND is ON, leave it ON - just PASS +else() + # If ZOFU_FOUND is ON, turn it OFF + set(ZOFU_FOUND OFF) +endif() + +### Display results ### + +# Diagnostics +if(ZOFU_FOUND) + message(STATUS "Zofu Fortran testing library is available") + # message(STATUS "ZOFU_DRIVER set to ${ZOFU_DRIVER}") + # message(STATUS "ZOFU_LIBRARY_NAME set to ${ZOFU_LIBRARY_NAME}") + # message(STATUS "ZOFU_LIBRARY set to ${ZOFU_LIBRARY}") + # message(STATUS "ZOFU_LIBRARY_DIR set to ${ZOFU_LIBRARY_DIR}") + # message(STATUS "ZOFU_MODULE (${ZOFU_LIBRARY_NAME}.mod) set to ${ZOFU_MODULE}") + # message(STATUS "ZOFU_MODULE_DIR set to ${ZOFU_MODULE_DIR}") +else() + message(STATUS "Zofu Fortran testing library is not available") + # message(STATUS "ZOFU_DRIVER set to ${ZOFU_DRIVER}") + # message(STATUS "ZOFU_LIBRARY_NAME set to ${ZOFU_LIBRARY_NAME}") + # message(STATUS "ZOFU_LIBRARY set to ${ZOFU_LIBRARY}") + # message(STATUS "ZOFU_LIBRARY_DIR set to ${ZOFU_LIBRARY_DIR}") + # message(STATUS "ZOFU_MODULE (${ZOFU_LIBRARY_NAME}.mod) set to ${ZOFU_MODULE}") + # message(STATUS "ZOFU_MODULE_DIR set to ${ZOFU_MODULE_DIR}") +endif() + +# if(ZOFU_BINARY_PATH) +# message(STATUS "Input ZOFU_BINARY_PATH specified as ${ZOFU_BINARY_PATH}") +# else() +# message(STATUS "Input ZOFU_BINARY_PATH not specified") +# endif() + +# if(ZOFU_LIBRARY_PATH) +# message(STATUS "Input ZOFU_LIBRARY_PATH specified as ${ZOFU_LIBRARY_PATH}") +# else() +# message(STATUS "Input ZOFU_LIBRARY_PATH not specified") +# endif() + +# if(ZOFU_MODULE_PATH) +# message(STATUS "Input ZOFU_MODULE_PATH specified as ${ZOFU_MODULE_PATH}") +# else() +# message(STATUS "Input ZOFU_MODULE_PATH not specified") +# endif() \ No newline at end of file diff --git a/contrib/cmake/README.md b/contrib/cmake/README.md new file mode 100644 index 0000000..cd345f2 --- /dev/null +++ b/contrib/cmake/README.md @@ -0,0 +1,339 @@ +# Zofu Installation and Configuration Notes + +## Installing Zofu on Windows with Meson and Ninja + +Example conditions: + +- Windows 8.1 environment +- User name of `brak` +- Home directory of `C:\Users\brak` +- zofu installation (target) directory of `C:\Users\brak\dev` +- Working directory is arbitrary; assume `C:\Users\brak\Documents\git-projects` +- GCC 9 including `gfortran.exe` is installed under `c:\TDM-GCC-64` +- Python 3.8 installed under `C:\Python38` +- `ninja.exe` installed under `C:\ProgramData\chocolatey\bin\ninja.EXE` + +~~~bat +cd C:\Users\brak\Documents\git-projects +C:\Python38\Scripts\pip3.8.exe install --user meson +C:\Users\brak\AppData\Roaming\Python\Python38\Scripts\meson.exe build --prefix=c:/Users/brak/dev --libdir=lib --includedir=finclude/zofu +git clone https://github.com/acroucher/zofu.git +cd zofu +C:\ProgramData\chocolatey\bin\ninja.EXE -C build +C:\ProgramData\chocolatey\bin\ninja.EXE -C build install +~~~ + +Due to issues with the meson/ninja build process, the Fortran `.mod` files must be manually copied to the installation directory: + +~~~bat +cp build\libzofu.dll.p\*.mod C:\Users\brak\dev\finclude\zofu +~~~ + +The resulting installation tree should look like: + +~~~ +C:\Users\brak\dev +|-- bin +| |-- libzofu.dll +| `-- zofu-driver.exe +|-- finclude +| `-- zofu +| |-- zofu.mod +| |-- zofu_kinds.mod +| |-- zofu_scan.mod +| `-- zofu_str_utils.mod +`-- lib + |-- libzofu.dll.a + `-- pkgconfig + `-- zofu.pc +~~~ + +The current working directory should be `C:\Users\brak\Documents\git-projects\zofu` and it should contain the subdirectory `test` + +To manually test zofu: + +~~~cmd +cd test +C:\TDM-GCC-64\bin\gfortran.exe -o test_integer_asserts check.F90 test_integer_asserts.F90 -Lc:\Users\brak\dev\lib -Ic:\Users\brak\dev\finclude\zofu -l zofu +cp C:\Users\brak\dev\bin\libzofu.dll . +.\test_integer_asserts.exe +~~~ + +The results should resemble: + +~~~yaml +failed assertions: +- {"case": 2, "reason": "value", "values": [1, -2]} +- {"case": 4, "reason": "value", "values": [1, -2]} +- {"case": 6, "reason": "value", "values": [7, -7], "index": 3, "count": 1} +- {"case": 8, "reason": "value", "values": [7, -7], "index": 3, "count": 1} +- {"case": 9, "reason": "shape", "values": [3, 4]} +- {"case": 11, "reason": "value", "values": [3, 2], "index": [3, 1], "count": 1} +- {"case": 13, "reason": "value", "values": [3, 2], "index": [3, 1], "count": 1} +- {"case": 14, "reason": "shape", "values": [[3, 2], [2, 3]]} +cases: {"count": 14, "passed": 6, "failed": 8} +assertions: {"count": 14, "passed": 6, "failed": 8} +passed: false +~~~ + +## Installating Zofu on Linux with Meson and Ninja + +Installation on Linux is essentially the same process as for Windows except the paths and library names differ. + +Example conditions: + +- Fairly recent Linux system (Fedora, Ubuntu, etc.) +- User name of `brak` +- Home directory of `/home/brak` +- zofu installation (target) directory of `/home/brak/dev` +- Working directory is arbitrary; assume `/home/brak/git-projects` +- Recent versions of `ninja`, `python`, and GCC including `gfortran` are installed and available on `PATH` (*e.g.* under `/usr/bin`, `/usr/local/bin`) + +~~~sh +cd /home/brak/git-projects +pip3 install --user meson +[/home/brak/.local/bin?]/meson build --prefix=/home/brak/dev --libdir=lib --includedir=finclude/zofu +git clone https://github.com/acroucher/zofu.git +cd zofu +ninja -C build +ninja -C build install +~~~ + +Due to issues with the meson/ninja build process, the Fortran `.mod` files must be manually copied to the installation directory: + +~~~sh +cp build/libzofu.so.p/*.mod /home/brak/dev/finclude/zofu +~~~ + +The resulting installation tree should look like: + +~~~ +/home/brak/dev +|-- bin +| `-- zofu-driver.exe +|-- finclude +| `-- zofu +| |-- zofu.mod +| |-- zofu_kinds.mod +| |-- zofu_scan.mod +| `-- zofu_str_utils.mod +`-- lib + |-- libzofu.so + `-- pkgconfig + `-- zofu.pc +~~~ + +The current working directory should be `/home/brak/git-projects/zofu` and it should contain the subdirectory `test` + +To manually test zofu: + +~~~sh +cd test +gfortran -o test_integer_asserts check.F90 test_integer_asserts.F90 -L/home/brak/dev/lib -I/home/brak/dev/finclude/zofu -l zofu +LD_LIBRARY_PATH=/home/brak/dev/lib ./test_integer_asserts +~~~ + +The results should resemble: + +~~~yaml +failed assertions: +- {"case": 2, "reason": "value", "values": [1, -2]} +- {"case": 4, "reason": "value", "values": [1, -2]} +- {"case": 6, "reason": "value", "values": [7, -7], "index": 3, "count": 1} +- {"case": 8, "reason": "value", "values": [7, -7], "index": 3, "count": 1} +- {"case": 9, "reason": "shape", "values": [3, 4]} +- {"case": 11, "reason": "value", "values": [3, 2], "index": [3, 1], "count": 1} +- {"case": 13, "reason": "value", "values": [3, 2], "index": [3, 1], "count": 1} +- {"case": 14, "reason": "shape", "values": [[3, 2], [2, 3]]} +cases: {"count": 14, "passed": 6, "failed": 8} +assertions: {"count": 14, "passed": 6, "failed": 8} +passed: false +~~~ + +## Alternate Build and Installation Using CMake + +The typical CMake build methods for Linux and Windows are similar. +This process assumes CMake and a low-level generator such as `make` +or `ninja` are installed. If FORD is installed, documentation will +automatically be created during the build process. + +### Windows + +Assuming `cmake` is installed under `C:\Program Files\CMake\bin` and +`ninja` is installed as `C:\ProgramData\chocolatey\bin\ninja.EXE`. + +~~~bat +cd C:\Users\brak\Documents\git-projects +git clone https://github.com/acroucher/zofu.git +cd zofu +mkdir build +cd build +C:\Program Files\CMake\bin\cmake.exe -G Ninja .. +C:\ProgramData\chocolatey\bin\ninja.EXE +C:\ProgramData\chocolatey\bin\ninja.EXE test +C:\ProgramData\chocolatey\bin\ninja.EXE package +C:\ProgramData\chocolatey\bin\ninja.EXE install +~~~ + +### Linux + +Assuming `cmake` and `make` or `ninja` are installed and are accessable on `$PATH`: + +~~~sh +cd /home/brak/git-projects +git clone https://github.com/acroucher/zofu.git +cd zofu +mkdir build +cd build +cmake .. +make +make test +make package +make install +~~~ + +The preceeding example expects that `cmake` chooses `UNIX Makefiles` +as the default generator. If `ninja` will be used as the generator, +replace the last five commands with + +~~~sh +cmake.exe -G Ninja .. +ninja +ninja test +ninja package +ninja install +~~~ + +### Customizing Release Type + +Pass `-D CMAKE_BUILD_TYPE=Debug` or `-D CMAKE_BUILD_TYPE=Release` +to `cmake` to specify the release type of the build. If not specified, +`Release` is assumed. + +### Customizing Installation Paths + +Four parameters are suppled for configuring the installation path of +each component of Zofu - the `zofu-driver` executable, the library, +Fortran `/.mod` files, and HTML documentation. + +`ZOFU_BINARY_INSTALL_DIR` sets the relative path to `zofu-driver` +under the root install directory. If not specified, it typically +defaults to `./bin`. + +`ZOFU_LIBRARY_INSTALL_DIR` sets the relative path to the library +(`.a`, `.dll`, `.dylib`). If not specified, it typically defaults to +`./lib`; see https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html +for details. + +`ZOFU_FORTRAN_MODULE_INSTALL_DIR` sets the relative path to the Fortran +`.mod` files; the default is `./finstall/zofu` + +`ZOFU_DOCUMENTATION_INSTALL_DIR` sets the relative path to the HTML +documentation generated by FORD; the default is `./doc/html` + +These variables are passed to CMake using the `-D` option. For example: + +~~~sh +cmake -D CMAKE_BUILD_TYPE=Debug \ + -D ZOFU_BINARY_INSTALL_DIR:PATH=debug/bin \ + -D ZOFU_LIBRARY_INSTALL_DIR:PATH=debug/lib \ + -D ZOFU_FORTRAN_MODULE_INSTALL_DIR:PATH=debug/finclude \ + -D ZOFU_DOCUMENTATION_INSTALL_DIR:PATH=debug/html \ + .. +~~~ + +then run `ninja` or `make` as appropriate. + +## CMake Integration + +Two CMake recipes have been included to assist with using zofu: +`FindZOFU.cmake` and `ZOFUhelper.cmake`. The former is used to detect +zofu using CMake's `find_package()` facility, the latter simplifies +compiling and running unit tests. + +Place these files in a directory where CMake's `include()` command can +find them and add the following prior to the section of `CMakeLists.txt` +where tests are defined: + +~~~cmake +find_package(ZOFU) +if(ZOFU_FOUND) + include(ZOFUhelper) +endif() +~~~ + +See also `contrib/zofu/cmake/CMakeLists_zofu_framents.txt` + +For CMake to find zofu, three parameters must be passed to CMake, each +pointing to a directory: + +- `ZOFU_BINARY_PATH`, path containing `zofu-driver`. Windows also contains `libzofu.dll` +- `ZOFU_LIBRARY_PATH`, path containing `libzofu.so`, `libzofu.dll.a`, *etc.* +- `ZOFU_MODULE_PATH`, path containing `zofu.mod`, `zofu_kinds.mod`, `zofu_scan.mod`, and `zofu_str_utils.mod` + +See the installation notes given previously for OS-specific values of these paths. + +Using the Windows installation example: + +~~~bat +cd my_cmake_project_root_dir +mkdir build +cd build +cmake.exe -D ZOFU_BINARY_PATH:PATH=/Users/brak/dev/bin ^ + -D ZOFU_LIBRARY_PATH:PATH=/Users/brak/dev/lib ^ + -D ZOFU_LIBRARY_PATH:PATH=/Users/brak/dev/finclude/zofu ^ + .. +~~~ + +(note: `^` is the DOS batch file line continuation character, +equivalent to `\` in a unix shell) + +If everything is properly configured, CMake will confirm it can find +zofu with a message like `Zofu Fortran testing library is available` + +To configure unit tests with CMake and zofu, you will need + +- Source code for unit tests +- A working directory which exists and is prepopulated with necessary + files, *etc.* for running the unit tests + +`ZOFUhelper.cmake` defines the CMake function `add_zofu_unit_test()` +which defines unit test compilation targets and adds tests to the +CTest test plan. + +Example: + +~~~cmake +if(ZOFU_FOUND) + set(TEST_SOURCES + "${TEST_SRC_BASE}/zofu_demo/check.F90" + "${TEST_SRC_BASE}/zofu_demo/test_real_asserts.F90" + ) + add_zofu_unit_test( + TARGET ut_c1_zofudemo + SOURCES "${TEST_SOURCES}" + DEPENDENCIES pretest_setup + WILL_FAIL + RUN_DIRECTORY ${SOFIRE2_1C_TEST_DIR} + ) +endif() +~~~ + +The test `ut_c1_zofudemo` is defined. The executable named +`ut_c1_zofudemo` is built from the source files `check.F90` +and `test_real_asserts.F90` located in the directory +`${TEST_SRC_BASE}/zofu_demo`. The directory `${SOFIRE2_1C_TEST_DIR}` +should be created before the test is run and populated with any files +or resources needed by the unit test executable. In this example, +the test directory setup work is performed by the custom target +`pretest_setup` which is declared elsewhere in `CMakeLists.txt` +(not included). The test is set to fail if it runs for more than +90 seconds. + +This demonstration test is based on test code from the zofu source +distribution and is expected to fail. `WILL_FAIL` is set so CMake/CTest +knows the test is expected to fail and will not fail the test plan if +failure is detected. For more information, detailed documentation is +provided in `ZOFUhelper.cmake` + +See also `contrib/cmake/demo/CMakeLists_zofu_framents.txt`. diff --git a/contrib/cmake/ZOFUhelper.cmake b/contrib/cmake/ZOFUhelper.cmake new file mode 100644 index 0000000..7056f67 --- /dev/null +++ b/contrib/cmake/ZOFUhelper.cmake @@ -0,0 +1,150 @@ +# Overview: +# +# Create a test target and executable with name specified by TARGET +# composed of source files listed in SOURCES with the targets specified +# by DEPENDENCIES set as target/test dependencies. A test is added with +# the TARGET name under the configurations Debug, Release, and "" +# (the latter to allow the test to run when no configuration is +# specified). The test will be run in the working directory +# specified by RUN_DIRECTORY. TIMEOUT and WILL_FAIL may be specified to +# change allowable test runtime (30 seconds by default) or the +# expectation the test will pass. +# +# The following are set from FindZOFU.cmake: +# ZOFU_FOUND - boolean; status indicating output variables are set +# ZOFU_LIBRARY_NAME - common name of library ("zofu"; as passed to -l) +# ZOFU_LIBRARY_DIR - confirmed library path (should equal ZOFU_LIBRARY_PATH) as passed to -L +# ZOFU_MODULE - confirmed path pathname to zofu.mod +# ZOFU_MODULE_DIR - confirmed Fortran module path (should equal ZOFU_MODULE_PATH) as passed to -I +# +# Arguments: +# +# - TARGET specifies the test target and executable name. Single value, +# required. +# - SOURCES indicates a list of source files required to build the test +# application. Multi-value, required. +# - RUN_DIRECTORY specifies the directory in which the test application +# should be run; this directory should exist prior to the test run. +# Single value. Optional but recommended; if omitted, +# CMAKE_CURRENT_BINARY_DIR will be specified as the test's +# WORKING_DIRECTORY. +# - DEPENDENCIES indicates a list of target names which are required to +# be built prior to running the test application. Multivalue, may be +# empty. +# - TIMEOUT specifies the maximum expected run time of the test, in +# seconds. Single value. Optional; will default to 30 seconds if +# omitted. +# - WILL_FAIL - if present as an argument, WILL_FAIL will be set to +# TRUE for the test (i.e. test is expected to fail). No value. +# Optional; default is WILL_FAIL FALSE (test expected to pass) +# +# Notes: +# +# When compiling the test Fortran_MODULE_DIRECTORY is set to +# ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_include +# (e.g. ./build/mytest_include) to prevent collisions with .mod +# (module) files built by other targets. +# +# Note that this is decoupled from BuildTOAST.cmake to allow TOAST unit +# tests if TOAST is installed via a different method. See the limitation +# regarding expectations of variables ZOFU_LIBRARY_NAME, etc. being set +# prior to using this function. +# +# Limitations: +# +# - This function depends on ZOFU_MODULE_DIR, ZOFU_LIBRARY_DIR, and +# ZOFU_LIBRARY_NAME which it inherits from current context. See +# FindZOFU.cmake for details. +# - No provision is made for specifying library or include directories +# or specific libraries to link the test application against; +# manually override settings made here by using +# `set_target_properties(${test_name} ...)` after calling this +# function +# - Unit tests are expected be single commands with no arguments +# (bare executable name) +function(add_zofu_unit_test) + # Define the supported set of keywords + set(prefix ARG) + set(noValues WILL_FAIL) + set(singleValues TARGET RUN_DIRECTORY TIMEOUT) + set(multiValues SOURCES DEPENDENCIES) + + cmake_parse_arguments( + ${prefix} + "${noValues}" "${singleValues}" "${multiValues}" + ${ARGN} + ) + + # # Log details for each supported keyword + # message("Option summary:") + # foreach(arg IN LISTS noValues) + # if(${prefix}_${arg}) + # message(" ${arg} enabled") + # else() + # message(" ${arg} disabled") + # endif() + # endforeach() + + # foreach(arg IN LISTS singleValues multiValues) + # # Single argument values will print as a string + # # Multiple argument values will print as a list + # message(" ${arg} = ${${prefix}_${arg}}") + # endforeach() + + if(ZOFU_FOUND) + message(STATUS "Adding unit test ${ARG_TARGET}") + set(TEST_FORTRAN_MODULE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_include") + file(MAKE_DIRECTORY "${TEST_FORTRAN_MODULE_DIR}") + # Note: It's not clear if more than just toast.mod is necessary to include here + add_executable(${ARG_TARGET} ${ARG_SOURCES}) # ${ZOFU_MODULE}) + add_coverage(${ARG_TARGET}) + set_target_properties( + ${ARG_TARGET} + PROPERTIES + OUTPUT_NAME ${ARG_TARGET} + Fortran_MODULE_DIRECTORY ${TEST_FORTRAN_MODULE_DIR} + INCLUDE_DIRECTORIES ${ZOFU_MODULE_DIR} + LINK_DIRECTORIES ${ZOFU_LIBRARY_DIR} + LINK_LIBRARIES ${ZOFU_LIBRARY_NAME} + ) + + add_test(NAME ${ARG_TARGET} + COMMAND $ + CONFIGURATIONS Debug Release "" + ) + + if("${ARG_TIMEOUT}" GREATER 1) + # Set timeout if specified + set_tests_properties(${ARG_TARGET} PROPERTIES + TIMEOUT "${ARG_TIMEOUT}") + else() + # Set default timeout of 30 seconds + set_tests_properties(${ARG_TARGET} PROPERTIES TIMEOUT 30) + endif() + + # This probably checks if the directory exists rather than if + # ARG_RUN_DIRECTORY could represent a directory :/ + # if(IS_DIRECTORY "${ARG_RUN_DIRECTORY}") + if("${ARG_RUN_DIRECTORY}" STREQUAL "") + # Default to current binary directory if ARG_RUN_DIRECTORY + # looks undefined + set_tests_properties(${ARG_TARGET} PROPERTIES + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + else() + # Set test WORKING_DIRECTORY to ARG_RUN_DIRECTORY + set_tests_properties(${ARG_TARGET} PROPERTIES + WORKING_DIRECTORY "${ARG_RUN_DIRECTORY}") + endif() + + if(${ARG_WILL_FAIL}) + set_tests_properties(${ARG_TARGET} PROPERTIES WILL_FAIL TRUE) + endif() + + foreach(dep IN LISTS ${ARG_DEPENDENCIES}) + add_dependencies(${ARG_TARGET} "${dep}") + endforeach() + else() + message(WARNING "ZOFU is not available; add_zofu_unit_test(${ARG_TARGET} ...) is ignored") + endif() +endfunction() +# __END__ \ No newline at end of file diff --git a/contrib/cmake/demo/CMakeLists_zofu_framents.txt b/contrib/cmake/demo/CMakeLists_zofu_framents.txt new file mode 100644 index 0000000..2d48085 --- /dev/null +++ b/contrib/cmake/demo/CMakeLists_zofu_framents.txt @@ -0,0 +1,26 @@ +############################################################################### +## Dependencies and CMake Modules ############################################ +############################################################################### + +find_package(ZOFU) +if(ZOFU_FOUND) + include(ZOFUhelper) +endif() + +############################################################################### +## Testing #################################################################### +############################################################################### + +if(ZOFU_FOUND) + set(TEST_SOURCES + "${TEST_SRC_BASE}/zofu_demo/check.F90" + "${TEST_SRC_BASE}/zofu_demo/test_real_asserts.F90" + ) + add_zofu_unit_test( + TARGET ut_c1_zofudemo + SOURCES "${TEST_SOURCES}" + DEPENDENCIES pretest_setup + WILL_FAIL + RUN_DIRECTORY ${SOFIRE2_1C_TEST_DIR} + ) +endif() diff --git a/contrib/cmake/demo/check.F90 b/contrib/cmake/demo/check.F90 new file mode 100644 index 0000000..16a232a --- /dev/null +++ b/contrib/cmake/demo/check.F90 @@ -0,0 +1,36 @@ +module check_module + + !! Utilities for checking numbers of cases, assertions in tests. + + use zofu + implicit none + +contains + +!------------------------------------------------------------------------ + + subroutine check(test, last_cases, last_assertions, & + cases, assertions, passed, failed, OK) + !! Checks total numbers of cases, assertions, passes and fails + !! against expected values, given the numbers expected for the + !! last case run. + + class(unit_test_type), intent(in) :: test + type(test_counter_type), intent(in out) :: last_cases, last_assertions + integer, intent(in) :: cases, assertions, passed, failed + logical, intent(in out) :: OK + + OK = ( OK .and. & + (test%cases%count == last_cases%count + cases) .and. & + (test%assertions%count == last_assertions%count + assertions) .and. & + (test%assertions%passed == last_assertions%passed + passed) .and. & + (test%assertions%failed == last_assertions%failed + failed)) + + last_cases = test%cases + last_assertions = test%assertions + + end subroutine check + +!------------------------------------------------------------------------ + +end module check_module diff --git a/contrib/cmake/demo/test_real_asserts.F90 b/contrib/cmake/demo/test_real_asserts.F90 new file mode 100644 index 0000000..9caa674 --- /dev/null +++ b/contrib/cmake/demo/test_real_asserts.F90 @@ -0,0 +1,131 @@ +program test_real_asserts + + ! Test Zofu real asserts. + + use zofu + use check_module + + implicit none + + type(unit_test_type) :: test + type(test_counter_type) :: last_cases, last_assertions + logical :: OK + + continue + + call test%init() + call last_cases%init() + call last_assertions%init() + OK = .true. + + call test%run(test_real_pass) + call check(test, last_cases, last_assertions, 1, 1, 1, 0, OK) + + call test%run(test_real_fail) + call check(test, last_cases, last_assertions, 1, 1, 0, 1, OK) + + call test%run(test_real_both_zero) + call check(test, last_cases, last_assertions, 1, 1, 1, 0, OK) + + call test%run(test_real_large_difference_fail) + call check(test, last_cases, last_assertions, 1, 1, 0, 1, OK) + + call test%run(test_real_small_difference_pass) + call check(test, last_cases, last_assertions, 1, 1, 1, 0, OK) + + call test%run(test_real_small_difference_fail) + call check(test, last_cases, last_assertions, 1, 1, 0, 1, OK) + + call test%run(test_real_small_difference_tol_pass) + call check(test, last_cases, last_assertions, 1, 1, 1, 0, OK) + + call test%run(test_real_array_1_pass) + call check(test, last_cases, last_assertions, 1, 1, 1, 0, OK) + + call test%run(test_real_array_1_fail) + call check(test, last_cases, last_assertions, 1, 1, 0, 1, OK) + + call test%run(test_real_array_1_fail_different_sizes) + call check(test, last_cases, last_assertions, 1, 1, 0, 1, OK) + + call test%run(test_real_array_2_pass) + call check(test, last_cases, last_assertions, 1, 1, 1, 0, OK) + + call test%run(test_real_array_2_fail) + call check(test, last_cases, last_assertions, 1, 1, 0, 1, OK) + + call test%summary() + + if (test%assertions%failed > 0) then + stop 2 + else if (.not. OK) then + stop 1 + end if + +contains + + subroutine test_real_pass(test) + class(unit_test_type), intent(in out) :: test + call test%assert(2.7, 2.7) + end subroutine test_real_pass + + subroutine test_real_fail(test) + class(unit_test_type), intent(in out) :: test + call test%assert(2.7, 2.6) + end subroutine test_real_fail + + subroutine test_real_both_zero(test) + class(unit_test_type), intent(in out) :: test + call test%assert(0.0, 0.0) + end subroutine test_real_both_zero + + subroutine test_real_large_difference_fail(test) + class(unit_test_type), intent(in out) :: test + call test%assert(1.e-11, -2.e16) + end subroutine test_real_large_difference_fail + + subroutine test_real_small_difference_pass(test) + class(unit_test_type), intent(in out) :: test + call test%assert(10., 10. + 1.e-6) + end subroutine test_real_small_difference_pass + + subroutine test_real_small_difference_fail(test) + class(unit_test_type), intent(in out) :: test + call test%assert(-10., -10. + 1.e-4) + end subroutine test_real_small_difference_fail + + subroutine test_real_small_difference_tol_pass(test) + class(unit_test_type), intent(in out) :: test + call test%assert(-10., -10. + 1.e-4, tol = 5.e-4) + end subroutine test_real_small_difference_tol_pass + + subroutine test_real_array_1_pass(test) + class(unit_test_type), intent(in out) :: test + call test%assert([2.718, -3.142], [2.718, -3.142]) + end subroutine test_real_array_1_pass + + subroutine test_real_array_1_fail(test) + class(unit_test_type), intent(in out) :: test + call test%assert([2.718, -3.142, 1.618], [2.718, -3.141, 1.618]) + end subroutine test_real_array_1_fail + + subroutine test_real_array_1_fail_different_sizes(test) + class(unit_test_type), intent(in out) :: test + call test%assert([2.718, -3.142], [2.718, -3.142, 1.01]) + end subroutine test_real_array_1_fail_different_sizes + + subroutine test_real_array_2_pass(test) + class(unit_test_type), intent(in out) :: test + call test%assert( & + reshape([2.718, -3.142, 1.618, 0.0], [2, 2]), & + reshape([2.718, -3.142, 1.618, 0.0], [2, 2])) + end subroutine test_real_array_2_pass + + subroutine test_real_array_2_fail(test) + class(unit_test_type), intent(in out) :: test + call test%assert( & + reshape([2.718, -3.142, 1.618, 0., 1., -0.5], [2, 3]), & + reshape([2.718, -3.141, 1.618, 1., 1., -0.5], [2, 3])) + end subroutine test_real_array_2_fail + +end program test_real_asserts diff --git a/doc.md.in b/doc.md.in new file mode 100644 index 0000000..f91026a --- /dev/null +++ b/doc.md.in @@ -0,0 +1,14 @@ +project: Zofu +version: @CPACK_PACKAGE_VERSION@ +author: Dr Adrian Croucher +author_description: Senior Research Fellow, Department of Engineering Science, University of Auckland +email: a.croucher@auckland.ac.nz +website: https://unidirectory.auckland.ac.nz/profile/a-croucher +github: https://github.com/acroucher +project_github: https://github.com/acroucher/zofu +project_dir: @CMAKE_CURRENT_SOURCE_DIR@ +src_dir: @ZOFU_SOURCE_DIR@ +output_dir: @FORD_BUILD_DIRECTORY@ +page_dir: @FORD_BUILD_DIRECTORY@ +extensions: F90 +Zofu is Object-oriented Fortran Unit-testing. \ No newline at end of file diff --git a/meson.build b/meson.build index bea2abf..49c0bc9 100644 --- a/meson.build +++ b/meson.build @@ -54,12 +54,20 @@ if includedir != '' foreach m: zofu_modules zofu_objs += [m + '.o'] endforeach + # NB this is a temporary measure until Meson gets specific # functionality for installing Fortran modules: - install_subdir(join_paths('build', 'zofu@sha'), + if meson.version() < '0.55' + mod_dir = 'zofu@sha' + else + mod_dir = zofu.full_path().split('/').get(-1) + '.p' + endif + mod_path = join_paths(meson.current_build_dir(), mod_dir) + install_subdir(mod_path, install_dir: module_install_dir, strip_directory: true, exclude_files: zofu_objs) + pkg = import('pkgconfig') pkg.generate(zofu, description: 'Zofu is Object-oriented Fortran Unit-testing') diff --git a/test/test_zofu_scan.F90 b/test/test_zofu_scan.F90 index 31f9fef..ce7fe73 100644 --- a/test/test_zofu_scan.F90 +++ b/test/test_zofu_scan.F90 @@ -12,7 +12,7 @@ program test_zofu_scan call test%run(test_subroutine_roles) call test%run(test_modules) - + call test%summary() if (test%failed) stop 1 @@ -72,6 +72,7 @@ subroutine module_test(test, filename, module_name, num_subroutines, & character(len = 512) :: data_path call get_environment_variable('ZOFU_TEST_DATA_PATH', data_path) + test_module%name = "" ierr = test_module%init(trim(data_path) // filename) call test%assert(ierr /= 0, err, filename // " error") call test%assert(test_module%name, module_name, filename // " name") @@ -79,7 +80,7 @@ subroutine module_test(test, filename, module_name, num_subroutines, & filename // " subroutine count") call test%assert(test_module%setup, setup, filename // " setup") call test%assert(test_module%teardown, teardown, filename // " teardown") - + end subroutine module_test !------------------------------------------------------------------------ @@ -95,9 +96,9 @@ subroutine test_modules(test) .true., .true., .false.) call module_test(test, "missing.F90", "", 0, & .false., .false., .true.) - + end subroutine test_modules - + !------------------------------------------------------------------------ end program test_zofu_scan