diff --git a/.github/workflows/ecbundle_release.yml b/.github/workflows/ecbundle_release.yml index 44bd7e49b..eaba4cbe5 100644 --- a/.github/workflows/ecbundle_release.yml +++ b/.github/workflows/ecbundle_release.yml @@ -145,7 +145,6 @@ jobs: cmake \ -DCMAKE_BUILD_TYPE=${{ github.event.inputs.build_type || 'Release' }} \ -DBUILD_TESTING=ON \ - -DENABLE_INTEGRATION_TESTS=ON \ -DENABLE_MPI_TESTS=ON \ -DTEST_TIMEOUT=${{ github.event.inputs.test_timeout || '600' }} \ -DCMAKE_C_COMPILER=gcc \ diff --git a/.github/workflows/fesom2_ctest_runner.yml b/.github/workflows/fesom2_ctest_runner.yml index 7f6ff65e2..2788e7a12 100644 --- a/.github/workflows/fesom2_ctest_runner.yml +++ b/.github/workflows/fesom2_ctest_runner.yml @@ -1,5 +1,10 @@ name: FESOM2 CTest Framework (Native Runner) +# This workflow tests FESOM2 core functionality using local meshes only. +# It skips remote mesh tests to avoid external dependencies in CI. +# For full testing including remote meshes, run locally with: +# ./configure.sh ubuntu -DBUILD_TESTING=ON -DBUILD_MESHDIAG=ON -DBUILD_MESHPARTITIONER=ON + # Controls when the action will run on: # Triggers the workflow on push or pull request events @@ -128,7 +133,8 @@ jobs: cmake \ -DCMAKE_BUILD_TYPE=${{ github.event.inputs.build_type || 'Release' }} \ -DBUILD_TESTING=ON \ - -DENABLE_INTEGRATION_TESTS=ON \ + -DBUILD_MESHDIAG=ON \ + -DBUILD_MESHPARTITIONER=ON \ -DENABLE_MPI_TESTS=ON \ -DTEST_TIMEOUT=${{ github.event.inputs.test_timeout || '600' }} \ -DCMAKE_C_COMPILER=gcc \ @@ -154,7 +160,7 @@ jobs: echo "Build completed successfully" - # Verify executable exists + # Verify executables exist if [ -f "bin/fesom.x" ]; then echo "✅ FESOM2 executable created successfully" ls -la bin/fesom.x @@ -163,6 +169,20 @@ jobs: find . -name "fesom.x" -type f || echo "No fesom.x found anywhere" exit 1 fi + + if [ -f "bin/fesom_meshdiag" ]; then + echo "✅ FESOM2 meshdiag executable created successfully" + ls -la bin/fesom_meshdiag + else + echo "⚠️ FESOM2 meshdiag executable not found" + fi + + if [ -f "bin/fesom_meshpart" ]; then + echo "✅ FESOM2 mesh partitioner executable created successfully" + ls -la bin/fesom_meshpart + else + echo "⚠️ FESOM2 mesh partitioner executable not found" + fi - name: List available tests run: | @@ -205,14 +225,15 @@ jobs: echo "Running tests matching pattern: $TEST_PATTERN" ctest $CTEST_ARGS -R "$TEST_PATTERN" else - echo "Running selected integration tests:" - echo "- integration_full_run_mpi2" - echo "- integration_full_run_mpi8" + echo "Running core integration tests:" + echo "- integration_pi_mpi2" + echo "- integration_pi_mpi8" echo "- integration_pi_cavity_mpi2" - echo "- integration_test_data_check" + echo "- integration_meshdiag_pi_mpi2" + echo "- meshpartitioner_partition_local_pi_mesh_4" - # Run only the specified tests using regex pattern - ctest $CTEST_ARGS -R "(integration_full_run_mpi2|integration_full_run_mpi8|integration_pi_cavity_mpi2|integration_test_data_check)" + # Run only the core local tests (skip remote tests in CI to avoid external dependencies) + ctest $CTEST_ARGS -R "(integration_pi_mpi2|integration_pi_mpi8|integration_pi_cavity_mpi2|integration_meshdiag_pi_mpi2|meshpartitioner_partition_local_pi_mesh_4)" fi # Capture exit code diff --git a/cmake/FesomTesting.cmake b/cmake/FesomTesting.cmake index ea65978c2..1466a0b5c 100644 --- a/cmake/FesomTesting.cmake +++ b/cmake/FesomTesting.cmake @@ -85,9 +85,9 @@ function(update_namelist_config_with_options NAMELIST_IN NAMELIST_OUT STEP_PER_D # Set logfile output frequency string(REGEX REPLACE "logfile_outfreq=[0-9]+" "logfile_outfreq=${LOGFILE_OUTFREQ}" CONTENT "${CONTENT}") # Force rotation for test geometry - string(REGEX REPLACE "force_rotation=.[a-zA-Z]." "force_rotation=${FORCE_ROTATION}" CONTENT "${CONTENT}") + string(REGEX REPLACE "force_rotation=\\.[a-zA-Z]+\\." "force_rotation=${FORCE_ROTATION}" CONTENT "${CONTENT}") # Set cavity usage - string(REGEX REPLACE "use_cavity=.[a-zA-Z]." "use_cavity=${USE_CAVITY}" CONTENT "${CONTENT}") + string(REGEX REPLACE "use_cavity=\\.[a-zA-Z]+\\." "use_cavity=${USE_CAVITY}" CONTENT "${CONTENT}") file(WRITE "${NAMELIST_OUT}" "${CONTENT}") endfunction() @@ -482,10 +482,15 @@ endfunction() # Function to add a FESOM meshdiag test with custom options function(add_fesom_meshdiag_test_with_options TEST_NAME MESH_NAME RUNID) - set(oneValueArgs NP TIMEOUT) + set(oneValueArgs NP TIMEOUT PREFIX) set(multiValueArgs COMMAND_ARGS) cmake_parse_arguments(FESOM_TEST "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + # Apply prefix if provided + if(FESOM_TEST_PREFIX) + set(TEST_NAME "${FESOM_TEST_PREFIX}_${TEST_NAME}") + endif() + # Set defaults (meshdiag requires MPI, minimum 2 processes) if(NOT DEFINED FESOM_TEST_NP) set(FESOM_TEST_NP 2) @@ -566,3 +571,187 @@ function(add_fesom_meshdiag_test_with_options TEST_NAME MESH_NAME RUNID) message(STATUS "Added FESOM meshdiag test: ${TEST_NAME} with mesh: ${MESH_NAME}, runid: ${RUNID}") endfunction() + +#=============================================================================== +# Generic Mesh Pipeline Functions +#=============================================================================== + +# Function to add mesh download fixture +function(add_mesh_download_fixture MESH_NAME) + # Parse optional prefix argument + set(oneValueArgs PREFIX) + cmake_parse_arguments(DOWNLOAD_ARGS "" "${oneValueArgs}" "" ${ARGN}) + + if(DOWNLOAD_ARGS_PREFIX) + set(DOWNLOAD_TEST_NAME "${DOWNLOAD_ARGS_PREFIX}_download_mesh_${MESH_NAME}") + else() + set(DOWNLOAD_TEST_NAME "download_mesh_${MESH_NAME}") + endif() + + add_test( + NAME ${DOWNLOAD_TEST_NAME} + COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DMESH_NAME=${MESH_NAME} + -P ${CMAKE_SOURCE_DIR}/tests/integration/mesh_download.cmake + ) + + set_tests_properties(${DOWNLOAD_TEST_NAME} PROPERTIES + TIMEOUT 1200 # 20 minutes for download + FIXTURES_SETUP "mesh_${MESH_NAME}" + LABELS "download_mesh" + ) + + message(STATUS "Added mesh download fixture: ${DOWNLOAD_TEST_NAME}") +endfunction() + +# Function to add mesh partition fixture +function(add_mesh_partition_fixture MESH_NAME NUM_PROCESSES) + # Parse optional prefix argument + set(oneValueArgs PREFIX) + cmake_parse_arguments(PARTITION_ARGS "" "${oneValueArgs}" "" ${ARGN}) + + if(PARTITION_ARGS_PREFIX) + set(PARTITION_TEST_NAME "${PARTITION_ARGS_PREFIX}_partition_mesh_${MESH_NAME}_${NUM_PROCESSES}") + else() + set(PARTITION_TEST_NAME "partition_mesh_${MESH_NAME}_${NUM_PROCESSES}") + endif() + + add_test( + NAME ${PARTITION_TEST_NAME} + COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DBUILD_DIR=${CMAKE_BINARY_DIR} + -DMESH_NAME=${MESH_NAME} + -DNUM_PROCESSES=${NUM_PROCESSES} + -P ${CMAKE_SOURCE_DIR}/tests/integration/mesh_partition.cmake + ) + + set_tests_properties(${PARTITION_TEST_NAME} PROPERTIES + TIMEOUT 1200 # 20 minutes for partitioning + FIXTURES_SETUP "mesh_${MESH_NAME}_${NUM_PROCESSES}" + FIXTURES_REQUIRED "mesh_${MESH_NAME}" + LABELS "partition_mesh" + ) + + message(STATUS "Added mesh partition fixture: ${PARTITION_TEST_NAME}") +endfunction() + +# Function to add complete mesh pipeline (download + multiple partitions) +function(add_mesh_pipeline MESH_NAME) + # Parse arguments + set(oneValueArgs PREFIX) + set(multiValueArgs PROCESS_COUNTS) + cmake_parse_arguments(PIPELINE_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Use remaining args as process counts if not specified + if(NOT PIPELINE_ARGS_PROCESS_COUNTS) + set(PIPELINE_ARGS_PROCESS_COUNTS ${PIPELINE_ARGS_UNPARSED_ARGUMENTS}) + if(NOT PIPELINE_ARGS_PROCESS_COUNTS) + set(PIPELINE_ARGS_PROCESS_COUNTS 2 4) # Default process counts + endif() + endif() + + # Add download fixture + if(PIPELINE_ARGS_PREFIX) + add_mesh_download_fixture(${MESH_NAME} PREFIX ${PIPELINE_ARGS_PREFIX}) + else() + add_mesh_download_fixture(${MESH_NAME}) + endif() + + # Add partition fixtures for each process count + foreach(NP ${PIPELINE_ARGS_PROCESS_COUNTS}) + if(PIPELINE_ARGS_PREFIX) + add_mesh_partition_fixture(${MESH_NAME} ${NP} PREFIX ${PIPELINE_ARGS_PREFIX}) + else() + add_mesh_partition_fixture(${MESH_NAME} ${NP}) + endif() + endforeach() + + message(STATUS "Added complete mesh pipeline for '${MESH_NAME}' with process counts: ${PIPELINE_ARGS_PROCESS_COUNTS}") +endfunction() + +# Enhanced function to add FESOM test with mesh pipeline integration +function(add_fesom_mesh_test TEST_NAME MESH_NAME NP) + set(options MPI_TEST) + set(oneValueArgs TIMEOUT) + set(multiValueArgs) + cmake_parse_arguments(FESOM_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Set defaults + if(NOT DEFINED FESOM_TEST_TIMEOUT) + set(FESOM_TEST_TIMEOUT 900) # 15 minutes default + endif() + + # Parse mesh configuration from registry + set(MESH_REGISTRY "${CMAKE_SOURCE_DIR}/tests/mesh_registry.json") + if(EXISTS "${MESH_REGISTRY}") + file(READ "${MESH_REGISTRY}" REGISTRY_CONTENT) + + # Extract mesh configuration + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"force_rotation\"[ ]*:[ ]*([^,}]+)" ROTATION_MATCH "${REGISTRY_CONTENT}") + if(ROTATION_MATCH) + string(STRIP "${CMAKE_MATCH_1}" FORCE_ROTATION_RAW) + string(REPLACE "true" ".true." FORCE_ROTATION "${FORCE_ROTATION_RAW}") + string(REPLACE "false" ".false." FORCE_ROTATION "${FORCE_ROTATION}") + else() + set(FORCE_ROTATION ".true.") # Default + endif() + + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"use_cavity\"[ ]*:[ ]*([^,}]+)" CAVITY_MATCH "${REGISTRY_CONTENT}") + if(CAVITY_MATCH) + string(STRIP "${CMAKE_MATCH_1}" USE_CAVITY_RAW) + string(REPLACE "true" ".true." USE_CAVITY "${USE_CAVITY_RAW}") + string(REPLACE "false" ".false." USE_CAVITY "${USE_CAVITY}") + else() + set(USE_CAVITY ".false.") # Default + endif() + + # Extract timing parameters + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"step_per_day\"[ ]*:[ ]*([^,}]+)" SPD_MATCH "${REGISTRY_CONTENT}") + if(SPD_MATCH) + string(STRIP "${CMAKE_MATCH_1}" STEP_PER_DAY) + else() + set(STEP_PER_DAY "288") # Default + endif() + + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"run_length\"[ ]*:[ ]*([^,}]+)" RL_MATCH "${REGISTRY_CONTENT}") + if(RL_MATCH) + string(STRIP "${CMAKE_MATCH_1}" RUN_LENGTH) + else() + set(RUN_LENGTH "1") # Default + endif() + + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"run_length_unit\"[ ]*:[ ]*\"([^\"]+)\"" RLU_MATCH "${REGISTRY_CONTENT}") + if(RLU_MATCH) + string(STRIP "${CMAKE_MATCH_1}" RUN_LENGTH_UNIT) + else() + set(RUN_LENGTH_UNIT "s") # Default + endif() + else() + # Fallback defaults + set(FORCE_ROTATION ".true.") + set(USE_CAVITY ".false.") + set(STEP_PER_DAY "288") + set(RUN_LENGTH "1") + set(RUN_LENGTH_UNIT "s") + endif() + + # Create FESOM test with appropriate configuration from registry + add_fesom_test_with_options(${TEST_NAME} + "${MESH_NAME}" "${STEP_PER_DAY}" "${RUN_LENGTH}" "${RUN_LENGTH_UNIT}" "1" "d" "10" "${FORCE_ROTATION}" "${USE_CAVITY}" + MPI_TEST + NP ${NP} + TIMEOUT ${FESOM_TEST_TIMEOUT} + ) + + # Add mesh pipeline dependency + set_tests_properties(${TEST_NAME} PROPERTIES + FIXTURES_REQUIRED "mesh_${MESH_NAME}_${NP}" + LABELS "mesh_simulation" + ) + + message(STATUS "Added FESOM mesh test: ${TEST_NAME} (mesh: ${MESH_NAME}, ${NP} processes)") + message(STATUS " Configuration: force_rotation=${FORCE_ROTATION}, use_cavity=${USE_CAVITY}") + message(STATUS " Timing: step_per_day=${STEP_PER_DAY}, run_length=${RUN_LENGTH} ${RUN_LENGTH_UNIT}") +endfunction() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2939b7a31..c51761f9e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,7 +16,6 @@ if(NOT TARGET fesom) endif() # Testing configuration options -option(ENABLE_INTEGRATION_TESTS "Enable integration tests that run FESOM" ON) option(ENABLE_MPI_TESTS "Enable MPI tests (requires mpiexec/mpirun)" ON) set(TEST_TIMEOUT 600 CACHE STRING "Default timeout for tests in seconds") @@ -56,26 +55,28 @@ endif() # Print test configuration message(STATUS "FESOM2 Testing Configuration:") -message(STATUS " Integration tests: ${ENABLE_INTEGRATION_TESTS}") message(STATUS " MPI tests: ${ENABLE_MPI_TESTS}") message(STATUS " Test timeout: ${TEST_TIMEOUT}s") message(STATUS " Test data directory: ${TEST_DATA_DIR}") message(STATUS " MPI executable: ${MPIEXEC_EXECUTABLE}") -# Add integration tests if enabled -if(ENABLE_INTEGRATION_TESTS) - add_subdirectory(integration) -endif() +# Add integration tests +add_subdirectory(integration) + +# Add mesh partitioner tests +add_subdirectory(meshpartitioner) + + +# Add remote mesh pipeline tests +add_subdirectory(remote) # Add unit tests (for future expansion) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/unit/CMakeLists.txt") add_subdirectory(unit) endif() -# Add ecbundle tests if ecbundle is available -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ecbundle/CMakeLists.txt") - add_subdirectory(ecbundle) -endif() +# Add ecbundle tests +add_subdirectory(ecbundle) # Create a convenience target to run all tests add_custom_target(run_tests @@ -106,9 +107,19 @@ endif() # Create target to run only meshdiag tests (if BUILD_MESHDIAG is enabled) if(BUILD_MESHDIAG) add_custom_target(run_meshdiag_tests - COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R "meshdiag_" + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R "(meshdiag_|generate_meshdiag_)" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Running FESOM2 meshdiag tests" VERBATIM ) endif() + +# Create target to run only mesh partitioner tests (if BUILD_MESHPARTITIONER is enabled) +if(BUILD_MESHPARTITIONER) + add_custom_target(run_meshpartitioner_tests + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -L "meshpartitioner" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running FESOM2 mesh partitioner tests" + VERBATIM + ) +endif() diff --git a/tests/README.md b/tests/README.md index 5033920e5..6a47de288 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,329 +1,329 @@ # FESOM2 Testing Framework -This directory contains the testing framework for FESOM2, including unit tests, integration tests, and system tests. +This directory contains the comprehensive testing framework for FESOM2, organized into focused test groups for different aspects of the model. ## Table of Contents - [Quick Start](#quick-start) -- [CMake Configuration](#cmake-configuration) -- [Running Tests](#running-tests) +- [Test Enablement](#test-enablement) - [Test Organization](#test-organization) -- [Test Data Setup](#test-data-setup) -- [Debugging Tests](#debugging-tests) -- [Adding New Tests](#adding-new-tests) +- [Listing and Filtering Tests](#listing-and-filtering-tests) +- [Running Tests](#running-tests) +- [Custom Test Targets](#custom-test-targets) +- [Test Configuration](#test-configuration) +- [Test Data](#test-data) +- [Troubleshooting](#troubleshooting) ## Quick Start -1. **Build with testing enabled** (default): - ```bash - # Create build directory and configure - mkdir -p build && cd build - cmake -DBUILD_TESTING=ON .. - - # Build the code and tests - make -j$(nproc) - - # Build specific test targets - make fesom_test # Build all tests - make fesom.x # Build main executable - ``` - -2. **Run tests**: - ```bash - # Run all tests with verbose output - ctest --output-on-failure -V - - # Run tests matching a pattern - ctest -R "integration" # Run integration tests - ctest -R "mpi" # Run all MPI tests - ctest -R "basic" # Run basic tests - - # Run specific test by name - ctest -R integration_basic_run_mpi8 - - # Run tests with more output - ctest --output-on-failure --verbose - - # Run tests in parallel (e.g., 4 jobs) - ctest -j4 --output-on-failure - ``` +```bash +# 1. Build with all testing components +./configure.sh ubuntu -DBUILD_TESTING=ON -DBUILD_MESHDIAG=ON -DBUILD_MESHPARTITIONER=ON -3. **Use convenience targets**: - ```bash - make test # Run all tests (same as 'ctest') - make run_tests # Alias for 'make test' - make run_integration_tests # Run only integration tests - make run_mpi_tests # Run only MPI tests - make run_unit_tests # Run only unit tests - ``` +# 2. List available tests (see what's enabled) +ctest -N -## CMake Configuration +# 3. Run all tests +ctest --output-on-failure +``` -### Build Configuration Options +## Test Enablement -When configuring the build with CMake, you can use these options: +**Tests are conditionally enabled based on build configuration.** The test system automatically detects available components and includes only relevant tests. +### Core Testing ```bash -cmake \ - -DBUILD_TESTING=ON \ - -DENABLE_MPI_TESTS=ON \ - -DMPIEXEC_MAX_NUMPROCS=8 \ - -DCMAKE_BUILD_TYPE=Debug \ - .. +-DBUILD_TESTING=ON # Required - enables basic test framework ``` +**Enables**: Integration tests, basic infrastructure -Key CMake options: -- `BUILD_TESTING`: Enable testing (default: ON) -- `ENABLE_MPI_TESTS`: Enable MPI tests (default: ON) -- `MPIEXEC_MAX_NUMPROCS`: Maximum number of MPI processes for tests -- `CMAKE_BUILD_TYPE`: Build type (Debug, Release, RelWithDebInfo, MinSizeRel) -- `TEST_TIMEOUT`: Global test timeout in seconds (default: 600) - -### Building Specific Test Targets - +### Component-Specific Testing ```bash -# Build all tests -make fesom_test +-DBUILD_MESHDIAG=ON # Enables mesh diagnostics tests +-DBUILD_MESHPARTITIONER=ON # Enables mesh partitioner tests +``` -# Build specific test -make integration_basic_run_mpi8 +### System Dependencies +The system **automatically detects** and reports: +- **Download capability**: wget or curl (for remote tests) +- **MPI availability**: mpiexec/mpirun (for MPI tests) -# Build and run a specific test -make integration_basic_run_mpi8 && ctest -R integration_basic_run_mpi8 -V +### Detection Report Example +``` +Remote mesh pipeline component availability: + Download capability: TRUE (wget: /usr/bin/wget) + Mesh partitioner: ON + Mesh diagnostics: ON ``` -## Running Tests - -### Listing Available Tests +**Missing components result in clear skip messages** with instructions to enable them. -```bash -# List all available tests -ctest -N +## Test Organization -# List tests matching a pattern -ctest -N -R "integration" +The test framework is organized into **4 focused directories**: -# Show detailed information about tests -ctest --show-only=json-v1 -``` +### 📁 `integration/` - Local Integration Tests +**Purpose**: Core FESOM functionality using local mesh files +- `integration_pi_mpi2` - PI mesh with 2 processes +- `integration_pi_mpi8` - PI mesh with 8 processes +- `integration_pi_cavity_mpi2` - PI cavity mesh with 2 processes +- `integration_meshdiag_pi_mpi2/8` - Generate mesh diagnostics (`fesom.mesh.diag.nc`) -### Running Specific Tests +**Requirements**: Local mesh files in `tests/data/MESHES/` -```bash -# Run a single test -ctest -R integration_basic_run_mpi8 +### 📁 `remote/` - Remote Mesh Pipeline Tests +**Purpose**: Test complete pipeline for downloading, partitioning, and validating remote meshes +- `remote_download_mesh_{name}` - Download mesh from remote repository +- `remote_partition_mesh_{name}_{procs}` - Partition mesh for specified process count +- `remote_generate_meshdiag_{name}_mpi{procs}` - Generate mesh diagnostics (`fesom.mesh.diag.nc`) -# Run tests matching a pattern -ctest -R "mpi" +**Supported Meshes**: core2, dars, pi_remote, ng5, orca25 +**Requirements**: wget/curl, internet connection -# Run tests excluding certain patterns -ctest -E "long_running|performance" +### 📁 `meshpartitioner/` - Local Mesh Partitioning Tests +**Purpose**: Test mesh partitioner tool on local meshes +- `meshpartitioner_partition_local_pi_mesh_4` - Create 4-process partition for PI mesh +- `meshpartitioner_partition_local_pi_cavity_mesh_4` - Create 4-process partition for PI cavity -# Run tests with labels -ctest -L "mpi" -``` +**Requirements**: `BUILD_MESHPARTITIONER=ON` -### Test Output and Debugging +### 📁 `ecbundle/` - External Tool Integration Tests +**Purpose**: Test ECBundle integration and build system +- `ecbundle_basic_build` - Basic ECBundle functionality +- `ecbundle_with_meshpart` - ECBundle with mesh partitioner +- `ecbundle_with_omp` - ECBundle with OpenMP +- `ecbundle_with_testing` - ECBundle with testing enabled -```bash -# Show test output on failure -ctest --output-on-failure +## Listing and Filtering Tests -# Run with verbose output -ctest -V +**Before running tests, it's helpful to see what's available** based on your build configuration. -# Run with debug output -ctest --debug +### List All Available Tests +```bash +ctest -N # List all tests (typically 20-26 depending on build options) +ctest -N -R "integration_" # List integration tests only +ctest -N -R "remote_" # List remote pipeline tests +ctest -N -R "meshpartitioner_" # List mesh partitioner tests +``` -# Run with extra output -ctest --extra-verbose +### Filter by Test Group +```bash +# Show tests by prefix +ctest -N -R "integration_" # All integration tests +ctest -N -R "remote_" # All remote mesh pipeline tests +ctest -N -R "meshpartitioner_" # All mesh partitioner tests +ctest -N -R "ecbundle_" # All ecbundle tests ``` -### Running Tests in Parallel +### Filter by Functionality +```bash +# Show tests by label +ctest -N -L "download_mesh" # All mesh download tests +ctest -N -L "partition_mesh" # All mesh partition tests +ctest -N -L "generate_meshdiag" # All meshdiag generation tests +ctest -N -R "_mpi" # All MPI tests +``` +### Example Output ```bash -# Run tests in parallel (4 jobs) -ctest -j4 +$ ctest -N +Test project /path/to/build + Test #1: integration_pi_mpi2 + Test #2: integration_pi_mpi8 + Test #3: integration_pi_cavity_mpi2 + Test #4: integration_meshdiag_pi_mpi2 + Test #5: integration_meshdiag_pi_mpi8 + Test #6: meshpartitioner_partition_local_pi_mesh_4 + Test #7: meshpartitioner_partition_local_pi_cavity_mesh_4 + Test #8: remote_download_mesh_core2 + ... +Total Tests: 26 +``` -# Run tests with timeout (seconds) -ctest --timeout 30 +## Running Tests -# Run tests repeatedly (good for flaky tests) -ctest --repeat until-pass:3 # Run up to 3 times until pass -ctest --repeat until-fail:3 # Run up to 3 times until failure -ctest --repeat after-timeout:3 # Repeat only after timeout +### Run by Test Group +```bash +ctest -R "integration_" # All integration tests +ctest -R "remote_" # All remote mesh pipeline tests +ctest -R "meshpartitioner_" # All mesh partitioner tests +ctest -R "ecbundle_" # All ecbundle tests ``` -## Test Organization - -Tests are organized into different categories: +### Run by Functionality +```bash +ctest -L "download_mesh" # All mesh download tests +ctest -L "partition_mesh" # All mesh partition tests +ctest -L "generate_meshdiag" # All meshdiag generation tests +ctest -R "_mpi" # All MPI tests +``` -1. **Unit Tests**: Tests for individual components/functions -2. **Integration Tests**: Tests that verify components work together -3. **MPI Tests**: Tests that require MPI parallel execution -4. **System Tests**: End-to-end tests of the complete system +### Run Specific Tests +```bash +ctest -R integration_pi_mpi2 -V # Single integration test +ctest -R remote_generate_meshdiag_core2_mpi16 -V # Single meshdiag test +ctest -R meshpartitioner_partition_local_pi_mesh_4 # Single partition test +``` -### Test Naming Convention +### Run All Tests +```bash +ctest --output-on-failure # Run all available tests +``` -- `unit_*`: Unit tests -- `integration_*`: Integration tests -- `*_mpi*`: MPI parallel tests -- `*_basic_*`: Basic functionality tests -- `*_init_*`: Initialization tests +## Custom Test Targets -## Test Data Setup +Convenient make targets for common test workflows: -For the integration tests to run successfully, you need minimal test data: +```bash +make run_tests # Run all tests +make run_integration_tests # Integration tests only +make run_mpi_tests # All MPI tests +make run_meshdiag_tests # All mesh diagnostics tests +make run_meshpartitioner_tests # Mesh partitioner tests only +``` -1. **Test mesh**: - ```bash - # Link to existing test mesh - mkdir -p tests/data/meshes - ln -s /path/to/fesom2/test/meshes/pi tests/data/meshes/ - ``` +## Test Configuration -2. **Test forcing data**: - ```bash - # Link to minimal forcing data - mkdir -p tests/data/forcing - ln -s /path/to/forcing/global tests/data/forcing/ - ``` +### CMake Build Options +```bash +# Core testing +-DBUILD_TESTING=ON # Enable all tests (required) -## Debugging Tests +# Component-specific testing +-DBUILD_MESHDIAG=ON # Enable mesh diagnostics tests +-DBUILD_MESHPARTITIONER=ON # Enable mesh partitioner tests -### Running Tests in Debugger +# MPI configuration +-DENABLE_MPI_TESTS=ON # Enable MPI tests (default: ON) +-DMPIEXEC_MAX_NUMPROCS=128 # Max MPI processes available -```bash -# Run a test under gdb -export TEST_EXECUTABLE="$(pwd)/bin/fesom.x" -cd tests/integration/integration_basic_run_mpi8 -gdb --args mpirun -np 2 $TEST_EXECUTABLE - -# Or use the test script directly -cd build -gdb --args ctest -V -R integration_basic_run_mpi8 +# Test timing +-DTEST_TIMEOUT=600 # Default test timeout (seconds) ``` -### Enabling Verbose Output +### Component Dependencies +The test system automatically detects and reports available components: -Add these to your `namelist.config`: ``` -&io_nml - verbose = .true. - debug_level = 2 -/ +Remote mesh pipeline component availability: + Download capability: TRUE (wget: /usr/bin/wget, curl: /usr/bin/curl) + Mesh partitioner: ON + Mesh diagnostics: ON ``` -## Adding New Tests - -1. **Unit Tests**: - - Add new test files in `tests/unit/` - - Follow the naming convention `test_*.f90` - -2. **Integration Tests**: - - Add new test configurations in `tests/integration/` - - Update `tests/integration/CMakeLists.txt` +**Missing components are clearly reported** with instructions to enable them. -3. **MPI Tests**: - - Use the `add_fesom_test` function with `MPI_TEST` option - - Specify the number of processes with `NP` +## Test Data -Example of adding a new test: - -```cmake -# In tests/integration/CMakeLists.txt -add_fesom_test(integration_my_new_test - MPI_TEST - NP 4 - TIMEOUT 300 # 5 minutes - COMMAND_ARGS "--my-arg=value" -) +### Local Test Data (`tests/data/`) +``` +tests/data/ +├── MESHES/ +│ ├── pi/ # PI test mesh (3k nodes) +│ ├── pi_cavity/ # PI cavity test mesh (7k nodes) +│ └── soufflet/ # Soufflet test mesh (3k nodes) +├── FORCING/ # Minimal forcing data +└── INITIAL/ # Initial condition data ``` -## Troubleshooting - -### Common Issues +### Remote Test Data +Remote meshes are **automatically downloaded** by the test pipeline: +- **CORE2**: ~127k nodes, global ocean mesh +- **DARS**: ~3.2M nodes, large regional mesh +- **NG5**: ~7.4M nodes, high-resolution global mesh +- **ORCA25**: Large global mesh (~0.25 degree) +- **PI Remote**: Same as local PI but downloaded -1. **Test Fails with MPI Errors**: - - Ensure MPI is properly installed and in your PATH - - Check that the number of processes matches the mesh partitioning +## Process Count Guidelines -2. **Missing Test Data**: - - Verify that test data is correctly linked in `tests/data/` - - Check the test output for missing files +Tests use **appropriate process counts** based on mesh size: +- **Small meshes** (pi, pi_cavity): 2, 4, 8 processes +- **Medium meshes** (core2): 16 processes +- **Large meshes** (dars, orca25): 128 processes +- **Very large meshes** (ng5): 128 processes -3. **Test Timeout**: - - Increase the test timeout with `ctest --timeout 600` - - Or modify the test's timeout in the CMake configuration +## Output Files -4. **Debugging Hanging Tests**: - - Run with `ctest -V` for verbose output - - Check system logs for OOM killer or other system events +### Integration Tests +All mesh diagnostics tests create: **`fesom.mesh.diag.nc`** -### Getting Help +Directory structure tells the complete story: +``` +integration_meshdiag_pi_mpi2/results/fesom.mesh.diag.nc # PI mesh, 2 processes +integration_meshdiag_pi_mpi8/results/fesom.mesh.diag.nc # PI mesh, 8 processes +remote_generate_meshdiag_core2_mpi16/results/fesom.mesh.diag.nc # CORE2 mesh, 16 processes +``` -For issues with the test framework: -1. Check the test output in `build/Testing/Temporary/LastTest.log` -2. Consult the FESOM2 documentation -3. Open an issue on the FESOM2 GitHub repository +### Test Logs +Each test creates detailed logs: +- `test_output.log` - Standard output from FESOM/meshdiag +- `test_error.log` - Error output (usually empty for successful tests) +- `namelist.*` - Configured namelists used by the test -## Test Data Setup +## Troubleshooting -For the integration tests to run successfully, you need minimal test data: +### Common Issues -1. **Add test mesh**: +1. **"No download capability"**: ```bash - # Link to existing test mesh - ln -s /path/to/fesom2/test/meshes/test_mesh ./data/meshes/ - - # Or copy minimal mesh files - cp -r /path/to/minimal/mesh ./data/meshes/test_mesh + sudo apt install wget curl ``` -2. **Add test forcing data**: +2. **"Mesh partitioner tests skipped"**: ```bash - # Link to minimal forcing data - ln -s /path/to/minimal/forcing ./data/forcing/ + cmake .. -DBUILD_MESHPARTITIONER=ON ``` -## Available Tests +3. **"Mesh diagnostics test skipped"**: + ```bash + cmake .. -DBUILD_MESHDIAG=ON + ``` -- `integration_basic_run`: Serial FESOM execution test -- `integration_init_test`: Quick initialization test (2 min timeout) -- `integration_basic_run_mpi2`: MPI test with 2 processes -- `integration_basic_run_mpi4`: MPI test with 4 processes -- `integration_namelist_config`: Verify namelist configuration -- `integration_test_data_check`: Validate test data setup +4. **Large mesh tests timeout**: + - DARS: 30-minute timeout (3.2M nodes) + - NG5: 60-minute timeout (7.4M nodes) + - These are appropriate for mesh complexity -## Test Configuration - -Configure tests with CMake options: +5. **MPI test failures**: + ```bash + # Check MPI installation + which mpiexec mpirun + mpirun -np 2 echo "MPI works" + ``` +### Debug Test Execution ```bash -# Disable integration tests -cmake -DENABLE_INTEGRATION_TESTS=OFF .. +# Run with maximum verbosity +ctest -R integration_pi_mpi2 -V --output-on-failure -# Disable MPI tests -cmake -DENABLE_MPI_TESTS=OFF .. +# Check specific test logs +cat build/tests/integration/integration_pi_mpi2/test_output.log -# Set custom timeout (default: 600s) -cmake -DTEST_TIMEOUT=300 .. +# Run test manually +cd build/tests/integration/integration_pi_mpi2 +mpirun -np 2 ../../../../bin/fesom.x ``` -## Troubleshooting +## Test Development + +### Adding New Tests + +1. **Integration tests** - Add to `integration/CMakeLists.txt` +2. **Remote meshes** - Add to mesh registry JSON and `remote/CMakeLists.txt` +3. **Mesh partitioner tests** - Add to `meshpartitioner/CMakeLists.txt` -1. **Missing test data**: Tests will warn about missing data but still pass for initial setup -2. **MPI not found**: Ensure MPI is installed and mpiexec/mpirun is in PATH -3. **Test timeouts**: Increase TEST_TIMEOUT for slower systems -4. **Build failures**: Ensure FESOM builds successfully before running tests +### Best Practices +- **Use group prefixes** (`integration_`, `remote_`, `meshpartitioner_`) +- **Include mesh name** in test names for clarity +- **Set appropriate timeouts** based on mesh complexity +- **Use conditional logic** with clear messages for missing dependencies +- **Create clean output files** (`fesom.mesh.diag.nc`) -## Test Output +## Test Infrastructure -Each integration test creates its own directory with: -- Modified namelists pointing to test data -- `test_output.log`: Standard output from FESOM -- `test_error.log`: Error output from FESOM -- `results/`: Directory for FESOM output files +The test system uses `cmake/FesomTesting.cmake` (757 lines) which provides: +- Automatic namelist configuration +- Mesh pipeline management +- Test script generation +- MPI setup and execution -The test data directory should contain the following subdirectories: -- `meshes/`: Test mesh files -- `forcing/`: Test forcing/climate data files +**Note**: A simplification roadmap is documented in `TODO_TEST_INFRASTRUCTURE_SIMPLIFICATION.md` for future maintenance. diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index a63bafca3..c4b3ffd99 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -4,15 +4,15 @@ # MPI tests (if enabled) if(ENABLE_MPI_TESTS) - # Basic integration test with 2 processes (minimum required for mesh partitioning) - add_fesom_test(integration_full_run_mpi2 + # Basic integration test with PI mesh and 2 processes (minimum required for mesh partitioning) + add_fesom_test(integration_pi_mpi2 MPI_TEST NP 2 TIMEOUT ${TEST_TIMEOUT} ) - # MPI test with 8 processes (as requested by user) - add_fesom_test(integration_full_run_mpi8 + # PI mesh test with 8 processes + add_fesom_test(integration_pi_mpi8 MPI_TEST NP 8 TIMEOUT ${TEST_TIMEOUT} @@ -32,44 +32,21 @@ else() message(WARNING "MPI tests are disabled. Set ENABLE_MPI_TESTS=ON to enable them.") endif() -# Create a test that verifies namelist configuration works -add_test( - NAME integration_namelist_config - COMMAND ${CMAKE_COMMAND} - -DTEST_DATA_DIR=${TEST_DATA_DIR} - -DCONFIG_DIR=${CMAKE_SOURCE_DIR}/config - -P ${CMAKE_CURRENT_SOURCE_DIR}/test_namelist_config.cmake -) - -set_tests_properties(integration_namelist_config PROPERTIES - TIMEOUT 30 -) - -# Test that checks if test data is properly set up -add_test( - NAME integration_test_data_check - COMMAND ${CMAKE_COMMAND} - -DTEST_DATA_DIR=${TEST_DATA_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/test_data_check.cmake -) - -set_tests_properties(integration_test_data_check PROPERTIES - TIMEOUT 10 -) # Meshdiag tests (only when BUILD_MESHDIAG is enabled) if(BUILD_MESHDIAG AND TARGET fesom_meshdiag) message(STATUS "Adding meshdiag tests...") # Basic meshdiag test with 2 processes (matches integration_full_run_mpi2) - add_fesom_meshdiag_test(meshdiag_pi_mpi2 + add_fesom_meshdiag_test_with_options(integration_meshdiag_pi_mpi2 + "pi" "fesom" NP 2 TIMEOUT 120 ) # Meshdiag test with 8 processes (matches integration_full_run_mpi8) - add_fesom_meshdiag_test_with_options(meshdiag_pi_mpi8 - "pi" "mesh8" + add_fesom_meshdiag_test_with_options(integration_meshdiag_pi_mpi8 + "pi" "fesom" NP 8 TIMEOUT 120 ) @@ -77,3 +54,9 @@ if(BUILD_MESHDIAG AND TARGET fesom_meshdiag) else() message(STATUS "Meshdiag tests disabled. Set BUILD_MESHDIAG=ON to enable them.") endif() + +#=============================================================================== +# Local Integration Tests Only +#=============================================================================== + +# Remote mesh pipeline tests have been moved to tests/remote/ diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 000000000..9ba88b691 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,161 @@ +# Integration Tests + +Local FESOM2 integration tests that validate core functionality using local mesh files. + +## Test Overview + +These tests verify that FESOM2 can successfully run with local meshes and generate expected outputs. + +### Available Tests + +1. **`integration_pi_mpi2`** - PI mesh with 2 MPI processes + - **Mesh**: PI test mesh (~3k nodes) + - **Duration**: ~5-10 minutes + - **Purpose**: Basic FESOM simulation test, minimum MPI configuration + +2. **`integration_pi_mpi8`** - PI mesh with 8 MPI processes + - **Mesh**: PI test mesh (~3k nodes) + - **Duration**: ~5-10 minutes + - **Purpose**: Multi-process FESOM simulation test + +3. **`integration_pi_cavity_mpi2`** - PI cavity mesh with 2 MPI processes + - **Mesh**: PI cavity test mesh (~7k nodes) + - **Duration**: ~5-10 minutes + - **Purpose**: Test cavity functionality + +4. **`integration_meshdiag_pi_mpi2`** - PI mesh diagnostics with 2 processes + - **Mesh**: PI test mesh (~3k nodes) + - **Duration**: ~2 minutes + - **Output**: `fesom.mesh.diag.nc` + - **Purpose**: Fast mesh validation and diagnostics + +5. **`integration_meshdiag_pi_mpi8`** - PI mesh diagnostics with 8 processes + - **Mesh**: PI test mesh (~3k nodes) + - **Duration**: ~2 minutes + - **Output**: `fesom.mesh.diag.nc` + - **Purpose**: Multi-process mesh validation + +## Requirements + +### Local Mesh Data +These tests require local mesh files in `tests/data/MESHES/`: +``` +tests/data/MESHES/ +├── pi/ +│ ├── nod2d.out # Node coordinates +│ ├── elem2d.out # Element connectivity +│ ├── aux3d.out # 3D mesh information +│ ├── dist_2/ # 2-process partition +│ ├── dist_4/ # 4-process partition (created by meshpartitioner tests) +│ └── dist_8/ # 8-process partition +└── pi_cavity/ + ├── nod2d.out + ├── elem2d.out + ├── aux3d.out + └── dist_2/ # 2-process partition +``` + +### Build Configuration +```bash +# Required +-DBUILD_TESTING=ON + +# For meshdiag tests +-DBUILD_MESHDIAG=ON + +# For MPI tests +-DENABLE_MPI_TESTS=ON +``` + +## Running Tests + +### All Integration Tests +```bash +make run_integration_tests +# Or: ctest -R "integration_" +``` + +### Individual Tests +```bash +# Full FESOM simulation tests +ctest -R integration_pi_mpi2 -V +ctest -R integration_pi_mpi8 -V +ctest -R integration_pi_cavity_mpi2 -V + +# Fast mesh diagnostics tests +ctest -R integration_meshdiag_pi_mpi2 -V +ctest -R integration_meshdiag_pi_mpi8 -V +``` + +### Quick Validation +```bash +# Fast validation of mesh and basic functionality +make run_meshdiag_tests +``` + +## Test Output + +Each integration test creates its own directory under `build/tests/integration/`: + +``` +build/tests/integration/ +├── integration_pi_mpi2/ +│ ├── namelist.config # Configured for test +│ ├── namelist.forcing # Forcing configuration +│ ├── namelist.ice # Ice configuration +│ ├── namelist.tra # Tracer configuration +│ ├── test_output.log # FESOM stdout +│ ├── test_error.log # FESOM stderr +│ └── results/ # FESOM output files +└── integration_meshdiag_pi_mpi2/ + ├── namelist.config # Configured for test + ├── test_output.log # meshdiag stdout + ├── test_error.log # meshdiag stderr (usually empty) + └── results/ + └── fesom.mesh.diag.nc # Mesh diagnostics file +``` + +## Debugging + +### Test Failure Investigation +```bash +# Check test logs +cat build/tests/integration/integration_pi_mpi2/test_output.log +cat build/tests/integration/integration_pi_mpi2/test_error.log + +# Run test manually +cd build/tests/integration/integration_pi_mpi2 +mpirun -np 2 ../../../../bin/fesom.x +``` + +### Common Issues + +1. **Missing mesh files**: Ensure local mesh data is present in `tests/data/MESHES/` +2. **MPI errors**: Check MPI installation with `mpirun -np 2 echo "test"` +3. **Timeout**: Integration tests may take 5-10 minutes on slower systems + +## Adding New Integration Tests + +To add a new integration test: + +1. **Edit `CMakeLists.txt`** in this directory +2. **Use the `add_fesom_test*` functions** from `cmake/FesomTesting.cmake` +3. **Follow naming convention**: `integration_{mesh}_mpi{n}` or `integration_meshdiag_{mesh}_mpi{n}` + +Example: +```cmake +# Add new PI mesh test with 4 processes +add_fesom_test(integration_pi_mpi4 + MPI_TEST + NP 4 + TIMEOUT 600 +) +``` + +## Notes + +- **Integration tests use local meshes only** - no downloading required +- **Mesh partitions must exist** for the specified process count +- **All meshdiag tests create `fesom.mesh.diag.nc`** for consistency +- **Test directories are unique** so runid can be simple ("fesom") +- **Tests validate core FESOM functionality** without external dependencies \ No newline at end of file diff --git a/tests/integration/mesh_download.cmake b/tests/integration/mesh_download.cmake new file mode 100644 index 000000000..13bed23cb --- /dev/null +++ b/tests/integration/mesh_download.cmake @@ -0,0 +1,184 @@ +#=============================================================================== +# mesh_download.cmake - Generic mesh download utility for FESOM2 +#=============================================================================== + +cmake_minimum_required(VERSION 3.16) + +# Required parameters +if(NOT DEFINED MESH_NAME) + message(FATAL_ERROR "MESH_NAME parameter is required") +endif() + +if(NOT DEFINED SOURCE_DIR) + message(FATAL_ERROR "SOURCE_DIR parameter is required") +endif() + +# Set paths +set(MESH_REGISTRY "${SOURCE_DIR}/tests/mesh_registry.json") +set(TEST_DATA_DIR "${SOURCE_DIR}/tests/data") +set(MESH_DIR "${TEST_DATA_DIR}/MESHES") +set(TARGET_MESH_DIR "${MESH_DIR}/${MESH_NAME}") + +# Function to parse JSON (simplified - could use more robust parser) +function(parse_mesh_registry MESH_NAME) + if(NOT EXISTS "${MESH_REGISTRY}") + message(FATAL_ERROR "Mesh registry not found: ${MESH_REGISTRY}") + endif() + + file(READ "${MESH_REGISTRY}" REGISTRY_CONTENT) + + # Simple JSON parsing - look for mesh entry + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"url\"[ ]*:[ ]*\"([^\"]+)\"" URL_MATCH "${REGISTRY_CONTENT}") + if(URL_MATCH) + set(MESH_URL "${CMAKE_MATCH_1}" PARENT_SCOPE) + else() + message(FATAL_ERROR "Mesh '${MESH_NAME}' not found in registry") + endif() + + # Extract archive type + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"archive_type\"[ ]*:[ ]*\"([^\"]+)\"" ARCHIVE_MATCH "${REGISTRY_CONTENT}") + if(ARCHIVE_MATCH) + set(ARCHIVE_TYPE "${CMAKE_MATCH_1}" PARENT_SCOPE) + else() + set(ARCHIVE_TYPE "tar.gz" PARENT_SCOPE) + endif() + + # Extract verification files + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"required_files\"[ ]*:[ ]*\\[([^\\]]+)\\]" FILES_MATCH "${REGISTRY_CONTENT}") + if(FILES_MATCH) + set(VERIFICATION_FILES "${CMAKE_MATCH_1}" PARENT_SCOPE) + else() + set(VERIFICATION_FILES "\"nod2d.out\", \"elem2d.out\"" PARENT_SCOPE) + endif() +endfunction() + +# Function to download and extract mesh +function(download_mesh MESH_NAME) + message(STATUS "=== FESOM2 Mesh Download: ${MESH_NAME} ===") + + # Parse mesh configuration + parse_mesh_registry("${MESH_NAME}") + + message(STATUS "Mesh URL: ${MESH_URL}") + message(STATUS "Archive type: ${ARCHIVE_TYPE}") + message(STATUS "Target directory: ${TARGET_MESH_DIR}") + + # Check if mesh already exists and is complete + set(MESH_EXISTS TRUE) + string(REPLACE "\"" "" CLEAN_FILES "${VERIFICATION_FILES}") + string(REPLACE " " "" CLEAN_FILES "${CLEAN_FILES}") + string(REPLACE "," ";" FILE_LIST "${CLEAN_FILES}") + + foreach(REQUIRED_FILE ${FILE_LIST}) + if(NOT EXISTS "${TARGET_MESH_DIR}/${REQUIRED_FILE}") + set(MESH_EXISTS FALSE) + break() + endif() + endforeach() + + if(MESH_EXISTS) + message(STATUS "Mesh '${MESH_NAME}' already exists and is complete") + message(STATUS "Skipping download") + return() + endif() + + # Create mesh directory + file(MAKE_DIRECTORY "${TARGET_MESH_DIR}") + + # Download mesh + message(STATUS "Downloading ${MESH_NAME} mesh...") + message(STATUS "This may take a few minutes...") + + set(ARCHIVE_FILE "${TARGET_MESH_DIR}/${MESH_NAME}.${ARCHIVE_TYPE}") + file(DOWNLOAD "${MESH_URL}" "${ARCHIVE_FILE}" + SHOW_PROGRESS + STATUS download_status + TIMEOUT 1200 # 20 minutes timeout + ) + + # Check download status + list(GET download_status 0 status_code) + if(NOT status_code EQUAL 0) + list(GET download_status 1 error_message) + message(FATAL_ERROR "Failed to download ${MESH_NAME} mesh: ${error_message}") + endif() + + message(STATUS "Extracting ${MESH_NAME} mesh...") + + # Extract based on archive type + if(ARCHIVE_TYPE STREQUAL "tar.gz") + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf "${ARCHIVE_FILE}" + WORKING_DIRECTORY "${TARGET_MESH_DIR}" + RESULT_VARIABLE extract_result + OUTPUT_VARIABLE extract_output + ERROR_VARIABLE extract_error + ) + else() + message(FATAL_ERROR "Unsupported archive type: ${ARCHIVE_TYPE}") + endif() + + if(NOT extract_result EQUAL 0) + message(STATUS "Extract output: ${extract_output}") + message(STATUS "Extract error: ${extract_error}") + message(FATAL_ERROR "Failed to extract ${MESH_NAME} mesh") + endif() + + # Clean up archive file + file(REMOVE "${ARCHIVE_FILE}") + + # Handle nested directory structure (most archives contain subdirectory) + # Check for common subdirectory patterns: core2/, dars/, pi/, etc. + set(POSSIBLE_SUBDIRS "core2" "dars" "pi" "glob" "arctic" "${MESH_NAME}") + set(FOUND_SUBDIR "") + + foreach(SUBDIR ${POSSIBLE_SUBDIRS}) + if(EXISTS "${TARGET_MESH_DIR}/${SUBDIR}" AND IS_DIRECTORY "${TARGET_MESH_DIR}/${SUBDIR}") + # Check if this subdirectory contains mesh files + if(EXISTS "${TARGET_MESH_DIR}/${SUBDIR}/nod2d.out" OR EXISTS "${TARGET_MESH_DIR}/${SUBDIR}/elem2d.out") + set(FOUND_SUBDIR "${SUBDIR}") + break() + endif() + endif() + endforeach() + + # If we found a subdirectory with mesh files, move them up + if(NOT FOUND_SUBDIR STREQUAL "") + message(STATUS "Found mesh files in subdirectory: ${FOUND_SUBDIR}") + message(STATUS "Moving files to target directory...") + + # Get list of files in subdirectory + file(GLOB MESH_FILES "${TARGET_MESH_DIR}/${FOUND_SUBDIR}/*") + + # Move each file + foreach(MESH_FILE ${MESH_FILES}) + get_filename_component(FILENAME "${MESH_FILE}" NAME) + file(RENAME "${MESH_FILE}" "${TARGET_MESH_DIR}/${FILENAME}") + endforeach() + + # Remove empty subdirectory + file(REMOVE_RECURSE "${TARGET_MESH_DIR}/${FOUND_SUBDIR}") + message(STATUS "Files moved successfully") + endif() + + # Verify extraction + set(VERIFICATION_FAILED FALSE) + foreach(REQUIRED_FILE ${FILE_LIST}) + if(NOT EXISTS "${TARGET_MESH_DIR}/${REQUIRED_FILE}") + message(WARNING "Required file missing after extraction: ${REQUIRED_FILE}") + set(VERIFICATION_FAILED TRUE) + endif() + endforeach() + + if(VERIFICATION_FAILED) + message(FATAL_ERROR "Mesh extraction verification failed for ${MESH_NAME}") + endif() + + message(STATUS "✓ ${MESH_NAME} mesh successfully downloaded and extracted") + message(STATUS "Location: ${TARGET_MESH_DIR}") +endfunction() + +# Execute download if this script is run directly +if(CMAKE_SCRIPT_MODE_FILE) + download_mesh("${MESH_NAME}") +endif() \ No newline at end of file diff --git a/tests/integration/mesh_partition.cmake b/tests/integration/mesh_partition.cmake new file mode 100644 index 000000000..b4b7ce900 --- /dev/null +++ b/tests/integration/mesh_partition.cmake @@ -0,0 +1,188 @@ +#=============================================================================== +# mesh_partition.cmake - Generic mesh partitioning utility for FESOM2 +#=============================================================================== + +cmake_minimum_required(VERSION 3.16) + +# Required parameters +if(NOT DEFINED MESH_NAME) + message(FATAL_ERROR "MESH_NAME parameter is required") +endif() + +if(NOT DEFINED NUM_PROCESSES) + message(FATAL_ERROR "NUM_PROCESSES parameter is required") +endif() + +if(NOT DEFINED SOURCE_DIR) + message(FATAL_ERROR "SOURCE_DIR parameter is required") +endif() + +if(NOT DEFINED BUILD_DIR) + message(FATAL_ERROR "BUILD_DIR parameter is required") +endif() + +# Set paths +set(MESH_REGISTRY "${SOURCE_DIR}/tests/mesh_registry.json") +set(TEST_DATA_DIR "${SOURCE_DIR}/tests/data") +set(MESH_DIR "${TEST_DATA_DIR}/MESHES") +set(TARGET_MESH_DIR "${MESH_DIR}/${MESH_NAME}") +set(CONFIG_DIR "${SOURCE_DIR}/config") +set(FESOM_MESHPART_EXECUTABLE "${BUILD_DIR}/bin/fesom_meshpart") + +# Function to parse mesh configuration from registry +function(parse_mesh_config MESH_NAME) + if(NOT EXISTS "${MESH_REGISTRY}") + message(FATAL_ERROR "Mesh registry not found: ${MESH_REGISTRY}") + endif() + + file(READ "${MESH_REGISTRY}" REGISTRY_CONTENT) + + # Extract force_rotation setting + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"force_rotation\"[ ]*:[ ]*([^,}]+)" ROTATION_MATCH "${REGISTRY_CONTENT}") + if(ROTATION_MATCH) + string(STRIP "${CMAKE_MATCH_1}" FORCE_ROTATION) + set(MESH_FORCE_ROTATION "${FORCE_ROTATION}" PARENT_SCOPE) + else() + set(MESH_FORCE_ROTATION "true" PARENT_SCOPE) # Default + endif() + + # Extract use_cavity setting + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"use_cavity\"[ ]*:[ ]*([^,}]+)" CAVITY_MATCH "${REGISTRY_CONTENT}") + if(CAVITY_MATCH) + string(STRIP "${CMAKE_MATCH_1}" USE_CAVITY) + set(MESH_USE_CAVITY "${USE_CAVITY}" PARENT_SCOPE) + else() + set(MESH_USE_CAVITY "false" PARENT_SCOPE) # Default + endif() + + # Extract timing parameters + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"step_per_day\"[ ]*:[ ]*([^,}]+)" SPD_MATCH "${REGISTRY_CONTENT}") + if(SPD_MATCH) + string(STRIP "${CMAKE_MATCH_1}" STEP_PER_DAY) + set(MESH_STEP_PER_DAY "${STEP_PER_DAY}" PARENT_SCOPE) + else() + set(MESH_STEP_PER_DAY "288" PARENT_SCOPE) # Default + endif() + + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"run_length\"[ ]*:[ ]*([^,}]+)" RL_MATCH "${REGISTRY_CONTENT}") + if(RL_MATCH) + string(STRIP "${CMAKE_MATCH_1}" RUN_LENGTH) + set(MESH_RUN_LENGTH "${RUN_LENGTH}" PARENT_SCOPE) + else() + set(MESH_RUN_LENGTH "1" PARENT_SCOPE) # Default + endif() + + string(REGEX MATCH "\"${MESH_NAME}\"[^}]*\"run_length_unit\"[ ]*:[ ]*\"([^\"]+)\"" RLU_MATCH "${REGISTRY_CONTENT}") + if(RLU_MATCH) + string(STRIP "${CMAKE_MATCH_1}" RUN_LENGTH_UNIT) + set(MESH_RUN_LENGTH_UNIT "${RUN_LENGTH_UNIT}" PARENT_SCOPE) + else() + set(MESH_RUN_LENGTH_UNIT "s" PARENT_SCOPE) # Default + endif() +endfunction() + +# Function to partition mesh for specified number of processes +function(partition_mesh MESH_NAME NUM_PROC) + message(STATUS "=== FESOM2 Mesh Partitioning: ${MESH_NAME} (${NUM_PROC} processes) ===") + + # Check if mesh exists + if(NOT EXISTS "${TARGET_MESH_DIR}/nod2d.out") + message(FATAL_ERROR "Mesh '${MESH_NAME}' not found at ${TARGET_MESH_DIR}. Run mesh download first.") + endif() + + # Check if mesh partitioner exists + if(NOT EXISTS "${FESOM_MESHPART_EXECUTABLE}") + message(FATAL_ERROR "Mesh partitioner executable not found at ${FESOM_MESHPART_EXECUTABLE}") + endif() + + # Create dist_N directory + set(DIST_DIR "${TARGET_MESH_DIR}/dist_${NUM_PROC}") + + # Check if partition already exists and is complete + if(EXISTS "${DIST_DIR}/rpart.out") + message(STATUS "Partition for ${NUM_PROC} processes already exists") + message(STATUS "Skipping partitioning") + return() + endif() + + file(MAKE_DIRECTORY "${DIST_DIR}") + + # Parse mesh configuration + parse_mesh_config("${MESH_NAME}") + + message(STATUS "Mesh configuration:") + message(STATUS " - force_rotation: ${MESH_FORCE_ROTATION}") + message(STATUS " - use_cavity: ${MESH_USE_CAVITY}") + message(STATUS " - step_per_day: ${MESH_STEP_PER_DAY}") + message(STATUS " - run_length: ${MESH_RUN_LENGTH} ${MESH_RUN_LENGTH_UNIT}") + + # Create temporary namelist.config for partitioning + set(TEMP_NAMELIST "${DIST_DIR}/namelist.config") + if(NOT EXISTS "${CONFIG_DIR}/namelist.config") + message(FATAL_ERROR "Base namelist.config not found at ${CONFIG_DIR}/namelist.config") + endif() + + file(COPY "${CONFIG_DIR}/namelist.config" DESTINATION "${DIST_DIR}") + + # Update the namelist for this mesh and partition count + file(READ "${TEMP_NAMELIST}" NAMELIST_CONTENT) + + # Set mesh path + string(REGEX REPLACE "MeshPath='[^']*'" "MeshPath='${TARGET_MESH_DIR}/'" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + string(REGEX REPLACE "ResultPath='[^']*'" "ResultPath='${DIST_DIR}/'" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + + # Set partitioning parameters - simple 1-level partitioning + string(REGEX REPLACE "n_levels=[0-9]+" "n_levels=1" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + string(REGEX REPLACE "n_part=[ 0-9,]+" "n_part=${NUM_PROC}" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + + # Apply mesh-specific configuration + string(REGEX REPLACE "force_rotation=\\.[a-zA-Z]+\\." "force_rotation=.${MESH_FORCE_ROTATION}." NAMELIST_CONTENT "${NAMELIST_CONTENT}") + string(REGEX REPLACE "use_cavity=\\.[a-zA-Z]+\\." "use_cavity=.${MESH_USE_CAVITY}." NAMELIST_CONTENT "${NAMELIST_CONTENT}") + + # Apply mesh-specific timing parameters + string(REGEX REPLACE "step_per_day=[0-9]+" "step_per_day=${MESH_STEP_PER_DAY}" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + string(REGEX REPLACE "run_length=[0-9]+" "run_length=${MESH_RUN_LENGTH}" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + string(REGEX REPLACE "run_length_unit='[^']*'" "run_length_unit='${MESH_RUN_LENGTH_UNIT}'" NAMELIST_CONTENT "${NAMELIST_CONTENT}") + + file(WRITE "${TEMP_NAMELIST}" "${NAMELIST_CONTENT}") + + # Run the mesh partitioner + message(STATUS "Running mesh partitioner...") + message(STATUS "Command: ${FESOM_MESHPART_EXECUTABLE}") + message(STATUS "Working directory: ${DIST_DIR}") + + execute_process( + COMMAND "${FESOM_MESHPART_EXECUTABLE}" + WORKING_DIRECTORY "${DIST_DIR}" + RESULT_VARIABLE PARTITION_RESULT + OUTPUT_VARIABLE PARTITION_OUTPUT + ERROR_VARIABLE PARTITION_ERROR + TIMEOUT 1200 # 20 minutes timeout + ) + + # Log the output + file(WRITE "${DIST_DIR}/partition_output.log" "${PARTITION_OUTPUT}") + file(WRITE "${DIST_DIR}/partition_error.log" "${PARTITION_ERROR}") + + if(NOT PARTITION_RESULT EQUAL 0) + message(STATUS "Partition output: ${PARTITION_OUTPUT}") + message(STATUS "Partition error: ${PARTITION_ERROR}") + message(FATAL_ERROR "Mesh partitioning failed with exit code: ${PARTITION_RESULT}") + endif() + + # Verify that partitioning files were created + set(REQUIRED_PARTITION_FILES "rpart.out") + foreach(REQUIRED_FILE ${REQUIRED_PARTITION_FILES}) + if(NOT EXISTS "${DIST_DIR}/${REQUIRED_FILE}") + message(FATAL_ERROR "Partitioning failed - ${REQUIRED_FILE} not created") + endif() + endforeach() + + message(STATUS "✓ ${MESH_NAME} mesh successfully partitioned for ${NUM_PROC} processes") + message(STATUS "Partition files location: ${DIST_DIR}") +endfunction() + +# Execute partitioning if this script is run directly +if(CMAKE_SCRIPT_MODE_FILE) + partition_mesh("${MESH_NAME}" "${NUM_PROCESSES}") +endif() \ No newline at end of file diff --git a/tests/integration/test_data_check.cmake b/tests/integration/test_data_check.cmake deleted file mode 100644 index 9cc53a63c..000000000 --- a/tests/integration/test_data_check.cmake +++ /dev/null @@ -1,66 +0,0 @@ -#=============================================================================== -# test_data_check.cmake - Check test data setup for FESOM2 -#=============================================================================== - -# Check that required variables are set -if(NOT DEFINED TEST_DATA_DIR) - message(FATAL_ERROR "TEST_DATA_DIR not defined") -endif() - -message(STATUS "Checking test data setup in: ${TEST_DATA_DIR}") - -# Check that test data directory exists -if(NOT EXISTS "${TEST_DATA_DIR}") - message(FATAL_ERROR "Test data directory does not exist: ${TEST_DATA_DIR}") -endif() - -# Check that required subdirectories exist -set(REQUIRED_SUBDIRS - MESHES - FORCING - INITIAL -) - -foreach(SUBDIR ${REQUIRED_SUBDIRS}) - set(FULL_PATH "${TEST_DATA_DIR}/${SUBDIR}") - if(NOT EXISTS "${FULL_PATH}") - message(WARNING "Test data subdirectory missing: ${FULL_PATH}") - message(STATUS "Creating directory: ${FULL_PATH}") - file(MAKE_DIRECTORY "${FULL_PATH}") - else() - message(STATUS "Found test data directory: ${FULL_PATH}") - endif() -endforeach() - -# Check for any mesh files -file(GLOB MESH_FILES "${TEST_DATA_DIR}/MESHES/*") -if(NOT MESH_FILES) - message(WARNING "No mesh files found in ${TEST_DATA_DIR}/MESHES/") - message(STATUS "Note: You may need to add test mesh data for integration tests to work") - -else() - list(LENGTH MESH_FILES NUM_MESH_FILES) - message(STATUS "Found ${NUM_MESH_FILES} items in meshes directory") -endif() - -# Check for any forcing files -file(GLOB FORCING_FILES "${TEST_DATA_DIR}/FORCING/*") -if(NOT FORCING_FILES) - message(WARNING "No forcing files found in ${TEST_DATA_DIR}/FORCING/") - message(STATUS "Note: You may need to add minimal forcing data for integration tests") -else() - list(LENGTH FORCING_FILES NUM_FORCING_FILES) - message(STATUS "Found ${NUM_FORCING_FILES} items in forcing directory") -endif() - -# Check for any initial files -file(GLOB INITIAL_FILES "${TEST_DATA_DIR}/INITIAL/*") -if(NOT INITIAL_FILES) - message(WARNING "No initial files found in ${TEST_DATA_DIR}/INITIAL/") - message(STATUS "Note: You may need to add minimal initial data for integration tests") -else() - list(LENGTH INITIAL_FILES NUM_INITIAL_FILES) - message(STATUS "Found ${NUM_INITIAL_FILES} items in initial directory") -endif() - -message(STATUS "Test data check completed") diff --git a/tests/integration/test_namelist_config.cmake b/tests/integration/test_namelist_config.cmake deleted file mode 100644 index d32be8084..000000000 --- a/tests/integration/test_namelist_config.cmake +++ /dev/null @@ -1,76 +0,0 @@ -#=============================================================================== -# test_namelist_config.cmake - Test namelist configuration for FESOM2 -#=============================================================================== - -# Check that required variables are set -if(NOT DEFINED TEST_DATA_DIR) - message(FATAL_ERROR "TEST_DATA_DIR not defined") -endif() - -if(NOT DEFINED CONFIG_DIR) - message(FATAL_ERROR "CONFIG_DIR not defined") -endif() - -# Check that config directory exists -if(NOT EXISTS "${CONFIG_DIR}") - message(FATAL_ERROR "Config directory does not exist: ${CONFIG_DIR}") -endif() - -# Check that main namelists exist -set(REQUIRED_NAMELISTS - namelist.config - namelist.forcing - namelist.oce - namelist.tra - namelist.ice - namelist.io - namelist.dyn - namelist.cvmix -) - -foreach(NAMELIST ${REQUIRED_NAMELISTS}) - if(NOT EXISTS "${CONFIG_DIR}/${NAMELIST}") - message(FATAL_ERROR "Required namelist not found: ${CONFIG_DIR}/${NAMELIST}") - endif() -endforeach() - -# Test path replacement functionality -set(TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/namelist_test") -file(MAKE_DIRECTORY "${TEST_DIR}") - -# Copy a test namelist -file(READ "${CONFIG_DIR}/namelist.config" ORIGINAL_CONTENT) -file(WRITE "${TEST_DIR}/namelist.config" "${ORIGINAL_CONTENT}") - -# Test path replacements -set(TEST_MESH_PATH "${TEST_DATA_DIR}/MESHES/") -set(TEST_FORCING_PATH "${TEST_DATA_DIR}/") -set(TEST_RESULT_PATH "${TEST_DIR}/results/") - -# Apply the same replacements as in FesomTesting.cmake -file(READ "${TEST_DIR}/namelist.config" CONTENT) -string(REGEX REPLACE "MeshPath='[^']*'" "MeshPath='${TEST_MESH_PATH}'" CONTENT "${CONTENT}") -string(REGEX REPLACE "ClimateDataPath='[^']*'" "ClimateDataPath='${TEST_FORCING_PATH}'" CONTENT "${CONTENT}") -string(REGEX REPLACE "ResultPath='[^']*'" "ResultPath='${TEST_RESULT_PATH}'" CONTENT "${CONTENT}") -file(WRITE "${TEST_DIR}/namelist.config" "${CONTENT}") - -# Verify the replacements worked -file(READ "${TEST_DIR}/namelist.config" MODIFIED_CONTENT) - -# Check that paths were actually changed -if(NOT MODIFIED_CONTENT MATCHES "MeshPath='${TEST_MESH_PATH}'") - message(FATAL_ERROR "MeshPath replacement failed") -endif() - -if(NOT MODIFIED_CONTENT MATCHES "ClimateDataPath='${TEST_FORCING_PATH}'") - message(FATAL_ERROR "ClimateDataPath replacement failed") -endif() - -if(NOT MODIFIED_CONTENT MATCHES "ResultPath='${TEST_RESULT_PATH}'") - message(FATAL_ERROR "ResultPath replacement failed") -endif() - -message(STATUS "Namelist configuration test passed") - -# Clean up -file(REMOVE_RECURSE "${TEST_DIR}") diff --git a/tests/mesh_registry.json b/tests/mesh_registry.json new file mode 100644 index 000000000..719e9ec80 --- /dev/null +++ b/tests/mesh_registry.json @@ -0,0 +1,106 @@ +{ + "mesh_registry": { + "core2": { + "name": "CORE2 Global Ocean Mesh", + "description": "Global ocean mesh used with CORE2 forcing data", + "url": "https://swift.dkrz.de/v1/dkrz_035d8f6ff058403bb42f8302e6badfbc/FESOM_MESHES2.1/core2.tar.gz", + "archive_type": "tar.gz", + "archive_size": "6.8MB", + "mesh_stats": { + "nodes_2d": 126858, + "elements_2d": 244659, + "vertical_levels": 48 + }, + "config": { + "force_rotation": true, + "use_cavity": false, + "typical_processors": [16], + "timing": { + "step_per_day": 32, + "run_length": 1, + "run_length_unit": "s" + } + }, + "verification": { + "required_files": ["nod2d.out", "elem2d.out", "aux3d.out"], + "checksum": "optional" + } + }, + "orca25": { + "name": "Global ~0.25-degree Ocean Mesh", + "description": "Global ocean mesh at ~1 degree resolution", + "url": "https://swift.dkrz.de/v1/dkrz_035d8f6ff058403bb42f8302e6badfbc/FESOM_MESHES2.1/orca25.tar.gz", + "archive_type": "tar.gz", + "config": { + "force_rotation": true, + "use_cavity": false, + "typical_processors": [16], + "timing": { + "step_per_day": 32, + "run_length": 1, + "run_length_unit": "s" + } + }, + "verification": { + "required_files": ["nod2d.out", "elem2d.out", "aux3d.out"] + } + }, + "ng5": { + "name": "Global ~5km mesh", + "description": "High resolution ~5km mesh", + "url": "https://swift.dkrz.de/v1/dkrz_035d8f6ff058403bb42f8302e6badfbc/FESOM_MESHES2.1/ng5.tar.gz", + "archive_type": "tar.gz", + "config": { + "force_rotation": true, + "use_cavity": false, + "typical_processors": [16], + "timing": { + "step_per_day": 32, + "run_length": 1, + "run_length_unit": "s" + } + }, + "verification": { + "required_files": ["nod2d.out", "elem2d.out", "aux3d.out"] + } + }, + "dars": { + "name": "DARS Ocean Mesh", + "description": "DARS (Data Assimilation Research System) ocean mesh", + "url": "https://swift.dkrz.de/v1/dkrz_035d8f6ff058403bb42f8302e6badfbc/FESOM_MESHES2.1/dars.tar.gz", + "archive_type": "tar.gz", + "config": { + "force_rotation": true, + "use_cavity": false, + "typical_processors": [16], + "timing": { + "step_per_day": 32, + "run_length": 1, + "run_length_unit": "s" + } + }, + "verification": { + "required_files": ["nod2d.out", "elem2d.out", "aux3d.out"] + } + }, + "pi_remote": { + "name": "PI Test Mesh (Remote)", + "description": "Alternative version of PI test mesh downloaded from remote repository", + "url": "https://swift.dkrz.de/v1/dkrz_035d8f6ff058403bb42f8302e6badfbc/FESOM_MESHES2.1/pi.tar.gz", + "archive_type": "tar.gz", + "config": { + "force_rotation": true, + "use_cavity": false, + "typical_processors": [8], + "timing": { + "step_per_day": 32, + "run_length": 1, + "run_length_unit": "s" + } + }, + "verification": { + "required_files": ["nod2d.out", "elem2d.out", "aux3d.out"] + } + } + } +} diff --git a/tests/meshpartitioner/CMakeLists.txt b/tests/meshpartitioner/CMakeLists.txt new file mode 100644 index 000000000..2cf0e00b1 --- /dev/null +++ b/tests/meshpartitioner/CMakeLists.txt @@ -0,0 +1,45 @@ +#=============================================================================== +# tests/meshpartitioner/CMakeLists.txt - Mesh partitioner tests for FESOM2 +#=============================================================================== + +# Mesh partitioner tests (only when BUILD_MESHPARTITIONER is enabled) +if(BUILD_MESHPARTITIONER) + message(STATUS "Adding mesh partitioner tests...") + + # Test partitioning local PI mesh for 4 processes (not currently present) + add_test( + NAME meshpartitioner_partition_local_pi_mesh_4 + COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DBUILD_DIR=${CMAKE_BINARY_DIR} + -DMESH_NAME=pi + -DNUM_PROCESSES=4 + -P ${CMAKE_SOURCE_DIR}/tests/integration/mesh_partition.cmake + ) + + set_tests_properties(meshpartitioner_partition_local_pi_mesh_4 PROPERTIES + TIMEOUT 300 # 5 minutes + LABELS "meshpartitioner" + ) + + # Test partitioning local PI cavity mesh for 4 processes + add_test( + NAME meshpartitioner_partition_local_pi_cavity_mesh_4 + COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DBUILD_DIR=${CMAKE_BINARY_DIR} + -DMESH_NAME=pi_cavity + -DNUM_PROCESSES=4 + -P ${CMAKE_SOURCE_DIR}/tests/integration/mesh_partition.cmake + ) + + set_tests_properties(meshpartitioner_partition_local_pi_cavity_mesh_4 PROPERTIES + TIMEOUT 300 # 5 minutes + LABELS "meshpartitioner" + ) + + message(STATUS "Added mesh partitioner tests for local meshes") + +else() + message(STATUS "Mesh partitioner tests disabled. Set BUILD_MESHPARTITIONER=ON to enable them.") +endif() \ No newline at end of file diff --git a/tests/meshpartitioner/README.md b/tests/meshpartitioner/README.md new file mode 100644 index 000000000..ab8266c2d --- /dev/null +++ b/tests/meshpartitioner/README.md @@ -0,0 +1,174 @@ +# Mesh Partitioner Tests + +Tests for the FESOM2 mesh partitioner tool (`fesom_meshpart`) using local mesh files. + +## Overview + +These tests validate the mesh partitioner functionality by creating missing partitions for local meshes and verifying the partitioner tool works correctly. + +## Available Tests + +### `meshpartitioner_partition_local_pi_mesh_4` +- **Purpose**: Create 4-process partition for local PI mesh +- **Mesh**: PI test mesh (~3k nodes) +- **Duration**: ~30 seconds +- **Output**: `tests/data/MESHES/pi/dist_4/` +- **Status**: **Creates new partition** (was missing) + +### `meshpartitioner_partition_local_pi_cavity_mesh_4` +- **Purpose**: Create 4-process partition for local PI cavity mesh +- **Mesh**: PI cavity test mesh (~7k nodes) +- **Duration**: ~30 seconds +- **Output**: `tests/data/MESHES/pi_cavity/dist_4/` +- **Status**: **Creates new partition** (was missing) + +## Requirements + +### Build Configuration +```bash +# Required for mesh partitioner tests +cmake .. -DBUILD_TESTING=ON \ + -DBUILD_MESHPARTITIONER=ON +``` + +### System Requirements +- **Local mesh files** in `tests/data/MESHES/` +- **METIS library** (automatically built with FESOM2) +- **Sufficient disk space** for partition files + +## Test Execution + +### Run All Mesh Partitioner Tests +```bash +make run_meshpartitioner_tests +# Or: ctest -L "meshpartitioner" +``` + +### Run Individual Tests +```bash +# Create PI mesh partition for 4 processes +ctest -R meshpartitioner_partition_local_pi_mesh_4 -V + +# Create PI cavity mesh partition for 4 processes +ctest -R meshpartitioner_partition_local_pi_cavity_mesh_4 -V +``` + +## Output Structure + +### Created Partition Files +``` +tests/data/MESHES/pi/dist_4/ +├── rpart.out # Main partition file (required by FESOM) +├── part.out # Partition mapping +├── my_list00000.out # Node list for process 0 +├── my_list00001.out # Node list for process 1 +├── my_list00002.out # Node list for process 2 +├── my_list00003.out # Node list for process 3 +├── com_info00000.out # Communication info for process 0 +├── com_info00001.out # Communication info for process 1 +├── com_info00002.out # Communication info for process 2 +├── com_info00003.out # Communication info for process 3 +├── namelist.config # Namelist used during partitioning +├── partition_output.log # Partitioner stdout +└── partition_error.log # Partitioner stderr +``` + +## Validation + +### Automatic Validation +The tests automatically verify: +- ✅ **Required files created** (`rpart.out`, `part.out`) +- ✅ **File sizes are reasonable** (not zero-length) +- ✅ **Partitioner exit code** (0 = success) +- ✅ **Process-specific files** created for each MPI rank + +### Manual Verification +```bash +# Check partition file contents +head tests/data/MESHES/pi/dist_4/rpart.out + +# Verify partition can be used by FESOM +cd build/tests/integration/integration_pi_mpi2 +# Edit namelist.config to use 4 processes and dist_4 +mpirun -np 4 ../../../bin/fesom.x +``` + +## Purpose and Benefits + +### Why These Tests Matter +1. **Fill gaps** - Creates missing 4-process partitions for local development +2. **Test partitioner tool** - Validates `fesom_meshpart` executable works correctly +3. **Enable 4-process testing** - Allows local development with 4 MPI processes +4. **Verify partition quality** - Ensures partitioner produces valid output + +### Integration with Other Tests +- **Enables new integration tests** that can use 4 processes +- **Supports mesh partitioner development** - changes can be tested locally +- **Validates partitioner before remote use** - same tool used for remote mesh pipeline + +## Troubleshooting + +### Common Issues + +1. **"BUILD_MESHPARTITIONER=OFF"**: + ```bash + cmake .. -DBUILD_MESHPARTITIONER=ON + make fesom_meshpart + ``` + +2. **Missing mesh files**: + ```bash + # Check required files exist + ls tests/data/MESHES/pi/nod2d.out + ls tests/data/MESHES/pi/elem2d.out + ls tests/data/MESHES/pi/aux3d.out + ``` + +3. **Partitioner fails**: + ```bash + # Check partitioner executable + ./build/bin/fesom_meshpart tests/data/MESHES/pi 2 + ``` + +4. **Permission issues**: + ```bash + # Ensure write permissions to mesh directory + chmod +w tests/data/MESHES/pi + ``` + +## Development + +### Adding New Mesh Partitioner Tests + +To test partitioning for a new mesh or process count: + +```cmake +# Add to CMakeLists.txt +add_test( + NAME meshpartitioner_partition_local_{mesh}_mesh_{procs} + COMMAND ${CMAKE_COMMAND} + -DSOURCE_DIR=${CMAKE_SOURCE_DIR} + -DBUILD_DIR=${CMAKE_BINARY_DIR} + -DMESH_NAME={mesh} + -DNUM_PROCESSES={procs} + -P ${CMAKE_SOURCE_DIR}/tests/integration/mesh_partition.cmake +) + +set_tests_properties(meshpartitioner_partition_local_{mesh}_mesh_{procs} PROPERTIES + TIMEOUT 300 + LABELS "meshpartitioner" +) +``` + +### Integration with Remote Tests +- **Same partitioner tool** used for both local and remote mesh partitioning +- **Same partition script** (`tests/integration/mesh_partition.cmake`) +- **Validates tool** before using it in remote pipeline + +## Notes + +- **Tests only run when** `BUILD_MESHPARTITIONER=ON` +- **Uses existing local meshes** - no downloads required +- **Creates real partition files** that can be used by FESOM +- **Tests are fast** - small local meshes partition quickly +- **Enables 4-process development** - fills partition gaps for local testing \ No newline at end of file diff --git a/tests/remote/CMakeLists.txt b/tests/remote/CMakeLists.txt new file mode 100644 index 000000000..7ba4a1ec3 --- /dev/null +++ b/tests/remote/CMakeLists.txt @@ -0,0 +1,235 @@ +#=============================================================================== +# tests/remote/CMakeLists.txt - Remote mesh pipeline tests for FESOM2 +#=============================================================================== + +# Check for download capability (wget, curl, or similar) +find_program(WGET_EXECUTABLE wget) +find_program(CURL_EXECUTABLE curl) +set(HAS_DOWNLOAD_CAPABILITY FALSE) +if(WGET_EXECUTABLE OR CURL_EXECUTABLE) + set(HAS_DOWNLOAD_CAPABILITY TRUE) +endif() + +# Report component availability +message(STATUS "Remote mesh pipeline component availability:") +message(STATUS " Download capability: ${HAS_DOWNLOAD_CAPABILITY}") +if(HAS_DOWNLOAD_CAPABILITY) + if(WGET_EXECUTABLE) + message(STATUS " - wget: ${WGET_EXECUTABLE}") + endif() + if(CURL_EXECUTABLE) + message(STATUS " - curl: ${CURL_EXECUTABLE}") + endif() +else() + message(STATUS " - No download tools found (wget or curl required)") +endif() + +message(STATUS " Mesh partitioner: ${BUILD_MESHPARTITIONER}") +if(NOT BUILD_MESHPARTITIONER) + message(STATUS " - Enable with -DBUILD_MESHPARTITIONER=ON") +endif() + +message(STATUS " Mesh diagnostics: ${BUILD_MESHDIAG}") +if(NOT BUILD_MESHDIAG) + message(STATUS " - Enable with -DBUILD_MESHDIAG=ON") +endif() + +# ============================================================================= +# CORE2 Remote Mesh Tests +# ============================================================================= + +if(HAS_DOWNLOAD_CAPABILITY) + message(STATUS "Adding CORE2 remote mesh download test...") + + # Add mesh pipeline for core2 (download + partitioning for 16 processes) + add_mesh_pipeline("core2" PREFIX remote PROCESS_COUNTS 16) + + if(BUILD_MESHPARTITIONER) + message(STATUS "Adding CORE2 mesh partitioning test...") + # Partitioning is included in add_mesh_pipeline above + else() + message(STATUS "CORE2 mesh partitioning test skipped - BUILD_MESHPARTITIONER=OFF") + endif() + + if(BUILD_MESHDIAG) + message(STATUS "Adding CORE2 mesh diagnostics test...") + + # Add meshdiag test that generates mesh diagnostics file + add_fesom_meshdiag_test_with_options(generate_meshdiag_core2_mpi16 "core2" "fesom" + NP 16 + TIMEOUT 300 + PREFIX remote + ) + + # Make meshdiag test depend on mesh pipeline + set_tests_properties(remote_generate_meshdiag_core2_mpi16 PROPERTIES + FIXTURES_REQUIRED "mesh_core2_16" + LABELS "generate_meshdiag" + ) + else() + message(STATUS "CORE2 mesh diagnostics test skipped - BUILD_MESHDIAG=OFF") + endif() + +else() + message(STATUS "CORE2 remote mesh tests skipped - no download capability (wget or curl required)") +endif() + +# ============================================================================= +# DARS Remote Mesh Tests +# ============================================================================= + +if(HAS_DOWNLOAD_CAPABILITY) + message(STATUS "Adding DARS remote mesh download test...") + + # Add mesh pipeline for DARS (download + partitioning for 128 processes for large mesh) + add_mesh_pipeline("dars" PREFIX remote PROCESS_COUNTS 128) + + if(BUILD_MESHPARTITIONER) + message(STATUS "Adding DARS mesh partitioning test...") + # Partitioning is included in add_mesh_pipeline above + else() + message(STATUS "DARS mesh partitioning test skipped - BUILD_MESHPARTITIONER=OFF") + endif() + + if(BUILD_MESHDIAG) + message(STATUS "Adding DARS mesh diagnostics test...") + + # Add meshdiag test that generates mesh diagnostics file (large mesh ~3M nodes, needs longer timeout) + add_fesom_meshdiag_test_with_options(generate_meshdiag_dars_mpi128 "dars" "fesom" + NP 128 + TIMEOUT 1800 # 30 minutes for large DARS mesh + PREFIX remote + ) + + # Make meshdiag test depend on mesh pipeline + set_tests_properties(remote_generate_meshdiag_dars_mpi128 PROPERTIES + FIXTURES_REQUIRED "mesh_dars_128" + LABELS "generate_meshdiag" + ) + else() + message(STATUS "DARS mesh diagnostics test skipped - BUILD_MESHDIAG=OFF") + endif() + +else() + message(STATUS "DARS remote mesh tests skipped - no download capability (wget or curl required)") +endif() + +# ============================================================================= +# PI Remote Mesh Tests +# ============================================================================= + +if(HAS_DOWNLOAD_CAPABILITY) + message(STATUS "Adding PI remote mesh download test...") + + # Add mesh pipeline for PI remote (using recommended processor count from registry) + add_mesh_pipeline("pi_remote" PREFIX remote PROCESS_COUNTS 8) + + if(BUILD_MESHPARTITIONER) + message(STATUS "Adding PI remote mesh partitioning test...") + # Partitioning is included in add_mesh_pipeline above + else() + message(STATUS "PI remote mesh partitioning test skipped - BUILD_MESHPARTITIONER=OFF") + endif() + + if(BUILD_MESHDIAG) + message(STATUS "Adding PI remote mesh diagnostics test...") + + # Add meshdiag test that generates mesh diagnostics file + add_fesom_meshdiag_test_with_options(generate_meshdiag_pi_remote_mpi8 "pi_remote" "fesom" + NP 8 + TIMEOUT 300 + PREFIX remote + ) + + # Make meshdiag test depend on mesh pipeline + set_tests_properties(remote_generate_meshdiag_pi_remote_mpi8 PROPERTIES + FIXTURES_REQUIRED "mesh_pi_remote_8" + LABELS "generate_meshdiag" + ) + else() + message(STATUS "PI remote mesh diagnostics test skipped - BUILD_MESHDIAG=OFF") + endif() + +else() + message(STATUS "PI remote mesh tests skipped - no download capability (wget or curl required)") +endif() + +# ============================================================================= +# NG5 Remote Mesh Tests (High Resolution ~5km) +# ============================================================================= + +if(HAS_DOWNLOAD_CAPABILITY) + message(STATUS "Adding NG5 remote mesh download test...") + + # Add mesh pipeline for ng5 (128 processes for very large mesh ~7.4M nodes) + add_mesh_pipeline("ng5" PREFIX remote PROCESS_COUNTS 128) + + if(BUILD_MESHPARTITIONER) + message(STATUS "Adding NG5 mesh partitioning test...") + # Partitioning is included in add_mesh_pipeline above + else() + message(STATUS "NG5 mesh partitioning test skipped - BUILD_MESHPARTITIONER=OFF") + endif() + + if(BUILD_MESHDIAG) + message(STATUS "Adding NG5 mesh diagnostics test...") + + # Add meshdiag test that generates mesh diagnostics file (very large mesh ~7.4M nodes, needs long timeout) + add_fesom_meshdiag_test_with_options(generate_meshdiag_ng5_mpi128 "ng5" "fesom" + NP 128 + TIMEOUT 3600 # 60 minutes for very large NG5 mesh + PREFIX remote + ) + + # Make meshdiag test depend on mesh pipeline + set_tests_properties(remote_generate_meshdiag_ng5_mpi128 PROPERTIES + FIXTURES_REQUIRED "mesh_ng5_128" + LABELS "generate_meshdiag" + ) + else() + message(STATUS "NG5 mesh diagnostics test skipped - BUILD_MESHDIAG=OFF") + endif() + +else() + message(STATUS "NG5 remote mesh tests skipped - no download capability (wget or curl required)") +endif() + +# ============================================================================= +# ORCA25 Remote Mesh Tests (Global ~0.25-degree) +# ============================================================================= + +if(HAS_DOWNLOAD_CAPABILITY) + message(STATUS "Adding ORCA25 remote mesh download test...") + + # Add mesh pipeline for orca25 (128 processes for large global mesh) + add_mesh_pipeline("orca25" PREFIX remote PROCESS_COUNTS 128) + + if(BUILD_MESHPARTITIONER) + message(STATUS "Adding ORCA25 mesh partitioning test...") + # Partitioning is included in add_mesh_pipeline above + else() + message(STATUS "ORCA25 mesh partitioning test skipped - BUILD_MESHPARTITIONER=OFF") + endif() + + if(BUILD_MESHDIAG) + message(STATUS "Adding ORCA25 mesh diagnostics test...") + + # Add meshdiag test that generates mesh diagnostics file (large global mesh, needs longer timeout) + add_fesom_meshdiag_test_with_options(generate_meshdiag_orca25_mpi128 "orca25" "fesom" + NP 128 + TIMEOUT 1800 # 30 minutes for large ORCA25 mesh + PREFIX remote + ) + + # Make meshdiag test depend on mesh pipeline + set_tests_properties(remote_generate_meshdiag_orca25_mpi128 PROPERTIES + FIXTURES_REQUIRED "mesh_orca25_128" + LABELS "generate_meshdiag" + ) + else() + message(STATUS "ORCA25 mesh diagnostics test skipped - BUILD_MESHDIAG=OFF") + endif() + +else() + message(STATUS "ORCA25 remote mesh tests skipped - no download capability (wget or curl required)") +endif() \ No newline at end of file diff --git a/tests/remote/README.md b/tests/remote/README.md new file mode 100644 index 000000000..f61b558d7 --- /dev/null +++ b/tests/remote/README.md @@ -0,0 +1,239 @@ +# Remote Mesh Pipeline Tests + +Comprehensive tests for the complete remote mesh pipeline: download → partition → validate. + +## Overview + +These tests validate the entire workflow for using remote meshes in FESOM2, from initial download through partitioning to final validation using mesh diagnostics. + +## Test Pipeline Structure + +Each remote mesh follows a **3-stage pipeline**: + +1. **Download** (`remote_download_mesh_{name}`) - Download and extract mesh from remote repository +2. **Partition** (`remote_partition_mesh_{name}_{procs}`) - Partition mesh for specified MPI process count +3. **Validate** (`remote_generate_meshdiag_{name}_mpi{procs}`) - Generate mesh diagnostics to verify setup + +## Supported Meshes + +### CORE2 (Medium Global Mesh) +- **Size**: ~127k nodes, global ocean mesh +- **Process Count**: 16 +- **Timeout**: 5 minutes for meshdiag +- **Tests**: `remote_*_core2_*` + +### DARS (Large Regional Mesh) +- **Size**: ~3.2M nodes, Data Assimilation Research System mesh +- **Process Count**: 128 +- **Timeout**: 30 minutes for meshdiag +- **Tests**: `remote_*_dars_*` + +### PI Remote (Small Test Mesh) +- **Size**: ~3k nodes, same as local PI but downloaded +- **Process Count**: 8 +- **Timeout**: 5 minutes for meshdiag +- **Tests**: `remote_*_pi_remote_*` + +### NG5 (Very Large High-Resolution Mesh) +- **Size**: ~7.4M nodes, ~5km resolution global mesh +- **Process Count**: 128 +- **Timeout**: 60 minutes for meshdiag +- **Tests**: `remote_*_ng5_*` + +### ORCA25 (Large Global Mesh) +- **Size**: TBD, ~0.25 degree global mesh +- **Process Count**: 128 +- **Timeout**: 30 minutes for meshdiag +- **Tests**: `remote_*_orca25_*` + +## Requirements + +### System Dependencies +```bash +# Download capability (at least one required) +wget # Preferred +curl # Alternative + +# Build options +cmake .. -DBUILD_TESTING=ON \ + -DBUILD_MESHPARTITIONER=ON \ # For partition step + -DBUILD_MESHDIAG=ON # For validation step +``` + +### Runtime Requirements +- **Internet connection** for mesh downloads +- **Sufficient disk space** (meshes range from MB to GB) +- **Sufficient RAM** for large mesh processing (especially NG5) + +## Component Detection + +The system automatically detects available components and reports status: + +``` +Remote mesh pipeline component availability: + Download capability: TRUE + - wget: /usr/bin/wget + - curl: /usr/bin/curl + Mesh partitioner: ON + Mesh diagnostics: ON +``` + +### Missing Component Messages +- `Download capability: FALSE` → Install wget or curl +- `Mesh partitioner: OFF` → Add `-DBUILD_MESHPARTITIONER=ON` +- `Mesh diagnostics: OFF` → Add `-DBUILD_MESHDIAG=ON` + +## Running Tests + +### Complete Pipeline for Single Mesh +```bash +# Run complete CORE2 pipeline (download → partition → validate) +ctest -R "remote.*core2" -V + +# Run complete DARS pipeline +ctest -R "remote.*dars" -V +``` + +### By Pipeline Stage +```bash +# All download tests +ctest -L "download_mesh" -V + +# All partition tests +ctest -L "partition_mesh" -V + +# All mesh diagnostics tests +ctest -L "generate_meshdiag" -V +``` + +### Individual Tests +```bash +# Download specific mesh +ctest -R remote_download_mesh_core2 -V + +# Partition specific mesh +ctest -R remote_partition_mesh_core2_16 -V + +# Generate diagnostics for specific mesh +ctest -R remote_generate_meshdiag_core2_mpi16 -V +``` + +## Test Dependencies + +Tests use **CMake fixtures** to ensure proper ordering: + +``` +Download → Partition → Validate + ↓ ↓ ↓ +(no deps) → mesh_X → mesh_X_N +``` + +- **Download tests** create fixture `mesh_{name}` +- **Partition tests** require `mesh_{name}`, create `mesh_{name}_{procs}` +- **Meshdiag tests** require `mesh_{name}_{procs}` + +## Output Files + +### Downloaded Meshes +``` +tests/data/MESHES/ +├── core2/ # Downloaded and extracted +├── dars/ # Downloaded and extracted +├── pi_remote/ # Downloaded and extracted +├── ng5/ # Downloaded and extracted +└── orca25/ # Downloaded and extracted +``` + +### Generated Partitions +``` +tests/data/MESHES/{mesh}/ +└── dist_{procs}/ + ├── rpart.out # Main partition file + ├── part.out # Partition mapping + ├── my_list*.out # Process-specific node lists + └── com_info*.out # Communication information +``` + +### Mesh Diagnostics +``` +build/tests/remote/remote_generate_meshdiag_{mesh}_mpi{procs}/ +└── results/ + └── fesom.mesh.diag.nc # Consistent filename across all tests +``` + +## Performance Expectations + +### Download Times (depends on connection) +- **Small meshes** (pi_remote): ~1 minute +- **Medium meshes** (core2): ~5 minutes +- **Large meshes** (dars, ng5, orca25): ~10-30 minutes + +### Partition Times +- **Small meshes**: ~30 seconds +- **Medium meshes**: ~2-5 minutes +- **Large meshes**: ~10-30 minutes + +### Meshdiag Times +- **Small meshes**: ~2 minutes +- **Medium meshes**: ~5 minutes +- **Large meshes**: ~30-60 minutes (hence the longer timeouts) + +## Troubleshooting + +### Download Issues +```bash +# Test download manually +wget https://swift.dkrz.de/v1/dkrz_035d8f6ff058403bb42f8302e6badfbc/FESOM_MESHES2.1/core2.tar.gz + +# Check network connectivity +ping swift.dkrz.de +``` + +### Partition Issues +```bash +# Check if partitioner executable exists +ls -la build/bin/fesom_meshpart + +# Test partitioner manually +cd tests/data/MESHES/pi +mkdir -p dist_test +cd dist_test +../../../../build/bin/fesom_meshpart .. 4 +``` + +### Meshdiag Issues +```bash +# Check if meshdiag executable exists +ls -la build/bin/fesom_meshdiag + +# Test meshdiag manually +cd build/tests/remote/remote_generate_meshdiag_core2_mpi16 +mpirun -np 16 ../../../bin/fesom_meshdiag +``` + +## Development + +### Adding New Remote Meshes + +1. **Add to mesh registry** (`tests/mesh_registry.json`) +2. **Add pipeline in `CMakeLists.txt`**: + ```cmake + if(HAS_DOWNLOAD_CAPABILITY) + add_mesh_pipeline("new_mesh" PREFIX remote PROCESS_COUNTS 16) + + if(BUILD_MESHDIAG) + add_fesom_meshdiag_test_with_options(generate_meshdiag_new_mesh_mpi16 "new_mesh" "fesom" + NP 16 + TIMEOUT 600 + PREFIX remote + ) + endif() + endif() + ``` + +### Best Practices +- **Use appropriate process counts** based on mesh size +- **Set realistic timeouts** based on mesh complexity +- **Include conditional logic** for missing components +- **Use consistent naming** with `remote_` prefix +- **Generate clean output** (`fesom.mesh.diag.nc`) \ No newline at end of file