From d336a42db9bda1f9d73d4e8dbe2d65a343bda25c Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 20 May 2024 14:48:47 -0400 Subject: [PATCH 1/8] STEPWAT2 headers are no longer required - SOILWAT2 code no longer requires STEPWAT2 headers since v7.1.0 -> remove no longer required "ST_defines.h" --- src/SW_Output_outarray.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/SW_Output_outarray.c b/src/SW_Output_outarray.c index 8a0dd30f8..26fe45ebd 100644 --- a/src/SW_Output_outarray.c +++ b/src/SW_Output_outarray.c @@ -28,10 +28,6 @@ #include "include/SW_Times.h" #include "include/myMemory.h" -#ifdef STEPWAT -#include "ST_defines.h" -#endif - /* =================================================== */ /* Global Variables */ From e15f7eb61256438ef52c2377621621532f890e79 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 20 May 2024 14:58:31 -0400 Subject: [PATCH 2/8] Fix SW_OUT_create_files() - Call SW_OUT_create_textfiles() only if SOILWAT and SW_OUTTEXT -- that is to exclude the call if SW_OUTTEXT and STEPWAT --- src/SW_Output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SW_Output.c b/src/SW_Output.c index 4585e9acb..55bbf5e37 100644 --- a/src/SW_Output.c +++ b/src/SW_Output.c @@ -3049,7 +3049,7 @@ void SW_OUT_create_files( } #endif - #if defined(SW_OUTTEXT) + #if defined(SOILWAT) && defined(SW_OUTTEXT) SW_OUT_create_textfiles(SW_FileStatus, SW_Output, SW_Domain->nMaxSoilLayers, SW_Domain->PathInfo.InFiles, GenOutput, LogInfo); From 8678a8987b09f1014878c471992d414fd260f302 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 20 May 2024 17:21:11 -0400 Subject: [PATCH 3/8] Basic mocked R functions to compile as RSOILWAT library - new external/Rmock with basic headers R.h and Rmath.h - this now allows to check the compilation of SOILWAT2 as library as used by rSOILWAT2 (without actually compiling against R), e.g., `CPPFLAGS='-DRSOILWAT' CFLAGS='-Iexternal/Rmock' sw_sources='SW_Output_outarray.c' make clean libr` --- external/Rmock/R.h | 27 +++++++++++++++++++++++++++ external/Rmock/Rmath.h | 20 ++++++++++++++++++++ include/generic.h | 2 +- src/SW_Main_lib.c | 2 +- src/rands.c | 4 ++-- 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 external/Rmock/R.h create mode 100644 external/Rmock/Rmath.h diff --git a/external/Rmock/R.h b/external/Rmock/R.h new file mode 100644 index 000000000..145a2db6a --- /dev/null +++ b/external/Rmock/R.h @@ -0,0 +1,27 @@ +/* Mocking R header + Goal: standalone compile SOILWAT2 library for rSOILWAT2 for test purposes +*/ + +#ifndef R_RSOILWAT_H +#define R_RSOILWAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +// From +void Rprintf(const char *, ...); + +// From +void GetRNGstate(void); +void PutRNGstate(void); + +// From +void error(const char *, ...); +void warning(const char *, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/external/Rmock/Rmath.h b/external/Rmock/Rmath.h new file mode 100644 index 000000000..a2ef488be --- /dev/null +++ b/external/Rmock/Rmath.h @@ -0,0 +1,20 @@ +/* Mocking Rmath header + Goal: standalone compile SOILWAT2 library for rSOILWAT2 for test purposes +*/ + +#ifndef RMATH_RSOILWAT_H +#define RMATH_RSOILWAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +double rnorm(double, double); +double runif(double, double); +double unif_rand(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/generic.h b/include/generic.h index 05c055974..141818710 100644 --- a/include/generic.h +++ b/include/generic.h @@ -46,7 +46,7 @@ #include #ifdef RSOILWAT - #include + #include // for Rprintf() from #endif diff --git a/src/SW_Main_lib.c b/src/SW_Main_lib.c index 7783a9aa7..ace6d3be9 100644 --- a/src/SW_Main_lib.c +++ b/src/SW_Main_lib.c @@ -29,7 +29,7 @@ #include "include/myMemory.h" #ifdef RSOILWAT - #include // for error(), and warning() + #include // for error(), and warning() from #endif /* =================================================== */ diff --git a/src/rands.c b/src/rands.c index 06478b59d..6526b948c 100644 --- a/src/rands.c +++ b/src/rands.c @@ -14,8 +14,8 @@ // R-API requires that we use it's own random number implementation // see https://cran.r-project.org/doc/manuals/R-exts.html#Writing-portable-packages // and https://cran.r-project.org/doc/manuals/R-exts.html#Random-numbers - #include // for the random number generators - #include // for rnorm() + #include // for the R random number generators from , ie., GetRNGstate() and PutRNGstate() + #include // for rnorm(), runif(), unif_rand() #else #include "external/pcg/pcg_basic.h" From 8b3819367fc3bfcac73e83a235c8f99b8628e917 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 20 May 2024 17:25:11 -0400 Subject: [PATCH 4/8] GHA: now also compile as library --- .github/workflows/main_nix.yml | 6 ++++++ .github/workflows/main_win.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/main_nix.yml b/.github/workflows/main_nix.yml index 242a5d68a..362607ad4 100644 --- a/.github/workflows/main_nix.yml +++ b/.github/workflows/main_nix.yml @@ -49,6 +49,12 @@ jobs: if: ${{ runner.os != 'macOS' }} run: make clean test_sanitizer + - name: Compile as library for STEPWAT2 + run: CPPFLAGS='-DSTEPWAT' sw_sources='SW_Output_outarray.c SW_Output_outtext.c' make clean lib + + - name: Compile as library for rSOILWAT2 + run: CPPFLAGS='-DRSOILWAT' CFLAGS='-Iexternal/Rmock' sw_sources='SW_Output_outarray.c' make clean libr + build_ncSW2: runs-on: ubuntu-latest diff --git a/.github/workflows/main_win.yml b/.github/workflows/main_win.yml index 67974db47..c90d9bda0 100644 --- a/.github/workflows/main_win.yml +++ b/.github/workflows/main_win.yml @@ -44,3 +44,9 @@ jobs: - name: Unit tests (shuffle and repeat 3x) run: make clean test_rep3rnd + + - name: Compile as library for STEPWAT2 + run: CPPFLAGS='-DSTEPWAT' sw_sources='SW_Output_outarray.c SW_Output_outtext.c' make clean lib + + - name: Compile as library for rSOILWAT2 + run: CPPFLAGS='-DRSOILWAT' CFLAGS='-Iexternal/Rmock' sw_sources='SW_Output_outarray.c' make clean libr From 10322ac4bd5579720e240e4077a18b9c2ae7b611 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 22 May 2024 12:15:08 -0400 Subject: [PATCH 5/8] Simplify compilation as library - make targets now include all SOILWAT2 source files, i.e., other repositories no longer need to know which files to pass via "sw_sources" - fix preprocessor #ifdef blocks - update GHAs: remove "sw_sources" when compiling as library --- .github/workflows/main_nix.yml | 5 +++-- makefile | 20 ++++++-------------- src/SW_Output_outarray.c | 15 +++++++++++++++ src/SW_Output_outtext.c | 2 +- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/main_nix.yml b/.github/workflows/main_nix.yml index 362607ad4..8a593d268 100644 --- a/.github/workflows/main_nix.yml +++ b/.github/workflows/main_nix.yml @@ -50,10 +50,11 @@ jobs: run: make clean test_sanitizer - name: Compile as library for STEPWAT2 - run: CPPFLAGS='-DSTEPWAT' sw_sources='SW_Output_outarray.c SW_Output_outtext.c' make clean lib + run: CPPFLAGS='-DSTEPWAT' make clean lib - name: Compile as library for rSOILWAT2 - run: CPPFLAGS='-DRSOILWAT' CFLAGS='-Iexternal/Rmock' sw_sources='SW_Output_outarray.c' make clean libr + # Rmock/ provides bare-bones headers of R C-API functions required by SOILWAT2 + run: CPPFLAGS='-DRSOILWAT' CFLAGS='-Iexternal/Rmock' make clean libr build_ncSW2: diff --git a/makefile b/makefile index 5f788e54c..dbab25ef1 100644 --- a/makefile +++ b/makefile @@ -18,7 +18,8 @@ # make doc_open open documentation # # --- SOILWAT2 library ------ -# make lib create SOILWAT2 library +# make lib create SOILWAT2 library (utilized by SOILWAT2 and STEPWAT2) +# make libr create SOILWAT2 library (utilized by rSOILWAT2) # # --- Tests ------ # make test create a test binary executable that includes the unit tests @@ -285,12 +286,6 @@ gmock_LDLIBS := -l$(gmock) #------ CODE FILES -# sw_sources is used by STEPWAT2 and rSOILWAT2 to pass relevant output code file -ifneq ($(origin sw_sources), undefined) - # Add source path - sw_sources := $(sw_sources:%.c=$(dir_src)/%.c) -endif - # SOILWAT2 files sources_core := \ $(dir_src)/SW_Main_lib.c \ @@ -315,18 +310,15 @@ sources_core := \ $(dir_src)/SW_Carbon.c \ $(dir_src)/SW_Domain.c \ $(dir_src)/SW_Output.c \ - $(dir_src)/SW_Output_get_functions.c + $(dir_src)/SW_Output_get_functions.c \ + $(dir_src)/SW_Output_outarray.c \ + $(dir_src)/SW_Output_outtext.c ifdef SWNETCDF sources_core += $(dir_src)/SW_netCDF.c -sources_core += $(dir_src)/SW_Output_outarray.c -else -sources_core += $(dir_src)/SW_Output_outtext.c endif -sources_lib = \ - $(sw_sources) \ - $(sources_core) +sources_lib = $(sources_core) objects_lib = $(sources_lib:$(dir_src)/%.c=$(dir_build_sw2)/%.o) diff --git a/src/SW_Output_outarray.c b/src/SW_Output_outarray.c index 26fe45ebd..bb26cfe30 100644 --- a/src/SW_Output_outarray.c +++ b/src/SW_Output_outarray.c @@ -136,10 +136,12 @@ void SW_OUT_deconstruct_outarray(SW_GEN_OUT *GenOutput) ForEachOutKey(k) { for (i = 0; i < SW_OUTNPERIODS; i++) { + #if defined(SW_OUTARRAY) if (!isnull(GenOutput->p_OUT[k][i])) { free(GenOutput->p_OUT[k][i]); GenOutput->p_OUT[k][i] = NULL; } + #endif #ifdef STEPWAT if (!isnull(GenOutput->p_OUTsd[k][i])) { @@ -149,6 +151,10 @@ void SW_OUT_deconstruct_outarray(SW_GEN_OUT *GenOutput) #endif } } + + #if !defined(SW_OUTARRAY) && !defined(STEPWAT) + (void) *GenOutput; + #endif } @@ -233,6 +239,7 @@ void SW_OUT_construct_outarray(SW_GEN_OUT *GenOutput, SW_OUTPUT* SW_Output, if (SW_Output[k].use && timeStepOutPeriod != eSW_NoTime) { + #if defined(SW_OUTARRAY) size = GenOutput->nrow_OUT[timeStepOutPeriod] * (GenOutput->ncol_OUT[k] + ncol_TimeOUT[timeStepOutPeriod]); @@ -245,6 +252,7 @@ void SW_OUT_construct_outarray(SW_GEN_OUT *GenOutput, SW_OUTPUT* SW_Output, if(LogInfo->stopRun) { return; // Exit function prematurely due to error } + #endif #if defined(STEPWAT) GenOutput->p_OUTsd[k][timeStepOutPeriod] = (RealD *) Mem_Calloc( @@ -260,6 +268,13 @@ void SW_OUT_construct_outarray(SW_GEN_OUT *GenOutput, SW_OUTPUT* SW_Output, } } } + + + #if !defined(SW_OUTARRAY) && !defined(STEPWAT) + (void) *LogInfo; + (void) s; + (void) size; + #endif } diff --git a/src/SW_Output_outtext.c b/src/SW_Output_outtext.c index 9c8b7499e..8c31cf39d 100644 --- a/src/SW_Output_outtext.c +++ b/src/SW_Output_outtext.c @@ -330,7 +330,7 @@ static void _create_csv_file_ST(int iteration, OutPeriod pd, char *InFiles[], -#if defined(SOILWAT) +#if defined(SOILWAT) && !defined(SWNETCDF) /** @brief create all of the user-specified output text files. * From 646a4c24d68ae9757fbae68995d4d30f87664ff0 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 22 May 2024 14:33:03 -0400 Subject: [PATCH 6/8] Overhauled scripts for comprehensive checks - tools/check_functionality.sh -- collection of functions that are useful for various checks of SOILWAT2 (that are used by other scripts or can be directly utilized) - tools/check_withCompilers.sh -- (renamed from check_SOILWAT2.sh) runs checks for several different compilers (based on macports) - tools/check_outputModes.sh -- (renamed from run_compare_outputs.sh) runs and compares text-based, nc-based, and R-based SOILWAT2 outputs --- .gitignore | 2 + tools/check_SOILWAT2.sh | 386 ----------------- tools/check_functionality.sh | 402 ++++++++++++++++++ tools/check_outputModes.sh | 41 ++ tools/check_withCompilers.sh | 125 ++++++ tools/many_test_runs.sh | 21 - .../Rscript__SW2_output_txt_vs_r_vs_nc.R} | 12 +- tools/run_compare_outputs.sh | 32 -- 8 files changed, 576 insertions(+), 445 deletions(-) delete mode 100755 tools/check_SOILWAT2.sh create mode 100755 tools/check_functionality.sh create mode 100755 tools/check_outputModes.sh create mode 100755 tools/check_withCompilers.sh delete mode 100755 tools/many_test_runs.sh rename tools/{check__output_txt_vs_r_vs_nc.R => rscripts/Rscript__SW2_output_txt_vs_r_vs_nc.R} (97%) delete mode 100755 tools/run_compare_outputs.sh diff --git a/.gitignore b/.gitignore index 0cb4f9592..506a23ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ # Figures created by scripts in tools/ /tools/Fig* /tools/*.pdf +/tools/figures/ +/tools/figures* # Google unit test output gtest-*.o diff --git a/tools/check_SOILWAT2.sh b/tools/check_SOILWAT2.sh deleted file mode 100755 index e924c2230..000000000 --- a/tools/check_SOILWAT2.sh +++ /dev/null @@ -1,386 +0,0 @@ -#!/bin/bash - -# Runs several tests for SOILWAT2 -# - is currently set up for use with macports -# - expects clang as default compiler - - -# Notes: -# - googletests (July 2023) requires a C++14 compliant compilers, -# gcc >= 7.3.1 or clang >= 7.0.0, -# and POSIX API (e.g., `_POSIX_C_SOURCE=200809L`) -# which is not enabled by default on all systems -# (https://google.github.io/googletest/platforms.html) - - -#------ USER SETTINGS ---------------------------------------------------------- - -#--- Name of reference output -dir_out_ref="tests/example/Output_ref" - - -#--- Test with all compilers (true) or with only one recent version per type (false) -use_all_compilers=true # options: true false - - -#--- Verbosity on failure -# * on error: if true, print entire output -# * on error: if false (default), print only selected parts that include the error message -verbosity_on_error=false # options: true false - - -#------ SETTINGS --------------------------------------------------------------- - -#--- List of (builtin and macport) compilers - -if [ "${use_all_compilers}" = true ]; then - declare -a port_compilers=( - "default compiler" - "mp-gcc10" "mp-gcc11" "mp-gcc12" "mp-gcc13" - "mp-clang-10" "mp-clang-11" "mp-clang-12" "mp-clang-13" "mp-clang-14" "mp-clang-15" "mp-clang-16" "mp-clang-17" - ) - declare -a ccs=( - "clang" - "gcc" "gcc" "gcc" "gcc" - "clang" "clang" "clang" "clang" "clang" "clang" "clang" "clang" - ) - declare -a cxxs=( - "clang++" - "g++" "g++" "g++" "g++" - "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" - ) -else - declare -a port_compilers=("default compiler" "mp-gcc12" "mp-clang-17") - declare -a ccs=("clang" "gcc" "clang") - declare -a cxxs=("clang++" "g++" "clang++") -fi - -ncomp=${#ccs[@]} - - - -#------ FUNCTIONS -------------------------------------------------------------- - -#--- Function to compare output against reference -compare_output_with_tolerance () { - if command -v Rscript >/dev/null 2>&1; then - Rscript \ - -e 'dir_out_ref <- as.character(commandArgs(TRUE)[[1L]])' \ - -e 'fnames <- list.files(path = dir_out_ref, pattern = ".csv$")' \ - -e 'res <- vapply(fnames, function(fn) isTRUE(all.equal(read.csv(file.path(dir_out_ref, fn)), read.csv(file.path("tests/example/Output", fn)))), FUN.VALUE = NA)' \ - -e 'if (!all(res)) for (k in which(!res)) cat(shQuote(fnames[[k]]), "and reference differ beyond tolerance.\n")' \ - "${dir_out_ref}" - else - echo "R is not installed: cannot compare output against reference with tolerance." - fi -} - -compare_output_against_reference () { - if diff -q -x "\.DS_Store" -x "\.gitignore" tests/example/Output/ "${dir_out_ref}"/ >/dev/null 2>&1; then - echo "Simulation: success: output reproduces reference exactly." - else - res=$(compare_output_with_tolerance) - if [ "${res}" ]; then - echo "Simulation: failure: output deviates beyond tolerance from reference:" - echo "${res}" - else - echo "Simulation: success: output reproduces reference within tolerance but not exactly:" - fi - - diff -qs -x "\.DS_Store" -x "\.gitignore" tests/example/Output/ "${dir_out_ref}"/ - fi -} - - -#--- Function to check for errors (but avoid false hits) -# $1 Text string -# Returns lines that contain "failed", "abort", "trap", "fault", " not ", or "error" -# (and exclude "Werror", "Wno-error", "default", and -# any unit test name: "*Test.Errors", "RNGBetaErrorsDeathTest") -check_error() { - echo "$1" | awk 'tolower($0) ~ /[^-.w]error|failed|abort|trap|[^e]fault|( not )/ && !/RNGBetaErrorsDeathTest/ && !/WarningsAndErrors/ && !/FailOnErrorDeath/' -} - -#--- Function to check for leaks (but avoid false hits) -# $1 Text string -# Returns lines that contain "leak" -# (besides name of the command, report of no leaks, or -# any unit test name: "WeatherNoMemoryLeakIfDecreasedNumberOfYears") -check_leaks() { - echo "$1" | awk 'tolower($0) ~ /leak/ && !/leaks Report/ && !/ 0 leaks/ && !/debuggable/ && !/NoMemoryLeak/ && !/leaks.sh/' -} - - -#--- Function to check if a command exists -# $1 name of command -exists() { - command -v "$1" >/dev/null 2>&1 -} - - -#------ MAIN ------------------------------------------------------------------- - -#--- Loop through tests and compilers -for ((k = 0; k < ncomp; k++)); do - echo $'\n'$'\n'$'\n'\ - ==================================================$'\n'\ - ${k}") Test SOILWAT2 with compiler "\'"${port_compilers[k]}"\'$'\n'\ - ================================================== - - echo $'\n'"Set compiler ..." - res="" - if [ "${port_compilers[k]}" = "default compiler" ]; then - res=$(sudo port select --set ${ccs[k]} none) - else - res=$(sudo port select --set ${ccs[k]} ${port_compilers[k]}) - fi - - if ( echo "${res}" | grep "failed" ); then - continue - fi - - CC=${ccs[k]} CXX=${cxxs[k]} make compiler_version - - - echo $'\n'$'\n'\ - --------------------------------------------------$'\n'\ - ${k}"-a) Run example simulation with "\'"${port_compilers[k]}"\'$'\n'\ - -------------------------------------------------- - - echo $'\n'"Target 'bin_run' ..." - res=$(CC=${ccs[k]} make clean bin_run 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - echo "Target: success." - - if [ ${k} -eq 0 ]; then - if [ ! -d "${dir_out_ref}" ]; then - echo $'\n'"Save default testing output as reference for future comparisons" - cp -r tests/example/Output "${dir_out_ref}" - fi - fi - - compare_output_against_reference - fi - - echo $'\n'"Target 'bin_debug_severe' ..." - res=$(CC=${ccs[k]} make clean bin_debug_severe 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - echo "Target: success." - compare_output_against_reference - fi - - - if exists valgrind ; then - echo $'\n'"Target 'bind_valgrind' ..." - res=$(CC=${ccs[k]} CXX=${cxxs[k]} make clean bind_valgrind) - fi - - - echo $'\n'$'\n'\ - --------------------------------------------------$'\n'\ - ${k}"-b) Run sanitizers on example simulation with "\'"${port_compilers[k]}"\'$'\n'\ - -------------------------------------------------- - - echo $'\n'"Target 'bin_sanitizer' ..." - res=$(CC=${ccs[k]} make clean bin_sanitizer 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - echo "Target: success." - fi - - - - if exists leaks ; then - - echo $'\n'"Target: 'bin_leaks' ..." - res=$(CC=${ccs[k]} make clean all 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - res=$(make bin_leaks 2>&1) - - res_leaks=$(check_leaks "${res}") - if [ "${res_leaks}" ]; then - echo "Target: failure: leaks detected:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_leaks}" - fi - - else - echo "Target: success: no leaks detected" - fi - fi - - else - echo "Target: skipped: 'leaks' command not found." - fi - - - - echo $'\n'$'\n'\ - --------------------------------------------------$'\n'\ - ${k}"-c) Run tests with "\'"${port_compilers[k]}"\'$'\n'\ - -------------------------------------------------- - - echo $'\n'"Target 'test_run' ..." - res=$(CXX=${cxxs[k]} make clean test_run 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - echo "Target: success." - fi - - - echo $'\n'"Target 'test_severe' ..." - res=$(CXX=${cxxs[k]} make clean test_severe 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - do_target_test_sanitizer=false - - else - echo "Target: success." - - do_target_test_sanitizer=true - fi - - - - echo $'\n'$'\n'\ - --------------------------------------------------$'\n'\ - ${k}"-d) Run sanitizers on tests with "\'"${port_compilers[k]}"\'$'\n'\ - -------------------------------------------------- - - if [ "${do_target_test_sanitizer}" = true ]; then - # CXX=${cxxs[k]} ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe test_run - # https://github.com/google/sanitizers/wiki/AddressSanitizer - echo $'\n'"Target 'test_sanitizer' ..." - res=$(CXX=${cxxs[k]} make clean test_sanitizer 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - echo "Target: success." - fi - - else - echo "Target: skipped: 'test_sanitizer' not operational for current compiler." - fi - - - if exists leaks ; then - - echo $'\n'"Target: 'test_leaks' ..." - res=$(CXX=${cxxs[k]} make clean test 2>&1) - - res_error=$(check_error "${res}") - if [ "${res_error}" ]; then - echo "Target: failure:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_error}" - fi - - else - res=$(make test_leaks 2>&1) - - res_leaks=$(check_leaks "${res}") - if [ "${res_leaks}" ]; then - echo "Target: failure: leaks detected:" - if [ "${verbosity_on_error}" = true ]; then - echo "${res}" - else - echo "${res_leaks}" - fi - - else - echo "Target: success: no leaks detected" - fi - fi - - else - echo "Target: skipped: 'leaks' command not found." - fi - - - echo $'\n'$'\n'\ - --------------------------------------------------$'\n'\ - "Unset compiler"$'\n'\ - -------------------------------------------------- - - sudo port select --set ${ccs[k]} none - -done - - -#------ CLEANUP ---------------------------------------------------------------- -unset -v ccs -unset -v cxxs -unset -v port_compilers - -echo $'\n'$'\n'\ - --------------------------------------------------$'\n'\ - "Complete!"$'\n'\ - --------------------------------------------------$'\n' diff --git a/tools/check_functionality.sh b/tools/check_functionality.sh new file mode 100755 index 000000000..81e91e94f --- /dev/null +++ b/tools/check_functionality.sh @@ -0,0 +1,402 @@ +#!/bin/bash + + +#------ . ------ +# Collection of functions that are useful for various checks of SOILWAT2 +# +# These functions are used by other scripts and they can be called directly. +# For instance, +# bash tools/check_functionality.sh check_SOILWAT2 "CC=" "CXX=" "txt" "tests/example/Output_ref" "false" +# bash tools/check_functionality.sh run_fresh_sw2 "CC=" noflags[@] "txt" "bin_run" +#------ . ------ + + + +#------ FUNCTIONS -------------------------------------------------------------- + + +#--- Function to check if a command exists +# $1 name of command +exists() { + command -v "$1" > /dev/null 2>&1 +} + +#--- Function to clean example inputs +clean_example_inputs() { + rm tests/example/Input_nc/progress.nc + rm tests/example/Input_nc/domain.nc + rm tests/example/Input_nc/domain_template.nc +} + +#--- Function to delete example output +clean_example_outputs() { + rm -r tests/example/Output +} + +#--- Function to run example SOILWAT2 simulation with fresh inputs +# $1 Compiler, e.g., CC=clang, CXX=g++, or (if not specify a compiler) CC= +# $2 Array of compiler flags, e.g., CPPFLAGS='-DDEBUG' +# $3 SOILWAT2 mode: txt or nc +# $4 Make target to run +run_fresh_sw2() { + local res="" status="" + + local compiler="$1" + local -a mflags=("${!2}") # copy content of array + local mode="$3" + local target="$4" + + if [ "${compiler}" != "CC=" ] && [ "${compiler}" != "CXX=" ]; then + if [ ${#mflags[@]} -eq 0 ] || [ -z "${mflags[0]}" ]; then + # array is either empty or its first value is an empty string + mflags=("${compiler}") + else + mflags+=("${compiler}") + fi + fi + + if [ "${mode}" = "nc" ]; then + mflags+=("CPPFLAGS='-DSWNETCDF -DSWUDUNITS'") + fi + + clean_example_inputs > /dev/null 2>&1 + clean_example_outputs > /dev/null 2>&1 + + res=$(make "${mflags[@]}" clean "${target}" 2>&1) + status=$? + + if [[ "$res" == *"Domain netCDF template has been created"* ]]; then + # Re-run after copying domain template as domain + mv tests/example/Input_nc/domain_template.nc tests/example/Input_nc/domain.nc > /dev/null 2>&1 + res=$(make "${mflags[@]}" "${target}" 2>&1) + status=$? + fi + + if [ $status -eq 0 ]; then + echo "${res}" + else + echo "make failed: ${res}" + fi +} + + +#--- Function to compare output against reference +compare_output_with_R () { + if exists Rscript ; then + local mode="$1" + local dirOutRef="$2" + + if [ "${mode}" = "txt" ]; then + Rscript \ + -e 'dor <- as.character(commandArgs(TRUE)[[1L]])' \ + -e 'fnames <- list.files(path = dor, pattern = ".csv$")' \ + -e 'res <- vapply(fnames, function(fn) isTRUE(all.equal(utils::read.csv(file.path(dor, fn)), utils::read.csv(file.path("tests/example/Output", fn)))), FUN.VALUE = NA)' \ + -e 'if (!all(res)) for (k in which(!res)) cat(shQuote(fnames[[k]]), "and reference differ beyond tolerance.\n")' \ + "${dirOutRef}" + + else + Rscript \ + -e 'dor <- as.character(commandArgs(TRUE)[[1L]])' \ + -e 'fnames <- list.files(path = dor, pattern = ".nc$")' \ + -e 'res <- vapply(fnames, function(fn) {'\ + -e ' nc1 <- RNetCDF::open.nc(file.path(dor, fn)); on.exit(RNetCDF::close.nc(nc1), add = TRUE)' \ + -e ' nc2 <- RNetCDF::open.nc(file.path("tests/example/Output", fn)); on.exit(RNetCDF::close.nc(nc2), add = TRUE)' \ + -e ' isTRUE(all.equal(RNetCDF::read.nc(nc1), RNetCDF::read.nc(nc2)))' \ + -e ' }, FUN.VALUE = NA)' \ + -e 'if (!all(res)) for (k in which(!res)) cat(shQuote(fnames[[k]]), "and reference differ beyond tolerance.\n")' \ + "${dirOutRef}" + fi + + else + echo "R is not installed: cannot compare output against reference with tolerance." + fi +} + +compare_output_against_reference () { + local mode="$1" + local dirOutRef="$2" + local verbosity="$3" + + if diff -q -x "\.DS_Store" -x "\.gitignore" tests/example/Output/ "${dirOutRef}"/ > /dev/null 2>&1; then + echo "Simulation: success: output reproduces reference exactly." + + else + local res=$(compare_output_with_R "${mode}" "${dirOutRef}") + if [ "${res}" ]; then + echo "Simulation: failure: output deviates beyond tolerance from reference:" + echo "${res}" + else + echo "Simulation: success: output reproduces reference within tolerance but not exactly" + fi + + if [ "${verbosity}" = true ]; then + local res=$(diff -qs -x "\.DS_Store" -x "\.gitignore" tests/example/Output/ "${dirOutRef}"/) + echo "${res}" | awk '{ if($NF == "differ") print }' + fi + fi +} + +#--- Function to check of sanitizers are supported +# $1 Text string +check_has_sanitizer() { + if [[ "$1" == *"detect_leaks is not supported"* ]]; then + echo "Sanitizers (detect_leaks) are not supported." + return 1 + else + return 0 + fi +} + + +#--- Function to check for errors (but avoid false hits) +# $1 Text string +# Returns lines that contain "failed", "abort", "trap", "fault", " not ", or "error" +# Exclusions +# * flags: "Werror", "Wno-error", "default", +# * unit test names: "*Test.Errors", "RNGBetaErrorsDeathTest", +# * program message: "Domain netCDF template has been created" (skip entire check) +check_error() { + if [[ "$1" != *"Domain netCDF template has been created"* ]]; then + echo "$1" | awk 'tolower($0) ~ /[^-.w]error|failed|abort|trap|[^e]fault|( not )/ && !/RNGBetaErrorsDeathTest/ && !/WarningsAndErrors/ && !/FailOnErrorDeath/' + fi +} + +#--- Function to report on errors and return whether any were found +# $1 Text string to check for errors +# $2 Verbosity (true, false) +# Returns 0 if no errors found; returns 1 if any errors were found +report_if_error() { + local x="$1" + local verbosity="$2" + + check_has_sanitizer "${x}" + local has_sanitizer=$? + + if [ $has_sanitizer -eq 0 ]; then + local res=$(check_error "${x}") + + if [ "${res}" ]; then + echo "Target: failure:" + if [ "${verbosity}" = true ]; then + echo "${x}" + else + echo "${res}" + fi + return 1 # return non-zero if errors were found + else + echo "Target: success." + return 0 # return zero if no errors were found + fi + + else + return 0 # return zero if sanitizer is not available + fi +} + + +#--- Function to check for leaks (but avoid false hits) +# $1 Text string +# Returns lines that contain "leak" +# (besides name of the command, report of no leaks, or +# any unit test name: "WeatherNoMemoryLeakIfDecreasedNumberOfYears") +check_leaks() { + echo "$1" | awk 'tolower($0) ~ /leak/ && !/leaks Report/ && !/ 0 leaks/ && !/debuggable/ && !/NoMemoryLeak/ && !/leaks.sh/' +} + +#--- Function to report on leaks and return whether any were found +# $1 Text string to check for leaks +# $2 Verbosity (true, false) +# Returns 0 if no leak found; returns 1 if leaks were found +report_if_leak() { + local x="$1" + local verbosity="$2" + + check_has_sanitizer "${x}" + local has_sanitizer=$? + + if [ $has_sanitizer -eq 0 ]; then + local res=$(check_leaks "${x}") + + if [ "${res}" ]; then + echo "Target: failure: leaks detected:" + if [ "${verbosity}" = true ]; then + echo "${x}" + else + echo "${res}" + fi + return 1 # return non-zero if leaks were found + + else + echo "Target: success: no leaks detected" + return 0 # return zero if no leak found + fi + + else + return 0 # return zero if sanitizer is not available + fi +} + + +#--- Function that compile, run, and check several SOILWAT2 targets +# $1 CC compiler, e.g., CC=clang +# $2 CXX compiler, e.g., CXX=clang++ +# $3 SOILWAT2 output mode, i.e., "txt" or "nc" +# $4 Path to the reference output +# $5 Should error messages be verbose, i.e., "true" or "false" +check_SOILWAT2() { + local ccomp="$1" + local cxxcomp="$2" + local mode="$3" + local dirOutRefBase="$4" + local verbosity="$5" + + local res="" status="" has_sanitizers="" + local -a aflags=() + + local dirOutRef="${dirOutRefBase}-${mode}" + + + echo $'\n'\ +--------------------------------------------------$'\n'\ +"Compile ""${mode}""-based library"$'\n'\ +-------------------------------------------------- + + echo $'\n'"Target 'lib' for SOILWAT2 ..." + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" lib) + report_if_error "${res}" "${verbosity}" + + if [ "${mode}" = "txt" ]; then + echo $'\n'"Target 'lib' for rSOILWAT2 ..." + aflags=("CPPFLAGS=-DRSOILWAT" "CFLAGS=-Iexternal/Rmock") + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" libr) + aflags=() + report_if_error "${res}" "${verbosity}" + + echo $'\n'"Target 'lib' for STEPWAT2 ..." + aflags=("CPPFLAGS=-DSTEPWAT") + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" lib) + aflags=() + report_if_error "${res}" "${verbosity}" + fi + + + echo $'\n'\ +--------------------------------------------------$'\n'\ +"Run ""${mode}""-based example simulation"$'\n'\ +-------------------------------------------------- + + echo $'\n'"Target 'bin_run' ..." + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" bin_run) + + report_if_error "${res}" "${verbosity}" + status=$? + + if [ $status -eq 0 ]; then + if [ ! -d "${dirOutRef}" ]; then + echo $'\n'"Save output from example simulation as reference for future comparisons" + cp -r tests/example/Output "${dirOutRef}" + fi + + compare_output_against_reference "${mode}" "${dirOutRef}" "${verbosity}" + fi + + echo $'\n'"Target 'bin_debug_severe' ..." + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" bin_debug_severe) + + report_if_error "${res}" "${verbosity}" + status=$? + if [ $status -eq 0 ]; then + compare_output_against_reference "${mode}" "${dirOutRef}" "${verbosity}" + fi + + + echo $'\n'"Target 'bin_sanitizer' ..." + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" bin_sanitizer) + report_if_error "${res}" "${verbosity}" + has_sanitizers=$? + + + if exists leaks ; then + echo $'\n'"Target: 'bin_leaks' ..." + res=$(run_fresh_sw2 "${ccomp}" aflags[@] "${mode}" all) + + report_if_error "${res}" "${verbosity}" + status=$? + if [ $status -eq 0 ]; then + + res=$(make bin_leaks 2>&1) + report_if_leak "${res}" "${verbosity}" + fi + + else + echo "Target: skipped: 'leaks' command not available." + fi + + + + echo $'\n'$'\n'\ + --------------------------------------------------$'\n'\ + "Run ""${mode}""-based tests"$'\n'\ + -------------------------------------------------- + + echo $'\n'"Target 'test_run' ..." + res=$(run_fresh_sw2 "${cxxcomp}" aflags[@] "${mode}" test_run) + report_if_error "${res}" "${verbosity}" + + + echo $'\n'"Target 'test_rep3rnd' ..." + res=$(run_fresh_sw2 "${cxxcomp}" aflags[@] "${mode}" test_rep3rnd) + report_if_error "${res}" "${verbosity}" + + + if [ $has_sanitizers -eq 0 ]; then + echo $'\n'"Target 'test_severe' ..." + res=$(run_fresh_sw2 "${cxxcomp}" aflags[@] "${mode}" test_severe) + report_if_error "${res}" "${verbosity}" + has_sanitizers=$? + else + echo "Target: skipped: 'test_severe' not operational for current compiler." + fi + + + if [ $has_sanitizers -eq 0 ]; then + # CXX=clang++ ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe test_run + # https://github.com/google/sanitizers/wiki/AddressSanitizer + echo $'\n'"Target 'test_sanitizer' ..." + res=$(run_fresh_sw2 "${cxxcomp}" aflags[@] "${mode}" test_sanitizer) + report_if_error "${res}" "${verbosity}" + else + echo "Target: skipped: 'test_sanitizer' not operational for current compiler." + fi + + + if exists leaks ; then + + echo $'\n'"Target: 'bin_leaks' ..." + res=$(run_fresh_sw2 "${cxxcomp}" aflags[@] "${mode}" test) + + report_if_error "${res}" "${verbosity}" + status=$? + if [ $status -eq 0 ]; then + res=$(make test_leaks 2>&1) + report_if_leak "${res}" "${verbosity}" + fi + + else + echo "Target: skipped: 'leaks' command not available." + fi +} + + + +#---- In case we want to execute a function directly from this script +# Check if there was an argument and if it is an existing function +if [ ! -z "$1" ]; then + if declare -f "$1"> /dev/null ; then + # call arguments verbatim + "$@" + else + echo "'$1' is not an implemented function." >&2 + exit 1 + fi +fi diff --git a/tools/check_outputModes.sh b/tools/check_outputModes.sh new file mode 100755 index 000000000..4d27697fc --- /dev/null +++ b/tools/check_outputModes.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +#------ . ------ +# Compare SOILWAT2 output among text-based, nc-based, and rSOILWAT2 runs +# +# Run this script with `tools/check_outputModes.sh` +#------ . ------ + + +#------ FUNCTIONS -------------------------------------------------------------- +# Import functions +myDir=$(dirname ${BASH_SOURCE[0]}) # directory of this script + +source "${myDir}/check_functionality.sh" + + +#------ MAIN ------------------------------------------------------------------- +declare -a noflags=() + +echo "Run text-based SOILWAT2 on example simulation ..." +rm -r tests/example/Output_comps-txt > /dev/null 2>&1 +run_fresh_sw2 "CC=" noflags[@] "txt" "bin_run" > /dev/null 2>&1 +cp -R tests/example/Output tests/example/Output_comps-txt > /dev/null 2>&1 + +echo "Run nc-based SOILWAT2 on example simulation ..." +rm -r tests/example/Output_comps-nc > /dev/null 2>&1 +run_fresh_sw2 "CC=" noflags[@] "nc" "bin_run" > /dev/null 2>&1 +cp -R tests/example/Output tests/example/Output_comps-nc > /dev/null 2>&1 +clean_example_inputs > /dev/null 2>&1 + +unset noflags + +echo "Compare output among text-based, nc-based, and rSOILWAT2 runs:" +res=$(Rscript tools/rscripts/Rscript__SW2_output_txt_vs_r_vs_nc.R 2>&1) + +echo "${res}" + +# Fail if not success +if [[ "${res}" != *"Success"* ]]; then + exit 1 +fi diff --git a/tools/check_withCompilers.sh b/tools/check_withCompilers.sh new file mode 100755 index 000000000..b3189559f --- /dev/null +++ b/tools/check_withCompilers.sh @@ -0,0 +1,125 @@ +#!/bin/bash + +#------ . ------ +# Run SOILWAT2 checks with several compilers +# +# Run this script with `tools/check_withCompilers.sh` +#------ . ------ + +# Runs several tests for SOILWAT2 +# - is currently set up for use with macports +# - expects clang as default compiler + + +# Notes: +# - googletests (July 2023) requires a C++14 compliant compilers, +# gcc >= 7.3.1 or clang >= 7.0.0, +# and POSIX API (e.g., `_POSIX_C_SOURCE=200809L`) +# which is not enabled by default on all systems +# (https://google.github.io/googletest/platforms.html) + + +#------ USER SETTINGS ---------------------------------------------------------- + +#--- Name of reference output (code will append -txt or -nc) +dirOutRefBase="tests/example/Output_v700" + + +#--- Test with all compilers (true) or with only one recent version per type (false) +use_all_compilers=false # options: true false + + +#--- Verbosity on failure +# * on error: if true, print entire output +# * on error: if false (default), print only selected parts that include the error message +verbosity_on_error=false # options: true false + + +#------ SETTINGS --------------------------------------------------------------- + +#--- List of (builtin and macport) compilers + +if [ "${use_all_compilers}" = true ]; then + declare -a port_compilers=( + "default compiler" + "mp-gcc10" "mp-gcc11" "mp-gcc12" "mp-gcc13" + "mp-clang-10" "mp-clang-11" "mp-clang-12" "mp-clang-13" "mp-clang-14" "mp-clang-15" "mp-clang-16" "mp-clang-17" "mp-clang-18" + ) + declare -a ccs=( + "clang" + "gcc" "gcc" "gcc" "gcc" + "clang" "clang" "clang" "clang" "clang" "clang" "clang" "clang" "clang" + ) + declare -a cxxs=( + "clang++" + "g++" "g++" "g++" "g++" + "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" + ) +else + declare -a port_compilers=("default compiler" "mp-gcc12" "mp-clang-17") + declare -a ccs=("clang" "gcc" "clang") + declare -a cxxs=("clang++" "g++" "clang++") +fi + +ncomp=${#ccs[@]} + + + +#------ FUNCTIONS -------------------------------------------------------------- + +# Import functions +myDir=$(dirname ${BASH_SOURCE[0]}) # directory of this script + +source "${myDir}/check_functionality.sh" + + +#------ MAIN ------------------------------------------------------------------- + +#--- Loop through compilers +for ((k = 0; k < ncomp; k++)); do + echo $'\n'$'\n'$'\n'\ +==================================================$'\n'\ +${k}") Test SOILWAT2 with compiler "\'"${port_compilers[k]}"\'$'\n'\ +================================================== + + echo $'\n'"Set compiler ..." + res="" + if [ "${port_compilers[k]}" = "default compiler" ]; then + res=$(sudo port select --set "${ccs[k]}" none) + else + res=$(sudo port select --set "${ccs[k]}" ${port_compilers[k]}) + fi + + if ( echo "${res}" | grep "failed" ); then + continue + fi + + CC=${ccs[k]} CXX=${cxxs[k]} make compiler_version + + + # Run checks + check_SOILWAT2 "CC=${ccs[k]}" "CXX=${cxxs[k]}" "txt" "${dirOutRefBase}" "${verbosity_on_error}" + + check_SOILWAT2 "CC=${ccs[k]}" "CXX=${cxxs[k]}" "nc" "${dirOutRefBase}" "${verbosity_on_error}" + + + + echo $'\n'$'\n'\ +--------------------------------------------------$'\n'\ +"Unset compiler"$'\n'\ +-------------------------------------------------- + + sudo port select --set ${ccs[k]} none + +done + + +#------ CLEANUP ---------------------------------------------------------------- +unset -v ccs +unset -v cxxs +unset -v port_compilers + +echo $'\n'$'\n'\ + --------------------------------------------------$'\n'\ + "Complete!"$'\n'\ + --------------------------------------------------$'\n' diff --git a/tools/many_test_runs.sh b/tools/many_test_runs.sh deleted file mode 100755 index e56f7b343..000000000 --- a/tools/many_test_runs.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -# N=3 ./tools/many_test_runs.sh: run unit tests repeatedly and report failures -# $N number of test runs; default 10 if empty or unset - -iters=${N:-10} - -echo $(date): will run ${iters} test replicates. - -make test - -for i in $(seq 1 $iters); do - temp=$( make test_run | grep -i "Fail" ) - - if [ ! -z "${temp}" ]; then - echo Replicate $i failed with: - echo ${temp} - fi -done - -echo $(date): Completed ${iters} test replicates. diff --git a/tools/check__output_txt_vs_r_vs_nc.R b/tools/rscripts/Rscript__SW2_output_txt_vs_r_vs_nc.R similarity index 97% rename from tools/check__output_txt_vs_r_vs_nc.R rename to tools/rscripts/Rscript__SW2_output_txt_vs_r_vs_nc.R index 09e241610..36b7b84e1 100644 --- a/tools/check__output_txt_vs_r_vs_nc.R +++ b/tools/rscripts/Rscript__SW2_output_txt_vs_r_vs_nc.R @@ -5,23 +5,23 @@ # # Run text-based SOILWAT2 on example simulation # rm -r tests/example/Output # make clean bin_run -# cp -R tests/example/Output tests/example/Output_txt +# cp -R tests/example/Output tests/example/Output_comps-txt # # # Run nc-based SOILWAT2 on example simulation # rm -r tests/example/Output # CPPFLAGS='-DSWNETCDF -DSWUDUNITS' make clean bin_run # mv tests/example/Input_nc/domain_template.nc tests/example/Input_nc/domain.nc # bin/SOILWAT2 -d ./tests/example -f files.in -# cp -R tests/example/Output tests/example/Output_nc +# cp -R tests/example/Output tests/example/Output_comps-nc # rm tests/example/Input_nc/domain.nc tests/example/Input_nc/progress.nc # ``` # # Compare output generated above # ``` -# Rscript tools/check__output_txt_vs_r_vs_nc.R +# Rscript tools/rscripts/Rscript__SW2_output_txt_vs_r_vs_nc.R # ``` # -# See also `tools/run_compare_outputs.sh` +# See also `toolscheck_outputModes.sh` # #------ . ------ stopifnot( @@ -36,8 +36,8 @@ qprobs <- c(0, 0.05, 0.25, 0.5, 0.75, 0.95, 1) #--- Paths ------ dir_example <- file.path("tests", "example") -dir_nc <- file.path(dir_example, "Output_nc") -dir_txt <- file.path(dir_example, "Output_txt") +dir_nc <- file.path(dir_example, "Output_comps-nc") +dir_txt <- file.path(dir_example, "Output_comps-txt") #--- SOILWAT2 metadata ------ diff --git a/tools/run_compare_outputs.sh b/tools/run_compare_outputs.sh deleted file mode 100755 index 0be100661..000000000 --- a/tools/run_compare_outputs.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -#------ . ------ -# Compare SOILWAT2 output among text-based, nc-based, and rSOILWAT2 runs -# -# Run this script with `./tools/run_compare_outputs.sh` -#------ . ------ - - -echo "Run text-based SOILWAT2 on example simulation ..." -res=$(rm -r tests/example/Output tests/example/Output_txt 2>&1) -res=$(make clean bin_run 2>&1) -res=$(cp -R tests/example/Output tests/example/Output_txt 2>&1) - -echo "Run nc-based SOILWAT2 on example simulation ..." -res=$(rm -r tests/example/Output tests/example/Output_nc 2>&1) -res=$(rm tests/example/Input_nc/domain.nc 2>&1) -res=$(CPPFLAGS='-DSWNETCDF -DSWUDUNITS' make clean bin_run 2>&1) -res=$(mv tests/example/Input_nc/domain_template.nc tests/example/Input_nc/domain.nc 2>&1) -res=$(bin/SOILWAT2 -d ./tests/example -f files.in 2>&1) -res=$(cp -R tests/example/Output tests/example/Output_nc 2>&1) -res=$(rm tests/example/Input_nc/domain.nc tests/example/Input_nc/progress.nc 2>&1) - -echo "Compare output among text-based, nc-based, and rSOILWAT2 runs:" -res=$(Rscript tools/check__output_txt_vs_r_vs_nc.R 2>&1) - -echo "${res}" - -# Fail if not success -if [[ "${res}" != *"Success"* ]]; then - exit 1 -fi From 8db0e4c7cf35d19cf84014807b7ade4b4ed651b5 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 22 May 2024 17:11:36 -0400 Subject: [PATCH 7/8] Overhauled scripts that run extra checks and figures - tools/check_extras.sh -- new script to run all extra checks - R scripts moved and renamed to tools/rscripts/*.R - figures produced by R scripts are now stored at tools/figures/* (which is ignored by git) - fixed code to correctly compile and run --- tests/gtests/test_SW_Flow_Lib_PET.cc | 64 +++++++++++++------ tests/gtests/test_SW_SpinUp.cc | 2 +- tools/check_extras.sh | 40 ++++++++++++ ...Rscript__SW2_PET_Test__petfunc_by_temps.R} | 23 ++++--- .../Rscript__SW2_SoilTemperature.R} | 37 +++++------ ...osition_Test__hourangles_by_lat_and_doy.R} | 17 +++-- ..._SolarPosition_Test__hourangles_by_lats.R} | 11 ++-- .../Rscript__SW2_SpinupEvaluation.R} | 9 ++- 8 files changed, 140 insertions(+), 63 deletions(-) create mode 100755 tools/check_extras.sh rename tools/{plot__SW2_PET_Test__petfunc_by_temps.R => rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R} (89%) rename tools/{plot__SW2_SoilTemperature.R => rscripts/Rscript__SW2_SoilTemperature.R} (85%) rename tools/{plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R => rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R} (98%) rename tools/{plot__SW2_SolarPosition_Test__hourangles_by_lats.R => rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R} (94%) rename tools/{plot__SW2_SpinupEvaluation.R => rscripts/Rscript__SW2_SpinupEvaluation.R} (88%) diff --git a/tests/gtests/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc index bce37b57b..f6bb2aef0 100644 --- a/tests/gtests/test_SW_Flow_Lib_PET.cc +++ b/tests/gtests/test_SW_Flow_Lib_PET.cc @@ -349,12 +349,13 @@ namespace #ifdef SW2_SolarPosition_Test__hourangles_by_lat_and_doy // Run SOILWAT2 unit tests with flag // ``` - // CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test test_run + // CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test + // bin/sw_test --gtest_filter=*SolarPosHourAnglesByLatAndDoy* // ``` // // Produce plots based on output generated above // ``` - // Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R + // Rscript tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R // ``` TEST(AtmDemandTest, SolarPosHourAnglesByLatAndDoy) { @@ -375,6 +376,11 @@ namespace *strnum, fname[FILENAME_MAX]; + SW_ATMD SW_AtmDemand; + SW_PET_init_run(&SW_AtmDemand); // Init radiation memoization + + LOG_INFO LogInfo; + sw_init_logs(NULL, &LogInfo); // Initialize logs and silence warn/error reporting for (isl = 0; isl <= 3; isl++) { slope = 90. * isl / 3.; @@ -391,7 +397,7 @@ namespace */ // Output file - strcpy(fname, output_prefix); + strcpy(fname, "Output/"); strcat( fname, "Table__SW2_SolarPosition_Test__hourangles_by_lat_and_doy" @@ -403,7 +409,7 @@ namespace snprintf(strnum, length_strnum + 1, "%d", (int) slope); strcat(fname, strnum); free(strnum); - strnum = NULL + strnum = NULL; strcat(fname, "__aspect"); length_strnum = snprintf(NULL, 0, "%d", (int) aspect); @@ -411,11 +417,13 @@ namespace snprintf(strnum, length_strnum + 1, "%d", (int) aspect); strcat(fname, strnum); free(strnum); - strnum = NULL + strnum = NULL; strcat(fname, ".csv"); - fp = OpenFile(fname, "w"); + fp = OpenFile(fname, "w", &LogInfo); + sw_fail_on_error(&LogInfo); // exit test program if unexpected error + // Column names fprintf( @@ -443,6 +451,7 @@ namespace ); sun_hourangles( + &SW_AtmDemand, idoy, ilat * deg_to_rad, slope * deg_to_rad, @@ -478,12 +487,13 @@ namespace fflush(fp); } - SW_PET_init_run(); // Re-init radiation memoization (for new latitude) + SW_PET_init_run(&SW_AtmDemand); // Re-init radiation memoization (for new latitude) } // Clean up - CloseFile(&fp); + CloseFile(&fp, &LogInfo); + sw_fail_on_error(&LogInfo); // exit test program if unexpected error if (isl == 0) { break; @@ -498,12 +508,13 @@ namespace #ifdef SW2_SolarPosition_Test__hourangles_by_lats // Run SOILWAT2 unit tests with flag // ``` - // CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test test_run + // CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test + // bin/sw_test --gtest_filter=*SolarPosHourAnglesByLats* // ``` // // Produce plots based on output generated above // ``` - // Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R + // Rscript tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R // ``` TEST(AtmDemandTest, SolarPosHourAnglesByLats) { @@ -523,9 +534,17 @@ namespace FILE *fp; char fname[FILENAME_MAX]; - strcpy(fname, output_prefix); + SW_ATMD SW_AtmDemand; + SW_PET_init_run(&SW_AtmDemand); // Init radiation memoization + + LOG_INFO LogInfo; + sw_init_logs(NULL, &LogInfo); // Initialize logs and silence warn/error reporting + + strcpy(fname, "Output/"); strcat(fname, "Table__SW2_SolarPosition_Test__hourangles_by_lats.csv"); - fp = OpenFile(fname, "w"); + fp = OpenFile(fname, "w", &LogInfo); + sw_fail_on_error(&LogInfo); // exit test program if unexpected error + // Column names fprintf( @@ -561,6 +580,7 @@ namespace ); sun_hourangles( + &SW_AtmDemand, doys[idoy], rlat, rslope, raspect, sun_angles, int_cos_theta, @@ -581,13 +601,14 @@ namespace fflush(fp); } - SW_PET_init_run(); // Re-init radiation memoization + SW_PET_init_run(&SW_AtmDemand); // Re-init radiation memoization } } } } - CloseFile(&fp); + CloseFile(&fp, &LogInfo); + sw_fail_on_error(&LogInfo); // exit test program if unexpected error } #endif // end of SW2_SolarPosition_Test__hourangles_by_lats @@ -1224,12 +1245,13 @@ namespace #ifdef SW2_PET_Test__petfunc_by_temps // Run SOILWAT2 unit tests with flag // ``` - // CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test test_run + // CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test + // bin/sw_test --gtest_filter=*PETPetfuncByTemps* // ``` // // Produce plots based on output generated above // ``` - // Rscript tools/plot__SW2_PET_Test__petfunc_by_temps.R + // Rscript tools/rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R // ``` TEST(AtmDemandTest, PETPetfuncByTemps) { @@ -1258,9 +1280,10 @@ namespace FILE *fp; char fname[FILENAME_MAX]; - strcpy(fname, output_prefix); + strcpy(fname, "Output/"); strcat(fname, "Table__SW2_PET_Test__petfunc_by_temps.csv"); - fp = OpenFile(fname, "w"); + fp = OpenFile(fname, "w", &LogInfo); + sw_fail_on_error(&LogInfo); // exit test program if unexpected error // Column names fprintf( @@ -1297,7 +1320,7 @@ namespace H_gt = fH_gt * solar_radiation( &SW_AtmDemand, doy, lat, elev, slope, aspect, reflec, - &cloudcover, RH, temp, + &cloudcover, RH, rsds, desc_rsds, &H_oh, &H_ot, &H_gh, &LogInfo ); @@ -1332,7 +1355,8 @@ namespace } // Clean up - CloseFile(&fp); + CloseFile(&fp, &LogInfo); + sw_fail_on_error(&LogInfo); // exit test program if unexpected error } #endif // end of SW2_PET_Test__petfunc_by_temps diff --git a/tests/gtests/test_SW_SpinUp.cc b/tests/gtests/test_SW_SpinUp.cc index 209cc3fac..5eb894c57 100644 --- a/tests/gtests/test_SW_SpinUp.cc +++ b/tests/gtests/test_SW_SpinUp.cc @@ -438,7 +438,7 @@ namespace { // Run (a short) simulation local_sw.Model.startyr = 1980; local_sw.Model.endyr = 1980; - SW_CTL_main(&local_sw, &SW_OutputPtrs, &local_LogInfo); + SW_CTL_main(&local_sw, SW_OutputPtrs, &local_LogInfo); sw_fail_on_error(&local_LogInfo); // exit test program if unexpected error // Print values after simulation diff --git a/tools/check_extras.sh b/tools/check_extras.sh new file mode 100755 index 000000000..c5545c724 --- /dev/null +++ b/tools/check_extras.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +#------ . ------ +# Extra SOILWAT2 checks and figures that evaluate specific features +# +# Run this script with `tools/check_extras.sh` +#------ . ------ + + +echo $'\n'"Additional checks ..." + +echo $'\n'"Spinup evaluation" +CPPFLAGS=-DSW2_SpinupEvaluation make clean test > /dev/null 2>&1 +bin/sw_test --gtest_filter=*SpinupEvaluation* +Rscript tools/rscripts/Rscript__SW2_SpinupEvaluation.R + + +echo $'\n'"Soil temperature evaluation" +make clean bin_run > /dev/null 2>&1 +Rscript tools/rscripts/Rscript__SW2_SoilTemperature.R + + +echo $'\n'"Effects of meteorological variables on PET" +CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make clean test > /dev/null 2>&1 +bin/sw_test --gtest_filter=*PETPetfuncByTemps* +Rscript tools/rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R + + +echo $'\n'"Numbers of daylight hours and of sunrises/sunsets for each latitude and day of year" +CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make clean test > /dev/null 2>&1 +bin/sw_test --gtest_filter=*SolarPosHourAnglesByLatAndDoy* +Rscript tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R + + +echo $'\n'"Horizontal and tilted first sunrise and last sunset hour angles for each latitude and a combination of slopes/aspects/day of year" +CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make clean test > /dev/null 2>&1 +bin/sw_test --gtest_filter=*SolarPosHourAnglesByLats* +Rscript tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R + +echo $'\n' diff --git a/tools/plot__SW2_PET_Test__petfunc_by_temps.R b/tools/rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R similarity index 89% rename from tools/plot__SW2_PET_Test__petfunc_by_temps.R rename to tools/rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R index 7a27eecae..e55e46cc0 100644 --- a/tools/plot__SW2_PET_Test__petfunc_by_temps.R +++ b/tools/rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R @@ -4,16 +4,19 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test_run +# CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test +# bin/sw_test --gtest_filter=*PETPetfuncByTemps* # ``` # # Produce plots based on output generated above # ``` -# Rscript tools/plot__SW2_PET_Test__petfunc_by_temps.R +# Rscript tools/rscripts/Rscript__SW2_PET_Test__petfunc_by_temps.R # ``` #------ dir_out <- file.path("tests", "example", "Output") +dir_fig <- file.path("tools", "figures") +dir.create(dir_fig, recursive = TRUE, showWarnings = FALSE) tag_filename <- "SW2_PET_Test__petfunc_by_temps" @@ -70,7 +73,7 @@ if (do_plot) { n_panels <- c(length(ws), length(cc)) pdf( - file = file.path(dir_out, paste0("Fig__", tag_filename, "__by_RH", ".pdf")), + file = file.path(dir_fig, paste0("Fig__", tag_filename, "__by_RH", ".pdf")), height = 2 * n_panels[1], width = 2 * n_panels[2] ) @@ -98,7 +101,7 @@ if (do_plot) { tmp <- tmp + ggplot2::theme( - legend.position = c( + legend.position.inside = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), @@ -117,7 +120,7 @@ if (do_plot) { n_panels <- c(length(rh), length(ws)) pdf( - file = file.path(dir_out, paste0("Fig__", tag_filename, "__by_CloudCover", ".pdf")), + file = file.path(dir_fig, paste0("Fig__", tag_filename, "__by_CloudCover", ".pdf")), height = 2 * n_panels[1], width = 2 * n_panels[2] ) @@ -145,7 +148,7 @@ if (do_plot) { tmp <- tmp + ggplot2::theme( - legend.position = c( + legend.position.inside = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), @@ -164,7 +167,7 @@ if (do_plot) { n_panels <- c(length(rh), length(cc)) pdf( - file = file.path(dir_out, paste0("Fig__", tag_filename, "__by_WindSpeed", ".pdf")), + file = file.path(dir_fig, paste0("Fig__", tag_filename, "__by_WindSpeed", ".pdf")), height = 2 * n_panels[1], width = 2 * n_panels[2] ) @@ -192,7 +195,7 @@ if (do_plot) { tmp <- tmp + ggplot2::theme( - legend.position = c( + legend.position.inside = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), @@ -214,7 +217,7 @@ if (do_plot) { n_panels <- c(length(rh), length(ws)) pdf( - file = file.path(dir_out, paste0("Fig__", tag_filename, "__by_fRSDS", ".pdf")), + file = file.path(dir_fig, paste0("Fig__", tag_filename, "__by_fRSDS", ".pdf")), height = 2 * n_panels[1], width = 2 * n_panels[2] ) @@ -242,7 +245,7 @@ if (do_plot) { tmp <- tmp + ggplot2::theme( - legend.position = c( + legend.position.inside = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), diff --git a/tools/plot__SW2_SoilTemperature.R b/tools/rscripts/Rscript__SW2_SoilTemperature.R similarity index 85% rename from tools/plot__SW2_SoilTemperature.R rename to tools/rscripts/Rscript__SW2_SoilTemperature.R index 229e804d0..34db9929c 100644 --- a/tools/plot__SW2_SoilTemperature.R +++ b/tools/rscripts/Rscript__SW2_SoilTemperature.R @@ -1,7 +1,7 @@ # Run script from within SOILWAT2/ with # ``` -# Rscript tools/plot__SW2_SoilTemperature.R +# Rscript tools/rscripts/Rscript__SW2_SoilTemperature.R # ``` # after running SOILWAT2 on "tests/example", e.g., # ``` @@ -14,16 +14,16 @@ dir_testing <- file.path("tests", "example") dir_sw2_input <- file.path(dir_testing, "Input") dir_sw2_output <- file.path(dir_testing, "Output") -dir_fig <- "tools" +dir_fig <- file.path("tools", "figures") dir.create(dir_fig, recursive = TRUE, showWarnings = FALSE) #--- Read data -soils <- read.table(file.path(dir_sw2_input, "soils.in"), header = FALSE) +soils <- utils::read.table(file.path(dir_sw2_input, "soils.in"), header = FALSE) -xw <- read.csv(file.path(dir_sw2_output, "sw2_daily.csv")) +xw <- utils::read.csv(file.path(dir_sw2_output, "sw2_daily.csv")) -x <- read.csv(file.path(dir_sw2_output, "sw2_daily_slyrs.csv")) +x <- utils::read.csv(file.path(dir_sw2_output, "sw2_daily_slyrs.csv")) tag_soil_temp <- "SOILTEMP_Lyr" tag_surface_temp <- "SOILTEMP_surfaceTemp_C" @@ -125,7 +125,7 @@ xvwc2 <- merge( #--- Panels by layer showing trends over one year -pdf(file = file.path(dir_fig, "Fig_SoilTemperature_by_Layer.pdf")) +grDevices::pdf(file = file.path(dir_fig, "Fig_SoilTemperature_by_Layer.pdf")) ggplot2::ggplot( data = dplyr::filter(xtsoil, Year == 1980) @@ -137,12 +137,12 @@ ggplot2::ggplot( dplyr::vars(depth_cm), scales = "fixed" ) + - egg::theme_article() + ggplot2::theme_bw() -dev.off() +grDevices::dev.off() -pdf(file = file.path(dir_fig, "Fig_SoilTemperatureDelta_by_Layer.pdf")) +grDevices::pdf(file = file.path(dir_fig, "Fig_SoilTemperatureDelta_by_Layer.pdf")) ggplot2::ggplot( data = dplyr::filter(xtsoil_delta, Year == 2010) @@ -154,14 +154,16 @@ ggplot2::ggplot( dplyr::vars(depth_cm), scales = "free_y" ) + - egg::theme_article() + ggplot2::theme_bw() -dev.off() +grDevices::dev.off() #--- Soil temperature range vs soil moisture -pdf(file = file.path(dir_fig, "Fig_SoilTemperatureRange_vs_SoilMoisture.pdf")) +grDevices::pdf( + file = file.path(dir_fig, "Fig_SoilTemperatureRange_vs_SoilMoisture.pdf") +) ggplot2::ggplot( data = xvwc2 @@ -170,10 +172,9 @@ ggplot2::ggplot( ggplot2::aes(x = VWC, y = SoilTempRange) ) + ggplot2::facet_wrap(dplyr::vars(depth_cm)) + - egg::theme_article() - + ggplot2::theme_bw() -dev.off() +grDevices::dev.off() @@ -203,7 +204,7 @@ tmp2$type = "Mean across: range > 0 C" xtsoilClim <- rbind(tmp1, tmp2) -pdf(file = file.path(dir_fig, "Fig_SoilTemperatureClim_by_Layer.pdf")) +grDevices::pdf(file = file.path(dir_fig, "Fig_SoilTemperatureClim_by_Layer.pdf")) ggplot2::ggplot( data = xtsoilClim @@ -214,6 +215,6 @@ ggplot2::ggplot( ggplot2::facet_grid(rows = dplyr::vars(type)) + ggplot2::scale_y_reverse() + ggplot2::geom_hline(yintercept = 0, color = "orange", linetype = "dashed") + - egg::theme_article() + ggplot2::theme_bw() -dev.off() +grDevices::dev.off() diff --git a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R b/tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R similarity index 98% rename from tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R rename to tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R index 7fe4bb28c..27eec781b 100644 --- a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R +++ b/tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R @@ -3,16 +3,19 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test_run +# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test +# bin/sw_test --gtest_filter=*SolarPosHourAnglesByLatAndDoy* # ``` # # Produce plots based on output generated above # ``` -# Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R +# Rscript tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R # ``` #------ dir_out <- file.path("tests", "example", "Output") +dir_fig <- file.path("tools", "figures") +dir.create(dir_fig, recursive = TRUE, showWarnings = FALSE) tag_filename <- "SW2_SolarPosition_Test__hourangles_by_lat_and_doy" @@ -188,7 +191,7 @@ if (do_plot_maps || do_plot_expectations) { # Start plot fname <- file.path( - dirname(file_sw2_test_outputs[fids[1]]), + dir_fig, sub( "Table__", "Fig__", @@ -299,7 +302,7 @@ if (do_plot_maps || do_plot_expectations) { # Start plot fname <- file.path( - dirname(file_sw2_test_outputs[fids[1]]), + dir_fig, sub( "Table__", "Fig__Symmetry_ReflectedAspect__", @@ -419,7 +422,7 @@ if (do_plot_maps || do_plot_expectations) { # Start plot fname <- file.path( - dirname(file_sw2_test_outputs[fids[1]]), + dir_fig, sub( "Table__", "Fig__Symmetry_ReflectedAspect2__", @@ -531,7 +534,7 @@ if (do_plot_maps || do_plot_expectations) { # Start plot fname <- file.path( - dirname(file_sw2_test_outputs[fids[1]]), + dir_fig, sub( "Table__", "Fig__Symmetry_ReflectedDOY__", @@ -642,7 +645,7 @@ if (do_plot_maps || do_plot_expectations) { if (n_panels[1] > 0) { # Start plot fname <- file.path( - dirname(file_sw2_test_outputs[fids[1]]), + dir_fig, sub( "Table__", "Fig__Symmetry_ShiftedDOY-ReflectedLatitude-FlippedAspect__", diff --git a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R b/tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R similarity index 94% rename from tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R rename to tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R index 14453f74d..40e71a5ad 100644 --- a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R +++ b/tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R @@ -4,16 +4,19 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test_run +# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test +# bin/sw_test --gtest_filter=*SolarPosHourAnglesByLats* # ``` # # Produce plots based on output generated above # ``` -# Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R +# Rscript tools/rscripts/Rscript__SW2_SolarPosition_Test__hourangles_by_lats.R # ``` #------ dir_out <- file.path("tests", "example", "Output") +dir_fig <- file.path("tools", "figures") +dir.create(dir_fig, recursive = TRUE, showWarnings = FALSE) tag_filename <- "SW2_SolarPosition_Test__hourangles_by_lats" @@ -105,7 +108,7 @@ if (do_plot) { x <- vals[doy, , slope, , vars] xH <- vals[doy, , 2, 1, vars_oH] - if (any(!is.na(x))) { + if (!all(is.na(x))) { graphics::plot( 1, @@ -188,7 +191,7 @@ if (do_plot) { for (iv in seq_along(var_sets)[-1]) { fname <- file.path( - dirname(file_sw2_test_output), + dir_fig, sub( "Table__", "Fig__", diff --git a/tools/plot__SW2_SpinupEvaluation.R b/tools/rscripts/Rscript__SW2_SpinupEvaluation.R similarity index 88% rename from tools/plot__SW2_SpinupEvaluation.R rename to tools/rscripts/Rscript__SW2_SpinupEvaluation.R index fa9c66d63..380496ce5 100644 --- a/tools/plot__SW2_SpinupEvaluation.R +++ b/tools/rscripts/Rscript__SW2_SpinupEvaluation.R @@ -1,11 +1,12 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_SpinupEvaluation make test && bin/sw_test --gtest_filter=*SpinupEvaluation* +# CPPFLAGS=-DSW2_SpinupEvaluation make test +# bin/sw_test --gtest_filter=*SpinupEvaluation* # ``` # # Produce plots based on output generated above # ``` -# Rscript tools/plot__SW2_SpinupEvaluation.R +# Rscript tools/rscripts/Rscript__SW2_SpinupEvaluation.R # ``` #------ @@ -17,6 +18,8 @@ stopifnot( ) dir_out <- file.path("tests", "example", "Output") +dir_fig <- file.path("tools", "figures") +dir.create(dir_fig, recursive = TRUE, showWarnings = FALSE) tag_filename <- "SW2_SpinupEvaluation" @@ -87,7 +90,7 @@ for (k in seq_along(file_sw2_test_outputs)) { grDevices::pdf( file = file.path( - dirname(file_sw2_test_outputs[[k]]), + dir_fig, sub( "Table__", "Fig__", sub(".csv", ".pdf", basename(file_sw2_test_outputs[[k]])) From c5f29c25f03a76915ecc48428e18a600e056656d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 22 May 2024 17:12:56 -0400 Subject: [PATCH 8/8] Updated Readme & Contributing - updated README - moved text to new contribution markdown - updated and expanded contributing sections --- README.md | 382 +--------------- doc/additional_pages/A_SOILWAT2_user_guide.md | 3 + .../code_contribution_guidelines.md | 414 ++++++++++++++++++ 3 files changed, 425 insertions(+), 374 deletions(-) create mode 100644 doc/additional_pages/code_contribution_guidelines.md diff --git a/README.md b/README.md index 477541416..93b0ac4f5 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,9 @@ [STEPWAT2]: https://github.com/DrylandEcology/STEPWAT2 [issues]: https://github.com/DrylandEcology/SOILWAT2/issues [pull request]: https://github.com/DrylandEcology/SOILWAT2/pulls -[guidelines]: https://github.com/DrylandEcology/workflow_guidelines [doxygen]: https://github.com/doxygen/doxygen -[GoogleTest]: https://github.com/google/googletest -[semantic versioning]: https://semver.org/ [netCDF]: https://downloads.unidata.ucar.edu/netcdf/ +[udunits2]: https://downloads.unidata.ucar.edu/udunits/
@@ -69,14 +67,6 @@ Some references 1. [Compilation](#compile) 2. [Documentation](#get_documentation) 2. [How to contribute](#contribute) - 1. [SOILWAT2 code](#SOILWAT2_code) - 2. [Code guidelines](#follow_guidelines) - 3. [Code documentation](#code_documentation) - 4. [Code tests](#code_tests) - 5. [Code debugging](#code_debugging) - 6. [Code versioning](#code_versioning) - 7. [Reverse dependencies](#revdep) -3. [Some additional notes](#more_notes)
@@ -103,6 +93,7 @@ A full code documentation may be built, see [here](#get_documentation). - GNU-compliant `make` - On Windows OS: an installation of `cygwin` - the `netCDF-C` library (if compiled with [netCDF][] support) + - the `udunits2` library (if compiled with [udunits2][] support) * Clone the repository (details can be found in the @@ -114,14 +105,8 @@ A full code documentation may be built, see [here](#get_documentation). * Build with `make` (see `make help` to print information about all available targets). For instance, ```{.sh} - make # text-based mode - CPPFLAGS=-DSWNETCDF make # netCDF-based mode -``` - - * You can use a specific compiler, e.g., -```{.sh} - CC=gcc make - CC=clang make + make # text-based mode + CPPFLAGS='-DSWNETCDF -DUDUNITS2' make # netCDF-based mode with units ```
@@ -138,365 +123,14 @@ A full code documentation may be built, see [here](#get_documentation). ## How to contribute -You can help us in different ways: +You can help us in different ways 1. Reporting [issues][] 2. Contributing code and sending a [pull request][] -
- - - -### `SOILWAT2` code is used as part of three applications - * Stand-alone, - - * Part/submodule of [STEPWAT2][] (code flag `STEPWAT`), and - - * Part/submodule of the R package [rSOILWAT2][] (code flag `RSOILWAT`) - -Changes in `SOILWAT2` must be reflected by updates to `STEPWAT2` or `rSOILWAT2`; -please see section [reverse dependencies](#revdep). - -
- - - -### Follow our guidelines as detailed [here][guidelines] - -
- - -### Code development, documentation, and tests go together - -We develop code on development branches and, -after they are reviewed and pass our checks, -merge them into the master branch for release. +Thank you! - - -#### Code documentation - * Document new code with [doxygen][] inline documentation - - * Check that new documentation renders correctly and does not - generate `doxygen` warnings, i.e., - run `make doc` and check that it returns successfully - (it checks that `doxygen doc/Doxyfile | grep warning` is empty). - Also check that new or amended documentation displays - as intended by opening `doc/html/index.html` and navigating to the item - in question - - * Keep `doc/html/` local, i.e., don't push to the repository - - * Use regular c-style comments for additional code documentation - -
- - - -#### Code tests -__Testing framework__ - -The goal is to cover all code development with new or amended tests. -`SOILWAT2` comes with unit tests and integration tests. -Additionally, the github repository runs continuous integration checks. +Please follow our code development and contribution guidelines +[here](doc/additional_pages/code_contribution_guidelines.md)
- -__Unit tests__ - -We use [GoogleTest][] for unit tests -to check that individual units of code, e.g., functions, work as expected. - -These tests are organized in the folder `test/` -in files with the naming scheme `test_*.cc`. - -Note: `SOILWAT2` is written in C whereas `GoogleTest` is a C++ framework. This -causes some complications, see `makefile`. - - -Run unit tests locally on the command-line with -```{.sh} - make test_run # compiles and executes the tests - make test_severe # compiles/executes with strict/severe flags - make clean_test # cleans build artifacts -``` - - -__Miscellaneous scripts for tests__ - -Users of `SOILWAT2` work with a variety of compilers across different platforms -and we aim to test that this works across a reasonable selection. -We can do that manually or use the bash-script `tools/check_SOILWAT2.sh` -which runs tests with different compiler versions. -Please note that this script currently works only with `macports`. - - -`SOILWAT2` is a deterministic simulation model; however, running unit tests -repeatedly may be helpful for debugging in rare situations. For that, -the bash-script `tools/many_test_runs.sh` will run `N` number of times and -only reports unit test failures, e.g., -```{.sh} - ./tools/many_test_runs.sh # will run a default (currently, 10) number of times - N=3 ./tool/many_test_runs.sh # will run 3 replicates -``` - -
- -__Integration tests__ - -We use integration tests to check that the entire simulation model works -as expected when used in a real-world application setting. - -The folder `tests/example/` contains all necessary inputs to run `SOILWAT2` -for one generic location -(it is a relatively wet and cool site in the sagebrush steppe). - -```{.sh} - make bin_run -``` - -The simulated output is stored at `tests/example/Output/`. - - -Another use case is to compare output of a new (development) branch to output -from a previous (reference) release. - -Depending on the purpose of the development branch -the new output should be exactly the same as reference output or -differ in specific ways in specific variables. - -The following steps provide a starting point for such comparisons: - -```{.sh} - # Simulate on refernce branch and copy output to "Output_ref" - git checkout master - make bin_run - cp -r tests/example/Output tests/example/Output_ref - - # Switch to development branch and run the same simulation - git checkout - make bin_run - - # Compare the two sets of outputs - # * Lists all output files and determine if they are exactly they same - diff tests/example/Output/ tests/example/Output_ref/ -qs -``` - - -__Additional tests__ - -Additional output can be generated by passing appropriate flags when running -unit tests. Scripts are available to analyze such output. -Currently, the following is implemented: - - - Sun hour angles plots for horizontal and tilted surfaces - - 1. Numbers of daylight hours and of sunrise(s)/sunset(s) - for each latitude and day of year for some slope/aspect combinations - 2. Sunrise(s)/sunset(s) hour angles for each latitude - and some slope/aspect/day of year combinations - -```{.sh} - CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test_run - Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R - - CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test_run - Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R -``` - - - PET plots as function of radiation, relative humidity, wind speed, and cover - -```{.sh} - CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test_run - Rscript tools/plot__SW2_PET_Test__petfunc_by_temps.R -``` - - - Spinup evaluation plots for spinup duration and initialization of - soil moisture and soil temperature - -```{.sh} - CPPFLAGS=-DSW2_SpinupEvaluation make test && bin/sw_test --gtest_filter=*SpinupEvaluation* - Rscript tools/plot__SW2_SpinupEvaluation.R -``` - -
- - -__Continous integration checks__ - -Development/feature branches can only be merged into the main branch and -released if they pass all checks on the continuous integration servers -(see `.github/workflows/`). - -Please run the "severe", "sanitizer", and "leak" targets locally -(see also `tools/check_SOILWAT2.sh`) -```{.sh} - make clean_build bin_debug_severe - make clean_test test_severe -``` - -
- -__Sanitizers & leaks__ - -Run the simulation and tests with the `leaks` program, For instance, -```{.sh} - make clean_build bin_leaks - make clean_test test_leaks -``` - -Run the simulation and tests with sanitizers. For instance, -```{.sh} - make clean_build bin_sanitizer - make clean_test test_sanitizer -``` - -The address sanitizer may not work correctly and/or fail when used with the -`Apple-clang` version that is shipped with macOS X -(see [Sanitizer issue #1026](https://github.com/google/sanitizers/issues/1026)). -A separate installation of `clang` may be required, -e.g., via `homebrew` or `macports`. - - -If `clang` is installed in a non-default location and -if shared dynamic libraries are not picked up correctly, then -the test executable may throw an error `... dyld: Library not loaded ...`. -This can be fixed, for instance, with the following steps -(details depend on the specific setup, below is for `macports` and `clang-8.0`): - -```{.sh} - # build test executable with clang and leak detection - CXX=clang++ ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe - - # check faulty library path - otool -L sw_test - - # figure out correct library path and insert with: e.g., - install_name_tool -change /opt/local/libexec/llvm-8.0/lib/libclang_rt.asan_osx_dynamic.dylib /opt/local/libexec/llvm-8.0/lib/clang/8.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib sw_test - - # run tests - make test_run -``` - -
- - - -#### Debugging - Debugging is controlled at two levels: - * at the preprocessor (pass `-DSWDEBUG`): - all debug code is wrapped by this flag so that it does not end up in - production code; unit testing is compiled in debugging mode. - - * in functions with local debug variable flags (`int debug = 1;`): - debug code can be conditional on such a variable, e.g., - -```{.c} - void foo() { - #ifdef SWDEBUG - int debug = 1; - #endif - ... - #ifdef SWDEBUG - if (debug) swprintf("hello, this is debugging code\n"); - ... - #endif - ... - } -``` - - * Clean, compile and run optimized `SOILWAT2`-standalone in debugging mode - with, e.g., - -```{.sh} - make bin_run CPPFLAGS=-DSWDEBUG -``` - - * Alternatively, use the pre-configured debugging targets - `bin_debug` and `bin_debug_severe`, for instance, with - -```{.sh} - make bin_debug_severe -``` - -
- - - - -#### Version numbers - -We attempt to follow guidelines of [semantic versioning][] with version -numbers of `MAJOR.MINOR.PATCH`; -however, our version number updates are focusing -on simulated output (e.g., identical output -> increase patch number) and -on dependencies `STEPWAT2` and `rSOILWAT2` -(e.g., no updates required -> increase patch number). - -We create a new release for each update to the master branch. -The master branch is updated via pull requests from development branches -after they are reviewed and pass required checks. - - -
- - - -## Reverse dependencies - -`STEPWAT2` and `rSOILWAT2` depend on `SOILWAT2`; -they utilize the master branch of `SOILWAT2` as a submodule. -Thus, changes in `SOILWAT2` need to be propagated to `STEPWAT2` and `rSOILWAT2`. - -The following steps can serve as starting point to resolve -the cross-repository reverse dependencies: - - 1. Create development branch `branch_*` in `SOILWAT2` - 1. Create respective development branches in `STEPWAT2` and `rSOILWAT2` - 1. Update the `SOILWAT2` submodule of `STEPWAT2` and `rSOILWAT2` as first - commit on these new development branches: - * Have `.gitmodules` point to the new `SOILWAT2` branch `branch_*` - * Update the submodule `git submodule update --remote` - 1. Develop and test code and follow guidelines of `STEPWAT2` and `rSOILWAT2` - 1. Create pull requests for each development branch - 1. Merge pull request `SOILWAT2` once development is finalized, reviewed, and - sufficiently tested across all three repositories; - create new `SOILWAT2` [release](#code_versioning) - 1. Finalize development branches in `STEPWAT2` and `rSOILWAT2` - * Have `.gitmodules` point to the new `SOILWAT2` release on `master` - * Update the submodule `git submodule update --remote` - 1. Handle pull requests for `STEPWAT2` and `rSOILWAT2` according to - their guidelines - -
- - - -## Notes - -__Organization renamed from Burke-Lauenroth-Lab to DrylandEcology on Dec 22, 2017__ - -All existing information should -[automatically be redirected](https://help.github.com/articles/renaming-a-repository/) -to the new name. -Contributors are encouraged, however, to update local clones to -[point to the new URL](https://help.github.com/articles/changing-a-remote-s-url/), -i.e., - -```{.sh} -git remote set-url origin https://github.com/DrylandEcology/SOILWAT2.git -``` - - -__Repository renamed from SOILWAT to SOILWAT2 on Feb 23, 2017__ - -All existing information should -[automatically be redirected](https://help.github.com/articles/renaming-a-repository/) -to the new name. -Contributors are encouraged, however, to update local clones to -[point to the new URL](https://help.github.com/articles/changing-a-remote-s-url/), -i.e., - -```{.sh} -git remote set-url origin https://github.com/DrylandEcology/SOILWAT2.git -``` diff --git a/doc/additional_pages/A_SOILWAT2_user_guide.md b/doc/additional_pages/A_SOILWAT2_user_guide.md index 8d1fadcc5..b5567d5a2 100644 --- a/doc/additional_pages/A_SOILWAT2_user_guide.md +++ b/doc/additional_pages/A_SOILWAT2_user_guide.md @@ -12,6 +12,7 @@ [tinytex]: https://yihui.name/tinytex/ [xcode]: https://developer.apple.com/xcode [netCDF]: https://downloads.unidata.ucar.edu/netcdf/ +[udunits2]: https://downloads.unidata.ucar.edu/udunits/ Note: this document is best viewed as part of the doxygen-built documentation @@ -59,6 +60,8 @@ on your side. - a minimal `latex` installation (see below) - to build with [netCDF][] support (optional) - the `netCDF-C` library + - to build with [udunits2][] support (optional) + - the `udunits2` library #### Example instructions for a minimal `latex` installation diff --git a/doc/additional_pages/code_contribution_guidelines.md b/doc/additional_pages/code_contribution_guidelines.md new file mode 100644 index 000000000..a39868ca6 --- /dev/null +++ b/doc/additional_pages/code_contribution_guidelines.md @@ -0,0 +1,414 @@ +# Suggestions for code development + +[SOILWAT2]: https://github.com/DrylandEcology/SOILWAT2 +[rSOILWAT2]: https://github.com/DrylandEcology/rSOILWAT2 +[STEPWAT2]: https://github.com/DrylandEcology/STEPWAT2 +[issues]: https://github.com/DrylandEcology/SOILWAT2/issues +[pull request]: https://github.com/DrylandEcology/SOILWAT2/pulls +[guidelines]: https://github.com/DrylandEcology/workflow_guidelines +[doxygen]: https://github.com/doxygen/doxygen +[GoogleTest]: https://github.com/google/googletest +[semantic versioning]: https://semver.org/ +[netCDF]: https://downloads.unidata.ucar.edu/netcdf/ +[udunits2]: https://downloads.unidata.ucar.edu/udunits/ + + + +# Code development, documentation, and tests go together + +We develop code on development branches and, +after they are reviewed and pass our checks, +merge them into the main branch for release. + + +
+Go back to the [main page](README.md). + +# Table of contents +1. [How to contribute](#contribute) + 1. [SOILWAT2 code](#SOILWAT2_code) + 2. [Code guidelines](#follow_guidelines) + 3. [Code documentation](#code_documentation) + 4. [Code tests](#code_tests) + 1. [Unit tests](#unit_tests) + 2. [Integration tests](#int_tests) + 3. [Extra checks](#extra_tests) + 4. [Continuous integration checks](#ci_tests) + 5. [Sanitizers & leaks](#leaks_tests) + 5. [Code debugging](#code_debugging) + 6. [Code versioning](#code_versioning) + 7. [Reverse dependencies](#revdep) +2. [Some additional notes](#more_notes) + +
+ + + + +## `SOILWAT2` code is used as part of three applications + * Stand-alone, + + * Part/submodule of [STEPWAT2][] (code flag `STEPWAT`), and + + * Part/submodule of the R package [rSOILWAT2][] (code flag `RSOILWAT`) + +Changes in `SOILWAT2` must be reflected by updates +to `STEPWAT2` and `rSOILWAT2`; +please see section [reverse dependencies](#revdep). + +
+ + + +## Follow our guidelines as detailed [here][guidelines] + +
+ + +## Code development, documentation, and tests go together + +We develop code on development branches and, +after they are reviewed and pass our checks, +merge them into the main branch for release. + + + +### Code documentation + * Document new code with [doxygen][] inline documentation + + * Check that new documentation renders correctly and does not + generate `doxygen` warnings, i.e., + run `make doc` and check that it returns successfully + (it checks that `doxygen doc/Doxyfile | grep warning` is empty). + Also check that new or amended documentation displays + as intended by opening `doc/html/index.html` and navigating to the item + in question + + * Keep `doc/html/` local, i.e., don't push to the repository + + * Use regular c-style comments for additional code documentation + +
+ + + +### Code tests +__Testing framework__ + +The goal is to cover all code development with new or amended tests. +`SOILWAT2` comes with unit tests, integration tests, and extra checks. +Additionally, the github repository runs continuous integration checks. + +Most of these tests and checks are run with the following steps + +```{.sh} + bash tools/check_functionality.sh check_SOILWAT2 "CC=" "CXX=" "txt" "tests/example/Output_ref" "false" + bash tools/check_functionality.sh check_SOILWAT2 "CC=" "CXX=" "nc" "tests/example/Output_ref" "false" + + tools/check_outputModes.sh + + tools/check_extras.sh +``` + +The following sections on tests provide details. + +
+ + +#### Unit tests + +We use [GoogleTest][] for unit tests +to check that individual units of code, e.g., functions, work as expected. + +These tests are organized in the folder `tests/gtests/` +in files with the naming scheme `test_*.cc`. + +Note: `SOILWAT2` is written in C whereas `GoogleTest` is a C++ framework. This +causes some complications, see `makefile`. + + +Run unit tests locally on the command-line with +```{.sh} + make test_run # compiles and executes the tests + make test_severe # compiles/executes with strict/severe flags + make clean_test # cleans build artifacts +``` + + +Users of `SOILWAT2` work with a variety of compilers across different platforms +and we aim to test that this works across a reasonable selection. +We can do that manually or use a bash-script +which runs tests with different compiler versions. +Please note that this script currently works only with `macports`. +```{.sh} + tools/check_withCompilers.sh +``` + + +
+ + +#### Integration tests + +We use integration tests to check that the entire simulation model works +as expected when used in a real-world application setting. + +The folder `tests/example/` contains all necessary inputs to run `SOILWAT2` +for one generic location +(it is a relatively wet and cool site in the sagebrush steppe). + +```{.sh} + make bin_run +``` + +The simulated output is stored at `tests/example/Output/`. + +SOILWAT2 is can be used in text-based or netCDF-based mode (or via rSOILWAT2), +this script makes sure that output between the different versions is the same + +```{.sh} + tools/check_outputModes.sh +``` + + +Another use case is to compare output of a new (development) branch to output +from a previous (reference) release. + +Depending on the purpose of the development branch +the new output should be exactly the same as reference output or +differ in specific ways in specific variables. + +The following steps provide a starting point for such comparisons: + +```{.sh} + # Simulate on reference branch and copy output to "Output_ref" + git checkout master + make bin_run + cp -r tests/example/Output tests/example/Output_ref + + # Switch to development branch and run the same simulation + git checkout + make bin_run + + # Compare the two sets of outputs + # * Lists all output files and determine if they are exactly they same + diff tests/example/Output/ tests/example/Output_ref/ -qs +``` + +
+ + + +#### Extra checks + +Additional output can be generated by passing appropriate flags when running +unit tests. Scripts are available to analyze such output and +figures are stored at `tools/figures/`. + +All of these extra checks are run by +```{.sh} + tools/check_extras.sh +``` + +Currently, the following are implemented: + + - Sun hour angles plots for horizontal and tilted surfaces + + 1. Numbers of daylight hours and of sunrise(s)/sunset(s) + for each latitude and day of year for some slope/aspect combinations + 2. Sunrise(s)/sunset(s) hour angles for each latitude + and some slope/aspect/day of year combinations + + - PET plots as function of radiation, relative humidity, wind speed, and cover + + - Spinup evaluation plots for spinup duration and initialization of + soil moisture and soil temperature + + - Soil temperature vs depth and soil moisture + + + +
+ + + +#### Continuous integration checks + +Development/feature branches can only be merged into the main branch and +released if they pass all checks on the continuous integration servers +(see `.github/workflows/`). + +Please run the "severe", "sanitizer", and "leak" targets locally +(see also `tools/check_SOILWAT2.sh`) +```{.sh} + make clean_build bin_debug_severe + make clean_test test_severe +``` + +
+ + + +#### Sanitizers & leaks + +Run the simulation and tests with the `leaks` program, For instance, +```{.sh} + make clean_build bin_leaks + make clean_test test_leaks +``` + +Run the simulation and tests with sanitizers. For instance, +```{.sh} + make clean_build bin_sanitizer + make clean_test test_sanitizer +``` + +The address sanitizer may not work correctly and/or fail when used with the +`Apple-clang` version that is shipped with macOS X +(see [Sanitizer issue #1026](https://github.com/google/sanitizers/issues/1026)). +A separate installation of `clang` may be required, +e.g., via `homebrew` or `macports`. + + +If `clang` is installed in a non-default location and +if shared dynamic libraries are not picked up correctly, then +the test executable may throw an error `... dyld: Library not loaded ...`. +This can be fixed, for instance, with the following steps +(details depend on the specific setup, below is for `macports` and `clang-8.0`): + +```{.sh} + # build test executable with clang and leak detection + CXX=clang++ ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe + + # check faulty library path + otool -L sw_test + + # figure out correct library path and insert with: e.g., + install_name_tool -change /opt/local/libexec/llvm-8.0/lib/libclang_rt.asan_osx_dynamic.dylib /opt/local/libexec/llvm-8.0/lib/clang/8.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib sw_test + + # run tests + make test_run +``` + +
+ + + +### Debugging + Debugging is controlled at two levels: + * at the preprocessor (pass `-DSWDEBUG`): + all debug code is wrapped by this flag so that it does not end up in + production code; unit testing is compiled in debugging mode. + + * in functions with local debug variable flags (`int debug = 1;`): + debug code can be conditional on such a variable, e.g., + +```{.c} + void foo() { + #ifdef SWDEBUG + int debug = 1; + #endif + ... + #ifdef SWDEBUG + if (debug) swprintf("hello, this is debugging code\n"); + ... + #endif + ... + } +``` + + * Clean, compile and run optimized `SOILWAT2`-standalone in debugging mode + with, e.g., + +```{.sh} + make bin_run CPPFLAGS=-DSWDEBUG +``` + + * Alternatively, use the pre-configured debugging targets + `bin_debug` and `bin_debug_severe`, for instance, with + +```{.sh} + make bin_debug_severe +``` + +
+ + + + +### Version numbers + +We attempt to follow guidelines of [semantic versioning][] with version +numbers of `MAJOR.MINOR.PATCH`; +however, our version number updates are focusing +on simulated output (e.g., identical output -> increase patch number) and +on dependencies `STEPWAT2` and `rSOILWAT2` +(e.g., no updates required -> increase patch number). + +We create a new release for each update to the master branch. +The master branch is updated via pull requests from development branches +after they are reviewed and pass required checks. + + +
+ + + +## Reverse dependencies + +`STEPWAT2` and `rSOILWAT2` depend on `SOILWAT2`; +they utilize the master branch of `SOILWAT2` as a submodule. +Thus, changes in `SOILWAT2` need to be propagated to `STEPWAT2` and `rSOILWAT2`. + +The following steps can serve as starting point to resolve +the cross-repository reverse dependencies: + + 1. Create development branch `branch_*` in `SOILWAT2` + 1. Create respective development branches in `STEPWAT2` and `rSOILWAT2` + 1. Update the `SOILWAT2` submodule of `STEPWAT2` and `rSOILWAT2` as first + commit on these new development branches: + * Have `.gitmodules` point to the new `SOILWAT2` branch `branch_*` + * Update the submodule `git submodule update --remote` + 1. Develop and test code and follow guidelines of `STEPWAT2` and `rSOILWAT2` + 1. Create pull requests for each development branch + 1. Merge pull request `SOILWAT2` once development is finalized, reviewed, and + sufficiently tested across all three repositories; + create new `SOILWAT2` [release](#code_versioning) + 1. Finalize development branches in `STEPWAT2` and `rSOILWAT2` + * Have `.gitmodules` point to the new `SOILWAT2` release on `master` + * Update the submodule `git submodule update --remote` + 1. Handle pull requests for `STEPWAT2` and `rSOILWAT2` according to + their guidelines + +
+ + + +## Notes + +__Organization renamed from Burke-Lauenroth-Lab to DrylandEcology on Dec 22, 2017__ + +All existing information should +[automatically be redirected](https://help.github.com/articles/renaming-a-repository/) +to the new name. +Contributors are encouraged, however, to update local clones to +[point to the new URL](https://help.github.com/articles/changing-a-remote-s-url/), +i.e., + +```{.sh} +git remote set-url origin https://github.com/DrylandEcology/SOILWAT2.git +``` + + +__Repository renamed from SOILWAT to SOILWAT2 on Feb 23, 2017__ + +All existing information should +[automatically be redirected](https://help.github.com/articles/renaming-a-repository/) +to the new name. +Contributors are encouraged, however, to update local clones to +[point to the new URL](https://help.github.com/articles/changing-a-remote-s-url/), +i.e., + +```{.sh} +git remote set-url origin https://github.com/DrylandEcology/SOILWAT2.git +```