forked from ibanos90/obs2ioda
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce Fortran Test Framework and CTest Integration
This PR introduces a Fortran testing framework and CTest integration to improve the testing and validation process for the library. Key updates include an assertion module, unit tests for core features, and CMake functions for streamlined test management.
- Loading branch information
Showing
12 changed files
with
413 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Set Fortran compiler flags specific to the GNU Compiler | ||
# -ffree-line-length-none: Remove the limit on the length of lines in the source file | ||
# -mcmodel=medium: Allow for larger datasets in memory | ||
set(FORTRAN_COMPILER_GNU_FLAGS | ||
$<$<COMPILE_LANGUAGE:Fortran>:-ffree-line-length-none -mcmodel=medium> | ||
) | ||
|
||
# Set Debugging Fortran compiler flags specific to the GNU Compiler | ||
# -fbacktrace: Provide a backtrace when an error occurs | ||
# -ffpe-trap=invalid,zero,overflow: Trap floating point exceptions (invalid calculation, divide by zero, overflow) | ||
# -fcheck=all: Execute all types of runtime checks | ||
# -g: Produce debugging information | ||
set(FORTRAN_COMPILER_GNU_DEBUG_FLAGS | ||
$<$<COMPILE_LANGUAGE:Fortran>:-g -fbacktrace -ffpe-trap=invalid,zero,overflow -fcheck=all> | ||
) | ||
|
||
# Set Fortran compiler flags for the Intel Compiler | ||
# -mcmodel=medium: Allow for larger datasets in memory | ||
set(FORTRAN_COMPILER_INTEL_FLAGS | ||
$<$<COMPILE_LANGUAGE:Fortran>:-mcmodel=medium> | ||
) | ||
|
||
# Set Debugging Fortran compiler flags for the Intel Compiler | ||
# -check uninit: Checks uninitialized variables | ||
# -ftrapuv: Enable trapping of uninitialized variables | ||
# -g: Enable production of debug information | ||
# -traceback: Give symbolic traceback on errors | ||
# -fpe0: Stop execution when a floating-point exception occurs | ||
set(FORTRAN_COMPILER_INTEL_DEBUG_FLAGS | ||
$<$<COMPILE_LANGUAGE:Fortran>:-check uninit -ftrapuv -g -traceback -fpe0> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
include("${CMAKE_SOURCE_DIR}/cmake/Obs2Ioda_CompilerFlags.cmake") | ||
|
||
# This CMake function, `obs2ioda_fortran_target`, configures Fortran targets for obs2ioda. | ||
# | ||
# Its arguments are: | ||
# - target: the name of the target to configure | ||
# - target_main: the main source file for the executable | ||
# | ||
# The function sets the following properties for the target: | ||
# - The directory for Fortran module files | ||
# - The include directories for the target (both build and install interfaces) | ||
# - The install RPATH to enable finding shared libraries at runtime | ||
# - Fortran format as FREE | ||
# - Compiler-specific options and flags, depending on whether the GNU Fortran or Intel Fortran compiler is used, | ||
# and whether the build type is Debug or not | ||
# | ||
# The function also links the public libraries to the target and creates an executable `obs2ioda_${target}` linked | ||
# to the original target. | ||
function(obs2ioda_fortran_target target target_main) | ||
set_target_properties(${target} PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/${OBS2IODA_MODULE_DIR}) | ||
target_include_directories(${target} INTERFACE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/${OBS2IODA_MODULE_DIR}> | ||
$<INSTALL_INTERFACE:${OBS2IODA_MODULE_DIR}>) | ||
#Relocatable, portable, runtime dynamic linking | ||
set_target_properties(${target} PROPERTIES INSTALL_RPATH "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") | ||
# Global Fortran configuration | ||
set(public_link_libraries_name ${target}_PUBLIC_LINK_LIBRARIES) | ||
set(public_link_libraries ${${public_link_libraries_name}}) | ||
set_target_properties(${target} PROPERTIES Fortran_FORMAT FREE) | ||
|
||
# Compiler-specific options and flags | ||
set(OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE "") | ||
if (CMAKE_Fortran_COMPILER_ID MATCHES GNU) | ||
list(APPEND OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE | ||
${FORTRAN_COMPILER_GNU_FLAGS} | ||
) | ||
if (CMAKE_BUILD_TYPE MATCHES Debug) | ||
list(APPEND OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE | ||
${FORTRAN_COMPILER_GNU_DEBUG_FLAGS} | ||
) | ||
endif () | ||
elseif (CMAKE_Fortran_COMPILER_ID MATCHES Intel) | ||
list(APPEND OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE | ||
${FORTRAN_COMPILER_INTEL_FLAGS} | ||
) | ||
if (CMAKE_BUILD_TYPE MATCHES Debug) | ||
list(APPEND OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE | ||
${FORTRAN_COMPILER_INTEL_DEBUG_FLAGS} | ||
) | ||
endif () | ||
endif () | ||
target_compile_options(${target} PRIVATE ${OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE}) | ||
target_link_libraries(${target} PUBLIC ${public_link_libraries}) | ||
add_executable(obs2ioda_${target} ${target_main}) | ||
target_link_libraries(obs2ioda_${target} PUBLIC ${target}) | ||
|
||
endfunction() | ||
|
||
# Function: add_memcheck_ctest | ||
# Adds a memory check test for a given target using Valgrind. | ||
# | ||
# Arguments: | ||
# - target (string): The name of the target to check for memory issues. | ||
# | ||
# Behavior: | ||
# - If Valgrind is found, it adds a CTest named `<target>_memcheck` that runs the target | ||
# with Valgrind's memory checking options (`--leak-check=full`, `--error-exitcode=1`). | ||
# - If Valgrind is not found, it outputs a status message and does not add the memory check. | ||
# | ||
# Example Usage: | ||
# add_memcheck_ctest(my_target) | ||
# | ||
# Notes: | ||
# - Ensure Valgrind is installed and accessible in the system's PATH for this function to work. | ||
function(add_memcheck_ctest target) | ||
find_program(VALGRIND "valgrind") | ||
if (VALGRIND) | ||
message(STATUS "Valgrind found: ${VALGRIND}") | ||
message(STATUS "Adding memory check for test: ${target}") | ||
set(VALGRIND_COMMAND valgrind --leak-check=full --error-exitcode=1 --undef-value-errors=no) | ||
add_test(NAME ${target}_memcheck | ||
COMMAND ${VALGRIND_COMMAND} $<TARGET_FILE:${target}>) | ||
else () | ||
message(STATUS "Valgrind not found") | ||
message(STATUS "Memory check for test: ${target} will not be added") | ||
endif () | ||
endfunction() | ||
|
||
# Function: add_fortran_ctest | ||
# Creates and registers a CTest for a Fortran test executable, handling mixed Fortran and C sources. | ||
# | ||
# Arguments: | ||
# - test_name (string): The name of the test. | ||
# - test_sources (list): List of source files for the test, including Fortran and optional C sources. | ||
# - library_deps (list): List of library dependencies to link with the test executable. | ||
# | ||
# Behavior: | ||
# - Identifies C source files (`*.c`) from the `test_sources` list and compiles them into shared libraries. | ||
# - Updates the `library_deps` list to include the created shared libraries for C sources. | ||
# - Creates a Fortran executable target named `Test_<test_name>` using the remaining Fortran sources. | ||
# - Links the test executable with the specified libraries and any generated C libraries. | ||
# - Registers the test with CTest, with the executable's path resolved to `${CMAKE_BINARY_DIR}/bin`. | ||
function(add_fortran_ctest test_name test_sources library_deps) | ||
foreach(test_source ${test_sources}) | ||
if (${test_source} MATCHES ".*\\.c$") | ||
get_filename_component(test_source_name ${test_source} NAME_WE) | ||
message(STATUS "Adding C test: ${test_source_name}") | ||
add_library("c_${test_source_name}" SHARED ${test_source}) | ||
list(APPEND library_deps "c_${test_source_name}") | ||
list(REMOVE_ITEM test_sources ${test_source}) | ||
endif () | ||
endforeach () | ||
add_executable("Test_${test_name}" ${test_sources}) | ||
target_link_libraries("Test_${test_name}" ${library_deps}) | ||
add_test(NAME ${test_name} | ||
COMMAND ${CMAKE_BINARY_DIR}/bin/Test_${test_name}) | ||
endfunction() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
module fortran_test_framework_mod | ||
implicit none | ||
|
||
public :: assertEqual | ||
|
||
interface assertEqual | ||
module procedure assertEqual_integer | ||
module procedure assertEqual_string | ||
module procedure assertEqual_logical | ||
end interface assertEqual | ||
|
||
abstract interface | ||
subroutine assert_interface(condition, message, status) | ||
logical, intent(in) :: condition | ||
character(len = *), intent(in) :: message | ||
integer, intent(out) :: status | ||
end subroutine assert_interface | ||
end interface | ||
|
||
contains | ||
|
||
! Subroutine: assert | ||
! Performs a basic assertion by evaluating a condition and printing a message. | ||
! Exits the program with an error status if the condition is not met. | ||
! | ||
! Arguments: | ||
! - condition (logical, in): The condition to evaluate. | ||
! - message (character, in): Message to print indicating the assertion result. | ||
! - status (integer, out): Status code (0 for success, 1 for failure). | ||
subroutine assert(condition, message, status) | ||
implicit none | ||
logical, intent(in) :: condition | ||
character(len = *), intent(in) :: message | ||
integer, intent(out) :: status | ||
if (.not. condition) then | ||
status = 1 | ||
write(*, '(A)') "Failed: " // message | ||
call exit(1) | ||
else | ||
status = 0 | ||
write(*, '(A)') "Success: " // message | ||
end if | ||
end subroutine assert | ||
|
||
! Subroutine: assertEqual_integer | ||
! Asserts that two integer values are equal, using a custom or default assertion handler. | ||
! | ||
! Arguments: | ||
! - expected (integer, in): The expected value. | ||
! - actual (integer, in): The actual value. | ||
! - status (integer, out): Status code (0 for success, 1 for failure). | ||
! - assert_procedure (procedure, optional): Custom procedure to handle assertion logic. | ||
subroutine assertEqual_integer(expected, actual, status, assert_procedure) | ||
implicit none | ||
integer, intent(in) :: expected, actual | ||
integer, intent(out) :: status | ||
procedure(assert_interface), optional :: assert_procedure | ||
procedure(assert_interface), pointer :: assert_handler => assert | ||
|
||
if (present(assert_procedure)) then | ||
assert_handler => assert_procedure | ||
end if | ||
|
||
call assert_handler(& | ||
expected == actual, "expected=" // trim(adjustl(itoa(expected))) // & | ||
" actual=" // trim(adjustl(itoa(actual))), & | ||
status & | ||
) | ||
end subroutine assertEqual_integer | ||
|
||
! Subroutine: assertEqual_string | ||
! Asserts that two strings are equal, using a custom or default assertion handler. | ||
! | ||
! Arguments: | ||
! - expected (character, in): The expected string value. | ||
! - actual (character, in): The actual string value. | ||
! - status (integer, out): Status code (0 for success, 1 for failure). | ||
! - assert_procedure (procedure, optional): Custom procedure to handle assertion logic. | ||
subroutine assertEqual_string(expected, actual, status, assert_procedure) | ||
implicit none | ||
character(len = *), intent(in) :: expected, actual | ||
integer, intent(out) :: status | ||
procedure(assert_interface), optional :: assert_procedure | ||
procedure(assert_interface), pointer :: assert_handler => assert | ||
|
||
if (present(assert_procedure)) then | ||
assert_handler => assert_procedure | ||
end if | ||
|
||
call assert_handler(& | ||
expected == actual, "expected='" // trim(expected) // "' actual='" // & | ||
trim(actual) // "'", & | ||
status & | ||
) | ||
end subroutine assertEqual_string | ||
|
||
! Subroutine: assertEqual_logical | ||
! Asserts that two logical values are equivalent, using a custom or default assertion handler. | ||
! | ||
! Arguments: | ||
! - expected (logical, in): The expected logical value. | ||
! - actual (logical, in): The actual logical value. | ||
! - status (integer, out): Status code (0 for success, 1 for failure). | ||
! - assert_procedure (procedure, optional): Custom procedure to handle assertion logic. | ||
subroutine assertEqual_logical(expected, actual, status, assert_procedure) | ||
implicit none | ||
logical, intent(in) :: expected, actual | ||
integer, intent(out) :: status | ||
procedure(assert_interface), optional :: assert_procedure | ||
procedure(assert_interface), pointer :: assert_handler => assert | ||
|
||
if (present(assert_procedure)) then | ||
assert_handler => assert_procedure | ||
end if | ||
|
||
call assert_handler(& | ||
expected .eqv. actual, "expected=" // trim(logical_to_string(expected)) // & | ||
" actual=" // trim(logical_to_string(actual)), & | ||
status & | ||
) | ||
end subroutine assertEqual_logical | ||
|
||
! Function: itoa | ||
! Converts an integer to a string representation. | ||
! | ||
! Arguments: | ||
! - value (integer, in): The integer to convert. | ||
! | ||
! Returns: | ||
! - str (character): String representation of the integer. | ||
function itoa(value) result(str) | ||
implicit none | ||
integer, intent(in) :: value | ||
character(len = 32) :: str | ||
write(str, '(I0)') value | ||
end function itoa | ||
|
||
! Function: logical_to_string | ||
! Converts a logical value to a string representation (".true." or ".false."). | ||
! | ||
! Arguments: | ||
! - value (logical, in): The logical value to convert. | ||
! | ||
! Returns: | ||
! - str (character): String representation of the logical value. | ||
function logical_to_string(value) result(str) | ||
implicit none | ||
logical, intent(in) :: value | ||
character(len = 6) :: str | ||
|
||
if (value) then | ||
str = ".true." | ||
else | ||
str = ".false." | ||
end if | ||
end function logical_to_string | ||
|
||
end module fortran_test_framework_mod |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/fortran) |
Oops, something went wrong.