Contributing to SOILWAT2

December 17, 2025 ยท View on GitHub

Go back to the main page.

Table of contents

  1. SOILWAT2 code
  2. Code guidelines
    1. Code format
    2. Code checks
    3. Include directives
    4. Other guidelines
  3. Code documentation
  4. Code tests
    1. Unit tests
    2. Integration tests
    3. Extra checks
    4. Continuous integration checks
    5. Sanitizers & leaks
  5. Code debugging
  6. Code versioning
  7. Reverse dependencies
  8. Some additional notes

Code development, documentation, and tests go together

We develop code on development branches and, after they are reviewed and pass our checks, we merge them into the main branch and issue a release. Each release is documented with the main changes in NEWS and on the GitHub release page.


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.


Code guidelines

All code style checks that are discussed below in greater detail, can be run with one script (note that iwyu output is verbose)

    tools/allCodeChecks.sh

Code format

We use a LLVM-derived code style for the SOILWAT2 repository.

The file ".clang-format" documents all details of the code style. We use clang-format v18 (see ClangFormat); please, note that other versions of clang-format do format code differently even if using the same ".clang-format".

A short illustration of our code style

void my_function(
    TYPE1 argument1,
    TYPE2 argument2,
    TYPE3 argument3,
    TYPE4 argument4,
    TYPE5 argument5,
    TYPE6 argument6,
    TYPE7 argument7
) {
    short k; /* short comments are ok after code */

    for (k = 0; k < (short) argument1; k++) {
        /* place long comments on their own line for better readability */
        function_with_few_arguments(k, argument2, argument3);

        function_with_some_arguments(
            k, argument2, argument3, argument4, argument5
        );

        function_with_many_arguments(
            k,
            argument1,
            argument2,
            argument3,
            argument4,
            argument5,
            argument6,
            argument7
        );
    }
}

A git integration allows to format just the lines that differ between the working directory and HEAD. This can be useful when preparing a new commit. More details can be found in the documentation (ClangFormat) including how to use options --staged or --diff

    git clang-format --style=file

We have a script "tools/run_format.sh" that applies the code style to all files in "include/", "src/", and "tests/gtests/" directories. However, please don't include formatting changes to a commit that are unrelated to the developed implementation for that commit.

A github action workflow ".github/workflows/clang-format-check.yml" checks code style for pull requests into the main branch and release branches.


Code checks

We use clang-tidy to check code in the SOILWAT2 repository.

The files ".clang-tidy" and ".clang-tidy_swtests" document all details related to these code checks.

We have make targets "tidy-bin" and "tidy-test" to run these checks on the code and on the test code respectively.

We also have a script "tools/run_tidy.sh" that runs all appropriate checks. The script exits with a failure code if any check reports code issues.

A github action workflow ".github/workflows/clang-tidy-check.yml" checks code for pull requests into the main branch and release branches.


Include directives

Our goal is to include headers only if actually used, and include all headers that are used.

The tool include-what-you-use (iwyu) can provide helpful information.

We have a script "tools/run_iwyu.sh" that illustrates some use cases.


Other guidelines

Additional (and partially outdated) guidelines can be found here


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, i.e., run make doc_open (this opens doc/html/index.html) and visually inspect the item in question.

Please note that the generated documentation at doc/html/ is kept local, i.e., git ignores the local copy and it is not pushed to the repository.

Use regular c-style comments for additional code documentation. Please note line width limit of 80 characters and prefer to place comments on their own line.

A short illustration of our doxygen style (using markdown is ok)

/**
@brief Short description

Some longer description.
Continuation of some longer description.

@param[in] argument1 Argument for foo
    Some more text for argument1
@param[in,out] *argument2 Argument for foo
*/
void foo(int argument1, int *argument2) {
    /* tmp is most important */
    int tmp;

    /* More code here */
}

Code output tests

All output checks that are discussed below in greater detail, can be run with one script

    tools/allOutputChecks.sh --outTag=<ref> --ntasks=2

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.

Ideally, create a reference output from a known state and name it "Output_<ref>" (where <ref> could be "v820" for example).

Most of these tests and checks can be run with the following steps (after substituting <ref> with the actual name)

    # Run text-based and nc-based SOILWAT2 and compare output against Output_<ref>/
    tools/check_functionality.sh check_SOILWAT2 "CC=" "CXX=" "txt" "" "tests/example/Output_<ref>" "false"
    tools/check_functionality.sh check_SOILWAT2 "CC=" "CXX=" "nc" "" "tests/example/Output_<ref>" "false"

    # Run mpi-based SOILWAT2 (using compiler mpicc/mpicxx) and compare output against Output_<ref>/
    tools/check_functionality.sh check_SOILWAT2 "CC=mpicc" "CXX=mpicxx" "mpi" "2" "tests/example/Output_<ref>" "false"

    # Compare output between text-based, nc-based, mpi-based SOILWAT2 and rSOILWAT2
    tools/check_outputModes.sh

    # Check output of a large set of nc-based and mpi-based simulation experiments agains a reference
    tools/check_ncTestRuns.nc
    tools/check_ncTestRuns.nc --mode=mpi --ntasks=2

    # Run checks with additional special use flags
    tools/check_extras.sh

