diff --git a/CMakeLists.txt b/CMakeLists.txt index 9345eda..2a91097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ # Check CMake version -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) -include("${CMAKE_SOURCE_DIR}/cmake/Functions/Obs2Ioda_Functions.cmake") +include("${CMAKE_SOURCE_DIR}/cmake/Obs2Ioda_Functions.cmake") # Define the project -project(obs2ioda LANGUAGES Fortran) +project(obs2ioda LANGUAGES Fortran C CXX) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Fortran module output directory for build interface set(OBS2IODA_MODULE_DIR ${PROJECT_NAME}/module/${CMAKE_Fortran_COMPILER_ID}/${CMAKE_Fortran_COMPILER_VERSION}) @@ -15,9 +15,22 @@ install(DIRECTORY ${CMAKE_BINARY_DIR}/${OBS2IODA_MODULE_DIR}/ DESTINATION ${CMAK # Set the Fortran compiler and flags set(NCEP_BUFR_LIB CACHE STRING "" ) +# Set to ON to build tests. Default is OFF. If testing is enabled, +set(BUILD_TESTS CACHE BOOL OFF) # Find required packages -find_package(NetCDF REQUIRED COMPONENTS Fortran C) +find_package(NetCDF REQUIRED COMPONENTS Fortran CXX C) add_subdirectory("${CMAKE_SOURCE_DIR}/obs2ioda-v2") +if (BUILD_TESTS) + enable_testing() + INCLUDE(FetchContent) + FETCHCONTENT_DECLARE( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip + ) + FETCHCONTENT_MAKEAVAILABLE(googletest) + add_subdirectory("${CMAKE_SOURCE_DIR}/test") +endif () + diff --git a/README.md b/README.md index 3c1e3e1..374ba32 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ File types that obs2ioda can handle: * HS_H08_YYYYMMDD_HH00_BNN_FLDK_R20_S0210.DAT * OR_ABI-L1b-Rad nc files -## See [obs2ioda-v1/README.md](https://github.com/jamiebresch/obs2ioda/blob/main/obs2ioda-v1/README.md) for compilation and usage. +## See [obs2ioda-v1/README.md](https://github.com/NCAR/obs2ioda/blob/main/obs2ioda-v1/README.md) for compilation and usage. -## See [obs2ioda-v2/README.md](https://github.com/jamiebresch/obs2ioda/blob/main/obs2ioda-v2/README.md) for compilation and usage. +## See [obs2ioda-v2/README.md](https://github.com/NCAR/obs2ioda/blob/main/obs2ioda-v2/README.md) for compilation and usage. -## See [goes_abi/README.md](https://github.com/jamiebresch/obs2ioda/blob/main/goes_abi/README.md) for compilation and usage. +## See [goes_abi/README.md](https://github.com/NCAR/obs2ioda/blob/main/goes_abi/README.md) for compilation and usage. ## Observation data sources: * https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod/ diff --git a/cmake/Modules/FindNetCDF.cmake b/cmake/FindNetCDF.cmake similarity index 100% rename from cmake/Modules/FindNetCDF.cmake rename to cmake/FindNetCDF.cmake diff --git a/cmake/Functions/Obs2Ioda_Functions.cmake b/cmake/Functions/Obs2Ioda_Functions.cmake deleted file mode 100644 index 189561c..0000000 --- a/cmake/Functions/Obs2Ioda_Functions.cmake +++ /dev/null @@ -1,30 +0,0 @@ -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 $ - $) - #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 - $<$:-mcmodel=medium> - ) - if (CMAKE_Fortran_COMPILER_ID MATCHES GNU) - list(APPEND OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE - $<$:-cpp -ffree-line-length-none> - ) - elseif (CMAKE_Fortran_COMPILER_ID MATCHES Intel) - list(APPEND OBS2IODA_FORTRAN_TARGET_COMPILE_OPTIONS_PRIVATE - $<$:-fpp> - ) - 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() diff --git a/cmake/Obs2Ioda_CompilerFlags.cmake b/cmake/Obs2Ioda_CompilerFlags.cmake new file mode 100644 index 0000000..d436386 --- /dev/null +++ b/cmake/Obs2Ioda_CompilerFlags.cmake @@ -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 + $<$:-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 + $<$:-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 + $<$:-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 + $<$:-check uninit -ftrapuv -g -traceback -fpe0> +) \ No newline at end of file diff --git a/cmake/Obs2Ioda_Functions.cmake b/cmake/Obs2Ioda_Functions.cmake new file mode 100644 index 0000000..b7ab837 --- /dev/null +++ b/cmake/Obs2Ioda_Functions.cmake @@ -0,0 +1,108 @@ +include(${CMAKE_SOURCE_DIR}/cmake/Obs2Ioda_CompilerFlags.cmake) + +function(obs2ioda_fortran_library target) + set_target_properties(${target} PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/${OBS2IODA_MODULE_DIR}) + target_include_directories(${target} INTERFACE $ + $) + #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 + $<$:-mcmodel=medium> + ) + 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}) +endfunction() + +function(obs2ioda_fortran_executable target) + set_target_properties(${target} PROPERTIES INSTALL_RPATH "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") + set(public_link_libraries_name ${target}_PUBLIC_LINK_LIBRARIES) + set(public_link_libraries ${${public_link_libraries_name}}) + target_link_libraries(${target} PUBLIC ${public_link_libraries}) +endfunction() + +function(obs2ioda_cxx_target target) + set_target_properties(${target} PROPERTIES INSTALL_RPATH "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") + set(public_link_libraries_name ${target}_PUBLIC_LINK_LIBRARIES) + set(public_link_libraries ${${public_link_libraries_name}}) + + target_link_libraries(${target} PUBLIC ${public_link_libraries}) +endfunction() + +# Function: add_memcheck_ctest +# +# Description: +# This function adds a memory check command as a test case in CTest +# for the given target, using the Valgrind tool. If the Valgrind +# tool is not found, it will print a message specifying that the +# memory check could not be added for the given target. +# +# The Valgrind command executed as part of the test includes options +# for full leak checking (--leak-check=full), exiting with error status +# if any leaks are detected (--error-exitcode=1), and keeping track of +# the origin of uninitialized values (--track-origins=yes). +# +# Arguments: +# target: The name of the target for which the memory check will be added. +# +# Usage: +# add_memcheck_ctest(my_target) +# +# Example: +# add_memcheck_ctest(my_executable) +# +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} $) + else () + message(STATUS "Valgrind not found") + message(STATUS "Memory check for test: ${target} will not be added") + endif () +endfunction() + +function(add_fortran_ctest test_name test_sources library_deps) + add_executable("Test_${test_name}" ${test_sources}) + message(STATUS "Adding test: ${test_name} with sources: ${test_sources} and dependencies: ${library_deps}") + target_link_libraries("Test_${test_name}" ${library_deps}) + add_test(NAME ${test_name} + COMMAND ${CMAKE_BINARY_DIR}/bin/Test_${test_name}) +endfunction() + +function(add_cxx_ctest name sources include_dirs library_deps) + add_executable(${name} ${sources}) + target_include_directories(${name} PUBLIC ${include_dirs}) + target_link_libraries(${name} PUBLIC ${library_deps}) + add_test( + NAME ${name} + COMMAND ${name} --gtest_filter=* + ) +endfunction() \ No newline at end of file diff --git a/obs2ioda-v2/CMakeLists.txt b/obs2ioda-v2/CMakeLists.txt index 5ead20d..4f8f901 100644 --- a/obs2ioda-v2/CMakeLists.txt +++ b/obs2ioda-v2/CMakeLists.txt @@ -2,6 +2,12 @@ # Set include directories include_directories(${NetCDF_INCLUDE_DIRS} ) +set(netcdf_cxx_SOURCES + netcdf_file.cc + netcdf_utils.cc +) +list(TRANSFORM netcdf_cxx_SOURCES PREPEND "src/cxx/") + # Define sources set(v2_SOURCES define_mod.f90 @@ -14,15 +20,32 @@ set(v2_SOURCES netcdf_mod.f90 prepbufr_mod.f90 radiance_mod.f90 - ufo_variables_mod.F90 + ufo_variables_mod.f90 utils_mod.f90 + f_c_string_t_mod.f90 + f_c_string_1D_t_mod.f90 + netcdf_cxx_i_mod.f90 + netcdf_cxx_mod.f90 ) -list(TRANSFORM v2_SOURCES PREPEND "src/") +list(TRANSFORM v2_SOURCES PREPEND "src/fortran/") set(v2_MAIN_SOURCE main.f90 ) -list(TRANSFORM v2_MAIN_SOURCE PREPEND "src/") -set(v2_PUBLIC_LINK_LIBRARIES "${NetCDF_LIBRARIES}" "${NCEP_BUFR_LIB}") +list(TRANSFORM v2_MAIN_SOURCE PREPEND "src/fortran/") +set(fortran_test_framework_SOURCES + fortran_test_framework_mod.f90 +) +list(TRANSFORM fortran_test_framework_SOURCES PREPEND "src/test/") +add_library(netcdf_cxx SHARED ${netcdf_cxx_SOURCES}) +target_include_directories(netcdf_cxx PUBLIC ${NetCDF_INCLUDE_DIRS}) +set(netcdf_cxx_PUBLIC_LINK_LIBRARIES NetCDF::NetCDF_CXX NetCDF::NetCDF_C) +obs2ioda_cxx_target(netcdf_cxx ${netcdf_cxx_SOURCES}) +set(v2_PUBLIC_LINK_LIBRARIES "${NCEP_BUFR_LIB}" NetCDF::NetCDF_CXX NetCDF::NetCDF_C NetCDF::NetCDF_Fortran netcdf_cxx) add_library(v2 SHARED ${v2_SOURCES}) -obs2ioda_fortran_target(v2 ${v2_MAIN_SOURCE}) - +target_include_directories(v2 PUBLIC ${NetCDF_INCLUDE_DIRS}) +obs2ioda_fortran_library(v2) +add_executable(obs2ioda_v2 ${v2_MAIN_SOURCE}) +set(obs2ioda_v2_PUBLIC_LINK_LIBRARIES v2) +obs2ioda_fortran_executable(obs2ioda_v2) +add_library(fortran_test_framework ${fortran_test_framework_SOURCES}) +obs2ioda_fortran_library(fortran_test_framework) diff --git a/obs2ioda-v2/README.md b/obs2ioda-v2/README.md index 25c526f..a73f117 100644 --- a/obs2ioda-v2/README.md +++ b/obs2ioda-v2/README.md @@ -3,46 +3,62 @@ obs2ioda-v2 utilizes CMake as its primary build system. Follow the steps below to build the project: ### Prerequisites -Please make sure the following libraries are installed: -- NetCDF -- NCEP BUFR library. (Instructions for installing the NCEP BUFR library are provided in a subsequent section) -If you have an environment preconfigured for `mpas-jedi`, simply source that environment prior to building `obs2ioda`. +Prior to building `obs2ioda`, ensure that you have installed the following libraries: + +- **CMake**: Required (version 3.12 or higher). +- **NetCDF**: Required. +- **NCEP BUFR Library**: Required. + +For instructions on how to install the `NCEP BUFR` library , please refer to the respective sections: + +- [Installing NCEP BUFR Library](#installing-ncep-bufr-library) + +If you have an environment preconfigured for `mpas-jedi` or `mpas-bundle`, simply source that environment prior to building `obs2ioda`. ### Build Instructions 1. First, clone the repository into your preferred directory (``): ```bash git clone https://github.com/NCAR/obs2ioda.git ``` -2. Create a new directory `build` and navigate into it: +1. Create a new directory `` and navigate into it: ```bash - mkdir build && cd build + mkdir && cd ``` -3. Locate the NCEP BUFR library by executing the following command in the `NCEP BUFR` library's build directory: +1. Set the variable OBS2IODA_CMAKE_ARGS to include the `obs2ioda` root directory and the absolute path of the `NCEP BUFR` library: + ```bash + OBS2IODA_CMAKE_ARGS=" -DNCEP_BUFR_LIB=" + ``` +1. If building in `Debug` mode, append the following to `OBS2IODA_CMAKE_ARGS`: + ```bash + OBS2IODA_CMAKE_ARGS="${OBS2IODA_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=Debug" + ``` +1. If building unit tests, append the following to `OBS2IODA_CMAKE_ARGS`: + ```bash + OBS2IODA_CMAKE_ARGS="${OBS2IODA_CMAKE_ARGS} -DBUILD_TESTS=ON" + ``` + +1. Next, run CMake to configure the build. ```bash - find . -name *libbufr* + cmake 'echo ${OBS2IODA_CMAKE_ARGS}' ``` -4. Next, run CMake to configure the build. Remember to specify the path to the NCEP BUFR library: - ```bash - cmake -DNCEP_BUFR_LIB= - ``` -5. Finally, build the project using this command: +1. Finally, build `obs2ioda` using `CMake`'s build tool. In this case, we use `GNU Make`, but other build tools supported by `CMake` can be used: ```bash make ``` The `obs2ioda-v2` executable will reside in the `bin` directory within the build directory. --- -## Installing NCEP BUFR Library +### Installing NCEP BUFR Library To install the NCEP BUFR library, follow these steps: 1. Clone the NCEP BUFR repository into a directory of your choice (``): ```bash git clone https://github.com/NOAA-EMC/NCEPLIBS-bufr.git ``` -2. Create a new directory `build` and navigate into it: +2. Create a new directory `` and navigate into it: ```bash - mkdir build && cd build + mkdir && cd ``` 3. Run CMake to configure the build (Ensure NetCDF is installed): ```bash @@ -54,9 +70,12 @@ To install the NCEP BUFR library, follow these steps: ``` 5. To locate the NCEP BUFR library, run: ```bash - find . -name *libbufr* + bash -c "find . -name *libbufr*" ``` Remember to note down the library path (``) required for the build process of `obs2ioda-v2`. + +--- + ## caveate NetCDF-Fortran interface does not allow reading/writing NF90_STRING, so ``station_id`` and ``variable_names`` are still written out as ``char station_id(nlocs, nstring)`` diff --git a/obs2ioda-v2/src/Makefile b/obs2ioda-v2/src/Makefile deleted file mode 100644 index a5f4b10..0000000 --- a/obs2ioda-v2/src/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -#! /bin/sh -v - -#------------ -#GNU compiler -#------------ -#FC = gfortran -#BUFR_LIB = -L/glade/u/home/hclin/extlib -lbufr -#GNU10 = #-fallow-argument-mismatch -fallow-invalid-boz -#FFLAGS = -ffree-line-length-none ${GNU10} #-fbacktrace -ggdb -fcheck=bounds,do,mem,pointer -ffpe-trap=invalid,zero,overflow - -#-------------- -#INTEL compiler -#-------------- -FC = ifort -#BUFR_LIB = -L/glade/u/home/hclin/extlib/intel -lbufr -#FFLAGS = -mcmodel medium # needed for intel error message "failed to convert GOTPCREL relocation" -BUFR_LIB = -L/glade/campaign/mmm/parc/ivette/pandac/converters/WRFDA_3DVAR_dmpar/var/external/bufr -lbufr -FFLAGS = -mcmodel medium # needed for intel error message "failed to convert GOTPCREL relocation" # -g -traceback -debug all -check all - -LIBS = -L$(NETCDF)/lib -lnetcdff -lnetcdf ${BUFR_LIB} -INCS = -I$(NETCDF)/include - -OBJS = \ - define_mod.o \ - gnssro_mod.o \ - hsd.o \ - satwnd_mod.o \ - kinds.o \ - main.o \ - ncio_mod.o \ - netcdf_mod.o \ - prepbufr_mod.o \ - radiance_mod.o \ - ufo_variables_mod.o \ - utils_mod.o - -all: obs2ioda - -obs2ioda: ${OBJS} - ${FC} -o obs2ioda-v2.x ${FFLAGS} ${OBJS} ${LIBS} - -kinds.o : kinds.f90 -define_mod.o : define_mod.f90 kinds.o ufo_variables_mod.o -gnssro_mod.o : gnssro_mod.f90 -hsd.o : hsd.f90 kinds.o define_mod.o ufo_variables_mod.o ncio_mod.o utils_mod.o -main.o : main.f90 define_mod.f90 prepbufr_mod.o ncio_mod.o radiance_mod.o gnssro_mod.o hsd.o satwnd_mod.o -ncio_mod.o : ncio_mod.f90 kinds.o prepbufr_mod.o netcdf_mod.o ufo_variables_mod.o define_mod.o -netcdf_mod.o : netcdf_mod.f90 -prepbufr_mod.o : prepbufr_mod.f90 kinds.o ufo_variables_mod.o utils_mod.o define_mod.o -radiance_mod.o : radiance_mod.f90 kinds.o define_mod.o ufo_variables_mod.o utils_mod.o -satwnd_mod.o : satwnd_mod.f90 kinds.o define_mod.o ufo_variables_mod.o -ufo_variables_mod.o : ufo_variables_mod.F90 -utils_mod.o : utils_mod.f90 - -.SUFFIXES : .F90 .f90 .o - -.F90.o : - ${FC} ${FFLAGS} ${INCS} -c $< - -.f90.o : - ${FC} ${FFLAGS} ${INCS} -c $< - -%.o: %.mod - -clean: - rm -f *.o *.mod *.exe *.x diff --git a/obs2ioda-v2/src/cxx/netcdf_file.cc b/obs2ioda-v2/src/cxx/netcdf_file.cc new file mode 100644 index 0000000..0bbf456 --- /dev/null +++ b/obs2ioda-v2/src/cxx/netcdf_file.cc @@ -0,0 +1,43 @@ +#include "netcdf_file.h" +#include "netcdf_utils.h" +#include +#include + +namespace Obs2Ioda { + + int netcdfCreate( + const char *path, + int *netcdfID + ) { + try { + auto file = std::make_shared( + path, + netCDF::NcFile::replace + ); + *netcdfID = file->getId(); + std::lock_guard lock(map_mutex); + NETCDF_FILE_MAP[*netcdfID] = file; + return 0; + } catch (netCDF::exceptions::NcException &e) { + return netcdfErrorMessage( + e, + 1 + ); + } + } + + int netcdfClose(int netcdfID) { + try { + std::lock_guard lock(map_mutex); + auto file = NETCDF_FILE_MAP[netcdfID]; + file->close(); + NETCDF_FILE_MAP.erase(netcdfID); + return 0; + } catch (netCDF::exceptions::NcException &e) { + return netcdfErrorMessage( + e, + 1 + ); + } + } +} diff --git a/obs2ioda-v2/src/cxx/netcdf_file.h b/obs2ioda-v2/src/cxx/netcdf_file.h new file mode 100644 index 0000000..8e3afe7 --- /dev/null +++ b/obs2ioda-v2/src/cxx/netcdf_file.h @@ -0,0 +1,26 @@ +#ifndef OBS2IODA_NETCDF_FILE_H +#define OBS2IODA_NETCDF_FILE_H + +#include +#include +#include +#include +#include + +namespace Obs2Ioda { + + extern "C" { + + +// Wrapper for nc_create + int netcdfCreate( + const char *path, int *netcdfID + ); + + int netcdfClose(int netcdfID); + + } + +} +#endif //OBS2IODA_NETCDF_FILE_H + diff --git a/obs2ioda-v2/src/cxx/netcdf_utils.cc b/obs2ioda-v2/src/cxx/netcdf_utils.cc new file mode 100644 index 0000000..f163bc6 --- /dev/null +++ b/obs2ioda-v2/src/cxx/netcdf_utils.cc @@ -0,0 +1,22 @@ +#include "netcdf_utils.h" +#include +#include + +namespace Obs2Ioda { + + std::unordered_map< + int, + std::shared_ptr> NETCDF_FILE_MAP; + std::mutex map_mutex; + + int netcdfErrorMessage( + netCDF::exceptions::NcException &e, + int errorCode + ) { + std::cerr + << "NetCDF Error: " + << e.what() + << std::endl; + return errorCode; + } +} diff --git a/obs2ioda-v2/src/cxx/netcdf_utils.h b/obs2ioda-v2/src/cxx/netcdf_utils.h new file mode 100644 index 0000000..27e5a7f --- /dev/null +++ b/obs2ioda-v2/src/cxx/netcdf_utils.h @@ -0,0 +1,27 @@ +#ifndef OBS2IODA_NETCDF_UTILS_H +#define OBS2IODA_NETCDF_UTILS_H + + +#include +#include +#include +#include +#include + +namespace Obs2Ioda { + + + extern std::unordered_map< + int, + std::shared_ptr> NETCDF_FILE_MAP; + extern std::mutex map_mutex; + + int netcdfErrorMessage( + netCDF::exceptions::NcException &e, + int errorCode + ); + +} + + +#endif //OBS2IODA_NETCDF_UTILS_H diff --git a/obs2ioda-v2/src/define_mod.f90 b/obs2ioda-v2/src/fortran/define_mod.f90 similarity index 100% rename from obs2ioda-v2/src/define_mod.f90 rename to obs2ioda-v2/src/fortran/define_mod.f90 diff --git a/obs2ioda-v2/src/fortran/f_c_string_1D_t_mod.f90 b/obs2ioda-v2/src/fortran/f_c_string_1D_t_mod.f90 new file mode 100644 index 0000000..9e37157 --- /dev/null +++ b/obs2ioda-v2/src/fortran/f_c_string_1D_t_mod.f90 @@ -0,0 +1,118 @@ +module f_c_string_1D_t_mod + use iso_c_binding, only : c_loc, c_ptr, c_null_char, c_char, c_f_pointer, c_null_ptr + use f_c_string_t_mod, only : f_c_string_t + implicit none + + type :: f_c_string_1D_t + ! Allocatable Fortran string + character(len = :), allocatable :: f_string_1D(:) + ! Allocatable C-compatible null-terminated string + type(f_c_string_t), allocatable :: f_c_string_t_array(:) + type(c_ptr), allocatable :: fc_string_1D(:) + ! C pointer to the null-terminated string + type(c_ptr) :: c_string_1D = c_null_ptr + ! Length of the Fortran string + integer :: m = -1, n = -1 + + contains + ! Type-bound procedures + procedure :: init => init + procedure :: to_c => to_c + procedure :: to_f => to_f + procedure :: cleanup => cleanup + end type f_c_string_1D_t + +contains + ! Convert the Fortran string to a C-compatible null-terminated string. + subroutine init(this, m, n) + class(f_c_string_1D_t), target, intent(inout) :: this + integer, intent(in) :: m, n + character(len=n), dimension(m) :: a + integer :: i + this%m = m + this%n = n + allocate(this%f_c_string_t_array(this%m)) + allocate(this%fc_string_1D(this%m)) + a = repeat('a', n) + this%f_string_1D = a + do i = 1, this%m + this%f_c_string_t_array(i)%f_string = this%f_string_1D(i) + call this%f_c_string_t_array(i)%to_c() + this%fc_string_1D(i) = this%f_c_string_t_array(i)%c_string + end do + this%c_string_1D = c_loc(this%fc_string_1D) + end subroutine init + ! Convert the Fortran string to a C-compatible null-terminated string. + subroutine to_c(this) + class(f_c_string_1D_t), target, intent(inout) :: this + integer :: i + this%m = size(this%f_string_1D) + this%n = len(this%f_string_1D(1)) + allocate(this%f_c_string_t_array(this%m)) + allocate(this%fc_string_1D(this%m)) + do i = 1, this%m + this%f_c_string_t_array(i)%f_string = this%f_string_1D(i) + call this%f_c_string_t_array(i)%to_c() + this%fc_string_1D(i) = this%f_c_string_t_array(i)%c_string + end do + this%c_string_1D = c_loc(this%fc_string_1D) + + end subroutine to_c + + ! Convert a C-compatible null-terminated string to a Fortran string. + subroutine to_f(this) + class(f_c_string_1D_t), intent(inout) :: this + type(c_ptr), pointer :: fc_string_1D_pointer(:) + integer :: i + if (this%m < 0) then + return + end if + if (this%n < 0) then + return + end if + if (allocated(this%f_string_1D)) then + deallocate(this%f_string_1D) + end if + if (allocated(this%f_c_string_t_array)) then + do i = 1, this%m + call this%f_c_string_t_array(i)%cleanup() + end do + deallocate(this%f_c_string_t_array) + end if + if (allocated(this%fc_string_1D)) then + deallocate(this%fc_string_1D) + end if + allocate(character(len = this%n) :: this%f_string_1D(1:this%m)) + allocate(this%f_c_string_t_array(this%m)) + allocate(this%fc_string_1D(this%m)) + call c_f_pointer(this%c_string_1D, fc_string_1D_pointer, [this%m]) + do i = 1, this%m + this%f_c_string_t_array(i)%c_string = fc_string_1D_pointer(i) + this%f_c_string_t_array(i)%n = this%n + call this%f_c_string_t_array(i)%to_f() + this%f_string_1D(i) = this%f_c_string_t_array(i)%f_string + end do + + end subroutine to_f + + subroutine cleanup(this) + class(f_c_string_1D_t), intent(inout) :: this + integer :: i + if (allocated(this%fc_string_1D)) then + deallocate(this%fc_string_1D) + end if + if (allocated(this%f_c_string_t_array)) then + do i = 1, this%m + call this%f_c_string_t_array(i)%cleanup() + end do + deallocate(this%f_c_string_t_array) + end if + if (allocated(this%f_string_1D)) then + deallocate(this%f_string_1D) + end if + this%c_string_1D = c_null_ptr + this%m = -1 + this%n = -1 + end subroutine cleanup + +end module f_c_string_1D_t_mod diff --git a/obs2ioda-v2/src/fortran/f_c_string_t_mod.f90 b/obs2ioda-v2/src/fortran/f_c_string_t_mod.f90 new file mode 100644 index 0000000..2567245 --- /dev/null +++ b/obs2ioda-v2/src/fortran/f_c_string_t_mod.f90 @@ -0,0 +1,60 @@ +module f_c_string_t_mod + use iso_c_binding, only : c_loc, c_ptr, c_null_char, c_char, c_f_pointer, c_null_ptr + implicit none + + type :: f_c_string_t + ! Allocatable Fortran string + character(len = :), allocatable :: f_string + ! Allocatable C-compatible null-terminated string + character(len = :, kind = c_char), allocatable :: fc_string + ! C pointer to the null-terminated string + type(c_ptr) :: c_string + ! Length of the Fortran string + integer :: n=-1 + + contains + ! Type-bound procedures + procedure :: to_c => to_c + procedure :: to_f => to_f + procedure :: cleanup => cleanup + end type f_c_string_t + +contains + + ! Convert the Fortran string to a C-compatible null-terminated string. + subroutine to_c(this) + class(f_c_string_t), target, intent(inout) :: this + this%n = len(this%f_string) + allocate(character(len = this%n + 1) :: this%fc_string) + this%fc_string = this%f_string // c_null_char + this%c_string = c_loc(this%fc_string) + end subroutine to_c + + ! Convert a C-compatible null-terminated string to a Fortran string. + subroutine to_f(this) + class(f_c_string_t), intent(inout) :: this + character(len=1, kind=c_char), pointer :: fc_string_pointer(:) + if (this%n < 0) then + return + end if + allocate(character(len=this%n) :: this%f_string) + call c_f_pointer(this%c_string, fc_string_pointer, [this%n + 1]) + this%f_string = transfer(fc_string_pointer(1:this%n), this%f_string) + end subroutine to_f + + ! Cleanup method to deallocate allocated resources. + subroutine cleanup(this) + class(f_c_string_t), intent(inout) :: this + + if (allocated(this%fc_string)) then + deallocate(this%fc_string) + end if + if (allocated(this%f_string)) then + deallocate(this%f_string) + end if + + this%c_string = c_null_ptr + this%n = -1 + end subroutine cleanup + +end module f_c_string_t_mod diff --git a/obs2ioda-v2/src/gnssro_mod.f90 b/obs2ioda-v2/src/fortran/gnssro_mod.f90 similarity index 100% rename from obs2ioda-v2/src/gnssro_mod.f90 rename to obs2ioda-v2/src/fortran/gnssro_mod.f90 diff --git a/obs2ioda-v2/src/hsd.f90 b/obs2ioda-v2/src/fortran/hsd.f90 similarity index 100% rename from obs2ioda-v2/src/hsd.f90 rename to obs2ioda-v2/src/fortran/hsd.f90 diff --git a/obs2ioda-v2/src/kinds.f90 b/obs2ioda-v2/src/fortran/kinds.f90 similarity index 100% rename from obs2ioda-v2/src/kinds.f90 rename to obs2ioda-v2/src/fortran/kinds.f90 diff --git a/obs2ioda-v2/src/main.f90 b/obs2ioda-v2/src/fortran/main.f90 similarity index 100% rename from obs2ioda-v2/src/main.f90 rename to obs2ioda-v2/src/fortran/main.f90 diff --git a/obs2ioda-v2/src/ncio_mod.f90 b/obs2ioda-v2/src/fortran/ncio_mod.f90 similarity index 99% rename from obs2ioda-v2/src/ncio_mod.f90 rename to obs2ioda-v2/src/fortran/ncio_mod.f90 index 4b28249..123b45d 100644 --- a/obs2ioda-v2/src/ncio_mod.f90 +++ b/obs2ioda-v2/src/fortran/ncio_mod.f90 @@ -89,7 +89,7 @@ subroutine write_obs (filedate, write_opt, outdir, itim) allocate (ichan(xdata(ityp,itim)%nvars)) ichan(:) = xdata(ityp,itim)%xseninfo_int(:,iv) allocate (obserr(xdata(ityp,itim)%nvars)) - if ( geoinst_list(ityp) == 'ahi_himawari8' ) then + if ( write_opt == write_nc_radiance_geo ) then call set_ahi_obserr(geoinst_list(ityp), xdata(ityp,itim)%nvars, obserr) else call set_brit_obserr(inst_list(ityp), xdata(ityp,itim)%nvars, obserr) diff --git a/obs2ioda-v2/src/fortran/netcdf_cxx_i_mod.f90 b/obs2ioda-v2/src/fortran/netcdf_cxx_i_mod.f90 new file mode 100644 index 0000000..dad5ae2 --- /dev/null +++ b/obs2ioda-v2/src/fortran/netcdf_cxx_i_mod.f90 @@ -0,0 +1,24 @@ +module netcdf_cxx_i_mod + use iso_c_binding + implicit none + public + + interface + function c_netcdfCreate(path, netcdfID) & + bind(C, name = "netcdfCreate") + import :: c_int + import :: c_ptr + type(c_ptr), value, intent(in) :: path + integer(c_int), intent(out) :: netcdfID + integer(c_int) :: c_netcdfCreate + end function + + function c_netcdfClose(netcdfID) & + bind(C, name = "netcdfClose") + import :: c_int + integer(c_int), value, intent(in) :: netcdfID + integer(c_int) :: c_netcdfClose + end function + end interface + +end module netcdf_cxx_i_mod \ No newline at end of file diff --git a/obs2ioda-v2/src/fortran/netcdf_cxx_mod.f90 b/obs2ioda-v2/src/fortran/netcdf_cxx_mod.f90 new file mode 100644 index 0000000..d7c70dd --- /dev/null +++ b/obs2ioda-v2/src/fortran/netcdf_cxx_mod.f90 @@ -0,0 +1,42 @@ +module netcdf_cxx_mod + use iso_c_binding, only : c_char, c_null_char, c_null_ptr, c_int + use f_c_string_t_mod, only : f_c_string_t + use f_c_string_1D_t_mod, only : f_c_string_1D_t + use netcdf_cxx_i_mod + use netcdf, only : NF90_INT, NF90_INT64, NF90_REAL + implicit none + public + +contains + ! Helper to handle optional groupName initialization + subroutine init_optional_string(input_string, c_string) + character(len = *), intent(in), optional :: input_string + type(f_c_string_t), intent(inout) :: c_string + if (present(input_string)) then + c_string%f_string = input_string + call c_string%to_c() + else + c_string%c_string = c_null_ptr + end if + end subroutine init_optional_string + + function netcdfCreate(path, netcdfID) + character(len = *), intent(in) :: path + integer(c_int), intent(inout) :: netcdfID + integer(c_int) :: netcdfCreate + type(f_c_string_t) :: c_path + + c_path%f_string = path + call c_path%to_c() + netcdfCreate = c_netcdfCreate(c_path%c_string, netcdfID) + call c_path%cleanup() + end function netcdfCreate + + function netcdfClose(netcdfID) + integer(c_int), value, intent(in) :: netcdfID + integer(c_int) :: netcdfClose + netcdfClose = c_netcdfClose(netcdfID) + end function netcdfClose + + +end module netcdf_cxx_mod \ No newline at end of file diff --git a/obs2ioda-v2/src/netcdf_mod.f90 b/obs2ioda-v2/src/fortran/netcdf_mod.f90 similarity index 100% rename from obs2ioda-v2/src/netcdf_mod.f90 rename to obs2ioda-v2/src/fortran/netcdf_mod.f90 diff --git a/obs2ioda-v2/src/prepbufr_mod.f90 b/obs2ioda-v2/src/fortran/prepbufr_mod.f90 similarity index 100% rename from obs2ioda-v2/src/prepbufr_mod.f90 rename to obs2ioda-v2/src/fortran/prepbufr_mod.f90 diff --git a/obs2ioda-v2/src/radiance_mod.f90 b/obs2ioda-v2/src/fortran/radiance_mod.f90 similarity index 100% rename from obs2ioda-v2/src/radiance_mod.f90 rename to obs2ioda-v2/src/fortran/radiance_mod.f90 diff --git a/obs2ioda-v2/src/satwnd_mod.f90 b/obs2ioda-v2/src/fortran/satwnd_mod.f90 similarity index 100% rename from obs2ioda-v2/src/satwnd_mod.f90 rename to obs2ioda-v2/src/fortran/satwnd_mod.f90 diff --git a/obs2ioda-v2/src/ufo_variables_mod.F90 b/obs2ioda-v2/src/fortran/ufo_variables_mod.f90 similarity index 100% rename from obs2ioda-v2/src/ufo_variables_mod.F90 rename to obs2ioda-v2/src/fortran/ufo_variables_mod.f90 diff --git a/obs2ioda-v2/src/utils_mod.f90 b/obs2ioda-v2/src/fortran/utils_mod.f90 similarity index 100% rename from obs2ioda-v2/src/utils_mod.f90 rename to obs2ioda-v2/src/fortran/utils_mod.f90 diff --git a/obs2ioda-v2/src/test/fortran_test_framework_mod.f90 b/obs2ioda-v2/src/test/fortran_test_framework_mod.f90 new file mode 100644 index 0000000..9417dc2 --- /dev/null +++ b/obs2ioda-v2/src/test/fortran_test_framework_mod.f90 @@ -0,0 +1,90 @@ +module fortran_test_framework_mod + implicit none + + interface assertEqual + module procedure assertEqual_integer + module procedure assertEqual_string + module procedure assertEqual_logical + end interface assertEqual + +contains + + ! Generic assertion subroutine to handle error messages + subroutine assert(condition, message) + implicit none + logical, intent(in) :: condition + character(len=*), intent(in) :: message + + if (.not. condition) then + print *, "Error: ", trim(message) + call exit(1) + else + print *, "Success: ", trim(message) + end if + end subroutine assert + + ! Integer equality assertion + subroutine assertEqual_integer(expected, actual) + implicit none + integer, intent(in) :: expected, actual + + call assert(expected == actual, "expected=" // trim(adjustl(itoa(expected))) // & + " actual=" // trim(adjustl(itoa(actual)))) + end subroutine assertEqual_integer + + ! String equality assertion + subroutine assertEqual_string(expected, actual) + implicit none + character(len=*), intent(in) :: expected, actual + + call assert(expected == actual, "expected='" // trim(expected) // "' actual='" // & + trim(actual) // "'") + end subroutine assertEqual_string + + ! Logical equality assertion + subroutine assertEqual_logical(expected, actual) + implicit none + logical, intent(in) :: expected, actual + + call assert(expected .eqv. actual, "expected=" // trim(logical_to_string(expected)) // & + " actual=" // trim(logical_to_string(actual))) + end subroutine assertEqual_logical + + ! Assert true + subroutine assertTrue(actual) + implicit none + logical, intent(in) :: actual + + call assert(actual, "expected=.true. actual=.false.") + end subroutine assertTrue + + ! Assert false + subroutine assertFalse(actual) + implicit none + logical, intent(in) :: actual + + call assert(.not. actual, "expected=.false. actual=.true.") + end subroutine assertFalse + + ! Helper function to convert integer to string + function itoa(value) result(str) + implicit none + integer, intent(in) :: value + character(len=32) :: str + write(str, '(I0)') value + end function itoa + + ! Helper function to convert logical to string + 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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..e51509c --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cxx) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/fortran) \ No newline at end of file diff --git a/test/cxx/CMakeLists.txt b/test/cxx/CMakeLists.txt new file mode 100644 index 0000000..2cd715c --- /dev/null +++ b/test/cxx/CMakeLists.txt @@ -0,0 +1,22 @@ +get_target_property( + GTEST_INCLUDE_DIRECTORIES GTest::gtest_main INTERFACE_INCLUDE_DIRECTORIES +) + +set(NETCDF_TEST_SOURCES + ${CMAKE_SOURCE_DIR}/test/cxx/netcdf_test_fixture.cc + ${CMAKE_SOURCE_DIR}/test/cxx/netcdf_test.cc + ) +set(NETCDF_TEST_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/test/cxx + ${CMAKE_SOURCE_DIR}/obs2ioda-v2/src/cxx + ${GTEST_INCLUDE_DIRECTORIES} + ) +set(NETCDF_TEST_LIBRARY_DEPENDENCIES + netcdf_cxx GTest::gtest_main + ) + +add_cxx_ctest(NetcdfTest "${NETCDF_TEST_SOURCES}" + "${NETCDF_TEST_INCLUDE_DIRS}" + "${NETCDF_TEST_LIBRARY_DEPENDENCIES}" + ) +add_memcheck_ctest(NetcdfTest) \ No newline at end of file diff --git a/test/cxx/netcdf_test.cc b/test/cxx/netcdf_test.cc new file mode 100644 index 0000000..f0e0471 --- /dev/null +++ b/test/cxx/netcdf_test.cc @@ -0,0 +1,20 @@ +#include "netcdf_test_fixture.h" +#include "netcdf_file.h" +#include + +// Example test case using the fixture +TEST_F(NetCDFTestFixture, NetCDFCreateTest) { + int netcdfID{}; + int status = Obs2Ioda::netcdfCreate( + this->test_file_path.c_str(), + &netcdfID + ); + EXPECT_EQ(status, 0); + status = Obs2Ioda::netcdfClose(netcdfID); + EXPECT_EQ(status, 0); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/test/cxx/netcdf_test_fixture.cc b/test/cxx/netcdf_test_fixture.cc new file mode 100644 index 0000000..4088888 --- /dev/null +++ b/test/cxx/netcdf_test_fixture.cc @@ -0,0 +1,37 @@ +// NetCDFTestFixture.cpp +#include "netcdf_test_fixture.h" + +void NetCDFTestFixture::SetUp() { + test_file_path = "test_file.nc"; + test_group_path = "test_group.nc"; + test_dim_path = "test_dim.nc"; + test_var_path = "test_var.nc"; + test_att_path = "test_att.nc"; + + test_group_name = "group"; + test_nested_group_name = "nested_group"; + + test_global_dim_name = "global_dim"; + test_dim_name = "dim"; + test_dim_len = 5; + test_global_dim_len = 5; + + test_int_var_name = "int_var"; + test_int64_var_name = "int64_var"; + test_real_var_name = "real_var"; + test_string_var_name = "string_var"; + + test_int_var_data = {1, 2, 3, 4, 5}; + test_int64_var_data = {11111, 22222, 33333, 44444, 55555}; + test_real_var_data = {1.1f, 2.2f, 3.3f, 4.4f, 5.5f}; + test_string_var_data = {"one", "two", "three", "four", "five"}; + + test_int_att_name = "int_var"; + test_string_att_name = "string_att"; + test_int_att_data = 2; + test_string_att_data = "string"; +} + +void NetCDFTestFixture::TearDown() { + // Cleanup if necessary +} diff --git a/test/cxx/netcdf_test_fixture.h b/test/cxx/netcdf_test_fixture.h new file mode 100644 index 0000000..36d9cb8 --- /dev/null +++ b/test/cxx/netcdf_test_fixture.h @@ -0,0 +1,64 @@ +// +// Created by astokely on 11/29/24. +// + +#ifndef OBS2IODA_NETCDF_TEST_FIXTURE_H +#define OBS2IODA_NETCDF_TEST_FIXTURE_H + +#include +#include +#include + +class NetCDFTestFixture : public ::testing::Test { +protected: + // NetCDF type constants + static constexpr int NC_BYTE = 1; // 1-byte signed integer + static constexpr int NC_CHAR = 2; // 1-byte character + static constexpr int NC_SHORT = 3; // 2-byte signed integer + static constexpr int NC_INT = 4; // 4-byte signed integer + static constexpr int NC_LONG = 4; // Alias for NC_INT + static constexpr int NC_FLOAT = 5; // 4-byte IEEE floating-point + static constexpr int NC_DOUBLE = 6; // 8-byte IEEE floating-point + static constexpr int NC_UBYTE = 7; // 1-byte unsigned integer + static constexpr int NC_USHORT = 8; // 2-byte unsigned integer + static constexpr int NC_UINT = 9; // 4-byte unsigned integer + static constexpr int NC_INT64 = 10; // 8-byte signed integer + static constexpr int NC_UINT64 = 11; // 8-byte unsigned integer + static constexpr int NC_STRING = 12; // Variable-length string + + // Paths and names + std::string test_file_path; + std::string test_group_path; + std::string test_dim_path; + std::string test_var_path; + std::string test_att_path; + + std::string test_group_name; + std::string test_nested_group_name; + + std::string test_dim_name; + std::string test_global_dim_name; + + int test_dim_len; + int test_global_dim_len; + + std::string test_int_var_name; + std::string test_int64_var_name; + std::string test_real_var_name; + std::string test_string_var_name; + + std::vector test_int_var_data; + std::vector test_int64_var_data; + std::vector test_real_var_data; + std::vector test_string_var_data; + + std::string test_int_att_name; + std::string test_string_att_name; + std::string test_string_att_data; + int test_int_att_data; + + void SetUp() override; + void TearDown() override; +}; + +#endif //OBS2IODA_NETCDF_TEST_FIXTURE_H diff --git a/test/fortran/CMakeLists.txt b/test/fortran/CMakeLists.txt new file mode 100644 index 0000000..2a9103c --- /dev/null +++ b/test/fortran/CMakeLists.txt @@ -0,0 +1,19 @@ +# Define test executable sources +set(TEST_F_C_STRING_SOURCES + f_c_string_t/Test_f_c_string_t_mod.f90 + f_c_string_t/Test_f_c_string_t_driver.f90 + +) +list(TRANSFORM TEST_F_C_STRING_SOURCES PREPEND ${CMAKE_SOURCE_DIR}/test/fortran/) + +# Define the libraries to link +set(TEST_F_C_STRING_LIBRARY_DEPENDENCIES + v2 + fortran_test_framework +) + +add_fortran_ctest(f_c_string + "${TEST_F_C_STRING_SOURCES}" + "${TEST_F_C_STRING_LIBRARY_DEPENDENCIES}" +) +add_memcheck_ctest(Test_f_c_string) diff --git a/test/fortran/f_c_string_t/Test_f_c_string_t_driver.f90 b/test/fortran/f_c_string_t/Test_f_c_string_t_driver.f90 new file mode 100644 index 0000000..d141604 --- /dev/null +++ b/test/fortran/f_c_string_t/Test_f_c_string_t_driver.f90 @@ -0,0 +1,7 @@ +program Test_f_c_string_t_driver + use Test_f_c_string_t_mod, only : Test_to_c_fc_string + implicit none + + call Test_to_c_fc_string() + +end program Test_f_c_string_t_driver \ No newline at end of file diff --git a/test/fortran/f_c_string_t/Test_f_c_string_t_mod.f90 b/test/fortran/f_c_string_t/Test_f_c_string_t_mod.f90 new file mode 100644 index 0000000..25b2963 --- /dev/null +++ b/test/fortran/f_c_string_t/Test_f_c_string_t_mod.f90 @@ -0,0 +1,17 @@ +module Test_f_c_string_t_mod + use fortran_test_framework_mod, only : assertEqual, assertTrue + use f_c_string_t_mod, only : f_c_string_t + use iso_c_binding, only : c_null_char + implicit none + contains + subroutine Test_to_c_fc_string() + type(f_c_string_t) :: f_c_string + character(len=:), allocatable :: f_string + f_string = "foo" + f_c_string%f_string = f_string + call f_c_string%to_c() + call assertEqual(len(trim(f_string))+1, len(trim(f_c_string%fc_string))) + call f_c_string%cleanup() + call assertTrue(.not.allocated(f_c_string%fc_string)) + end subroutine Test_to_c_fc_string +end module Test_f_c_string_t_mod