diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 22ddc72..784793a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -16,6 +16,7 @@ env: jobs: build: + name: Build # The CMake configure and build commands are platform agnostic and should work equally # well on Windows or Mac. You can convert this to a matrix build if you need # cross-platform coverage. @@ -23,40 +24,96 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true + - name: Cache pFUnit install + uses: actions/cache@v4 + id: pfunit-cache + with: + path: pFUnit/build/installed + key: pfunit-${{ runner.os }}-${{ env.BUILD_TYPE }}-${{ hashFiles('.gitmodules', 'pFUnit/CMakeLists.txt') }} + restore-keys: | + pfunit-${{ runner.os }}-${{ env.BUILD_TYPE }}- + - name: Configure CMake pFUnit + if: steps.pfunit-cache.outputs.cache-hit != 'true' # Configure pFUnit run: cmake -S pFUnit -B ${{github.workspace}}/pFUnit/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_OpenMP_SUPPORT=OFF -DENABLE_POSTGRESQL_SUPPORT=OFF - name: Build pFUnit + if: steps.pfunit-cache.outputs.cache-hit != 'true' # Build your program with the given configuration run: cmake --build ${{github.workspace}}/pFUnit/build --config ${{env.BUILD_TYPE}} - name: Install pFUnit + if: steps.pfunit-cache.outputs.cache-hit != 'true' # install libs and includes in libslam/build/ run: cmake --install ${{github.workspace}}/pFUnit/build --config ${{env.BUILD_TYPE}} - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_PREFIX_PATH=${{github.workspace}}/pFUnit/build/installed/ + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_PREFIX_PATH=${{github.workspace}}/pFUnit/build/installed/ -DENABLE_PFUNIT=ON - name: Build # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: libslam-build + path: build + + test: + name: Test + needs: [build] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Cache pFUnit install + uses: actions/cache@v4 + id: pfunit-cache + with: + path: pFUnit/build/installed + key: pfunit-${{ runner.os }}-${{ env.BUILD_TYPE }}-${{ hashFiles('.gitmodules', 'pFUnit/CMakeLists.txt') }} + restore-keys: | + pfunit-${{ runner.os }}-${{ env.BUILD_TYPE }}- + + - name: Configure CMake pFUnit + if: steps.pfunit-cache.outputs.cache-hit != 'true' + run: cmake -S pFUnit -B ${{github.workspace}}/pFUnit/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_OpenMP_SUPPORT=OFF -DENABLE_POSTGRESQL_SUPPORT=OFF + + - name: Build pFUnit + if: steps.pfunit-cache.outputs.cache-hit != 'true' + run: cmake --build ${{github.workspace}}/pFUnit/build --config ${{env.BUILD_TYPE}} + + - name: Install pFUnit + if: steps.pfunit-cache.outputs.cache-hit != 'true' + run: cmake --install ${{github.workspace}}/pFUnit/build --config ${{env.BUILD_TYPE}} + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: libslam-build + path: build + + - name: Restore execute permissions on build artifact + run: chmod -R a+x build + + - name: Run tests working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} --verbose --output-junit ../test_report.xml - name: Publish Test Report uses: mikepenz/action-junit-report@v5 - if: success() || failure() # always run even if the previous step fails + if: success() || failure() with: report_paths: '**/test_report.xml' comment: true diff --git a/build.sh b/build.sh index c7f7cca..da39760 100755 --- a/build.sh +++ b/build.sh @@ -27,13 +27,13 @@ elif [[ "$OSTYPE" == "msys"* ]]; then LIBSUFFIX="dll" GENERATOR_FLAGS="-G MSYS Makefiles" fi -git submodule update --init --recursive ################################################################################ # # # Build pFUnit # # # ################################################################################ if [[ "$ENABLE_PFUNIT" == "ON" ]]; then + git submodule update --init --recursive || exit cd pFUnit || exit # Create the build directory if it does not exist if [[ ! -d "build" ]]; then @@ -43,7 +43,7 @@ if [[ "$ENABLE_PFUNIT" == "ON" ]]; then fi cd build || exit echo "Updating cmake" - export PFUNIT_DIR=..//pFUnit/build/installed + export PFUNIT_DIR=../pFUnit/build/installed export FC=$Fortran_COMPILER cmake -DSKIP_MPI=yes "$GENERATOR_FLAGS" ../ echo "Building pFUnit" @@ -82,4 +82,4 @@ cd ../ || exit ln -sf build/include include || exit ln -sf build/lib lib || exit echo "Leaving libslam" -echo "Done with $BUILD_TYPE build" \ No newline at end of file +echo "Done with $BUILD_TYPE build" diff --git a/pFUnit b/pFUnit index 74e6f4a..581cd32 160000 --- a/pFUnit +++ b/pFUnit @@ -1 +1 @@ -Subproject commit 74e6f4a0c5ff622d3e127b17c29baafe2ca45e5d +Subproject commit 581cd32be937df1922ce50fb06ddbc5522e6ce66 diff --git a/pFUnittests/CMakeLists.txt b/pFUnittests/CMakeLists.txt index c69121d..121e05a 100644 --- a/pFUnittests/CMakeLists.txt +++ b/pFUnittests/CMakeLists.txt @@ -32,3 +32,11 @@ LINK_LIBRARIES slam-Fortran) add_pfunit_ctest (slam_strings_test TEST_SOURCES test_slamstrings.pf LINK_LIBRARIES slam-Fortran) + +add_pfunit_ctest (slam_error_handling_test + TEST_SOURCES test_slam_error_handling.pf + LINK_LIBRARIES slam-Fortran) + +add_pfunit_ctest (slam_io_test + TEST_SOURCES test_slam_io.pf + LINK_LIBRARIES slam-Fortran) diff --git a/pFUnittests/test_slam_error_handling.pf b/pFUnittests/test_slam_error_handling.pf new file mode 100644 index 0000000..59d0b8c --- /dev/null +++ b/pFUnittests/test_slam_error_handling.pf @@ -0,0 +1,106 @@ +!============================================================================== +! +!> @anchor test_slam_error_handling +!! +!> @brief pFUnit tests for slam_error_handling (language/verbosity, setError, compile_log_record) +!! +!------------------------------------------------------------------------ +module test_slam_error_handling + use funit + use slam_error_handling + implicit none + + ! Language constants (match module: 1=ENGLISH, 2=GERMAN) + integer, parameter :: LANG_ENGLISH = 1 + integer, parameter :: LANG_GERMAN = 2 + +contains + + @test + subroutine test_setErrorLanguage_getErrorLanguage() + integer :: prev + prev = setErrorLanguage(LANG_GERMAN) + @assertEqual(LANG_GERMAN, getErrorLanguage()) + prev = setErrorLanguage(LANG_ENGLISH) + @assertEqual(LANG_ENGLISH, getErrorLanguage()) + end subroutine test_setErrorLanguage_getErrorLanguage + + @test + subroutine test_setCliVerbosity_getCliVerbosity() + integer :: junk + junk = setCliVerbosity(WARNINGS) + @assertEqual(WARNINGS, getCliVerbosity()) + junk = setCliVerbosity(ERRORS) + @assertEqual(ERRORS, getCliVerbosity()) + end subroutine test_setCliVerbosity_getCliVerbosity + + @test + subroutine test_setLogVerbosity_getLogVerbosity() + integer :: junk + junk = setLogVerbosity(REMARKS) + @assertEqual(REMARKS, getLogVerbosity()) + junk = setLogVerbosity(QUIET) + @assertEqual(QUIET, getLogVerbosity()) + end subroutine test_setLogVerbosity_getLogVerbosity + + @test + subroutine test_setError_report_updates_latest_error() + call initErrorHandler(errAction='REPORT') + call setError(E_FILE_NOT_FOUND, WARNING, errorMessage='test message') + @assertEqual(E_FILE_NOT_FOUND, getLatestError()) + @assertEqual(WARNING, getLatestErrorType()) + end subroutine test_setError_report_updates_latest_error + + @test + subroutine test_setError_fatal_hasFailed() + call initErrorHandler(errAction='REPORT') + call setError(E_FILE_NOT_FOUND, FATAL, errorMessage='fatal test') + @assertEqual(FATAL, getLatestErrorType()) + @assertTrue(hasFailed()) + end subroutine test_setError_fatal_hasFailed + + @test + subroutine test_setError_remark_type() + call initErrorHandler(errAction='REPORT') + call setError(E_STRING_LENGTH, REMARK, errorMessage='remark') + @assertEqual(E_STRING_LENGTH, getLatestError()) + @assertEqual(REMARK, getLatestErrorType()) + end subroutine test_setError_remark_type + + @test + subroutine test_setError_debug_msg_type() + call initErrorHandler(errAction='REPORT') + call setError(E_UNKNOWN_PARAMETER, DEBUG_MSG, errorMessage='debug') + @assertEqual(E_UNKNOWN_PARAMETER, getLatestError()) + @assertEqual(DEBUG_MSG, getLatestErrorType()) + end subroutine test_setError_debug_msg_type + + @test + subroutine test_resetError_clears_latest() + call initErrorHandler(errAction='REPORT') + call setError(E_FILE_NOT_FOUND, WARNING, errorMessage='x') + call resetError() + @assertEqual(0, getLatestError()) + @assertEqual(ERR_UNDEFINED, getLatestErrorType()) + end subroutine test_resetError_clears_latest + + @test + subroutine test_compile_log_record_format() + character(len=23) :: timestamp + character(len=1024) :: rec + timestamp = '2024-01-15 10:30:00.000' + rec = compile_log_record(timestamp, 2, ' - ', 'test message') + @assertTrue(len_trim(rec) > 0) + @assertTrue(index(trim(rec), '2024-01-15') > 0) + @assertTrue(index(trim(rec), 'WARNING') > 0) + @assertTrue(index(trim(rec), 'test message') > 0) + end subroutine test_compile_log_record_format + + @test + subroutine test_getErrorMessage_returns_message() + character(len=512) :: msg + call getErrorMessage(E_FILE_NOT_FOUND, msg) + @assertTrue(len_trim(msg) > 0) + end subroutine test_getErrorMessage_returns_message + +end module test_slam_error_handling diff --git a/pFUnittests/test_slam_io.pf b/pFUnittests/test_slam_io.pf new file mode 100644 index 0000000..d44a5e3 --- /dev/null +++ b/pFUnittests/test_slam_io.pf @@ -0,0 +1,149 @@ +!============================================================================== +! +!> @anchor test_slam_io +!! +!> @brief pFUnit tests for slam_io (sprint, parse_from_fid) +!! +!------------------------------------------------------------------------ +module test_slam_io + use funit + use slam_io, only: & + openFile, closeFile, parse_from_fid, sprint, message, & + SEQUENTIAL, IN_FORMATTED, OUT_FORMATTED_OVERWRITE, & + STDOUT, LOG_AND_STDOUT, LOGFILE + use slam_error_handling, only: initErrorHandler, getLatestError, resetError + use slam_types, only: dp + implicit none + + character(*), parameter :: TEST_FILE = 'test_slam_io_parse.tmp' + +contains + + !-------- message -------- + @test + subroutine test_message_stdout() + call message('test message to stdout', STDOUT) + end subroutine test_message_stdout + + @test + subroutine test_message_log_and_stdout() + call message('test message to both', LOG_AND_STDOUT) + end subroutine test_message_log_and_stdout + + @test + subroutine test_message_logfile_only() + call message('test message to logfile only', LOGFILE) + end subroutine test_message_logfile_only + + !-------- sprint -------- + @test + subroutine test_sprint_integer() + @assertEqual('7', trim(sprint(7, 'I1'))) + @assertEqual('007', trim(sprint(7, 'I3.3'))) + end subroutine test_sprint_integer + + @test + subroutine test_sprint_real() + @assertEqual('7.1', trim(sprint(7.1, 'F3.1'))) + @assertEqual('7.10', trim(sprint(7.1_dp, 'F4.2'))) + end subroutine test_sprint_real + + @test + subroutine test_sprint_character() + @assertEqual(' x', trim(sprint('x', 'A3'))) + end subroutine test_sprint_character + + @test + subroutine test_sprint_invalid_format_fails() + character(len=20) :: dummy + call initErrorHandler(control='YES', errAction='RETURN') + dummy = sprint(7, 'F4.1') + @assertTrue(getLatestError() /= 0) + call resetError() + end subroutine test_sprint_invalid_format_fails + + @test + subroutine test_sprint_real_as_integer_fails() + character(len=20) :: dummy + call initErrorHandler(control='YES', errAction='RETURN') + dummy = sprint(7.1_dp, 'I3.3') + @assertTrue(getLatestError() /= 0) + call resetError() + end subroutine test_sprint_real_as_integer_fails + + !-------- parse_from_fid -------- + @test + subroutine test_parse_from_fid_real_integer() + integer :: fid + real(dp) :: value_real + integer :: value_int + fid = create_parse_test_file() + fid = openFile(TEST_FILE, SEQUENTIAL, IN_FORMATTED) + call parse_from_fid(fid, '#', value_real, 'real(DP)') + @assertRelativelyEqual(12.50_dp, value_real, tolerance=1.0e-12_dp) + call parse_from_fid(fid, '#', value_int, 'integer') + @assertEqual(7, value_int) + fid = closeFile(fid) + end subroutine test_parse_from_fid_real_integer + + @test + subroutine test_parse_from_fid_logical_string() + integer :: fid + real(dp) :: value_real + integer :: value_int + logical :: value_log + character(len=17) :: value_char + fid = create_parse_test_file() + fid = openFile(TEST_FILE, SEQUENTIAL, IN_FORMATTED) + call parse_from_fid(fid, '#', value_real, 'real(DP)') + call parse_from_fid(fid, '#', value_int, 'integer') + call parse_from_fid(fid, '#', value_log, 'logical true') + @assertTrue(value_log) + call parse_from_fid(fid, '#', value_log, 'logical false') + @assertFalse(value_log) + call parse_from_fid(fid, '#', value_char, 'short string') + @assertEqual('String', trim(value_char)) + fid = closeFile(fid) + end subroutine test_parse_from_fid_logical_string + + @test + subroutine test_parse_from_fid_invalid_raises_error() + integer :: fid + real(dp) :: value_real + integer :: value_int + logical :: value_log + character(len=80) :: value_char + fid = create_parse_test_file() + fid = openFile(TEST_FILE, SEQUENTIAL, IN_FORMATTED) + call initErrorHandler(control='YES', errAction='RETURN') + ! Skip to line "12.50 # real(DP) parsed as integer (should fail)" + call parse_from_fid(fid, '#', value_real, 'real(DP)') + call parse_from_fid(fid, '#', value_int, 'integer') + call parse_from_fid(fid, '#', value_log, 'logical true') + call parse_from_fid(fid, '#', value_log, 'logical false') + call parse_from_fid(fid, '#', value_char, 'short string') + call parse_from_fid(fid, '#', value_char, 'long string') + call parse_from_fid(fid, '#', value_char, 'long string with comment') + call parse_from_fid(fid, '#', value_real, 'integer parsed as real(DP)') + call parse_from_fid(fid, '#', value_int, 'real(DP) parsed as integer') + @assertTrue(getLatestError() /= 0) + call resetError() + fid = closeFile(fid) + end subroutine test_parse_from_fid_invalid_raises_error + + integer function create_parse_test_file() + integer :: u + u = openFile(TEST_FILE, SEQUENTIAL, OUT_FORMATTED_OVERWRITE) + write(u, '(A)') '12.50 # real(DP)' + write(u, '(A)') '7 # integer' + write(u, '(A)') 'T # logical true (string)' + write(u, '(A)') 'F # logical false (string)' + write(u, '(A)') "'String' # short string" + write(u, '(A)') "'This is a string' # long string" + write(u, '(A)') "'This is an entire line # long string with comment'" + write(u, '(A)') '7 # integer parsed as real(DP)' + write(u, '(A)') '12.50 # real(DP) parsed as integer (should fail)' + create_parse_test_file = closeFile(u) + end function create_parse_test_file + +end module test_slam_io diff --git a/pFUnittests/test_slamreduction.pf b/pFUnittests/test_slamreduction.pf index c6ad34a..780607f 100644 --- a/pFUnittests/test_slamreduction.pf +++ b/pFUnittests/test_slamreduction.pf @@ -465,7 +465,7 @@ module test_slamreduction ! Test case 1 : LEO r_ef = (/-3485.799126707284, -5898.652976745232, 835.9701786284777/) v_ef = (/-1.3525457950562447, -0.2804534841971075, -7.4721873681232385/) - mjd = 59868.16721065_dp + mjd = 59868.16721065_dp ! Call the subroutine to be tested call reduction_model%earthFixed2inertial_rv(r_ef, v_ef, mjd, r_inr, v_inr) diff --git a/src/inout/slam_error_handling.f90 b/src/inout/slam_error_handling.f90 index 5a89b0a..e756cbe 100644 --- a/src/inout/slam_error_handling.f90 +++ b/src/inout/slam_error_handling.f90 @@ -50,10 +50,10 @@ module slam_error_handling integer, parameter, public :: REMARK = 1 ! remark, meaning that expected results will still be achieved ! This message/error type can thus be seen as an information (INFO) integer, parameter, public :: DEBUG_MSG = 0 ! debug msg , meaning a message intended to be used for debugging purposes - integer, parameter, public :: ERR_UNDEFINED = -1 ! undefined error type + integer, parameter, public :: ERR_UNDEFINED = -1 ! undefined error type integer :: latestError = 0 ! indicating the latest error (0 = no error) - integer :: latestErrorType = ERR_UNDEFINED ! error type undefined as default (equivalent to 'no error occurred yet') + integer :: latestErrorType = ERR_UNDEFINED ! error type undefined as default (equivalent to 'no error occurred yet') !$omp threadprivate(latestError,latestErrorType) @@ -69,18 +69,17 @@ module slam_error_handling integer :: errorLanguage = ENGLISH ! english language as default - character(len=*), dimension(nlangs), parameter :: C_DEBUG_MSG = (/'DEBUG MSG ', & + character(len=*), dimension(nlangs), parameter, public :: C_DEBUG_MSG = (/'DEBUG ', & 'DEBUG-NACHRICHT'/) - - character(len=*), dimension(nlangs), parameter :: C_FATAL = (/'FATAL ERROR ', & + character(len=*), dimension(nlangs), parameter, public :: C_FATAL = (/'ERROR ', & 'SCHWERER FEHLER'/) - character(len=*), dimension(nlangs), parameter :: C_REMARK = (/'REMARK ', & + character(len=*), dimension(nlangs), parameter, public :: C_REMARK = (/'INFO ', & 'HINWEIS'/) - character(len=*), dimension(nlangs), parameter :: C_WARNING = (/'WARNING', & + character(len=*), dimension(nlangs), parameter, public :: C_WARNING = (/'WARN ', & 'WARNUNG'/) - character(len=*), dimension(nlangs), parameter :: C_TERMINATED = (/'+++ PROGRAM TERMINATED +++', & + character(len=*), dimension(nlangs), parameter, public :: C_TERMINATED = (/'+++ PROGRAM TERMINATED +++', & '+++ PROGRAMM BEENDET +++ '/) - character(len=*), dimension(nlangs), parameter :: C_TRACEBACK = (/'Traceback', & + character(len=*), dimension(nlangs), parameter, public :: C_TRACEBACK = (/'Traceback', & 'Traceback'/) !** verbosity @@ -91,12 +90,12 @@ module slam_error_handling integer, parameter, public :: DEBUG_MSGS = 3 ! print debug msgs, remarks, warnings and errors integer, parameter, public :: ALL_MSG = 4 ! print all messages - integer :: log_verbosity = ALL_MSG ! logfile verbosity level (all messages as default + integer :: log_verbosity = REMARKS ! logfile verbosity level (all messages as default ! 0 = error messages only ! 1 = errors and warnings ! 2 = errors, warnings and remarks ! 3 = all messages (incl. file open/close) - integer :: cli_verbosity = ERRORS ! CLI verbosity level (only error messages as default) + integer :: cli_verbosity = REMARKS ! CLI verbosity level (only error messages as default) ! 0 = error messages only ! 1 = errors and warnings ! 2 = errors, warnings and remarks @@ -219,6 +218,7 @@ module slam_error_handling public :: initErrorHandler public :: isControlled public :: isSetErrorHandling + public :: compile_log_record public :: getErrorMessage public :: setControlled public :: setErrorLanguage @@ -241,6 +241,7 @@ module slam_error_handling public :: printTrace public :: resetError public :: resetTrace + public :: write_log_message contains @@ -353,6 +354,8 @@ subroutine initErrorHandler(control, errAction, language, verbLog, verbCli, logf itemp = setLogVerbosity(WARNINGS) case('REMARKS') itemp = setLogVerbosity(REMARKS) + case('DEBUG_MSGS') + itemp = setLogVerbosity(DEBUG_MSGS) case default itemp = setLogVerbosity(ALL_MSG) end select @@ -371,6 +374,8 @@ subroutine initErrorHandler(control, errAction, language, verbLog, verbCli, logf itemp = setCliVerbosity(WARNINGS) case('REMARKS') itemp = setCliVerbosity(REMARKS) + case('DEBUG_MSGS') + itemp = setCliVerbosity(DEBUG_MSGS) case default itemp = setCliVerbosity(ALL_MSG) end select @@ -1316,20 +1321,22 @@ end subroutine resetTrace !============================================================================== ! !> @brief Set error code -!> @author Vitali Braun +!> @author Vitali Braun (VB) +!> @author Christopher Kebschull (CHK) !! !> @date !!