These checks can be modified to run the checks with a specific compiler and compare output from example runs against a previously created reference output (here, Output_<ref>/ -- instead of creating a new reference), e.g.,

    tools/check_functionality.sh check_SOILWAT2 "CC=clang" "CXX=clang++" "txt" "tests/example/Output_<ref>" "false"

The next sections provide additional 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

    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.

    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.

Example

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).

    make bin_run                                           # text-based SOILWAT2
    make CPPFLAGS=-DSWNC bin_run                           # nc-based SOILWAT2
    make CPPFLAGS=-DSWMPI CC=mpicc SW_NTASKS=1 bin_run     # mpi-based SOILWAT2

The simulated output is stored at tests/example/Output/.

ncTestRuns

The folder tests/ncTestRuns contains several complete simulation projects for nc-based SOILWAT2. They are designed to cover the most common combinations of simulation domains and inputs, e.g., gridded vs. site-based, geographic vs. projected CRS, external weather datasets.

One site/grid cell in the simulation domain is set up to correspond to the reference run (which, by default, is equivalent to tests/example); the output of that site/grid cell is compared against the reference output.

    tools/check_ncTestRuns.sh --help                    # Display a help page
    tools/check_ncTestRuns.sh                           # Do all tests in nc-mode
    tools/check_ncTestRuns.nc --mode=mpi --ntasks=2     # Do all tests in mpi-mode

Output from different modes

SOILWAT2 is can be used in text-based, nc-based or mpi-based mode and via rSOILWAT2, this script checks whether output between the different versions is the same

    tools/check_outputModes.sh

Environmental variables that make needs to see when running the script should be exported. For instance,

    export UD_LIBS="-L/path/to/libudunits2"
    tools/check_outputModes.sh

Output comparison between two nc-based simulation runs

The R script "Rscript__SW2_compareOutputNC.R" compares nc-output from two different single-site simulations:

  • equality check (that can be skipped)
  • visualizing differences at each time step (day, week, month, year) as plots stored to PDF files

For instance

Rscript \
    tools/rscripts/Rscript__SW2_compareOutputNC.R \
    --pathToOut1=tests/example/Output \
    --pathToOut2=tests/ncTestRuns/results/referenceRuns/example/Output

See "Rscript__SW2_compareOutputNC.R" for a complete list of arguments.

Output comparison to another version

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:

    # 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 <branch_xxx> and run the same simulation
    git checkout <branch_xxx>
    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

    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)

      make clean_build bin_debug_severe
      make clean_test test_severe

Sanitizers & leaks

Run the simulation and tests with the leaks program, For instance,

      make clean_build bin_leaks
      make clean_test test_leaks

Run the simulation and tests with sanitizers. For instance,

      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). 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):

      # 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.,

    void foo() {
      #ifdef SWDEBUG
      int debug = 1;
      #endif
      ...
      #ifdef SWDEBUG
      if (debug) sw_printf("hello, this is debugging code\n");
      ...
      #endif
      ...
    }
  • Clean, compile and run optimized SOILWAT2-standalone in debugging mode with, e.g.,
    make bin_run CPPFLAGS=-DSWDEBUG
  • Alternatively, use the pre-configured debugging targets bin_debug and bin_debug_severe, for instance, with
    make bin_debug_severe

Releases and version numbering

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 main branch. The main 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
  2. Create respective development branches in STEPWAT2 and rSOILWAT2
  3. 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
  4. Develop and test code and follow guidelines of STEPWAT2 and rSOILWAT2
  5. Create pull requests for each development branch
  6. Merge pull request SOILWAT2 once development is finalized, reviewed, and sufficiently tested across all three repositories; create new SOILWAT2 release
  7. Finalize development branches in STEPWAT2 and rSOILWAT2
    • Have .gitmodules point to the new SOILWAT2 release on master
    • Update the submodule git submodule update --remote
  8. 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 to the new name. Contributors are encouraged, however, to update local clones to point to the new URL, i.e.,

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 to the new name. Contributors are encouraged, however, to update local clones to point to the new URL, i.e.,

git remote set-url origin https://github.com/DrylandEcology/SOILWAT2.git