======= Testing ======= Cocoa uses Catch2 for unit and integration testing, with Kokkos/Trilinos initialization handled in a custom main entry point. Test Framework -------------- Cocoa uses `Catch2 `_ (v3) as its testing framework. Tests are organized using ``TEST_CASE`` and ``SECTION`` macros: .. code-block:: cpp #include TEST_CASE("Mesh", "[mesh]") { // Setup code runs for each section const auto mesh = create_test_mesh(); SECTION("Valid mesh construction") { REQUIRE(mesh.num_nodes() == 9); REQUIRE(mesh.num_elements() == 8); } SECTION("Coordinate access") { const auto& nodes = mesh.nodes(); REQUIRE_THAT(nodes[0].location().x(), WithinAbs(-88.0, 1e-10)); } } The test executable uses a custom ``main()`` to initialize Tpetra/Kokkos before running tests: .. code-block:: cpp #define CATCH_CONFIG_RUNNER #include #include "Tpetra_Core.hpp" int main(int argc, char* argv[]) { Tpetra::ScopeGuard tpetra_scope_guard(&argc, &argv); return Catch::Session().run(argc, argv); } Building and Running Tests -------------------------- .. code-block:: bash # Build with tests (enabled by default) mkdir build && cd build cmake .. make -j8 # Run all tests via CTest ctest --output-on-failure # Run the test executable directly ./test/cocoa_tests # Run specific test cases by tag ./test/cocoa_tests "[mesh]" ./test/cocoa_tests "[gwce]" # Run specific test case by name ./test/cocoa_tests "Mesh" # List all available tests ./test/cocoa_tests --list-tests Test Files ---------- Test files are located in the ``test/`` directory: .. list-table:: :header-rows: 1 :widths: 35 65 * - File - Description * - ``test_main.cpp`` - Entry point with Tpetra/Kokkos initialization * - ``test_mesh.cpp`` - Mesh construction and access * - ``test_neighbor_table.cpp`` - Node connectivity and neighbor tables * - ``test_point2d.cpp`` - 2D point and geometry operations * - ``test_projection.cpp`` - Coordinate projection (CPP) * - ``test_datetime.cpp`` - DateTime and TimeDelta classes * - ``test_timestepper.cpp`` - Time stepping iteration * - ``test_temporal_field.cpp`` - Multi-level temporal field storage * - ``test_gwce_consistent.cpp`` - GWCE consistent (implicit) solver and matrix assembly * - ``test_gwce_lumped.cpp`` - GWCE lumped (explicit) solver * - ``test_wetdry.cpp`` - Wetting and drying algorithm * - ``test_tide_potential.cpp`` - Tidal potential forcing * - ``test_tidal_boundary.cpp`` - Tidal boundary conditions * - ``test_momentum.cpp`` - Momentum solver * - ``test_bottom_friction.cpp`` - Bottom friction computation * - ``test_coriolis.cpp`` - Coriolis force computation * - ``test_advection.cpp`` - Advection terms * - ``test_pressure_gradient.cpp`` - Pressure gradient computation * - ``test_inline_lateral_stress.cpp`` - Inline lateral stress computation * - ``test_smagorinsky.cpp`` - Smagorinsky turbulence model * - ``test_basis_functions.cpp`` - Finite element basis functions * - ``test_coordinate_rotation.cpp`` - Coordinate rotation for global meshes * - ``test_ramp_function.cpp`` - Forcing ramp functions * - ``test_configuration_reader.cpp`` - YAML configuration reader * - ``test_netcdf_io.cpp`` - NetCDF I/O operations * - ``test_logger.cpp`` - Logging system * - ``test_tide_astronomic.cpp`` - Astronomical tide potential * - ``test_forcing_manager.cpp`` - Forcing manager orchestration * - ``test_boundary_type.cpp`` - Boundary type classification * - ``test_flow_boundary.cpp`` - Flow boundary conditions (:math:`Q_{\text{force}}` kernels, time series, segment integration) * - ``test_schema_validation.cpp`` - YAML configuration schema validation * - ``test_distributed.cpp`` - Distributed computing (serial mode) * - ``test_distributed_mpi.cpp`` - MPI distributed computing (multi-rank) Test Data --------- Test data and helper utilities are organized as follows: .. code-block:: text test/ ├── TestMeshData.hpp # Test mesh generation utilities ├── TestPrecision.hpp # Mixed-precision test tolerance helpers ├── test_*.cpp # Test source files ├── run_integration_test.cmake # Integration test runner script └── data/ ├── test_config.yaml # ConfigurationReader test fixture └── wnat/ # WNAT integration test data ├── wnat.nc # Mesh file ├── cocoa_config_integration_test.yaml # Tides-only config ├── compare_tides_reference.py # Reference comparison script ├── plot_comparison.py # Comparison plot generator ├── wnat_reference_fp64.nc # Serial reference └── wnat_reference_mpi_fp64.nc # MPI reference examples/ ├── flow_channel/ # Specified flux (type 22) channel ├── flow_channel_radiation/ # Flux + radiation (type 32) channel ├── global_case/ # Global ocean tidal case └── wnat/ # WNAT example configurations and met data See :doc:`../user_guide/test_cases` for detailed descriptions of each test case, including expected results and verification plots. The ``TestMeshData.hpp`` helper provides functions to create small test meshes: .. code-block:: cpp #include "TestMeshData.hpp" using TestData::TestMeshData; TEST_CASE("Example", "[example]") { const auto [x_coords, y_coords] = TestMeshData::create_coordinates(); const auto elevation = TestMeshData::create_elevation(); const auto triangles = TestMeshData::create_triangles(); const auto boundaries = TestMeshData::empty_boundaries(); // Construct HostMesh (held by unique_ptr), then wrap in Mesh auto host_mesh = std::make_unique( x_coords, y_coords, elevation, triangles, boundaries, TestMeshData::origin, TestMeshData::projection_type); const Cocoa::Mesh mesh(std::move(host_mesh)); } Catch2 Assertions ----------------- Common assertions used in Cocoa tests: **Basic Assertions:** .. code-block:: cpp REQUIRE(expr); // Fatal assertion CHECK(expr); // Non-fatal assertion REQUIRE_FALSE(expr); // Require expression is false REQUIRE_NOTHROW(expr); // Require no exception thrown REQUIRE_THROWS_AS(expr, ExceptionType); // Require specific exception **Floating-Point Comparisons:** .. code-block:: cpp #include #include using Catch::Approx; using Catch::Matchers::WithinAbs; using Catch::Matchers::WithinRel; REQUIRE(value == Approx(expected)); // Default tolerance REQUIRE(value == Approx(expected).epsilon(1e-6)); // Custom tolerance REQUIRE_THAT(value, WithinAbs(expected, 1e-10)); // Absolute tolerance REQUIRE_THAT(value, WithinRel(expected, 1e-6)); // Relative tolerance **Sections for Test Organization:** .. code-block:: cpp TEST_CASE("Component", "[tag]") { // Setup code runs before each section auto component = create_component(); SECTION("First behavior") { // Test first behavior REQUIRE(component.first_method() == expected); } SECTION("Second behavior") { // Test second behavior (fresh component) REQUIRE(component.second_method() == expected); } } Writing New Tests ----------------- 1. Create or edit a test file in ``test/``: .. code-block:: cpp #include #include "your_header.hpp" TEST_CASE("ComponentName", "[component][tag]") { SECTION("Descriptive behavior name") { // Arrange auto obj = create_object(); // Act auto result = obj.method(); // Assert REQUIRE(result == expected); } } 2. Add the file to ``test/CMakeLists.txt`` if creating a new file: .. code-block:: cmake set(COCOA_TEST_SOURCES test_main.cpp # ... existing files ... test_your_new_file.cpp) 3. Rebuild and run: .. code-block:: bash make cocoa_tests ./test/cocoa_tests "[your_tag]" Compile-Time Tests ------------------ For ``constexpr`` functionality, use ``static_assert``: .. code-block:: cpp // Compile-time tests for constexpr functions static_assert(TimeDelta::fromDays(1).totalHours() == 24, "Day to hours conversion failed"); static_assert(TimeDelta::fromHours(24) == TimeDelta::fromDays(1), "Equality comparison failed"); Integration Tests ----------------- In addition to unit tests, Cocoa includes end-to-end integration tests that run a complete simulation on the Western North Atlantic (WNAT) mesh and compare results against known-good reference solutions. These tests verify that the full simulation pipeline (mesh loading, tidal forcing with SAL, time stepping, output) produces bitwise-reproducible results. Output is always delivered on a background writer thread, so every run exercises the asynchronous write path; there is no I/O mode to select. .. list-table:: :header-rows: 1 :widths: 35 15 50 * - Test Name - Ranks - Description * - ``Integration_WNAT_tides`` - 1 - Serial simulation compared against serial reference * - ``Integration_WNAT_tides_mpi`` - 2 - Two compute ranks (partitioned mesh, MPI-specific reference) Each test runs a 1-day tides-only simulation (M2 boundary + tide potential + SAL) with :math:`\Delta t` = 20 s and compares the output against the serial reference solution (``wnat_reference_fp64.nc``). The MPI test uses a separate reference (``wnat_reference_mpi_fp64.nc``) because mesh partitioning changes floating-point operation order. All integration tests use a tolerance of ``1e-10``, enforcing near-exact agreement with the reference. The comparison script also verifies that wet/dry state (NaN locations) matches between test and reference at every timestep. After comparison, a Cartopy-based plotting script generates a visual summary (``wnat_comparison.png``) with peak water level and velocity maps, difference maps, and time series at probe points. In CI, this plot is uploaded as a GitHub Actions artifact. **Prerequisites:** Integration tests require Python 3 with ``numpy``, ``xarray``, ``netCDF4``, and ``cartopy``. If these modules are not available, the integration tests are silently disabled at CMake configure time. **CMake options:** Integration tests are **off by default** and must be explicitly enabled: .. code-block:: bash # Enable integration tests cmake .. -Dcocoa_ENABLE_INTEGRATION_TESTS=ON In CI, integration tests are enabled for the serial build-and-test jobs and the coverage job. GPU compile-check jobs do not run tests. **Running integration tests selectively:** .. code-block:: bash # Run only integration tests ctest -R Integration # Run only the serial integration test ctest -R Integration_WNAT_tides$ # Run only the MPI integration test ctest -R Integration_WNAT_tides_mpi # Exclude integration tests (run unit tests only) ctest -E Integration **Regenerating reference solutions:** If numerical methods change and produce different (but correct) results, the reference solutions must be regenerated: .. code-block:: bash cd test/data/wnat # Serial reference /path/to/cocoa -i cocoa_config_integration_test.yaml mv cocoa_output.nc wnat_reference_fp64.nc # MPI reference (2 compute ranks) mpiexec -np 2 /path/to/cocoa -i cocoa_config_integration_test.yaml mv cocoa_output.nc wnat_reference_mpi_fp64.nc Debugging Failed Tests ---------------------- .. code-block:: bash # Run with verbose output ./test/cocoa_tests -s "[failing_tag]" # Run under debugger lldb ./test/cocoa_tests -- "[failing_tag]" # Run specific named test ./test/cocoa_tests "Exact Test Name" # Show test durations ./test/cocoa_tests -d yes