diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 820553af..7247100f 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -15,7 +15,38 @@ jobs: steps: - uses: actions/checkout@v3 - name: update repos and install dependencies - run: sudo apt-get update && sudo apt-get install -y build-essential ${{matrix.fortran-compiler}} cmake python3 git libxml2-utils + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + libopenmpi-dev \ + ${{matrix.fortran-compiler}} \ + cmake \ + python3 \ + git \ + libxml2-utils + pip install --user pytest + + - name: Build the framework + run: | + cmake -S. -B./build -DOPENMP=ON -DCCPP_FRAMEWORK_ENABLE_TESTS=ON + cd build + make + - name: Run unit tests - run: cd test && ./run_fortran_tests.sh + run: | + cd build + ctest --rerun-failed --output-on-failure . --verbose + + - name: Run python tests + run: | + BUILD_DIR=./build \ + PYTHONPATH=test/:scripts/ \ + pytest \ + test/capgen_test/capgen_test_reports.py \ + test/advection_test/advection_test_reports.py \ + test/ddthost_test/ddthost_test_reports.py \ + test/var_compatibility_test/var_compatibility_test_reports.py + - name: Run Fortran to metadata test + run: cd test && ./test_fortran_to_metadata.sh diff --git a/.github/workflows/prebuild.yaml b/.github/workflows/prebuild.yaml index 3ccce8fc..43eda35c 100644 --- a/.github/workflows/prebuild.yaml +++ b/.github/workflows/prebuild.yaml @@ -47,14 +47,15 @@ jobs: export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools cd test_prebuild pytest - - name: ccpp-prebuild blocked data tests - run: | - cd test_prebuild/test_blocked_data - python3 ../../scripts/ccpp_prebuild.py --config=ccpp_prebuild_config.py --builddir=build - cd build - cmake .. - make - ./test_blocked_data.x + # No longer possible because of https://github.com/NCAR/ccpp-framework/pull/659 + #- name: ccpp-prebuild blocked data tests + # run: | + # cd test_prebuild/test_blocked_data + # python3 ../../scripts/ccpp_prebuild.py --config=ccpp_prebuild_config.py --builddir=build + # cd build + # cmake .. + # make + # ./test_blocked_data.x - name: ccpp-prebuild chunked data tests run: | cd test_prebuild/test_chunked_data diff --git a/.gitignore b/.gitignore index c2eb493d..8982481f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ # All compiled Python modules *.pyc +# CMake build directory +build/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 100192cd..00000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: python - -python: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - -branches: - only: - - feature/capgen - -install: - - pip install pylint - -script: - - env PYTHONPATH=scripts:${PYTHONPATH} pylint --rcfile ./test/.pylintrc ./test/unit_tests/test_metadata_table.py - - env PYTHONPATH=scripts:${PYTHONPATH} pylint --rcfile ./test/.pylintrc ./test/unit_tests/test_metadata_scheme_file.py - - python test/unit_tests/test_metadata_table.py - - python test/unit_tests/test_metadata_scheme_file.py - -notifications: - email: - recipients: - - dom.heinzeller@noaa.gov - - goldy@ucar.edu - on_success: always # default: change - on_failure: always # default: always diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d03783d..e0316793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,46 +4,117 @@ project(ccpp_framework VERSION 5.0.0 LANGUAGES Fortran) +include(cmake/ccpp_capgen.cmake) + #------------------------------------------------------------------------------ # Set package definitions set(PACKAGE "ccpp-framework") -set(AUTHORS "Dom Heinzeller" "Grant Firl" "Mike Kavulich" "Dustin Swales" "Courtney Peverley") string(TIMESTAMP YEAR "%Y") +option(OPENMP "Enable OpenMP support for the framework" OFF) +option(BUILD_SHARED_LIBS "Build using shared libraries" ON) +option(CCPP_FRAMEWORK_BUILD_DOCUMENTATION + "Create and install the HTML documentation (requires Doxygen)" OFF) +option(CCPP_FRAMEWORK_ENABLE_TESTS "Enable building/running CCPP regression tests" OFF) +option(CCPP_RUN_ADVECTION_TEST "Enable advection regression test" OFF) +option(CCPP_RUN_CAPGEN_TEST "Enable capgen regression test" OFF) +option(CCPP_RUN_DDT_HOST_TEST "Enable ddt host regression test" OFF) +option(CCPP_RUN_VAR_COMPATIBILITY_TEST "Enable variable compatibility regression test" OFF) + +message("") +message("OPENMP .............................. ${OPENMP}") +message("BUILD_SHARED_LIBS ................... ${BUILD_SHARED_LIBS}") +message("") +message("CCPP_FRAMEWORK_BUILD_DOCUMENTATION ...${CCPP_FRAMEWORK_BUILD_DOCUMENTATION}") +message("CCPP_FRAMEWORK_ENABLE_TESTS ......... ${CCPP_FRAMEWORK_ENABLE_TESTS}") +message("CCPP_RUN_ADVECTION_TEST ............. ${CCPP_RUN_ADVECTION_TEST}") +message("CCPP_RUN_CAPGEN_TEST ................ ${CCPP_RUN_CAPGEN_TEST}") +message("CCPP_RUN_DDT_HOST_TEST .............. ${CCPP_RUN_DDT_HOST_TEST}") +message("CCPP_RUN_VAR_COMPATIBILITY_TEST ..... ${CCPP_RUN_VAR_COMPATIBILITY_TEST}") +message("") + +set(CCPP_VERBOSITY "0" CACHE STRING "Verbosity level of output (default: 0)") + +# Warn user on conflicting test options +if(CCPP_RUN_ADVECTION_TEST OR + CCPP_RUN_CAPGEN_TEST OR + CCPP_RUN_DDT_HOST_TEST OR + CCPP_RUN_VAR_COMPATIBILITY_TEST) + set(CCPP_MANUALLY_DECLARED_TEST ON BOOL) +endif() +if(CCPP_MANUALLY_DECLARED_TEST AND CCPP_FRAMEWORK_ENABLE_TESTS) + message(WARNING "Detected a manual test flag and the flag to run all tests. If only expected to run a single test, please unset CCPP_FRAMEWORK_ENABLE_TESTS option.") +endif() +set(CCPP_RUNNING_TESTS CCPP_FRAMEWORK_ENABLE_TESTS OR CCPP_MANUALLY_DECLARED_TEST) + +# If running tests, set appropriate flags to help with debugging test issues. +if(CCPP_RUNNING_TESTS) + if(${CMAKE_Fortran_COMPILER_ID} STREQUAL "GNU") + ADD_COMPILE_OPTIONS(-fcheck=all) + ADD_COMPILE_OPTIONS(-fbacktrace) + ADD_COMPILE_OPTIONS(-ffpe-trap=zero) + ADD_COMPILE_OPTIONS(-finit-real=nan) + ADD_COMPILE_OPTIONS(-ggdb) + ADD_COMPILE_OPTIONS(-ffree-line-length-none) + ADD_COMPILE_OPTIONS(-cpp) + elseif(${CMAKE_Fortran_COMPILER_ID} STREQUAL "Intel") + ADD_COMPILE_OPTIONS(-fpe0) + ADD_COMPILE_OPTIONS(-warn) + ADD_COMPILE_OPTIONS(-traceback) + ADD_COMPILE_OPTIONS(-debug extended) + ADD_COMPILE_OPTIONS(-fpp) + ADD_COMPILE_OPTIONS(-diag-disable=10448) + elseif(${CMAKE_Fortran_COMPILER_ID} STREQUAL "IntelLLVM") + ADD_COMPILE_OPTIONS(-fpe0) + ADD_COMPILE_OPTIONS(-warn) + ADD_COMPILE_OPTIONS(-traceback) + ADD_COMPILE_OPTIONS(-debug full) + ADD_COMPILE_OPTIONS(-fpp) + elseif (${CMAKE_Fortran_COMPILER_ID} STREQUAL "NVIDIA" OR ${CMAKE_Fortran_COMPILER_ID} STREQUAL "NVHPC") + ADD_COMPILE_OPTIONS(-Mnoipa) + ADD_COMPILE_OPTIONS(-traceback) + ADD_COMPILE_OPTIONS(-Mfree) + ADD_COMPILE_OPTIONS(-Ktrap=fp) + ADD_COMPILE_OPTIONS(-Mpreprocess) + else() + message (WARNING "This program may not be able to be compiled with compiler :${CMAKE_Fortran_COMPILER_ID}") + endif() +endif() + +# Use rpaths on MacOSX +set(CMAKE_MACOSX_RPATH 1) + #------------------------------------------------------------------------------ # Set MPI flags for Fortran with MPI F08 interface -find_package(MPI REQUIRED Fortran) +find_package(MPI COMPONENTS Fortran REQUIRED) if(NOT MPI_Fortran_HAVE_F08_MODULE) message(FATAL_ERROR "MPI implementation does not support the Fortran 2008 mpi_f08 interface") endif() #------------------------------------------------------------------------------ # Set OpenMP flags for C/C++/Fortran -if (OPENMP) +if(OPENMP) find_package(OpenMP REQUIRED) - set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") -endif (OPENMP) +endif() #------------------------------------------------------------------------------ # Set a default build type if none was specified if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "Coverage") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) endif() #------------------------------------------------------------------------------ -# Pass debug/release flag to Fortran files for preprocessor -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-DDEBUG) +# Add the sub-directories +add_subdirectory(src) + +if(CCPP_RUNNING_TESTS) + enable_testing() + add_subdirectory(test) endif() -#------------------------------------------------------------------------------ -# Request a static build -option(BUILD_SHARED_LIBS "Build a static library" OFF) +if (CCPP_FRAMEWORK_BUILD_DOCUMENTATION) + find_package(Doxygen REQUIRED) + add_subdirectory(doc) +endif() -#------------------------------------------------------------------------------ -# Add the sub-directories -add_subdirectory(src) -add_subdirectory(doc) diff --git a/cmake/ccpp_capgen.cmake b/cmake/ccpp_capgen.cmake new file mode 100644 index 00000000..24c891f2 --- /dev/null +++ b/cmake/ccpp_capgen.cmake @@ -0,0 +1,129 @@ +# CMake wrapper for ccpp_capgen.py +# Currently meant to be a CMake API needed for generating caps for regression tests. +# +# CAPGEN_DEBUG - ON/OFF (Default: OFF) - Enables debug capability through ccpp_capgen.py +# CAPGEN_EXPECT_THROW_ERROR - ON/OFF (Default: OFF) - Scans ccpp_capgen.py log for error string and errors if not found. +# HOST_NAME - String name of host +# OUTPUT_ROOT - String path to put generated caps +# VERBOSITY - Number of --verbose flags to pass to capgen +# HOSTFILES - CMake list of host metadata filenames +# SCHEMEFILES - CMake list of scheme metadata files +# SUITES - CMake list of suite xml files +function(ccpp_capgen) + set(optionalArgs CAPGEN_DEBUG CAPGEN_EXPECT_THROW_ERROR) + set(oneValueArgs HOST_NAME OUTPUT_ROOT VERBOSITY) + set(multi_value_keywords HOSTFILES SCHEMEFILES SUITES) + + cmake_parse_arguments(arg "${optionalArgs}" "${oneValueArgs}" "${multi_value_keywords}" ${ARGN}) + + # Error if script file not found. + set(CCPP_CAPGEN_CMD_LIST "${CMAKE_SOURCE_DIR}/scripts/ccpp_capgen.py") + if(NOT EXISTS ${CCPP_CAPGEN_CMD_LIST}) + message(FATAL_ERROR "function(ccpp_capgen): Could not find ccpp_capgen.py. Looked for ${CCPP_CAPGEN_CMD_LIST}.") + endif() + + # Interpret parsed arguments + if(DEFINED arg_CAPGEN_DEBUG) + list(APPEND CCPP_CAPGEN_CMD_LIST "--debug") + endif() + if(DEFINED arg_HOSTFILES) + list(JOIN arg_HOSTFILES "," HOSTFILES_SEPARATED) + list(APPEND CCPP_CAPGEN_CMD_LIST "--host-files" "${HOSTFILES_SEPARATED}") + endif() + if(DEFINED arg_SCHEMEFILES) + list(JOIN arg_SCHEMEFILES "," SCHEMEFILES_SEPARATED) + list(APPEND CCPP_CAPGEN_CMD_LIST "--scheme-files" "${SCHEMEFILES_SEPARATED}") + endif() + if(DEFINED arg_SUITES) + list(JOIN arg_SUITES "," SUITES_SEPARATED) + list(APPEND CCPP_CAPGEN_CMD_LIST "--suites" "${SUITES_SEPARATED}") + endif() + if(DEFINED arg_HOST_NAME) + list(APPEND CCPP_CAPGEN_CMD_LIST "--host-name" "${arg_HOST_NAME}") + endif() + if(DEFINED arg_OUTPUT_ROOT) + message(STATUS "Creating output directory: ${arg_OUTPUT_ROOT}") + file(MAKE_DIRECTORY "${arg_OUTPUT_ROOT}") + list(APPEND CCPP_CAPGEN_CMD_LIST "--output-root" "${arg_OUTPUT_ROOT}") + endif() + if(DEFINED arg_VERBOSITY) + string(REPEAT "--verbose" ${arg_VERBOSITY} VERBOSE_PARAMS_SEPERATED) + separate_arguments(VERBOSE_PARAMS UNIX_COMMAND "${VERBOSE_PARAMS_SEPERATED}") + list(APPEND CCPP_CAPGEN_CMD_LIST ${VERBOSE_PARAMS}) + endif() + + message(STATUS "Running ccpp_capgen.py from ${CMAKE_CURRENT_SOURCE_DIR}") + + unset(CAPGEN_OUT) # Unset CAPGEN_OUT to prevent incorrect output on subsequent ccpp_capgen(...) calls. + execute_process(COMMAND ${CCPP_CAPGEN_CMD_LIST} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE CAPGEN_OUT + ERROR_VARIABLE CAPGEN_OUT + RESULT_VARIABLE RES + COMMAND_ECHO STDOUT) + + message(STATUS "ccpp-capgen stdout: ${CAPGEN_OUT}") + + if(arg_CAPGEN_EXPECT_THROW_ERROR) + # Determine if the process succeeded but had an expected string in the process log. + string(FIND "${CAPGEN_OUT}" "Variables of type ccpp_constituent_properties_t only allowed in register phase" ERROR_INDEX) + + if (ERROR_INDEX GREATER -1) + message(STATUS "Capgen build produces expected error message.") + else() + message(FATAL_ERROR "CCPP cap generation did not generate expected error. Expected 'Variables of type constituent_properties_t only allowed in register phase.") + endif() + else() + if(RES EQUAL 0) + message(STATUS "ccpp-capgen completed successfully") + else() + message(FATAL_ERROR "CCPP cap generation FAILED: result = ${RES}") + endif() + endif() +endfunction() + +# CMake wrapper for ccpp_datafile.py +# Currently meant to be a CMake API needed for generating caps for regression tests. +# +# DATATABLE - Path to generated datatable.xml file +# REPORT_NAME - String report name to get list of generated files form capgen (typically --ccpp-files) +function(ccpp_datafile) + set(oneValueArgs DATATABLE REPORT_NAME) + cmake_parse_arguments(arg "" "${oneValueArgs}" "" ${ARGN}) + + set(CCPP_DATAFILE_CMD "${CMAKE_SOURCE_DIR}/scripts/ccpp_datafile.py") + + if(NOT EXISTS ${CCPP_DATAFILE_CMD}) + message(FATAL_ERROR "function(ccpp_datafile): Could not find ccpp_datafile.py. Looked for ${CCPP_DATAFILE_CMD}.") + endif() + + if(NOT DEFINED arg_REPORT_NAME) + message(FATAL_ERROR "function(ccpp_datafile): REPORT_NAME not set. Must specify the report to generate to run cpp_datafile.py") + endif() + list(APPEND CCPP_DATAFILE_CMD "${arg_REPORT_NAME}") + + if(NOT DEFINED arg_DATATABLE) + message(FATAL_ERROR "function(ccpp_datafile): DATATABLE not set. A datatable file must be configured to call ccpp_datafile.") + endif() + list(APPEND CCPP_DATAFILE_CMD "${arg_DATATABLE}") + + message(STATUS "Running ccpp_datafile from ${CMAKE_CURRENT_SOURCE_DIR}") + + unset(CCPP_CAPS) # Unset CCPP_CAPS to prevent incorrect output on subsequent ccpp_datafile(...) calls. + execute_process(COMMAND ${CCPP_DATAFILE_CMD} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE CCPP_CAPS + RESULT_VARIABLE RES + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE + COMMAND_ECHO STDOUT) + message(STATUS "CCPP_CAPS = ${CCPP_CAPS}") + if(RES EQUAL 0) + message(STATUS "CCPP cap files retrieved") + else() + message(FATAL_ERROR "CCPP cap file retrieval FAILED: result = ${RES}") + endif() + string(REPLACE "," ";" CCPP_CAPS_LIST ${CCPP_CAPS}) # Convert "," separated list from python back to ";" separated list for CMake. + set(CCPP_CAPS_LIST "${CCPP_CAPS_LIST}" PARENT_SCOPE) +endfunction() + diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 9e96e4e4..b7997658 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -2,28 +2,16 @@ # Doxygen rules # # Add a target to generate API documentation with Doxygen -find_package(Doxygen) -option(BUILD_DOCUMENTATION - "Create and install the HTML documentation (requires Doxygen)" - ${DOXYGEN_FOUND}) +set(doxyfile_in ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) +set(doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) -# -if(BUILD_DOCUMENTATION) - if(NOT DOXYGEN_FOUND) - message(FATAL_ERROR "Doxygen is needed to build the documentation.") - endif() - - set(doxyfile_in ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) - set(doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - - configure_file(${doxyfile_in} ${doxyfile} @ONLY) +configure_file(${doxyfile_in} ${doxyfile} @ONLY) - add_custom_target(doc - COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" - VERBATIM) -endif() +add_custom_target(doc + COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM) set(gmtb_sty_in ${CMAKE_CURRENT_SOURCE_DIR}/DevelopersGuide/gmtb.sty) set(gmtb_sty ${CMAKE_CURRENT_BINARY_DIR}/DevelopersGuide/gmtb.sty) diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000..fe2284ec --- /dev/null +++ b/doc/README.md @@ -0,0 +1,11 @@ +# Building the documentation + +Similar to building the source code, building the documentation can be done by: + +```bash +$ cmake -S -B -DCCPP_FRAMEWORK_BUILD_DOCUMENTATION=ON +cd build_directory +make doc +``` + +and the compiled documentation should be in `/doc/html`. diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 9eac0ecf..a0b1e032 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -25,7 +25,7 @@ from framework_env import parse_command_line from host_cap import write_host_cap from host_model import HostModel -from metadata_table import parse_metadata_file, SCHEME_HEADER_TYPE +from metadata_table import parse_metadata_file, register_ddts, SCHEME_HEADER_TYPE from parse_tools import init_log, set_log_level, context_string from parse_tools import register_fortran_ddt_name from parse_tools import CCPPError, ParseInternalError @@ -44,7 +44,7 @@ _EXTRA_VARIABLE_TABLE_TYPES = ['module', 'host', 'ddt'] ## Metadata table types where order is significant -_ORDERED_TABLE_TYPES = [SCHEME_HEADER_TYPE] +_ORDERED_TABLE_TYPES = [] ## CCPP Framework supported DDT types _CCPP_FRAMEWORK_DDT_TYPES = ["ccpp_hash_table_t", @@ -309,6 +309,17 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): if not list_match: errmsg = 'Variable mismatch in {}, variables missing from {}.' errors_found = add_error(errors_found, errmsg.format(title, etype)) + if etype == "metadata header": + # Look for missing metadata variables + for fvar in flist: + lname = fvar.get_prop_value('local_name') + _, find = find_var_in_list(lname, mlist) + if (find < 0) and (not fvar.get_prop_value('optional')): + errmsg = f"Fortran variable, {lname}, not in metadata" + errors_found = add_error(errors_found, errmsg) + # end if + # end for + # end if # end if for mind, mvar in enumerate(mlist): lname = mvar.get_prop_value('local_name') @@ -468,7 +479,8 @@ def duplicate_item_error(title, filename, itype, orig_item): raise CCPPError(errmsg.format(**edict)) ############################################################################### -def parse_host_model_files(host_filenames, host_name, run_env): +def parse_host_model_files(host_filenames, host_name, run_env, + known_ddts=list()): ############################################################################### """ Gather information from host files (e.g., DDTs, registry) and @@ -476,7 +488,6 @@ def parse_host_model_files(host_filenames, host_name, run_env): """ header_dict = {} table_dict = {} - known_ddts = list() logger = run_env.logger for filename in host_filenames: logger.info('Reading host model data from {}'.format(filename)) @@ -524,7 +535,8 @@ def parse_host_model_files(host_filenames, host_name, run_env): return host_model ############################################################################### -def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): +def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False, + known_ddts=list()): ############################################################################### """ Gather information from scheme files (e.g., init, run, and finalize @@ -532,7 +544,6 @@ def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): """ table_dict = {} # Duplicate check and for dependencies processing header_dict = {} # To check for duplicates - known_ddts = list() logger = run_env.logger for filename in scheme_filenames: logger.info('Reading CCPP schemes from {}'.format(filename)) @@ -637,15 +648,20 @@ def capgen(run_env, return_db=False): if run_env.generate_docfiles: raise CCPPError("--generate-docfiles not yet supported") # end if - # First up, handle the host files - host_model = parse_host_model_files(host_files, host_name, run_env) + # The host model may depend on suite DDTs + scheme_ddts = register_ddts(scheme_files) + # Handle the host files + host_model = parse_host_model_files(host_files, host_name, run_env, + known_ddts=scheme_ddts) # Next, parse the scheme files # We always need to parse the constituent DDTs const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") if const_prop_mod not in scheme_files: - scheme_files= [const_prop_mod] + scheme_files + scheme_files = [const_prop_mod] + scheme_files # end if - scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env) + host_ddts = register_ddts(host_files) + scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env, + known_ddts=host_ddts) if run_env.verbose: ddts = host_model.ddt_lib.keys() if ddts: diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 6bb5e537..2318143e 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -1067,6 +1067,8 @@ def _add_generated_files(parent, host_files, suite_files, ccpp_kinds, src_dir): entry = ET.SubElement(utilities, "file") entry.text = os.path.join(src_dir, "ccpp_constituent_prop_mod.F90") entry = ET.SubElement(utilities, "file") + entry.text = os.path.join(src_dir, "ccpp_scheme_utils.F90") + entry = ET.SubElement(utilities, "file") entry.text = os.path.join(src_dir, "ccpp_hashable.F90") entry = ET.SubElement(utilities, "file") entry.text = os.path.join(src_dir, "ccpp_hash_table.F90") @@ -1096,7 +1098,7 @@ def _add_suite_object(parent, suite_object): obj_elem.set("dimension_name", suite_object.dimension_name) # end if if isinstance(suite_object, Subcycle): - obj_elem.set("loop", suite_object.loop) + obj_elem.set("loop", suite_object._loop) # end if for obj_part in suite_object.parts: _add_suite_object(obj_elem, obj_part) diff --git a/scripts/ccpp_fortran_to_metadata.py b/scripts/ccpp_fortran_to_metadata.py index 4c301d1d..afb3597d 100755 --- a/scripts/ccpp_fortran_to_metadata.py +++ b/scripts/ccpp_fortran_to_metadata.py @@ -39,6 +39,7 @@ from parse_tools import init_log, set_log_level from parse_tools import CCPPError, ParseInternalError from parse_tools import reset_standard_name_counter, unique_standard_name +from parse_tools import register_fortran_ddt_name from fortran_tools import parse_fortran_file from file_utils import create_file_list from metadata_table import blank_metadata_line @@ -67,6 +68,10 @@ def parse_command_line(args, description): metavar='VARDEF1[,VARDEF2 ...]', type=str, default='', help="Proprocessor directives used to correctly parse source files") + parser.add_argument("--ddt-names", + metavar='DDT_NAME1[,DDT_NAME2 ...]', type=str, default='', + help="Comma-separated DDT names that may be used in the parsed Fortran files") + parser.add_argument("--output-root", type=str, metavar='', default=os.getcwd(), @@ -198,6 +203,11 @@ def _main_func(): elif verbosity > 0: set_log_level(_LOGGER, logging.INFO) # end if + if args.ddt_names: + for dname in args.ddt_names.split(','): + register_fortran_ddt_name(dname) + # end for + # end if # Make sure we know where output is going output_dir = os.path.abspath(args.output_root) # Optional table separator comment diff --git a/scripts/ccpp_prebuild.py b/scripts/ccpp_prebuild.py index b8829b6f..c6198e27 100755 --- a/scripts/ccpp_prebuild.py +++ b/scripts/ccpp_prebuild.py @@ -13,7 +13,8 @@ import sys # CCPP framework imports -from common import encode_container, decode_container, decode_container_as_dict, execute +from common import lowercase_keys_and_values +from common import encode_container, decode_container, decode_container_as_dict from common import CCPP_STAGES, CCPP_INTERNAL_VARIABLES, CCPP_STATIC_API_MODULE, CCPP_INTERNAL_VARIABLE_DEFINITON_FILE from common import STANDARD_VARIABLE_TYPES, STANDARD_INTEGER_TYPE, CCPP_TYPE from common import SUITE_DEFINITION_FILENAME_PATTERN @@ -157,9 +158,14 @@ def clean_files(config, namespace): os.path.join(config['static_api_dir'], static_api_file), config['static_api_sourcefile'], ] - # Not very pythonic, but the easiest way w/o importing another Python module - cmd = 'rm -vf {0}'.format(' '.join(files_to_remove)) - execute(cmd) + for f in files_to_remove: + try: + os.remove(f) + except FileNotFoundError: + pass + except Exception as e: + logging.error(f"Error removing {f}: {e}") + success = False return success def get_all_suites(suites_dir): @@ -356,11 +362,12 @@ def check_schemes_in_suites(arguments, suites): """Check that all schemes that are requested in the suites exist""" success = True logging.info("Checking for existence of schemes in suites ...") + argument_keys = [x.lower() for x in arguments.keys()] for suite in suites: for group in suite.groups: for subcycle in group.subcycles: for scheme_name in subcycle.schemes: - if not scheme_name in arguments.keys(): + if not scheme_name in argument_keys: success = False logging.critical("Scheme {} in suite {} cannot be found".format(scheme_name, suite.name)) return success @@ -396,19 +403,19 @@ def filter_metadata(metadata, arguments, dependencies, schemes_in_files, suites) # Filter argument lists for scheme in arguments.keys(): for suite in suites: - if scheme in suite.all_schemes_called: + if scheme.lower() in suite.all_schemes_called: arguments_filtered[scheme] = arguments[scheme] break # Filter dependencies for scheme in dependencies.keys(): for suite in suites: - if scheme in suite.all_schemes_called: + if scheme.lower() in suite.all_schemes_called: dependencies_filtered[scheme] = dependencies[scheme] break # Filter schemes_in_files for scheme in schemes_in_files.keys(): for suite in suites: - if scheme in suite.all_schemes_called: + if scheme.lower() in suite.all_schemes_called: schemes_in_files_filtered[scheme] = schemes_in_files[scheme] return (success, metadata_filtered, arguments_filtered, dependencies_filtered, schemes_in_files_filtered) @@ -744,6 +751,9 @@ def main(): logging.info('CCPP prebuild clean completed successfully, exiting.') sys.exit(0) + # Convert TYPEDEFS_NEW_METATA config to lowercase + config['typedefs_new_metadata'] = lowercase_keys_and_values(config['typedefs_new_metadata']) + # If no suite definition files were given, get all of them if not sdfs: (success, sdfs) = get_all_suites(config['suites_dir']) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index d2634c89..1182fd43 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -426,15 +426,15 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env): self.__ddt_library = ddt_library # Collect all relevant schemes # For all groups, find associated init and final methods - scheme_set = set() + scheme_list = list() for group in self.groups: for scheme in group.schemes(): - scheme_set.add(scheme.name) + scheme_list.append(scheme.name) # end for # end for no_scheme_entries = {} # Skip schemes that are not in this suite - for module in scheme_library: - if module in scheme_set: + for module in scheme_list: + if scheme_library[module]: scheme_entries = scheme_library[module] else: scheme_entries = no_scheme_entries @@ -658,8 +658,13 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env): run_env, ddts=all_ddts) for header in [d for d in scheme_headers if d.header_type != 'ddt']: if header.header_type != 'scheme': - errmsg = "{} is an unknown CCPP API metadata header type, {}" - raise CCPPError(errmsg.format(header.title, header.header_type)) + if header.header_type == 'module': + errmsg = f"{header.title} is a module metadata header type." + errmsg+=" This is not an allowed CCPP scheme header type." + else: + errmsg = f"{header.title} is an unknown CCPP API metadata header type, {header.header_type}" + # end if + raise CCPPError(errmsg) # end if func_id, _, match_trans = \ CCPP_STATE_MACH.function_match(header.title) @@ -773,7 +778,7 @@ def write_var_set_loop(ofile, varlist_name, var_list, indent, if add_allocate: ofile.write(f"allocate({varlist_name}({len(var_list)}))", indent) # end if - for ind, var in enumerate(sorted(var_list)): + for ind, var in enumerate(var_list): if start_var: ind_str = f"{start_var} + {ind + start_index}" else: diff --git a/scripts/ccpp_track_variables.py b/scripts/ccpp_track_variables.py index 64dfc518..6d3f6d0b 100755 --- a/scripts/ccpp_track_variables.py +++ b/scripts/ccpp_track_variables.py @@ -10,6 +10,7 @@ from metadata_table import find_scheme_names, parse_metadata_file from ccpp_prebuild import import_config, gather_variable_definitions from mkstatic import Suite +from common import lowercase_keys from parse_checkers import registered_fortran_ddt_names from parse_tools import init_log, set_log_level from framework_env import CCPPFrameworkEnv @@ -78,7 +79,7 @@ def create_metadata_filename_dict(metapath): # The above returns a list of schemes in each filename, but # we want a dictionary of schemes associated with filenames: for scheme in schemes: - metadata_dict[scheme] = scheme_fn + metadata_dict[scheme.lower()] = scheme_fn return metadata_dict diff --git a/scripts/common.py b/scripts/common.py index f5ba7f1d..234e2521 100755 --- a/scripts/common.py +++ b/scripts/common.py @@ -78,35 +78,6 @@ # Maximum number of concurrent CCPP instances per MPI task CCPP_NUM_INSTANCES = 200 -def execute(cmd, abort = True): - """Runs a local command in a shell. Waits for completion and - returns status, stdout and stderr. If abort = True, abort in - case an error occurs during the execution of the command.""" - - # Set debug to true if logging level is debug - debug = logging.getLogger().getEffectiveLevel() == logging.DEBUG - - logging.debug('Executing "{0}"'.format(cmd)) - p = subprocess.Popen(cmd, stdout = subprocess.PIPE, - stderr = subprocess.PIPE, shell = True) - (stdout, stderr) = p.communicate() - status = p.returncode - if debug: - message = 'Execution of "{0}" returned with exit code {1}\n'.format(cmd, status) - message += ' stdout: "{0}"\n'.format(stdout.decode(encoding='ascii', errors='ignore').rstrip('\n')) - message += ' stderr: "{0}"'.format(stderr.decode(encoding='ascii', errors='ignore').rstrip('\n')) - logging.debug(message) - if not status == 0: - message = 'Execution of command {0} failed, exit code {1}\n'.format(cmd, status) - message += ' stdout: "{0}"\n'.format(stdout.decode(encoding='ascii', errors='ignore').rstrip('\n')) - message += ' stderr: "{0}"'.format(stderr.decode(encoding='ascii', errors='ignore').rstrip('\n')) - if abort: - raise Exception(message) - else: - logging.error(message) - return (status, stdout.decode(encoding='ascii', errors='ignore').rstrip('\n'), - stderr.decode(encoding='ascii', errors='ignore').rstrip('\n')) - def split_var_name_and_array_reference(var_name): """Split an expression like foo(:,a,1:ddt%ngas) into components foo and (:,a,1:ddt%ngas).""" @@ -130,13 +101,14 @@ def split_var_name_and_array_reference(var_name): def encode_container(*args): """Encodes a container, i.e. the location of a metadata table for CCPP. Currently, there are three possibilities with different numbers of input - arguments: module, module+typedef, module+scheme+subroutine.""" + arguments: module, module+typedef, module+scheme+subroutine. Convert all + names to lowercase to support the new case-insensitive capgen parser.""" if len(args)==3: - container = 'MODULE_{0} SCHEME_{1} SUBROUTINE_{2}'.format(*args) + container = 'MODULE_{0} SCHEME_{1} SUBROUTINE_{2}'.format(*[arg.lower() for arg in args]) elif len(args)==2: - container = 'MODULE_{0} TYPE_{1}'.format(*args) + container = 'MODULE_{0} TYPE_{1}'.format(*[arg.lower() for arg in args]) elif len(args)==1: - container = 'MODULE_{0}'.format(*args) + container = 'MODULE_{0}'.format(*[arg.lower() for arg in args]) else: raise Exception("encode_container not implemented for {0} arguments".format(len(args))) return container @@ -177,6 +149,21 @@ def isstring(s): """Return true if a variable is a string""" return isinstance(s, str) +def insert_plus_sign_for_positive_exponents(string): + """Parse a string (a unit string) and insert plus (+) signs + for positive exponents where needed""" + # Break up the string by spaces + items = string.split() + # Identify units with positive exponents + # without a plus sign (m2 instead of m+2). + pattern = re.compile(r"([a-zA-Z]+)([0-9]+)") + for index, item in enumerate(items): + match = pattern.match(item) + if match: + items[index] = "+".join(match.groups()) + # Recombine items to string + return " ".join(items) + def string_to_python_identifier(string): """Replaces forbidden characters in strings with standard substitutions so that the result is a valid Python object (variable, function) name. @@ -198,3 +185,49 @@ def string_to_python_identifier(string): return string else: raise Exception("Resulting string '{0}' is not a valid Python identifier".format(string)) + +# New utilities added 2025/07/25 for dealing with case-insensitivity changes from capgen. Used to convert XML data read with the xml library and other ccpp_prebuild dictionaries + +def lowercase_keys_and_values(d): + """Recursively convert all keys and values in a regular dictionary to lowercase""" + if isinstance(d, dict): + return { + (k.lower() if isinstance(k, str) else k): + lowercase_keys_and_values(v) + for k, v in d.items() + } + elif isinstance(d, list): + return [lowercase_keys_and_values(item) for item in d] + elif isinstance(d, str): + return d.lower() + else: + return d + +def lowercase_keys(d): + """Recursively convert all keys in an OrderedDict to lowercase""" + if isinstance(d, OrderedDict): + new_dict = OrderedDict() + for k, v in d.items(): + new_key = k.lower() if isinstance(k, str) else k + new_dict[new_key] = lowercase_keys(v) + return new_dict + elif isinstance(d, list): + return [lowercase_keys(item) for item in d] + else: + return d + +def lowercase_xml(element): + """Recursively convert XML elements to lowercase""" + # Lowercase the tag name + element.tag = element.tag.lower() + # Lowercase the text content, if it exists + if element.text: + element.text = element.text.lower() + if element.tail: + element.tail = element.tail.lower() + # Lowercase attribute keys and values + element.attrib = {k.lower(): v.lower() for k, v in element.attrib.items()} + # Recurse into child elements + for i in range(len(element)): + element[i] = lowercase_xml(element[i]) + return element diff --git a/scripts/constituents.py b/scripts/constituents.py index a8c11144..bb83c3c7 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -608,6 +608,8 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"{substmt}(ncols, num_layers, {err_dummy_str})", 1) cap.comment("Initialize constituent data", 2) cap.blank_line() + cap.write("use ccpp_scheme_utils, only: ccpp_initialize_constituent_ptr", 2) + cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("integer, intent(in) :: ncols", 2) cap.write("integer, intent(in) :: num_layers", 2) @@ -617,6 +619,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() call_str = f"call {const_obj_name}%lock_data(ncols, num_layers, {obj_err_callstr})" cap.write(call_str, 2) + cap.write(f"call ccpp_initialize_constituent_ptr({const_obj_name})", 2) cap.write(f"end {substmt}", 1) # Write num_consts routine substmt = f"subroutine {num_const_funcname}" diff --git a/scripts/conversion_tools/unit_conversion.py b/scripts/conversion_tools/unit_conversion.py index b9afa144..25a44cab 100755 --- a/scripts/conversion_tools/unit_conversion.py +++ b/scripts/conversion_tools/unit_conversion.py @@ -176,3 +176,31 @@ def W_m_minus_2__to__erg_cm_minus_2_s_minus_1(): def erg_cm_minus_2_s_minus_1__to__W_m_minus_2(): """Convert erg per square centimeter and second to Watt per square meter""" return '1.0E-3{kind}*{var}' + +#################### +# Equivalent units # +#################### + +def m_plus_2_s_minus_2__to__J_kg_minus_1(): + """Equivalent units""" + return '{var}' + +def J_kg_minus_1__to__m_plus_2_s_minus_2(): + """Equivalent units""" + return '{var}' + +def V_A__to__W(): + """Equivalent units""" + return '{var}' + +def W__to__V_A(): + """Equivalent units""" + return '{var}' + +def N_m_minus_2__to__Pa(): + """Equivalent units""" + return '{var}' + +def Pa__to__N_m_minus_2(): + """Equivalent units""" + return '{var}' diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 35425599..1c362108 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -252,9 +252,10 @@ def check_ddt_type(self, var, header, lname=None): # end if (no else needed) def collect_ddt_fields(self, var_dict, var, run_env, - ddt=None, skip_duplicates=False): + ddt=None, skip_duplicates=False, parent=None): """Add all the reachable fields from DDT variable of type, to . Each field is added as a VarDDT. + If , add VarDDT recursively using parent. Note: By default, it is an error to try to add a duplicate field to (i.e., the field already exists in or one of its parents). To simply skip duplicate @@ -272,12 +273,16 @@ def collect_ddt_fields(self, var_dict, var, run_env, # end if # end if for dvar in ddt.variable_list(): - subvar = VarDDT(dvar, var, self.run_env) + if parent is None: + subvar = VarDDT(dvar, var, self.run_env) + else: + subvar = VarDDT(VarDDT(dvar, var, self.run_env), parent, self.run_env) + # end if dvtype = dvar.get_prop_value('type') if (dvar.is_ddt()) and (dvtype in self): # If DDT in our library, we need to add sub-fields recursively. subddt = self[dvtype] - self.collect_ddt_fields(var_dict, subvar, run_env, ddt=subddt) + self.collect_ddt_fields(var_dict, dvar, run_env, parent=var, ddt=subddt) # end if # add_variable only checks the current dictionary. By default, # for a DDT, the variable also cannot be in our parent diff --git a/scripts/fortran_tools/fortran_write.py b/scripts/fortran_tools/fortran_write.py index 5b186c1c..e147b48c 100644 --- a/scripts/fortran_tools/fortran_write.py +++ b/scripts/fortran_tools/fortran_write.py @@ -217,6 +217,12 @@ def write(self, statement, indent_level, continue_line=False): if len(outstr) > best: if self._in_quote(outstr[0:best+1]): line_continue = '&' + elif not outstr[best+1:].lstrip(): + # If the next line is empty, the current line is done + # and is equal to the max line length. Do not use + # continue and set best to line_max (best+1) + line_continue = False + best = best+1 else: # If next line is just comment, do not use continue line_continue = outstr[best+1:].lstrip()[0] != '!' diff --git a/scripts/host_cap.py b/scripts/host_cap.py index f1d2880b..b06906fe 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -101,14 +101,6 @@ def constituent_initialize_subname(host_model): Because this is a user interface API function, the name is fixed.""" return f"{host_model.name}_ccpp_initialize_constituents" -############################################################################### -def constituent_initialize_subname(host_model): -############################################################################### - """Return the name of the subroutine used to initialize the - constituents for this run. - Because this is a user interface API function, the name is fixed.""" - return f"{host_model.name}_ccpp_initialize_constituents" - ############################################################################### def constituent_num_consts_funcname(host_model): ############################################################################### @@ -125,14 +117,6 @@ def query_scheme_constituents_funcname(host_model): Because this is a user interface API function, the name is fixed.""" return f"{host_model.name}_ccpp_is_scheme_constituent" -############################################################################### -def query_scheme_constituents_funcname(host_model): -############################################################################### - """Return the name of the function to return True if the standard name - passed in matches an existing constituent - Because this is a user interface API function, the name is fixed.""" - return f"{host_model.name}_ccpp_is_scheme_constituent" - ############################################################################### def constituent_copyin_subname(host_model): ############################################################################### diff --git a/scripts/metadata_parser.py b/scripts/metadata_parser.py index d1277ebe..31078b64 100755 --- a/scripts/metadata_parser.py +++ b/scripts/metadata_parser.py @@ -10,6 +10,7 @@ from common import encode_container, CCPP_STAGES from common import CCPP_ERROR_CODE_VARIABLE, CCPP_ERROR_MSG_VARIABLE +from common import insert_plus_sign_for_positive_exponents from mkcap import Var sys.path.append(os.path.join(os.path.split(__file__)[0], 'fortran_tools')) @@ -230,12 +231,17 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub #kind = new_var.get_prop_value('kind') # *DH 20210812 - var = Var(standard_name = standard_name, + # Workaround to support units with positive exponents with + # and without a plus (+) sign. Internally, we convert all + # units from capgen to the "+"-format (i.e. "m2 s-2" --> "m+2 s-2") + units = insert_plus_sign_for_positive_exponents(new_var.get_prop_value('units')) + + var = Var(standard_name = standard_name.lower(), long_name = new_var.get_prop_value('long_name') + legacy_note, - units = new_var.get_prop_value('units'), - local_name = new_var.get_prop_value('local_name'), + units = units, + local_name = new_var.get_prop_value('local_name').lower(), type = new_var.get_prop_value('type').lower(), - dimensions = dimensions, + dimensions = [dim.lower() for dim in dimensions], container = container, kind = kind, intent = new_var.get_prop_value('intent'), diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 942f8d81..959866a9 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -63,6 +63,7 @@ type = scheme relative_path = dependencies = + module = # only needed if module name differs from filename dynamic_constituent_routine = [ccpp-arg-table] @@ -167,7 +168,7 @@ def _parse_config_line(line, context): else: properties = line.strip().split('|') for prop in properties: - pitems = prop.split('=', 1) + pitems = [x.strip() for x in prop.split('=', 1)] if len(pitems) >= 2: parse_items.append(pitems) else: @@ -204,7 +205,7 @@ def parse_metadata_file(filename, known_ddts, run_env, skip_ddt_check=False): meta_tables.append(new_table) table_titles.append(ntitle) if new_table.table_type == 'ddt': - known_ddts.append(ntitle) + known_ddts.append(ntitle.lower()) # end if else: errmsg = 'Duplicate metadata table, {}, at {}:{}' @@ -245,8 +246,8 @@ def find_scheme_names(filename): props = _parse_config_line(line, context) for prop in props: # Look for name property - key = prop[0].strip().lower() - value = prop[1].strip() + key = prop[0].lower() + value = prop[1] if key == 'name': scheme_names.append(value) # end if @@ -264,6 +265,90 @@ def find_scheme_names(filename): ######################################################################## +def register_ddts(file_list): + """Scan the metadata files in and register all + DDT tables found. + Return a list of the DDTs type names found. + """ + errors = "" + ddt_names = set() + for mfile in file_list: + if os.path.exists(mfile): + with open(mfile, 'r') as infile: + fin_lines = infile.readlines() + # end with + pobj = ParseObject(mfile, fin_lines) + in_table = False # Line number of table start + ddt_name = "" + table_is_ddt = False + # Search the file for ccpp-table-properties sections + curr_line, line_num = pobj.next_line() + while(curr_line is not None): + if in_table: + # We are in a table properties sec, look for name and type + if MetadataSection.header_start(curr_line) or \ + MetadataTable.table_start(curr_line): + # We have exited the table, record if a DDT + if table_is_ddt: + if ddt_name: + ddt_names.add(ddt_name) + else: + emsg = "Unnamed CCPP metadata table" + pobj.add_syntax_err(emsg) + # end if + # end if + if MetadataTable.table_start(curr_line): + in_table = line_num + 1 + else: + in_table = False + # end if + ddt_name = "" + table_is_ddt = False + else: + for prop in _parse_config_line(curr_line, context=pobj): + if prop[0].lower() == 'name': + ddt_name = prop[1].lower() + elif prop[0].lower() == 'type': + table_is_ddt = prop[1].lower() == 'ddt' + # end if + # end for + # end if + elif MetadataTable.table_start(curr_line): + in_table = line_num + 1 + # end if + curr_line, line_num = pobj.next_line() + # end while + if pobj.error_message: + if errors: + errors += "\n" + # end if + errors += pobj.error_message + # end if + else: + if errors: + errors += "\n" + # end if + errors += f"Metadata file, '{mfile}', not found." + # end if + # end for + if in_table: + # This is a malformed CCPP metadata file! + if errors: + errors += "\n" + # end if + errors += f"Malformed CCPP metadata file, '{mfile}'" + # end if + if errors: + raise CCPPError(f"{errors}") + else: + for ddt in ddt_names: + register_fortran_ddt_name(ddt) + # end for + # end if + return list(ddt_names) + +######################################################################## + class MetadataTable(): """Class to hold a CCPP Metadata table including the table header (ccpp-table-properties section) and all of the associated table @@ -285,6 +370,7 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, """ self.__pobj = parse_object self.__dependencies = dependencies + self.__module_name = module self.__relative_path = relative_path self.__sections = [] self.__run_env = run_env @@ -364,6 +450,7 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): in_properties_header = True skip_rest_of_section = False self.__dependencies = [] # Default is no dependencies + self.__module_name = self.__pobj.default_module_name() # Process lines until the end of the file or start of the next table. while ((curr_line is not None) and (not MetadataTable.table_start(curr_line))): @@ -376,8 +463,8 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): # Process the properties in this table header line for prop in _parse_config_line(curr_line, self.__pobj): # Manually parse name, type, and table properties - key = prop[0].strip().lower() - value = prop[1].strip() + key = prop[0].lower() + value = prop[1] if key == 'name': self.__table_name = value elif key == 'type': @@ -405,11 +492,13 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): if x.strip()] self.__dependencies.extend(depends) # end if + elif key == 'module_name': + self.__module_name = value elif key == 'relative_path': self.__relative_path = value else: tok_type = "metadata table start property" - self.__pobj.add_syntax_err(tok_type, token=value) + self.__pobj.add_syntax_err(tok_type, token=key) # end if # end for curr_line, _ = self.__pobj.next_line() @@ -419,6 +508,7 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): skip_rest_of_section = False section = MetadataSection(self.table_name, self.table_type, run_env, parse_object=self.__pobj, + module=self.__module_name, known_ddts=known_ddts, skip_ddt_check=skip_ddt_check) # Some table types only allow for one associated section @@ -450,7 +540,7 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): raise CCPPError(self.__pobj.error_message) # end if if self.table_type == "ddt": - known_ddts.append(self.table_name) + known_ddts.append(self.table_name.lower()) # end if if self.__dependencies is None: self.__dependencies = [] @@ -483,6 +573,11 @@ def dependencies(self): """Return the dependencies for this table""" return self.__dependencies + @property + def module_name(self): + """Return the module name for this metadata table""" + return self.__module_name + @property def relative_path(self): """Return the relative path for the table's dependencies""" @@ -518,87 +613,87 @@ def table_start(cls, line): class MetadataSection(ParseSource): """Class to hold all information from a metadata header >>> from framework_env import CCPPFrameworkEnv - >>> _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, {'host_files':'', \ - 'scheme_files':'', \ + >>> _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, {'host_files':'', \ + 'scheme_files':'', \ 'suites':''}) - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = footable", "type = scheme", "module = foo", \ - "[ im ]", "standard_name = horizontal_loop_extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foo", \ + parse_object=ParseObject("foobar.txt", \ + ["name = footable", "type = scheme", \ + "[ im ]", "standard_name = horizontal_loop_extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ "dimensions = () | intent = in"])) #doctest: +ELLIPSIS - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = footable", "type = scheme", "module = foobar", \ - "[ im ]", "standard_name = horizontal_loop_extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foobar", \ + parse_object=ParseObject("foobar.txt", \ + ["name = footable", "type = scheme", \ + "[ im ]", "standard_name = horizontal_loop_extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ "dimensions = () | intent = in"])).find_variable('horizontal_loop_extent') #doctest: +ELLIPSIS - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = footable", "type = scheme", "module = foobar", \ - "process = microphysics", "[ im ]", \ - "standard_name = horizontal_loop_extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foobar", \ + parse_object=ParseObject("foobar.txt", \ + ["name = footable", "type = scheme", \ + "process = microphysics", "[ im ]", \ + "standard_name = horizontal_loop_extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ "dimensions = () | intent = in"])).find_variable('horizontal_loop_extent') #doctest: +ELLIPSIS - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = footable", "type=scheme", "module = foo", \ - "[ im ]", "standard_name = horizontal_loop_extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ - "dimensions = () | intent = in", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foo", \ + parse_object=ParseObject("foobar.txt", \ + ["name = footable", "type=scheme", \ + "[ im ]", "standard_name = horizontal_loop_extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ + "dimensions = () | intent = in", \ " subroutine foo()"])).find_variable('horizontal_loop_extent') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): parse_source.ParseSyntaxError: Invalid variable property syntax, 'subroutine foo()', at foobar.txt:9 - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = footable", "type = scheme", "module=foobar", \ - "[ im ]", "standard_name = horizontal_loop_extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ - "dimensions = () | intent = in", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foobar", \ + parse_object=ParseObject("foobar.txt", \ + ["name = footable", "type = scheme", \ + "[ im ]", "standard_name = horizontal_loop_extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ + "dimensions = () | intent = in", \ ""], line_start=0)).find_variable('horizontal_loop_extent').get_prop_value('local_name') 'im' - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = footable", "type = scheme" \ - "[ im ]", "standard_name = horizontalloop extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ - "dimensions = () | intent = in", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foo", \ + parse_object=ParseObject("foobar.txt", \ + ["name = footable", "type = scheme" \ + "[ im ]", "standard_name = horizontalloop extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ + "dimensions = () | intent = in", \ ""], line_start=0)).find_variable('horizontal_loop_extent') - >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["[ccpp-arg-table]", "name = foobar", "type = scheme" \ - "[ im ]", "standard_name = horizontal loop extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ - "dimensions = () | intent = in", \ + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, module="foo", \ + parse_object=ParseObject("foobar.txt", \ + ["[ccpp-arg-table]", "name = foobar", "type = scheme" \ + "[ im ]", "standard_name = horizontal loop extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ + "dimensions = () | intent = in", \ ""], line_start=0)).find_variable('horizontal_loop_extent') - >>> MetadataSection("foobar", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = foobar", "module = foo" \ - "[ im ]", "standard_name = horizontal loop extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ - "dimensions = () | intent = in", \ + >>> MetadataSection("foobar", "scheme", _DUMMY_RUN_ENV, module="foo", \ + parse_object=ParseObject("foobar.txt", \ + ["name = foobar" \ + "[ im ]", "standard_name = horizontal loop extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ + "dimensions = () | intent = in", \ ""], line_start=0)).find_variable('horizontal_loop_extent') - >>> MetadataSection("foobar", "scheme", _DUMMY_RUN_ENV, \ - parse_object=ParseObject("foobar.txt", \ - ["name = foobar", "foo = bar" \ - "[ im ]", "standard_name = horizontal loop extent", \ - "long_name = horizontal loop extent, start at 1", \ - "units = index | type = integer", \ - "dimensions = () | intent = in", \ + >>> MetadataSection("foobar", "scheme", _DUMMY_RUN_ENV, module="foo", \ + parse_object=ParseObject("foobar.txt", \ + ["name = foobar", "foo = bar" \ + "[ im ]", "standard_name = horizontal loop extent", \ + "long_name = horizontal loop extent, start at 1", \ + "units = index | type = integer", \ + "dimensions = () | intent = in", \ ""], line_start=0)).find_variable('horizontal_loop_extent') >>> MetadataSection.header_start('[ ccpp-arg-table ]') @@ -652,7 +747,7 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, self.__variables = None # In case __init__ crashes self.__section_title = None self.__header_type = None - self.__module_name = None + self.__module_name = module self.__process_type = UNKNOWN_PROCESS_TYPE self.__section_valid = True self.__run_env = run_env @@ -682,9 +777,7 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, if mismatch: raise CCPPError(mismatch) # end if - if module is not None: - self.__module_name = module - else: + if module is None: perr = "MetadataSection requires a module name" self.__pobj.add_syntax_err(perr) self.__section_valid = False @@ -706,8 +799,8 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) - self.__init_from_file(table_name, table_type, known_ddts, run_env, - skip_ddt_check=skip_ddt_check) + self.__init_from_file(table_name, table_type, known_ddts, self.module, + run_env, skip_ddt_check=skip_ddt_check) # end if # Register this header if it is a DDT if self.header_type == 'ddt': @@ -722,35 +815,21 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, # end if # end for - def _default_module(self): - """Set a default module for this header""" - mfile = self.__pobj.filename - if mfile[-5:] == '.meta': - # Default value is a Fortran module that matches the filename - def_mod = os.path.basename(mfile)[:-5] - else: - def_mod = os.path.basename(mfile) - last_dot = def_mod.rfind('.') - if last_dot >= 0: - ldef = len(def_mod) - def_mod = def_mod[:last_dot-ldef] - # end if - # end if - return def_mod - - def __init_from_file(self, table_name, table_type, known_ddts, run_env, skip_ddt_check=False): + def __init_from_file(self, table_name, table_type, known_ddts, module_name, + run_env, skip_ddt_check=False): """ Read the section preamble, assume the caller already figured out the first line of the header using the header_start method.""" start_ctx = context_string(self.__pobj) curr_line, _ = self.__pobj.next_line() # Skip past [ccpp-arg-table] + self.__module_name = module_name while ((curr_line is not None) and (not MetadataSection.variable_start(curr_line, self.__pobj)) and (not MetadataSection.header_start(curr_line)) and (not MetadataTable.table_start(curr_line))): for prop in _parse_config_line(curr_line, self.__pobj): # Manually parse name, type, and module properties - key = prop[0].strip().lower() - value = prop[1].strip() + key = prop[0].lower() + value = prop[1] if key == 'name': self.__section_title = value elif key == 'type': @@ -765,19 +844,11 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env, skip_ddt # end if # Set value even if error so future error msgs make sense self.__header_type = value - elif key == 'module': - if value != "None": - self.__module_name = value - else: - self.__pobj.add_syntax_err("metadata table, no module") - self.__module_name = 'INVALID' # Allow error continue - self.__section_valid = False - # end if elif key == 'process': self.__process_type = value else: self.__pobj.add_syntax_err("metadata table start property", - token=value) + token=key) self.__process_type = 'INVALID' # Allow error continue self.__section_valid = False # end if @@ -811,11 +882,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env, skip_ddt self.title, start_ctx)) # end if if self.header_type == "ddt": - known_ddts.append(self.title) - # end if - # We need a default module if none was listed - if self.module is None: - self.__module_name = self._default_module() + known_ddts.append(self.title.lower()) # end if # Initialize our ParseSource parent super().__init__(self.title, self.header_type, self.__pobj) @@ -854,7 +921,7 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): (not MetadataTable.table_start(curr_line))) if valid_line: # variable_start handles exception - local_name = MetadataSection.variable_start(curr_line, self.__pobj) + local_name = MetadataSection.variable_start(curr_line, self.__pobj).lower() else: local_name = None # end if @@ -883,15 +950,15 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): if valid_line: properties = _parse_config_line(curr_line, self.__pobj) for prop in properties: - pname = prop[0].strip().lower() - pval_str = prop[1].strip() + pname = prop[0].lower() + pval_str = prop[1] if ((pname == 'type') and (not check_fortran_intrinsic(pval_str, error=False))): - if skip_ddt_check or pval_str in known_ddts: + if skip_ddt_check or pval_str.lower() in known_ddts: if skip_ddt_check: register_fortran_ddt_name(pval_str) # end if - pval = pval_str + pval = pval_str.lower() pname = 'ddt_type' else: errmsg = "Unknown DDT type, {}".format(pval_str) @@ -926,13 +993,35 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): pval = [] for dim in porig: if ':' in dim: - pval.append(dim) + for dim2 in dim.split(':'): + dim_ok = VarDictionary.loop_var_okay(standard_name=dim2, + is_run_phase=self.__section_title.endswith("_run")) + if not dim_ok: + emsg = "horizontal dimension" + self.__pobj.add_syntax_err(emsg, token=dim2) + self.__section_valid = False + var_ok = False + # end if + # end for + pval.append(dim.lower()) else: + dim_ok = VarDictionary.loop_var_okay(standard_name=dim, + is_run_phase=self.__section_title.endswith("_run")) + if not dim_ok: + emsg = "horizontal dimension" + self.__pobj.add_syntax_err(emsg, token=dim) + self.__section_valid = False + var_ok = False + # end if cone_str = 'ccpp_constant_one:{}' - pval.append(cone_str.format(dim)) + pval.append(cone_str.format(dim.lower())) # end if # end for # end if + # Special handling for standard_names (convert to lowercase) + if pname == 'standard_name': + pval = pval.lower() + # end if # Add the property to our Var dictionary var_props[pname] = pval # end if diff --git a/scripts/metavar.py b/scripts/metavar.py index 35878bc4..8f283df6 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1677,17 +1677,40 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, context=newvar.context) # end if # end if + # Check if local_name exists in Group. If applicable, Create new + # variable with unique name. There are two instances when new names are + # created: + # - Same used in different DDTs. + # - Different using the same in a Group. + # During the Group analyze phase, is True. lname = newvar.get_prop_value('local_name') lvar = self.find_local_name(lname) if lvar is not None: + # Check if is part of a different DDT than . + # The API uses the full variable references when calling the Group Caps, + # and . + # Within the context of a full reference, it is allowable for local_names + # to be the same in different data containers. + newvar_callstr = newvar.call_string(self) + lvar_callstr = lvar.call_string(self) + if newvar_callstr and lvar_callstr: + if newvar_callstr != lvar_callstr: + if not gen_unique: + exists_ok = True + # end if + # end if + # end if if gen_unique: new_lname = self.new_internal_variable_name(prefix=lname) newvar = newvar.clone(new_lname) + # Local_name needs to be the local_name for the new + # internal variable, otherwise multiple instances of the same + # local_name in the Group cap will all be overwritten with the + # same local_name + lname = new_lname elif not exists_ok: - errstr = 'Invalid local_name: {} already registered{}' - cstr = context_string(lvar.source.context, with_comma=True) - raise ParseSyntaxError(errstr.format(lname, cstr), - context=newvar.source.context) + errstr = f"Invalid local_name: {lname} already registered" + raise ParseSyntaxError(errstr, context=newvar.source.context) # end if (no else, things are okay) # end if (no else, things are okay) # Check if this variable has a parent (i.e., it is an array reference) diff --git a/scripts/mkstatic.py b/scripts/mkstatic.py index 618d3a86..e33164a3 100755 --- a/scripts/mkstatic.py +++ b/scripts/mkstatic.py @@ -13,6 +13,7 @@ import xml.etree.ElementTree as ET from common import encode_container +from common import lowercase_keys, lowercase_xml from common import CCPP_STAGES from common import CCPP_T_INSTANCE_VARIABLE, CCPP_ERROR_CODE_VARIABLE, CCPP_ERROR_MSG_VARIABLE, CCPP_LOOP_COUNTER, CCPP_LOOP_EXTENT from common import CCPP_BLOCK_NUMBER, CCPP_BLOCK_COUNT, CCPP_BLOCK_SIZES, CCPP_THREAD_NUMBER, CCPP_THREAD_COUNT, CCPP_INTERNAL_VARIABLES @@ -291,6 +292,24 @@ class API(object): public :: {subroutines} contains + + ! Necessary to convert incoming suite and group names to lowercase + function to_lower(str) result(lower) + implicit none + character(len=*), intent(in) :: str + character(len=len(str)) :: lower + integer, parameter :: upper_to_lower = ichar('a') - ichar('A') + integer :: i, ichar_val + + do i = 1, len(str) + ichar_val = ichar(str(i:i)) + if (ichar_val >= ichar('A') .and. ichar_val <= ichar('Z')) then + lower(i:i) = char(ichar_val + upper_to_lower) + else + lower(i:i) = str(i:i) + end if + end do + end function to_lower ''' sub = ''' @@ -310,7 +329,7 @@ class API(object): {suite_switch} else - write({ccpp_var_name}%errmsg,'(*(a))') 'Invalid suite ' // trim(suite_name) + write({ccpp_var_name}%errmsg,'(*(a))') 'Invalid suite ' // to_lower(trim(suite_name)) ierr = 1 end if @@ -436,7 +455,7 @@ def write(self): clause = 'else if' argument_list_group = create_argument_list_wrapped_explicit(group.arguments[ccpp_stage]) group_calls += ''' - {clause} (trim(group_name)=="{group_name}") then + {clause} (to_lower(trim(group_name))=="{group_name}") then ierr = {suite_name}_{group_name}_{stage}_cap({arguments})'''.format(clause=clause, suite_name=group.suite, group_name=group.name, @@ -444,7 +463,7 @@ def write(self): arguments=argument_list_group) group_calls += ''' else - write({ccpp_var_name}%errmsg, '(*(a))') 'Group ' // trim(group_name) // ' not found' + write({ccpp_var_name}%errmsg, '(*(a))') 'Group ' // to_lower(trim(group_name)) // ' not found' ierr = 1 end if '''.format(ccpp_var_name=ccpp_var.local_name, group_name=group.name) @@ -463,7 +482,7 @@ def write(self): else: clause = 'else if' suite_switch += ''' - {clause} (trim(suite_name)=="{suite_name}") then + {clause} (to_lower(trim(suite_name))=="{suite_name}") then if (present(group_name)) then {group_calls} @@ -690,19 +709,8 @@ def parse(self, make_call_tree=False): return success tree = ET.parse(self._sdf_name) - suite_xml = tree.getroot() + suite_xml = lowercase_xml(tree.getroot()) self._name = suite_xml.get('name') - # Validate name of suite in XML tag against filename; could be moved to common.py - if not (os.path.basename(self._sdf_name) == '{}.xml'.format(self._name)): - if (os.path.basename(self._sdf_name) == 'suite_{}.xml'.format(self._name)): - logging.debug("Parsing suite using legacy naming convention") - logging.debug(f"Filename {os.path.basename(self._sdf_name)}") - logging.debug(f"Suite name {format(self._name)}") - else: - logging.critical("Invalid suite name {0} in suite definition file {1}.".format( - self._name, self._sdf_name)) - success = False - return success # Check if suite name is too long if len(self._name) > SUITE_NAME_MAX_CHARS: @@ -726,15 +734,15 @@ def parse(self, make_call_tree=False): self._call_tree[group_xml.attrib['name']] = [] # Add suite-wide init scheme to group 'init', similar for finalize - if group_xml.tag.lower() == 'init' or group_xml.tag.lower() == 'finalize': + if group_xml.tag == 'init' or group_xml.tag == 'finalize': self._all_schemes_called.append(group_xml.text) - self._all_subroutines_called.append(group_xml.text + '_' + group_xml.tag.lower()) + self._all_subroutines_called.append(group_xml.text + '_' + group_xml.tag) schemes = [group_xml.text] subcycles.append(Subcycle(loop=1, schemes=schemes)) - if group_xml.tag.lower() == 'init': - self._groups.append(Group(name=group_xml.tag.lower(), subcycles=subcycles, suite=self._name, init=True)) - elif group_xml.tag.lower() == 'finalize': - self._groups.append(Group(name=group_xml.tag.lower(), subcycles=subcycles, suite=self._name, finalize=True)) + if group_xml.tag == 'init': + self._groups.append(Group(name=group_xml.tag, subcycles=subcycles, suite=self._name, init=True)) + elif group_xml.tag == 'finalize': + self._groups.append(Group(name=group_xml.tag, subcycles=subcycles, suite=self._name, finalize=True)) continue # Parse subcycles of all regular groups @@ -761,7 +769,6 @@ def parse(self, make_call_tree=False): self._all_schemes_called = list(set(self._all_schemes_called)) self._all_subroutines_called = list(set(self._all_subroutines_called)) - return success def print_debug(self): @@ -1058,10 +1065,13 @@ def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, "_"+key, value) - def write(self, metadata_request, metadata_define, arguments, debug): + def write(self, metadata_request, metadata_define, arguments_in, debug): """Create caps for all stages of this group. Add additional code for debugging if debug flag is True.""" + # First, convert all keys in arguments_in to lowercase (recursively) + arguments = lowercase_keys(arguments_in) + # Create an inverse lookup table of local variable names defined (by the host model) and standard names standard_name_by_local_name_define = collections.OrderedDict() for standard_name in metadata_define.keys(): @@ -1127,8 +1137,7 @@ def write(self, metadata_request, metadata_define, arguments, debug): # First, add a few mandatory variables to the list of required # variables. This is mostly for handling horizontal dimensions - # correctly for the different CCPP phases and for cases when - # blocked data structures or chunked arrays are used. + # correctly for the different CCPP phases and for chunked arrays additional_variables_required = [] if CCPP_HORIZONTAL_LOOP_EXTENT in metadata_define.keys(): for add_var in [ CCPP_CONSTANT_ONE, CCPP_HORIZONTAL_LOOP_EXTENT]: diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 309bbda0..bfd2cfbf 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -21,6 +21,7 @@ from parse_checkers import fortran_list_match from parse_checkers import registered_fortran_ddt_name from parse_checkers import register_fortran_ddt_name +from parse_checkers import registered_fortran_ddt_names from parse_checkers import check_units, check_dimensions, check_cf_standard_name from parse_checkers import check_default_value, check_valid_values, check_molar_mass from parse_log import init_log, set_log_level, flush_log @@ -65,9 +66,10 @@ 'ParseObject', 'PreprocStack', 'PrettyElementTree', - 'register_fortran_ddt_name', 'read_xml_file', + 'register_fortran_ddt_name', 'registered_fortran_ddt_name', + 'registered_fortran_ddt_names', 'reset_standard_name_counter', 'set_log_level', 'set_log_to_file', diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 469bf1d0..5aaaaeab 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -16,7 +16,8 @@ _NON_LEADING_ZERO_NUM = "[1-9]\d*" _CHAR_WITH_UNDERSCORE = "([a-zA-Z]+_[a-zA-Z]+)+" _NEGATIVE_NON_LEADING_ZERO_NUM = f"[-]{_NON_LEADING_ZERO_NUM}" -_UNIT_EXPONENT = f"({_NEGATIVE_NON_LEADING_ZERO_NUM}|{_NON_LEADING_ZERO_NUM})" +_POSITIVE_NON_LEADING_ZERO_NUM = f"[+]{_NON_LEADING_ZERO_NUM}" +_UNIT_EXPONENT = f"({_NEGATIVE_NON_LEADING_ZERO_NUM}|{_POSITIVE_NON_LEADING_ZERO_NUM}|{_NON_LEADING_ZERO_NUM})" _UNIT_REGEX = f"[a-zA-Z]+{_UNIT_EXPONENT}?" _UNITS_REGEX = f"^({_CHAR_WITH_UNDERSCORE}|{_UNIT_REGEX}(\s{_UNIT_REGEX})*|{_UNITLESS_REGEX})$" _UNITS_RE = re.compile(_UNITS_REGEX) @@ -29,6 +30,10 @@ def check_units(test_val, prop_dict, error): 'm s-1' >>> check_units('kg m-3', None, True) 'kg m-3' + >>> check_units('m2 s-2', None, True) + 'm2 s-2' + >>> check_units('m+2 s-2', None, True) + 'm+2 s-2' >>> check_units('1', None, True) '1' >>> check_units('', None, False) @@ -96,7 +101,13 @@ def check_dimensions(test_val, prop_dict, error, max_len=0): >>> check_dimensions("hi_mom", None, True) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: 'hi_mom' is invalid; not a list + >>> check_dimensions(["1:dim1", "dim2name"], None, True) #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + CCPPError: '1:dim1 is an invalid dimension name; integer dimension indices not supported + >>> check_dimensions(["ccpp_constant_one:1", "dim2name"], None, True) + ['ccpp_constant_one:1', 'dim2name'] """ + info_msg = None if not isinstance(test_val, list): if error: raise CCPPError("'{}' is invalid; not a list".format(test_val)) @@ -118,10 +129,24 @@ def check_dimensions(test_val, prop_dict, error, max_len=0): # end if # Check possible dim styles (a, a:b, a:, :b, :, ::, a:b:c, a::c) tdims = [x.strip() for x in isplit if len(x) > 0] + starts_at_one = False + if len(tdims) > 0 and tdims[0] == 'ccpp_constant_one': + starts_at_one = True + # end if + is_int = False for tdim in tdims: # Check numeric value first try: - valid = isinstance(int(tdim), int) + is_int = isinstance(int(tdim), int) + # Allow integer dimensions, but not indices + if is_int: + valid = starts_at_one or len(tdims) == 1 + if not valid: + info_msg = 'integer dimension indices not supported' + # end if + else: + valid = False + # end if except ValueError as ve: # Not an integer, try a Fortran ID valid = check_fortran_id(tdim, None, @@ -142,8 +167,12 @@ def check_dimensions(test_val, prop_dict, error, max_len=0): # End try if not valid: if error: - errmsg = "'{}' is an invalid dimension name" - raise CCPPError(errmsg.format(item)) + if info_msg: + errmsg = f"'{item}' is an invalid dimension name; {info_msg}" + else: + errmsg = f"'{item}' is an invalid dimension name" + # end if + raise CCPPError(errmsg) else: test_val = None # end if @@ -229,7 +258,7 @@ def check_cf_standard_name(test_val, prop_dict, error): FORTRAN_DP_RE = re.compile(r"(?i)double\s*precision") FORTRAN_TYPE_RE = re.compile(r"(?i)type\s*\(\s*("+FORTRAN_ID+r")\s*\)") -_REGISTERED_FORTRAN_DDT_NAMES = [] +_REGISTERED_FORTRAN_DDT_NAMES = ["ccpp_constituent_prop_ptr_t"] ######################################################################## diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 2ed0e5b8..1a4082cb 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -244,6 +244,10 @@ def __init__(self, linenum=None, filename=None, context=None): self.__linenum = linenum self.__filename = filename + def default_module_name(self): + """Return a default module for this file""" + return os.path.splitext(os.path.basename(self.filename))[0] + @property def line_num(self): """Return the current line""" diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 4fac98b8..7100d479 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -142,7 +142,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ raise CCPPError(errmsg.format(stdname, clnames)) # end if lname = dvar.get_prop_value('local_name') - # Optional variables in the caps are associated with + # Optional variables in the caps are associated with # local pointers of _ptr if dvar.get_prop_value('optional'): lname = dummy+'_ptr' @@ -1161,7 +1161,7 @@ def update_group_call_list_variable(self, var): gvar = None # end if if gvar is None: - my_group.add_call_list_variable(var) + my_group.add_call_list_variable(var, gen_unique=True) # end if # end if @@ -1219,7 +1219,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end if # We have a match, make sure var is in call list if new_dims == vdims: - self.add_call_list_variable(var, exists_ok=True) + self.add_call_list_variable(var, exists_ok=True, gen_unique=True) self.update_group_call_list_variable(var) else: subst_dict = {'dimensions':new_dims} @@ -1360,18 +1360,32 @@ def add_var_debug_check(self, var): if not ':' in dim: dim_var = self.find_variable(standard_name=dim) if not dim_var: - raise Exception(f"No dimension with standard name '{dim}'") - self.update_group_call_list_variable(dim_var) + # To allow for numerical dimensions in metadata. + if not dim.isnumeric(): + raise Exception(f"No dimension with standard name '{dim}'") + # end if + else: + self.update_group_call_list_variable(dim_var) + # end if else: (ldim, udim) = dim.split(":") ldim_var = self.find_variable(standard_name=ldim) if not ldim_var: - raise Exception(f"No dimension with standard name '{ldim}'") + # To allow for numerical dimensions in metadata. + if not ldim.isnumeric(): + raise Exception(f"No dimension with standard name '{ldim}'") + # end if + # end if self.update_group_call_list_variable(ldim_var) udim_var = self.find_variable(standard_name=udim) if not udim_var: - raise Exception(f"No dimension with standard name '{udim}'") - self.update_group_call_list_variable(udim_var) + # To allow for numerical dimensions in metadata. + if not udim.isnumeric(): + raise Exception(f"No dimension with standard name '{udim}'") + # end if + else: + self.update_group_call_list_variable(udim_var) + # end if # Add the variable to the list of variables to check. Record which internal_var to use. self.__var_debug_checks.append([var, internal_var]) @@ -1436,6 +1450,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er dimensions = var.get_dimensions() active = var.get_prop_value('active') allocatable = var.get_prop_value('allocatable') + vtype = var.get_prop_value('type') # Need the local name from the group call list, # from the locally-defined variables of the group, @@ -1461,7 +1476,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er local_name = dvar.get_prop_value('local_name') # If the variable is allocatable and the intent for the scheme is 'out', - # then we can't test anything because the scheme is going to allocate + # then we can't test anything because the scheme is going to allocate # the variable. We don't have this information earlier in # add_var_debug_check, therefore need to back out here, # using the information from the scheme variable (call list). @@ -1496,6 +1511,8 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er dim_strings = [] lbound_strings = [] ubound_strings = [] + dim_lengths = [] + local_names = [] for dim in dimensions: if not ':' in dim: # In capgen, any true dimension (that is not a single index) does @@ -1524,18 +1541,30 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er for var_dict in cldicts: dvar = var_dict.find_variable(standard_name=ldim, any_scope=False) if dvar is not None: + ldim_lname = dvar.get_prop_value('local_name') break if not dvar: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - ldim_lname = dvar.get_prop_value('local_name') + # To allow for numerical dimensions in metadata. + if ldim.isnumeric(): + ldim_lname = ldim + else: + raise Exception(f"No variable with standard name '{ldim}' in cldicts") + # endif + # endif # Get dimension for upper bound for var_dict in cldicts: dvar = var_dict.find_variable(standard_name=udim, any_scope=False) if dvar is not None: + udim_lname = dvar.get_prop_value('local_name') break if not dvar: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - udim_lname = dvar.get_prop_value('local_name') + # To allow for numerical dimensions in metadata. + if udim.isnumeric(): + udim_lname = udim + else: + raise Exception(f"No variable with standard name '{udim}' in cldicts") + # end if + # end if # Assemble dimensions and bounds for size checking dim_length = f'{udim_lname}-{ldim_lname}+1' dim_string = ":" @@ -1546,7 +1575,9 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er lbound_strings.append(lbound_string) ubound_strings.append(ubound_string) array_size = f'{array_size}*({dim_length})' - + dim_lengths.append(dim_length) + local_names.append(local_name) + # end for # Various strings needed to get the right size # and lower/upper bound of the array dim_string = '(' + ','.join(dim_strings) + ')' @@ -1554,38 +1585,70 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er ubound_string = '(' + ','.join(ubound_strings) + ')' # Write size check - tmp_indent = indent - if conditional != '.true.': - tmp_indent = indent + 1 - outfile.write(f"if {conditional} then", indent) - # end if - outfile.write(f"! Check size of array {local_name}", tmp_indent) - outfile.write(f"if (size({local_name}{dim_string}) /= {array_size}) then", tmp_indent) - outfile.write(f"write({errmsg}, '(a)') 'In group {self.__group.name} before {self.__subroutine_name}:'", tmp_indent+1) - outfile.write(f"write({errmsg}, '(2(a,i8))') 'for array {local_name}, expected size ', {array_size}, ' but got ', size({local_name})", tmp_indent+1) - outfile.write(f"{errcode} = 1", tmp_indent+1) - outfile.write(f"return", tmp_indent+1) - outfile.write(f"end if", tmp_indent) - if conditional != '.true.': - outfile.write(f"end if", indent) - # end if - outfile.write('',indent) - - # Assign lower/upper bounds to internal_var (scalar) if intent is not out - if not intent == 'out': - internal_var_lname = internal_var.get_prop_value('local_name') + # - Only for types int and real. + if (vtype == "integer") or (vtype == "real"): tmp_indent = indent if conditional != '.true.': tmp_indent = indent + 1 outfile.write(f"if {conditional} then", indent) # end if - outfile.write(f"! Assign lower/upper bounds of {local_name} to {internal_var_lname}", tmp_indent) - outfile.write(f"{internal_var_lname} = {local_name}{lbound_string}", tmp_indent) - outfile.write(f"{internal_var_lname} = {local_name}{ubound_string}", tmp_indent) + outfile.write(f"! Check size of array {local_name}", tmp_indent) + outfile.write(f"if (size({local_name}{dim_string}) /= {array_size}) then", tmp_indent) + outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before "\ + f"{self.__subroutine_name}: for array {local_name}, expected size ', "\ + f"{array_size}, ' but got ', size({local_name})", tmp_indent+1) + outfile.write(f"{errcode} = 1", tmp_indent+1) + outfile.write(f"return", tmp_indent+1) + outfile.write(f"end if", tmp_indent) if conditional != '.true.': outfile.write(f"end if", indent) # end if outfile.write('',indent) + # end if + + # Write size check for each dimension in array. + # - If intent is not out. + # - Only for types int and real. + if (vtype == "integer") or (vtype == "real"): + if not intent == 'out': + tmp_indent = indent + if conditional != '.true.': + tmp_indent = indent + 1 + outfile.write(f"if {conditional} then", indent) + # end if + ndims = len(dim_lengths) + + # Loop through dimensions in var and check if length of each dimension + # is the correct size. Skip for 1D variables. + if (ndims > 1): + for index, dim_length in enumerate(dim_lengths): + array_ref = '(' + # Dimension(s) before current rank to be checked. + array_ref += '1,'*(index) + # Dimension to check. + array_ref += dim_strings[index] + # Dimension(s) after current rank to be checked. + array_ref += ',1'*(ndims-(index+1)) + array_ref += ')' + # + outfile.write(f"! Check length of {local_names[index]}{array_ref}", tmp_indent) + outfile.write(f"if (size({local_names[index]}{array_ref}) /= {dim_length}) then ", \ + tmp_indent) + outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before " \ + f"{self.__subroutine_name}: for array {local_names[index]}{array_ref}, "\ + f"expected size ', {dim_length}, ' but got ', " \ + f"size({local_names[index]}{array_ref})", tmp_indent+1) + outfile.write(f"{errcode} = 1", tmp_indent+1) + outfile.write(f"return", tmp_indent+1) + outfile.write(f"end if", tmp_indent) + # end for + #end if + if conditional != '.true.': + outfile.write(f"end if", indent) + # end if + outfile.write('',indent) + # endif + # end if def associate_optional_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): """Write local pointer association for optional variables.""" @@ -1794,11 +1857,11 @@ def write(self, outfile, errcode, errmsg, indent): # if self.__optional_vars: outfile.write('! Associate conditional variables', indent+1) - # end if + # end if for (dict_var, var, var_ptr, has_transform) in self.__optional_vars: tstmt = self.associate_optional_var(dict_var, var, var_ptr, has_transform, cldicts, indent+1, outfile) # end for - # + # # Write the scheme call. # if self._has_run_phase: @@ -1813,7 +1876,7 @@ def write(self, outfile, errcode, errmsg, indent): # first_ptr_declaration=True for (dict_var, var, var_ptr, has_transform) in self.__optional_vars: - if first_ptr_declaration: + if first_ptr_declaration: outfile.write('! Copy any local pointers to dummy/local variables', indent+1) first_ptr_declaration=False # end if @@ -1977,23 +2040,29 @@ class Subcycle(SuiteObject): """Class to represent a subcycled group of schemes or scheme collections""" def __init__(self, sub_xml, context, parent, run_env): - name = sub_xml.get('name', None) # Iteration count - loop_extent = sub_xml.get('loop', "1") # Number of iterations + self._loop_extent = sub_xml.get('loop', "1") # Number of iterations + self._loop = None # See if our loop variable is an interger or a variable try: - loop_int = int(loop_extent) # pylint: disable=unused-variable - self._loop = loop_extent + _ = int(self._loop_extent) + self._loop = self._loop_extent self._loop_var_int = True + name = f"loop{self._loop}" + super().__init__(name, context, parent, run_env, active_call_list=False) except ValueError: self._loop_var_int = False - lvar = parent.find_variable(standard_name=self.loop, any_scope=True) + lvar = parent.find_variable(standard_name=self._loop_extent, any_scope=True) if lvar is None: - emsg = "Subcycle, {}, specifies {} iterations but {} not found" - raise CCPPError(emsg.format(name, self.loop, self.loop)) + emsg = "Subcycle, {}, specifies {} iterations, variable not found" + raise CCPPError(emsg.format(name, self._loop_extent)) + else: + self._loop_var_int = False + self._loop = lvar.get_prop_value('local_name') # end if + name = f"loop_{self._loop_extent}"[0:63] + super().__init__(name, context, parent, run_env, active_call_list=True) parent.add_call_list_variable(lvar) # end try - super().__init__(name, context, parent, run_env) for item in sub_xml: new_item = new_suite_object(item, context, self, run_env) self.add_part(new_item) @@ -2004,12 +2073,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): if self.name is None: self.name = "subcycle_index{}".format(level) # end if - # Create a variable for the loop index - self.add_variable(Var({'local_name':self.name, - 'standard_name':'loop_variable', - 'type':'integer', 'units':'count', - 'dimensions':'()'}, _API_SOURCE, self.run_env), - self.run_env) + # Create a Group variable for the subcycle index. + newvar = Var({'local_name':self.name, 'standard_name':self.name, + 'type':'integer', 'units':'count', 'dimensions':'()'}, + _API_LOCAL, self.run_env) + group.manage_variable(newvar) # Handle all the suite objects inside of this subcycle scheme_mods = set() for item in self.parts: @@ -2023,7 +2091,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): def write(self, outfile, errcode, errmsg, indent): """Write code for the subcycle loop, including contents, to """ - outfile.write('do {} = 1, {}'.format(self.name, self.loop), indent) + outfile.write('do {} = 1, {}'.format(self.name, self._loop), indent) # Note that 'scheme' may be a sybcycle or other construct for item in self.parts: item.write(outfile, errcode, errmsg, indent+1) @@ -2033,13 +2101,7 @@ def write(self, outfile, errcode, errmsg, indent): @property def loop(self): """Return the loop value or variable local_name""" - lvar = self.find_variable(standard_name=self.loop, any_scope=True) - if lvar is None: - emsg = "Subcycle, {}, specifies {} iterations but {} not found" - raise CCPPError(emsg.format(self.name, self.loop, self.loop)) - # end if - lname = lvar.get_prop_value('local_name') - return lname + return self._loop ############################################################################### @@ -2273,7 +2335,7 @@ def manage_variable(self, newvar): ParseSource(_API_SOURCE_NAME, _API_LOCAL_VAR_NAME, newvar.context), self.run_env) - self.add_variable(local_var, self.run_env, exists_ok=True) + self.add_variable(local_var, self.run_env, exists_ok=True, gen_unique=True) # Finally, make sure all dimensions are accounted for emsg = self.add_variable_dimensions(local_var, _API_LOCAL_VAR_TYPES, adjust_intent=True, @@ -2408,8 +2470,8 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if # end if # end for - # All optional dummy variables within group need to have - # an associated pointer array declared. + # All optional dummy variables within group need to have + # an associated pointer array declared. for cvar in self.call_list.variable_list(): opt_var = cvar.get_prop_value('optional') if opt_var: @@ -2483,12 +2545,11 @@ def write(self, outfile, host_arglist, indent, const_mod, # end for # Look for any DDT types call_vars = self.call_list.variable_list() - self._ddt_library.write_ddt_use_statements(call_vars, outfile, - indent+1, pad=modmax) - decl_vars = ([x[0] for x in subpart_allocate_vars.values()] + + all_vars = ([x[0] for x in subpart_allocate_vars.values()] + [x[0] for x in subpart_scalar_vars.values()] + [x[0] for x in subpart_optional_vars.values()]) - self._ddt_library.write_ddt_use_statements(decl_vars, outfile, + all_vars.extend(call_vars) + self._ddt_library.write_ddt_use_statements(all_vars, outfile, indent+1, pad=modmax) outfile.write('', 0) # Write out dummy arguments diff --git a/scripts/var_props.py b/scripts/var_props.py index fcbed74c..a53e8b68 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -852,6 +852,20 @@ class VarCompatObj: "var_stdname", "real", "kind_phys", "km",['horizontal_dimension', 'vertical_layer_dimension'], "var2_lname", True, \ _DOCTEST_RUNENV).reverse_transform("var1_lname", "var2_lname", ('i','k'), ('i','nk-k+1')) 'var1_lname(i,nk-k+1) = 1.0E+3_kind_phys*var2_lname(i,k)' + + # Test that a 2-D var with equivalent units works and that it + # skips any unit transformations + >>> VarCompatObj("var_stdname", "real", "kind_phys", "m2 s-2", ['horizontal_dimension'], "var1_lname", False, \ + "var_stdname", "real", "kind_phys", "J kg-1", ['horizontal_dimension'], "var2_lname", False, \ + _DOCTEST_RUNENV).forward_transform("var1_lname", "var2_lname", 'i', 'i') + 'var1_lname(i) = var2_lname(i)' + + # Test that a 2-D var with identical units works and that it + # skips any unit transformations + >>> VarCompatObj("var_stdname", "real", "kind_phys", "m2 s-2", ['horizontal_dimension'], "var1_lname", False, \ + "var_stdname", "real", "kind_phys", "m+2 s-2", ['horizontal_dimension'], "var2_lname", False, \ + _DOCTEST_RUNENV).forward_transform("var1_lname", "var2_lname", 'i', 'i') + 'var1_lname(i) = var2_lname(i)' """ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, @@ -960,20 +974,27 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, # A tendency variable's units should be " s-1" tendency_split_units = var1_units.split('s-1')[0].strip() if tendency_split_units != var2_units: - # We don't currently support unit conversions for tendency variables - emsg = f"\nMismatch tendency variable units '{var1_units}'" - emsg += f" for variable '{var1_stdname}'." - emsg += " No variable transforms supported for tendencies." - emsg += f" Tendency units should be '{var2_units} s-1' to match state variable." - self.__equiv = False - self.__compat = False - incompat_reason.append(emsg) + # We don't currently support unit conversions for tendency variables, + # but we can check if the units are identical or equivalent + unit_transforms = self._get_unit_convstrs(tendency_split_units, + var2_units) + if not unit_transforms == (None, None): + emsg = f"\nMismatch tendency variable units '{var1_units}'" + emsg += f" for variable '{var1_stdname}'." + emsg += " No variable transforms supported for tendencies." + emsg += f" Tendency units should be '{var2_units} s-1' to match state variable." + self.__equiv = False + self.__compat = False + incompat_reason.append(emsg) # end if elif var1_units != var2_units: # Try to find a set of unit conversions - self.__equiv = False - self.__unit_transforms = self._get_unit_convstrs(var1_units, - var2_units) + unit_transforms = self._get_unit_convstrs(var1_units, + var2_units) + # Handle equivalent or identical units = (None, None) + if not unit_transforms == (None, None): + self.__equiv = False + self.__unit_transforms = unit_transforms # end if # end if if self.__compat: @@ -1148,7 +1169,8 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): def _get_unit_convstrs(self, var1_units, var2_units): """Attempt to retrieve the forward and reverse unit transformations for transforming a variable in to / from a variable in - . + . Return (None, None) if units are equivalent or identical + after parsing (this can happen when comparing m2 and m+2). # Initial setup >>> from parse_tools import init_log, set_log_to_null @@ -1177,6 +1199,16 @@ def _get_unit_convstrs(self, var1_units, var2_units): ('1.0E+3{kind}*{var}', '1.0E-3{kind}*{var}') >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'K') ('{var}+273.15{kind}', '{var}-273.15{kind}') + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('V A', 'W') + (None, None) + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('N m-2', 'Pa') + (None, None) + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m2 s-2', 'J kg-1') + (None, None) + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m+2 s-2', 'J kg-1') + (None, None) + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m+2 s-2', 'm2 s-2') + (None, None) # Try an invalid conversion >>> _DOCTEST_VCOMPAT._get_unit_convstrs('1', 'none') #doctest: +ELLIPSIS @@ -1192,6 +1224,11 @@ def _get_unit_convstrs(self, var1_units, var2_units): """ u1_str = self.units_to_string(var1_units, self.__v1_context) u2_str = self.units_to_string(var2_units, self.__v2_context) + # If u1_str and u2_str are identical, for example after parsing + # "m2 s-2" and "m+2 s-2", return (None, None) to signal that + # the units are in fact identical + if u1_str == u2_str: + return (None, None) unit_conv_str = "{0}__to__{1}".format(u1_str, u2_str) try: forward_transform = getattr(unit_conversion, unit_conv_str)() @@ -1210,7 +1247,11 @@ def _get_unit_convstrs(self, var1_units, var2_units): self.__stdname, context=self.__v1_context)) # end if - return (forward_transform, reverse_transform) + # For equivalent units, return (None, None) + if forward_transform == '{var}' and reverse_transform == '{var}': + return (None, None) + else: + return (forward_transform, reverse_transform) def _get_dim_transforms(self, var1_dims, var2_dims): """Attempt to find forward and reverse permutations for transforming a @@ -1355,8 +1396,17 @@ def units_to_string(self, units, context=None): """Replace variable unit description with string that is a legal Python identifier. If the resulting string is a Python keyword, raise an exception.""" - # Replace each whitespace with an underscore - string = units.replace(" ","_") + # Start with breaking up the string by spaces + items = units.split() + # Identify units with positive exponents + # without a plus sign (m2 instead of m+2). + pattern = re.compile(r"([a-zA-Z]+)([0-9]+)") + for index, item in enumerate(items): + match = pattern.match(item) + if match: + items[index] = "+".join(match.groups()) + # Combine list into string using underscores + string = "_".join(items) # Replace each minus sign with '_minus_' string = string.replace("-","_minus_") # Replace each plus sign with '_plus_' @@ -1458,7 +1508,7 @@ def has_unit_transforms(self): and arguments to produce code to transform one variable into the correct units of the other. """ - return self.__unit_transforms is not None + return self.__unit_transforms is not None and self.__unit_transforms[0] def __bool__(self): """Return True if this object describes two Var objects which are diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ff78a81..a7428a77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,9 +12,12 @@ set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_I # Define the executable and what to link add_library(ccpp_framework STATIC ${SOURCES_F90}) target_link_libraries(ccpp_framework PUBLIC MPI::MPI_Fortran) -set_target_properties(ccpp_framework PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} - LINK_FLAGS ${CMAKE_Fortran_FLAGS}) +if(OPENMP) + target_link_libraries(ccpp_framework PUBLIC OpenMP::OpenMP_Fortran) +endif() +set_target_properties(ccpp_framework PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}) #------------------------------------------------------------------------------ # Installation diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 16019571..a08291b6 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -23,7 +23,7 @@ module ccpp_constituent_prop_mod integer, parameter :: mass_mixing_ratio = -5 integer, parameter :: volume_mixing_ratio = -6 integer, parameter :: number_concentration = -7 - integer, parameter :: int_unassigned = -HUGE(1) + integer, public, parameter :: int_unassigned = -HUGE(1) real(kind_phys), parameter :: kphys_unassigned = HUGE(1.0_kind_phys) !! \section arg_table_ccpp_constituent_properties_t diff --git a/src/ccpp_scheme_utils.F90 b/src/ccpp_scheme_utils.F90 new file mode 100644 index 00000000..bb3a4d41 --- /dev/null +++ b/src/ccpp_scheme_utils.F90 @@ -0,0 +1,121 @@ +module ccpp_scheme_utils + + ! Module of utilities available to CCPP schemes + + use ccpp_constituent_prop_mod, only: ccpp_model_constituents_t, int_unassigned + + implicit none + private + + !! Public interfaces + public :: ccpp_initialize_constituent_ptr ! Used by framework to initialize + public :: ccpp_constituent_index ! Lookup index constituent by name + public :: ccpp_constituent_indices ! Lookup indices of consitutents by name + + !! Private module variables & interfaces + + ! initialized set to .true. once hash table pointer is initialized + logical :: initialized = .false. + type(ccpp_model_constituents_t), pointer :: constituent_obj => NULL() + + private :: check_initialization + private :: status_ok + +contains + + subroutine check_initialization(caller, errcode, errmsg) + ! Dummy arguments + character(len=*), intent(in) :: caller + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + if (initialized) then + if (present(errcode)) then + errcode = 0 + end if + if (present(errmsg)) then + errmsg = '' + end if + else + if (present(errcode)) then + errcode = 1 + end if + if (present(errmsg)) then + errmsg = trim(caller)//' FAILED, module not initialized' + end if + end if + end subroutine check_initialization + + logical function status_ok(errcode) + ! Dummy argument + integer, optional, intent(in) :: errcode + + if (present(errcode)) then + status_ok = (errcode == 0) .and. initialized + else + status_ok = initialized + end if + + end function status_ok + + subroutine ccpp_initialize_constituent_ptr(const_obj) + ! Dummy arguments + type(ccpp_model_constituents_t), pointer, intent(in) :: const_obj + + if (.not. initialized) then + constituent_obj => const_obj + initialized = .true. + end if + end subroutine ccpp_initialize_constituent_ptr + + subroutine ccpp_constituent_index(standard_name, const_index, errcode, errmsg) + ! Dummy arguments + character(len=*), intent(in) :: standard_name + integer, intent(out) :: const_index + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + ! Local variable + character(len=*), parameter :: subname = 'ccpp_constituent_index' + + call check_initialization(caller=subname, errcode=errcode, errmsg=errmsg) + if (status_ok(errcode)) then + call constituent_obj%const_index(const_index, standard_name, & + errcode, errmsg) + else + const_index = int_unassigned + end if + end subroutine ccpp_constituent_index + + subroutine ccpp_constituent_indices(standard_names, const_inds, errcode, errmsg) + ! Dummy arguments + character(len=*), intent(in) :: standard_names(:) + integer, intent(out) :: const_inds(:) + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + ! Local variables + integer :: indx + character(len=*), parameter :: subname = 'ccpp_constituent_indices' + + const_inds = int_unassigned + call check_initialization(caller=subname, errcode=errcode, errmsg=errmsg) + if (status_ok(errcode)) then + if (size(const_inds) < size(standard_names)) then + errcode = 1 + write(errmsg, '(3a)') subname, ": const_inds array too small. ", & + "Must be greater than or equal to the size of standard_names" + else + do indx = 1, size(standard_names) + ! For each std name in , find the const. index + call constituent_obj%const_index(const_inds(indx), & + standard_names(indx), errcode, errmsg) + if (errcode /= 0) then + exit + end if + end do + end if + end if + end subroutine ccpp_constituent_indices + +end module ccpp_scheme_utils diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..5666599d --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,14 @@ +add_subdirectory(utils) + +if(CCPP_FRAMEWORK_ENABLE_TESTS OR CCPP_RUN_ADVECTION_TEST) + add_subdirectory(advection_test) +endif() +if(CCPP_FRAMEWORK_ENABLE_TESTS OR CCPP_RUN_CAPGEN_TEST) + add_subdirectory(capgen_test) +endif() +if(CCPP_FRAMEWORK_ENABLE_TESTS OR CCPP_RUN_DDT_HOST_TEST) + add_subdirectory(ddthost_test) +endif() +if(CCPP_FRAMEWORK_ENABLE_TESTS OR CCPP_RUN_VAR_COMPATIBILITY_TEST) + add_subdirectory(var_compatibility_test) +endif() diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..9b8d808c --- /dev/null +++ b/test/README.md @@ -0,0 +1,75 @@ +# Testing + +## Unit tests +To run the Python based unit tests, see the associated documentation in the `unit_tests` directory. + +## Doc tests +The Python source code has a wide range of doctests that can be used to verify implementation details quickly. To run the Python based doc tests, run: +```bash +$ export PYTHONPATH=/scripts:/scripts/parse_tools +$ pytest -v /scripts/ --doctest-modules +``` + +## Regression tests +The run the regression tests with mock host models, build the main project with your test option: + +```bash +$ cmake -S -B ... +$ cd +$ make +$ ctest +``` + +For example, to run all of the regression tests from the root of the project, you can use: +```bash +cmake -B./build -S./ -DCCPP_FRAMEWORK_ENABLE_TESTS=ON +``` + +Currently (as of July 2025), if everything works as expected, you should see something like: +``` +Test project + Start 1: ctest_advection_host_integration +1/4 Test #1: ctest_advection_host_integration ........... Passed 0.01 sec + Start 2: ctest_capgen_host_integration +2/4 Test #2: ctest_capgen_host_integration .............. Passed 0.01 sec + Start 3: ctest_ddt_host_integration +3/4 Test #3: ctest_ddt_host_integration ................. Passed 0.01 sec + Start 4: ctest_var_compatibility_host_integration +4/4 Test #4: ctest_var_compatibility_host_integration ... Passed 0.02 sec + +100% tests passed, 0 tests failed out of 4 + +Total Test time (real) = 0.06 sec +``` + +There are several `...` to enable tests: + +1) `-DCCPP_FRAMEWORK_ENABLE_TESTS=ON` Turns on all regression tests. +2) `-DCCPP_RUN_ADVECTION_TEST=ON` Turns on only the advection test +3) `-DCCPP_RUN_CAPGEN_TEST=ON` Turns on only the capgen test +4) `-DCCPP_RUN_DDT_HOST_TEST=ON` Turns on only the ddt host test +5) `-DCCPP_RUN_VAR_COMPATIBILITY_TEST=ON` Turns on only the variable compatibility test + +By default, the tests will build in release mode. To enable debug mode, you will need to set the build type: `-DCMAKE_BUILD_TYPE=Release` (or if you want release with debug symbols: `-DCMAKE_BUILD_TYPE=RelWithDebInfo`). + +To enable more verbose output for `ccpp_capgen.py`, add `-DCCPP_VERBOSITY=` to the `cmake` command line arguments where `n={1,2,3}` (`n=0` or no verbosity by default). + +If needed, the generated caps will be in `/test//ccpp`. + +### Python regression test interface + +There is a matching Python based API for each regression test. To run the corresponding python tests, build the framework using the build process from above and then you can run: + +```bash +BUILD_DIR= \ +PYTHONPATH=/test/:/scripts/ \ + pytest \ + /test/capgen_test/capgen_test_reports.py \ + /test/advection_test/advection_test_reports.py \ + /test/ddthost_test/ddthost_test_reports.py \ + /test/var_compatibility_test/var_compatibility_test_reports.py +``` + +You may run tests individually instead of all tests as your use case needs. + +Please see each test directory for more information on that specific test. diff --git a/test/advection_test/CMakeLists.txt b/test/advection_test/CMakeLists.txt index c3f45190..0f1be200 100644 --- a/test/advection_test/CMakeLists.txt +++ b/test/advection_test/CMakeLists.txt @@ -1,233 +1,57 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -PROJECT(test_host) -ENABLE_LANGUAGE(Fortran) - -include(CMakeForceCompiler) - -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) - -#------------------------------------------------------------------------------ -# -# Set where the CCPP Framework lives -# -#------------------------------------------------------------------------------ -get_filename_component(TEST_ROOT "${CMAKE_SOURCE_DIR}" DIRECTORY) -get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) -#------------------------------------------------------------------------------ -# # Create list of SCHEME_FILES, HOST_FILES, and SUITE_FILES -# Paths should be relative to CMAKE_SOURCE_DIR (this file's directory) -# -#------------------------------------------------------------------------------ -LIST(APPEND SCHEME_FILES "cld_suite_files.txt") -LIST(APPEND SCHEME_FILES_ERROR "cld_suite_files_error.txt") -LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") -LIST(APPEND SUITE_FILES "cld_suite.xml") -LIST(APPEND SUITE_FILES_ERROR "cld_suite_error.xml") +set(SCHEME_FILES "cld_liq" "cld_ice" "apply_constituent_tendencies" "const_indices") +set(SCHEME_FILES_ERROR "cld_liq" "cld_ice" "dlc_liq") +set(HOST_FILES "test_host_data" "test_host_mod") +set(SUITE_FILES "cld_suite.xml") +set(SUITE_FILES_ERROR "cld_suite_error.xml") # HOST is the name of the executable we will build. -# We assume there are files ${HOST}.meta and ${HOST}.F90 in CMAKE_SOURCE_DIR -SET(HOST "${CMAKE_PROJECT_NAME}") - -#------------------------------------------------------------------------------ -# -# End of project-specific input -# -#------------------------------------------------------------------------------ - -# By default, no verbose output -SET(VERBOSITY 0 CACHE STRING "Verbosity level of output (default: 0)") -# By default, generated caps go in ccpp subdir -SET(CCPP_CAP_FILES "${CMAKE_BINARY_DIR}/ccpp" CACHE - STRING "Location of CCPP-generated cap files") - -SET(CCPP_FRAMEWORK ${CCPP_ROOT}/scripts) - -# Use rpaths on MacOSX -set(CMAKE_MACOSX_RPATH 1) - -#------------------------------------------------------------------------------ -# Set a default build type if none was specified -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - #message(STATUS "Setting build type to 'Debug' as none was specified.") - #set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" - "MinSizeRel" "RelWithDebInfo") -endif() +set(HOST "test_host") -ADD_COMPILE_OPTIONS(-O0) +# By default, generated caps go in this test specific ccpp subdir +set(CCPP_CAP_FILES "${CMAKE_CURRENT_BINARY_DIR}/ccpp") -if (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") -# gfortran -# MESSAGE("gfortran being used.") - ADD_COMPILE_OPTIONS(-fcheck=all) - ADD_COMPILE_OPTIONS(-fbacktrace) - ADD_COMPILE_OPTIONS(-ffpe-trap=zero) - ADD_COMPILE_OPTIONS(-finit-real=nan) - ADD_COMPILE_OPTIONS(-ggdb) - ADD_COMPILE_OPTIONS(-ffree-line-length-none) - ADD_COMPILE_OPTIONS(-cpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "Intel") -# ifort -# MESSAGE("ifort being used.") - #ADD_COMPILE_OPTIONS(-check all) - ADD_COMPILE_OPTIONS(-fpe0) - ADD_COMPILE_OPTIONS(-warn) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-debug extended) - ADD_COMPILE_OPTIONS(-fpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "PGI") -# pgf90 -# MESSAGE("pgf90 being used.") - ADD_COMPILE_OPTIONS(-g) - ADD_COMPILE_OPTIONS(-Mipa=noconst) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-Mfree) - ADD_COMPILE_OPTIONS(-Mfptrap) - ADD_COMPILE_OPTIONS(-Mpreprocess) -else (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - message (WARNING "This program has only been compiled with gfortran, pgf90 and ifort. If another compiler is needed, the appropriate flags SHOULD be added in ${CMAKE_SOURCE_DIR}/CMakeLists.txt") -endif (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") +# Create lists for Fortran and meta data files from file names +list(TRANSFORM SCHEME_FILES APPEND ".F90" OUTPUT_VARIABLE SCHEME_FORTRAN_FILES) +list(TRANSFORM SCHEME_FILES APPEND ".meta" OUTPUT_VARIABLE SCHEME_META_FILES) +list(TRANSFORM SCHEME_FILES_ERROR APPEND ".F90" OUTPUT_VARIABLE SCHEME_ERROR_FORTRAN_FILES) +list(TRANSFORM SCHEME_FILES_ERROR APPEND ".meta" OUTPUT_VARIABLE SCHEME_ERROR_META_FILES) +list(TRANSFORM HOST_FILES APPEND ".F90" OUTPUT_VARIABLE ADVECTION_HOST_FORTRAN_FILES) +list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE ADVECTION_HOST_METADATA_FILES) -#------------------------------------------------------------------------------ -# CMake Modules -# Set the CMake module path -list(APPEND CMAKE_MODULE_PATH "${CCPP_FRAMEWORK}/cmake") -#------------------------------------------------------------------------------ -# Set OpenMP flags for C/C++/Fortran -if (OPENMP) - include(detect_openmp) - detect_openmp() - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") - message(STATUS "Enable OpenMP support for C/C++/Fortran compiler") -else(OPENMP) - message (STATUS "Disable OpenMP support for C/C++/Fortran compiler") -endif() - -# Create metadata and source file lists -FOREACH(FILE ${SCHEME_FILES}) - FILE(STRINGS ${FILE} FILENAMES) - LIST(APPEND SCHEME_FILENAMES ${FILENAMES}) -ENDFOREACH(FILE) -string(REPLACE ";" "," SCHEME_METADATA "${SCHEME_FILES}") - -# Create metadata and source file lists -FOREACH(FILE ${SCHEME_FILES_ERROR}) - FILE(STRINGS ${FILE} FILENAMES) - LIST(APPEND SCHEME_FILENAMES_ERROR ${FILENAMES}) -ENDFOREACH(FILE) -string(REPLACE ";" "," SCHEME_METADATA_ERROR "${SCHEME_FILES_ERROR}") - -FOREACH(FILE ${SCHEME_FILENAMES}) - # target_sources prefers absolute pathnames - string(REPLACE ".meta" ".F90" TEMP "${FILE}") - get_filename_component(ABS_PATH "${TEMP}" ABSOLUTE) - list(APPEND LIBRARY_LIST ${ABS_PATH}) -ENDFOREACH(FILE) - -FOREACH(FILE ${HOST_FILES}) - LIST(APPEND HOST_METADATA "${FILE}.meta") - # target_sources prefers absolute pathnames - get_filename_component(ABS_PATH "${FILE}.F90" ABSOLUTE) - LIST(APPEND HOST_SOURCE "${ABS_PATH}") -ENDFOREACH(FILE) -list(APPEND LIBRARY_LIST ${HOST_SOURCE}) -string(REPLACE ";" ".meta," HOST_METADATA "${HOST_FILES}") -set(HOST_METADATA "${HOST_METADATA}.meta,${HOST}.meta") - -string(REPLACE ";" "," SUITE_XML "${SUITE_FILES}") -string(REPLACE ";" "," SUITE_XML_ERROR "${SUITE_FILES_ERROR}") +list(APPEND ADVECTION_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen that we expect to fail -set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") -list(APPEND CAPGEN_CMD "--host-files") -list(APPEND CAPGEN_CMD "${HOST_METADATA}") -list(APPEND CAPGEN_CMD "--scheme-files") -list(APPEND CAPGEN_CMD "${SCHEME_METADATA_ERROR}") -list(APPEND CAPGEN_CMD "--suites") -list(APPEND CAPGEN_CMD "${SUITE_XML_ERROR}") -list(APPEND CAPGEN_CMD "--host-name") -list(APPEND CAPGEN_CMD "test_host") -list(APPEND CAPGEN_CMD "--output-root") -list(APPEND CAPGEN_CMD "${CCPP_CAP_FILES}") -while (VERBOSITY GREATER 0) - list(APPEND CAPGEN_CMD "--verbose") - MATH(EXPR VERBOSITY "${VERBOSITY} - 1") -endwhile () -list(APPEND CAPGEN_CMD "--debug") -string(REPLACE ";" " " CAPGEN_STRING "${CAPGEN_CMD}") -MESSAGE(STATUS "Running: ${CAPGEN_STRING}") -EXECUTE_PROCESS(COMMAND ${CAPGEN_CMD} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE CAPGEN_OUT ERROR_VARIABLE CAPGEN_OUT RESULT_VARIABLE RES) -MESSAGE(STATUS "${CAPGEN_OUT}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap generation completed") -else() - # Example: Validate the error message - string(FIND "${CAPGEN_OUT}" "Variables of type ccpp_constituent_properties_t only allowed in register phase" ERROR_INDEX) - - if (ERROR_INDEX GREATER -1) - MESSAGE(STATUS "Capgen build produces expected error message.") - else() - MESSAGE(FATAL_ERROR "CCPP cap generation did not generate expected error. Expected 'Variables of type ccpp_cosntituent_properties_t only allowed in register phase. Got: " ${CAPGEN_OUT}"") - endif() -endif(RES EQUAL 0) +ccpp_capgen(CAPGEN_EXPECT_THROW_ERROR ON + CAPGEN_DEBUG ON + VERBOSITY ${CCPP_VERBOSITY} + HOSTFILES ${ADVECTION_HOST_METADATA_FILES} + SCHEMEFILES ${SCHEME_ERROR_META_FILES} + SUITES ${SUITE_FILES_ERROR} + HOST_NAME ${HOST} + OUTPUT_ROOT "${CCPP_CAP_FILES}") # Run ccpp_capgen -set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") -list(APPEND CAPGEN_CMD "--host-files") -list(APPEND CAPGEN_CMD "${HOST_METADATA}") -list(APPEND CAPGEN_CMD "--scheme-files") -list(APPEND CAPGEN_CMD "${SCHEME_METADATA}") -list(APPEND CAPGEN_CMD "--suites") -list(APPEND CAPGEN_CMD "${SUITE_XML}") -list(APPEND CAPGEN_CMD "--host-name") -list(APPEND CAPGEN_CMD "test_host") -list(APPEND CAPGEN_CMD "--output-root") -list(APPEND CAPGEN_CMD "${CCPP_CAP_FILES}") -while (VERBOSITY GREATER 0) - list(APPEND CAPGEN_CMD "--verbose") - MATH(EXPR VERBOSITY "${VERBOSITY} - 1") -endwhile () -list(APPEND CAPGEN_CMD "--debug") -string(REPLACE ";" " " CAPGEN_STRING "${CAPGEN_CMD}") -MESSAGE(STATUS "Running: ${CAPGEN_STRING}") -EXECUTE_PROCESS(COMMAND ${CAPGEN_CMD} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE CAPGEN_OUT ERROR_VARIABLE CAPGEN_OUT RESULT_VARIABLE RES) -MESSAGE(STATUS "${CAPGEN_OUT}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap generation completed") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap generation FAILED: result = ${RES}") -endif(RES EQUAL 0) - -# Retrieve the list of files from datatable.xml and set to CCPP_CAPS -set(DTABLE_CMD "${CCPP_FRAMEWORK}/ccpp_datafile.py") -list(APPEND DTABLE_CMD "${CCPP_CAP_FILES}/datatable.xml") -list(APPEND DTABLE_CMD "--ccpp-files") -list(APPEND DTABLE_CMD "--separator=\\;") -string(REPLACE ";" " " DTABLE_STRING "${DTABLE_CMD}") -MESSAGE(STATUS "Running: ${DTABLE_STRING}") -EXECUTE_PROCESS(COMMAND ${DTABLE_CMD} OUTPUT_VARIABLE CCPP_CAPS - RESULT_VARIABLE RES - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) -message(STATUS "CCPP_CAPS = ${CCPP_CAPS}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap files retrieved") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap file retrieval FAILED: result = ${RES}") -endif(RES EQUAL 0) -list(APPEND LIBRARY_LIST ${CCPP_CAPS}) -add_library(TESTLIB OBJECT ${LIBRARY_LIST}) -ADD_EXECUTABLE(${HOST} ${HOST}.F90 $) - -INCLUDE_DIRECTORIES(${CCPP_CAP_FILES}) - -set_target_properties(${HOST} PROPERTIES - COMPILE_FLAGS "${CMAKE_Fortran_FLAGS}" - LINK_FLAGS "${CMAKE_Fortran_FLAGS}") +ccpp_capgen(CAPGEN_DEBUG ON + VERBOSITY ${CCPP_VERBOSITY} + HOSTFILES ${ADVECTION_HOST_METADATA_FILES} + SCHEMEFILES ${SCHEME_META_FILES} + SUITES ${SUITE_FILES} + HOST_NAME ${HOST} + OUTPUT_ROOT "${CCPP_CAP_FILES}") + +# Retrieve the list of Fortran files required for test host from datatable.xml and set to CCPP_CAPS_LIST +ccpp_datafile(DATATABLE "${CCPP_CAP_FILES}/datatable.xml" + REPORT_NAME "--ccpp-files") + +# Create test host library +add_library(ADVECTION_TESTLIB OBJECT ${SCHEME_FORTRAN_FILES} + ${ADVECTION_HOST_FORTRAN_FILES} + ${CCPP_CAPS_LIST}) + +# Setup test executable with needed dependencies +add_executable(advection_host_integration test_advection_host_integration.F90 ${HOST}.F90) +target_link_libraries(advection_host_integration PRIVATE ADVECTION_TESTLIB test_utils) +target_include_directories(advection_host_integration PRIVATE "$") + +# Add executable to be called with ctest +add_test(NAME ctest_advection_host_integration COMMAND advection_host_integration) diff --git a/test/advection_test/README.md b/test/advection_test/README.md new file mode 100644 index 00000000..ce3e285d --- /dev/null +++ b/test/advection_test/README.md @@ -0,0 +1,21 @@ +# Advection Test + +Contains tests to exercise the capabilities of the constituents object, including: +- Adding a build-time constituent via metadata property +- Adding a run-time constituent via a register phase + - Also tests that trying to add a constituent outside of the register phase errors as expected +- Passing around and modifying the constituent array +- Accessing and modifying a constituent tendency variable +- Passing around the constituent tendency array +- Dimensions are case-insensitive + +## Building/Running + +To explicitly build/run the advection test host, run: + +```bash +$ cmake -S -B -DCCPP_RUN_ADVECTION_TEST=ON +$ cd +$ make +$ ctest +``` diff --git a/test/advection_test/advection_test_reports.py b/test/advection_test/advection_test_reports.py new file mode 100644 index 00000000..4fbe8e68 --- /dev/null +++ b/test/advection_test/advection_test_reports.py @@ -0,0 +1,127 @@ +#! /usr/bin/env python3 +""" +----------------------------------------------------------------------- + Description: Test advection database report python interface + + Assumptions: + + Command line arguments: build_dir database_filepath + + Usage: python test_reports +----------------------------------------------------------------------- +""" +import os +import unittest + +from test_stub import BaseTests + +_BUILD_DIR = os.path.join(os.path.abspath(os.environ['BUILD_DIR']), "test", "advection_test") +_DATABASE = os.path.abspath(os.path.join(_BUILD_DIR, "ccpp", "datatable.xml")) + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) +_SCRIPTS_DIR = os.path.abspath(os.path.join(_FRAMEWORK_DIR, "scripts")) + +# Check data +_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] +_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_cld_suite_cap.F90")] +_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), + os.path.join(_FRAMEWORK_DIR, "src", + "ccpp_constituent_prop_mod.F90"), + os.path.join(_FRAMEWORK_DIR, "src", + "ccpp_scheme_utils.F90"), + os.path.join(_FRAMEWORK_DIR, "src", "ccpp_hashable.F90"), + os.path.join(_FRAMEWORK_DIR, "src", "ccpp_hash_table.F90")] +_CCPP_FILES = _UTILITY_FILES + _HOST_FILES + _SUITE_FILES +_DEPENDENCIES = [""] +_PROCESS_LIST = [""] +_MODULE_LIST = ["cld_ice", "cld_liq", "const_indices", "apply_constituent_tendencies"] +_SUITE_LIST = ["cld_suite"] +_REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", + "horizontal_loop_begin", "horizontal_loop_end", + "surface_air_pressure", "temperature", + "tendency_of_cloud_liquid_dry_mixing_ratio", + "time_step_for_physics", "water_temperature_at_freezing", + "water_vapor_specific_humidity", + "cloud_ice_dry_mixing_ratio", + "cloud_liquid_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", + "number_of_ccpp_constituents", + "dynamic_constituents_for_cld_ice", + "dynamic_constituents_for_cld_liq", + "test_banana_constituent_indices", "test_banana_name", + "banana_array_dim", + "test_banana_name_array", + "test_banana_constituent_index", + # Added by --debug option + "horizontal_dimension", + "vertical_layer_dimension"] +_INPUT_VARS_CLD = ["surface_air_pressure", "temperature", + "horizontal_loop_begin", "horizontal_loop_end", + "time_step_for_physics", "water_temperature_at_freezing", + "water_vapor_specific_humidity", + "cloud_ice_dry_mixing_ratio", + "cloud_liquid_dry_mixing_ratio", + "tendency_of_cloud_liquid_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", + "number_of_ccpp_constituents", + "banana_array_dim", + "test_banana_name_array", "test_banana_name", + # Added by --debug option + "horizontal_dimension", + "vertical_layer_dimension"] +_OUTPUT_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", + "water_vapor_specific_humidity", "temperature", + "tendency_of_cloud_liquid_dry_mixing_ratio", + "cloud_ice_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", + "cloud_liquid_dry_mixing_ratio", + "dynamic_constituents_for_cld_ice", + "dynamic_constituents_for_cld_liq", + "dynamic_constituents_for_cld_liq", + "test_banana_constituent_indices", + "test_banana_constituent_index"] + + +class TestAdvectionHostDataTables(unittest.TestCase, BaseTests.TestHostDataTables): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + +class CommandLineAdvectionHostDatafileRequiredFiles(unittest.TestCase, BaseTests.TestHostCommandLineDataFiles): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" + + +class TestCapgenCldSuite(unittest.TestCase, BaseTests.TestSuite): + database = _DATABASE + required_vars = _REQUIRED_VARS_CLD + input_vars = _INPUT_VARS_CLD + output_vars = _OUTPUT_VARS_CLD + suite_name = "cld_suite" + + +class CommandLineCapgenDdtSuite(unittest.TestCase, BaseTests.TestSuiteCommandLine): + database = _DATABASE + required_vars = _REQUIRED_VARS_CLD + input_vars = _INPUT_VARS_CLD + output_vars = _OUTPUT_VARS_CLD + suite_name = "cld_suite" + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 69ad3f4f..e55dcfe7 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -52,7 +52,7 @@ [ temp ] standard_name = temperature units = K - dimensions = (horizontal_loop_extent, vertical_layer_dimension) + dimensions = (horizontal_loop_extent, vertical_LAYER_dimension) type = real kind = kind_phys intent = inout diff --git a/test/advection_test/cld_suite.xml b/test/advection_test/cld_suite.xml index 361518d1..fac613e8 100644 --- a/test/advection_test/cld_suite.xml +++ b/test/advection_test/cld_suite.xml @@ -2,6 +2,7 @@ + const_indices cld_liq apply_constituent_tendencies cld_ice diff --git a/test/advection_test/cld_suite_files.txt b/test/advection_test/cld_suite_files.txt deleted file mode 100644 index 301bb4ee..00000000 --- a/test/advection_test/cld_suite_files.txt +++ /dev/null @@ -1,3 +0,0 @@ -cld_liq.meta -cld_ice.meta -apply_constituent_tendencies.meta diff --git a/test/advection_test/cld_suite_files_error.txt b/test/advection_test/cld_suite_files_error.txt deleted file mode 100644 index 63ff75b0..00000000 --- a/test/advection_test/cld_suite_files_error.txt +++ /dev/null @@ -1,3 +0,0 @@ -cld_liq.meta -cld_ice.meta -dlc_liq.meta diff --git a/test/advection_test/const_indices.F90 b/test/advection_test/const_indices.F90 new file mode 100644 index 00000000..0d9cf2e7 --- /dev/null +++ b/test/advection_test/const_indices.F90 @@ -0,0 +1,94 @@ +! Test collection of constituent indices +! + +MODULE const_indices + + USE ccpp_kinds, ONLY: kind_phys + + IMPLICIT NONE + PRIVATE + + PUBLIC :: const_indices_init + PUBLIC :: const_indices_run + +CONTAINS + + !> \section arg_table_const_indices_run Argument Table + !! \htmlinclude arg_table_const_indices_run.html + !! + subroutine const_indices_run(const_std_name, num_consts, test_stdname_array, & + const_index, const_inds, errmsg, errflg) + use ccpp_constituent_prop_mod, only: int_unassigned + use ccpp_scheme_utils, only: ccpp_constituent_index + use ccpp_scheme_utils, only: ccpp_constituent_indices + + character(len=*), intent(in) :: const_std_name + integer, intent(in) :: num_consts + character(len=*), intent(in) :: test_stdname_array(:) + integer, intent(out) :: const_index + integer, intent(out) :: const_inds(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + + integer :: indx + integer :: test_indx + + errmsg = '' + errflg = 0 + + ! Find the constituent index for + call ccpp_constituent_index(const_std_name, const_index, errflg, errmsg) + if (errflg == 0) then + call ccpp_constituent_indices(test_stdname_array, const_inds, errflg, errmsg) + end if + ! Check that a non-registered constituent is detectable but + ! does not cause an error + if (errflg == 0) then + call ccpp_constituent_index('unobtainium', test_indx, errflg, errmsg) + if (test_indx /= int_unassigned) then + if (errflg == 0) then + ! Do not add an error if one is already reported + errflg = 2 + write(errmsg, '(2a,i0,a,i0)') "ccpp_constituent_index called for ", & + "'unobtainium' returned an index of ", test_indx, ", not ", & + int_unassigned + end if + end if + end if + + end subroutine const_indices_run + + !> \section arg_table_const_indices_init Argument Table + !! \htmlinclude arg_table_const_indices_init.html + !! + subroutine const_indices_init(const_std_name, num_consts, test_stdname_array, & + const_index, const_inds, errmsg, errflg) + use ccpp_scheme_utils, only: ccpp_constituent_index, ccpp_constituent_indices + + character(len=*), intent(in) :: const_std_name + integer, intent(in) :: num_consts + character(len=*), intent(in) :: test_stdname_array(:) + integer, intent(out) :: const_index + integer, intent(out) :: const_inds(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + + integer :: indx + + errmsg = '' + errflg = 0 + + ! Find the constituent index for + call ccpp_constituent_index(const_std_name, const_index, errflg, errmsg) + if (errflg == 0) then + call ccpp_constituent_indices(test_stdname_array, const_inds, errflg, errmsg) + end if + + end subroutine const_indices_init + + !! @} + !! @} + +END MODULE const_indices diff --git a/test/advection_test/const_indices.meta b/test/advection_test/const_indices.meta new file mode 100644 index 00000000..a4cc98e2 --- /dev/null +++ b/test/advection_test/const_indices.meta @@ -0,0 +1,108 @@ +# const_indices just returns some constituent indices as a test +[ccpp-table-properties] + name = const_indices + type = scheme +[ccpp-arg-table] + name = const_indices_run + type = scheme +[ const_std_name ] + standard_name = test_banana_name + type = character | kind = len=* + units = 1 + dimensions = () + protected = true + intent = in +[ num_consts ] + standard_name = banana_array_dim + long_name = Size of test_banana_name_array + units = 1 + dimensions = () + type = integer + intent = in +[ test_stdname_array ] + standard_name = test_banana_name_array + type = character | kind = len=* + units = count + dimensions = (banana_array_dim) + intent = in +[ const_index ] + standard_name = test_banana_constituent_index + long_name = Constituent index + units = 1 + dimensions = () + type = integer + intent = out +[ const_inds ] + standard_name = test_banana_constituent_indices + long_name = Array of constituent indices + units = 1 + dimensions = (banana_array_dim) + type = integer + intent = out +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = const_indices_init + type = scheme +[ const_std_name ] + standard_name = test_banana_name + type = character | kind = len=* + units = 1 + dimensions = () + protected = true + intent = in +[ num_consts ] + standard_name = banana_array_dim + long_name = Size of test_banana_name_array + units = 1 + dimensions = () + type = integer + intent = in +[ test_stdname_array ] + standard_name = test_banana_name_array + type = character | kind = len=* + units = count + dimensions = (banana_array_dim) + intent = in +[ const_index ] + standard_name = test_banana_constituent_index + long_name = Constituent index + units = 1 + dimensions = () + type = integer + intent = out +[ const_inds ] + standard_name = test_banana_constituent_indices + long_name = Array of constituent indices + units = 1 + dimensions = (banana_array_dim) + type = integer + intent = out +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/advection_test/run_test b/test/advection_test/run_test deleted file mode 100755 index 5f9f8a8b..00000000 --- a/test/advection_test/run_test +++ /dev/null @@ -1,262 +0,0 @@ -#! /bin/bash - -currdir="`pwd -P`" -scriptdir="$( cd $( dirname $0 ); pwd -P )" - -## -## Option default values -## -defdir="at_build" -build_dir="${currdir}/${defdir}" -cleanup="PASS" # Other supported options are ALWAYS and NEVER -verbosity=0 - -## -## General syntax help function -## Usage: help -## -help () { - local hname="Usage: `basename ${0}`" - local hprefix="`echo ${hname} | tr '[!-~]' ' '`" - echo "${hname} [ --build-dir ] [ --cleanup ]" - echo "${hprefix} [ --verbosity <#> ]" - hprefix=" " - echo "" - echo "${hprefix} : Directory for building and running the test" - echo "${hprefix} default is /${defdir}" - echo "${hprefix} : Cleanup option is ALWAYS, NEVER, or PASS" - echo "${hprefix} default is PASS" - echo "${hprefix} verbosity: 0, 1, or 2" - echo "${hprefix} default is 0" - exit $1 -} - -## -## Error output function (should be handed a string) -## -perr() { - >&2 echo -e "\nERROR: ${@}\n" - exit 1 -} - -## -## Cleanup the build and test directory -## -docleanup() { - # We start off in the build directory - if [ "${build_dir}" == "${currdir}" ]; then - echo "WARNING: Cannot clean ${build_dir}" - else - cd ${currdir} - rm -rf ${build_dir} - fi -} - -## Process our input arguments -while [ $# -gt 0 ]; do - case $1 in - --h | -h | --help | -help) - help 0 - ;; - --build-dir) - if [ $# -lt 2 ]; then - perr "${1} requires a build directory" - fi - build_dir="${2}" - shift - ;; - --cleanup) - if [ $# -lt 2 ]; then - perr "${1} requies a cleanup option (ALWAYS, NEVER, PASS)" - fi - if [ "${2}" == "ALWAYS" -o "${2}" == "NEVER" -o "${2}" == "PASS" ]; then - cleanup="${2}" - else - perr "Allowed cleanup options: ALWAYS, NEVER, PASS" - fi - shift - ;; - --verbosity) - if [ $# -lt 2 ]; then - perr "${1} requires a verbosity value (0, 1, or 2)" - fi - if [ "${2}" == "0" -o "${2}" == "1" -o "${2}" == "2" ]; then - verbosity=$2 - else - perr "allowed verbosity levels are 0, 1, 2" - fi - shift - ;; - *) - perr "Unrecognized option, \"${1}\"" - ;; - esac - shift -done - -# Create the build directory, if necessary -if [ -d "${build_dir}" ]; then - # Always make sure build_dir is not in the test dir - if [ "$( cd ${build_dir}; pwd -P )" == "${currdir}" ]; then - build_dir="${build_dir}/${defdir}" - fi -else - mkdir -p ${build_dir} - res=$? - if [ $res -ne 0 ]; then - perr "Unable to create build directory, '${build_dir}'" - fi -fi -build_dir="$( cd ${build_dir}; pwd -P )" - -## framework is the CCPP Framework root dir -framework="$( cd $( dirname $( dirname ${scriptdir} ) ); pwd -P )" -fsrc="${framework}/src" - -## -## check strings for datafile command-list test -## NB: This has to be after build_dir is finalized -## -host_files="${build_dir}/ccpp/test_host_ccpp_cap.F90" -hash_files="${fsrc}/ccpp_hashable.F90,${fsrc}/ccpp_hash_table.F90" -suite_files="${build_dir}/ccpp/ccpp_cld_suite_cap.F90" -utility_files="${build_dir}/ccpp/ccpp_kinds.F90" -utility_files="${utility_files},${fsrc}/ccpp_constituent_prop_mod.F90" -utility_files="${utility_files},${hash_files}" -ccpp_files="${utility_files},${host_files},${suite_files}" -process_list="" -module_list="apply_constituent_tendencies,cld_ice,cld_liq" -dependencies="" -suite_list="cld_suite" -required_vars="ccpp_constituent_tendencies,ccpp_constituents" -required_vars="${required_vars},ccpp_error_code,ccpp_error_message" -required_vars="${required_vars},cloud_ice_dry_mixing_ratio" -required_vars="${required_vars},cloud_liquid_dry_mixing_ratio" -required_vars="${required_vars},dynamic_constituents_for_cld_ice" -required_vars="${required_vars},dynamic_constituents_for_cld_liq" -required_vars="${required_vars},horizontal_dimension" -required_vars="${required_vars},horizontal_loop_begin" -required_vars="${required_vars},horizontal_loop_end" -required_vars="${required_vars},number_of_ccpp_constituents" -required_vars="${required_vars},surface_air_pressure" -required_vars="${required_vars},temperature" -required_vars="${required_vars},tendency_of_cloud_liquid_dry_mixing_ratio" -required_vars="${required_vars},time_step_for_physics" -required_vars="${required_vars},vertical_layer_dimension" -required_vars="${required_vars},water_temperature_at_freezing" -required_vars="${required_vars},water_vapor_specific_humidity" -input_vars="ccpp_constituent_tendencies,ccpp_constituents" -input_vars="${input_vars},cloud_ice_dry_mixing_ratio,cloud_liquid_dry_mixing_ratio" -input_vars="${input_vars},horizontal_dimension" -input_vars="${input_vars},horizontal_loop_begin" -input_vars="${input_vars},horizontal_loop_end" -input_vars="${input_vars},number_of_ccpp_constituents" -input_vars="${input_vars},surface_air_pressure,temperature" -input_vars="${input_vars},tendency_of_cloud_liquid_dry_mixing_ratio" -input_vars="${input_vars},time_step_for_physics" -input_vars="${input_vars},vertical_layer_dimension" -input_vars="${input_vars},water_temperature_at_freezing" -input_vars="${input_vars},water_vapor_specific_humidity" -output_vars="ccpp_constituent_tendencies,ccpp_constituents" -output_vars="${output_vars},ccpp_error_code,ccpp_error_message" -output_vars="${output_vars},cloud_ice_dry_mixing_ratio" -output_vars="${output_vars},cloud_liquid_dry_mixing_ratio" -output_vars="${output_vars},dynamic_constituents_for_cld_ice" -output_vars="${output_vars},dynamic_constituents_for_cld_liq" -output_vars="${output_vars},temperature" -output_vars="${output_vars},tendency_of_cloud_liquid_dry_mixing_ratio" -output_vars="${output_vars},water_vapor_specific_humidity" - -## -## Run a database report and check the return string -## $1 is the report program file -## $2 is the database file -## $3 is the report string -## $4 is the check string -## $5+ are any optional arguments -## -check_datatable() { - local checkstr=${4} - local teststr - local prog=${1} - local database=${2} - local report=${3} - shift 4 - echo "Checking ${report} report" - teststr="`${prog} ${database} ${report} $@`" - if [ "${teststr}" != "${checkstr}" ]; then - perr "datatable check:\nExpected: '${checkstr}'\nGot: '${teststr}'" - fi -} - -# cd to the build directory -cd ${build_dir} -res=$? -if [ $res -ne 0 ]; then - perr "Unable to cd to build directory, '${build_dir}'" -fi -# Clean build directory -rm -rf * -res=$? -if [ $res -ne 0 ]; then - perr "Unable to clean build directory, '${build_dir}'" -fi -# Run CMake -opts="" -if [ $verbosity -gt 0 ]; then - opts="${opts} -DVERBOSITY=${verbosity}" -fi -# Run cmake -cmake ${scriptdir} ${opts} -res=$? -if [ $res -ne 0 ]; then - perr "CMake failed with exit code, ${res}" -fi -# Test the datafile user interface -report_prog="${framework}/scripts/ccpp_datafile.py" -datafile="${build_dir}/ccpp/datatable.xml" -echo "Running python interface tests" -python3 ${scriptdir}/test_reports.py ${build_dir} ${datafile} -res=$? -if [ $res -ne 0 ]; then - perr "python interface tests failed" -fi -echo "Running command line tests" -echo "Checking required files from command line:" -check_datatable ${report_prog} ${datafile} "--host-files" ${host_files} -check_datatable ${report_prog} ${datafile} "--suite-files" ${suite_files} -check_datatable ${report_prog} ${datafile} "--utility-files" ${utility_files} -check_datatable ${report_prog} ${datafile} "--ccpp-files" ${ccpp_files} -echo -e "\nChecking lists from command line" -check_datatable ${report_prog} ${datafile} "--process-list" "${process_list}" -check_datatable ${report_prog} ${datafile} "--module-list" ${module_list} -check_datatable ${report_prog} ${datafile} "--dependencies" "${dependencies}" -check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ - --sep ";" -echo -e "\nChecking variables from command line" -check_datatable ${report_prog} ${datafile} "--required-variables" \ - ${required_vars} "cld_suite" -check_datatable ${report_prog} ${datafile} "--input-variables" \ - ${input_vars} "cld_suite" -check_datatable ${report_prog} ${datafile} "--output-variables" \ - ${output_vars} "cld_suite" -# Run make -make -res=$? -if [ $res -ne 0 ]; then - perr "make failed with exit code, ${res}" -fi -# Run test -./test_host -res=$? -if [ $res -ne 0 ]; then - perr "test_host failed with exit code, ${res}" -fi - -if [ "${cleanup}" == "ALWAYS" ]; then - docleanup -elif [ $res -eq 0 -a "${cleanup}" == "PASS" ]; then - docleanup -fi - -exit $res diff --git a/test/advection_test/test_advection_host_integration.F90 b/test/advection_test/test_advection_host_integration.F90 new file mode 100644 index 00000000..728137fa --- /dev/null +++ b/test/advection_test/test_advection_host_integration.F90 @@ -0,0 +1,77 @@ +program test + use test_prog, only: test_host, suite_info, cm, cs + + implicit none + + character(len=cs), target :: test_parts1(1) + character(len=cm), target :: test_invars1(12) + character(len=cm), target :: test_outvars1(13) + character(len=cm), target :: test_reqvars1(18) + + type(suite_info) :: test_suites(1) + logical :: run_okay + + test_parts1 = (/ 'physics '/) + test_invars1 = (/ & + 'banana_array_dim ', & + 'cloud_ice_dry_mixing_ratio ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'surface_air_pressure ', & + 'temperature ', & + 'time_step_for_physics ', & + 'water_temperature_at_freezing ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'number_of_ccpp_constituents ', & + 'water_vapor_specific_humidity ' /) + test_outvars1 = (/ & + 'ccpp_error_message ', & + 'ccpp_error_code ', & + 'temperature ', & + 'water_vapor_specific_humidity ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'test_banana_constituent_index ', & + 'test_banana_constituent_indices ', & + 'cloud_ice_dry_mixing_ratio ' /) + test_reqvars1 = (/ & + 'banana_array_dim ', & + 'surface_air_pressure ', & + 'temperature ', & + 'time_step_for_physics ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'cloud_ice_dry_mixing_ratio ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & + 'water_temperature_at_freezing ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'number_of_ccpp_constituents ', & + 'test_banana_constituent_index ', & + 'test_banana_constituent_indices ', & + 'water_vapor_specific_humidity ', & + 'ccpp_error_message ', & + 'ccpp_error_code ' /) + + ! Setup expected test suite info + test_suites(1)%suite_name = 'cld_suite' + test_suites(1)%suite_parts => test_parts1 + test_suites(1)%suite_input_vars => test_invars1 + test_suites(1)%suite_output_vars => test_outvars1 + test_suites(1)%suite_required_vars => test_reqvars1 + + call test_host(run_okay, test_suites) + + if (run_okay) then + STOP 0 + else + STOP -1 + end if + +end program test diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index c5a2121a..c1482a93 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -25,8 +25,6 @@ module test_prog type(ccpp_constituent_properties_t), private, target, allocatable :: host_constituents(:) - - private :: check_list private :: check_suite private :: advect_constituents ! Move data around private :: check_errflg @@ -50,92 +48,10 @@ subroutine check_errflg(subname, errflg, errmsg, errflg_final) end subroutine check_errflg - logical function check_list(test_list, chk_list, list_desc, suite_name) - ! Check a list () against its expected value () - - ! Dummy arguments - character(len=*), intent(in) :: test_list(:) - character(len=*), intent(in) :: chk_list(:) - character(len=*), intent(in) :: list_desc - character(len=*), optional, intent(in) :: suite_name - - ! Local variables - logical :: found - integer :: num_items - integer :: lindex, tindex - integer, allocatable :: check_unique(:) - character(len=2) :: sep - character(len=256) :: errmsg - - check_list = .true. - errmsg = '' - - ! Check the list size - num_items = size(chk_list) - if (size(test_list) /= num_items) then - write(errmsg, '(a,i0,2a)') 'ERROR: Found ', size(test_list), & - ' ', trim(list_desc) - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' for suite, ', & - trim(suite_name) - end if - write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items - write(6, *) trim(errmsg) - errmsg = '' - check_list = .false. - end if - - ! Now, check the list contents for 1-1 correspondence - if (check_list) then - allocate(check_unique(num_items)) - check_unique = -1 - do lindex = 1, num_items - found = .false. - do tindex = 1, num_items - if (trim(test_list(lindex)) == trim(chk_list(tindex))) then - check_unique(tindex) = lindex - found = .true. - exit - end if - end do - if (.not. found) then - check_list = .false. - write(errmsg, '(5a)') 'ERROR: ', trim(list_desc), ' item, ', & - trim(test_list(lindex)), ', was not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - write(6, *) trim(errmsg) - errmsg = '' - end if - end do - if (check_list .and. ANY(check_unique < 0)) then - check_list = .false. - write(errmsg, '(3a)') 'ERROR: The following ', trim(list_desc), & - ' items were not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - sep = '; ' - do lindex = 1, num_items - if (check_unique(lindex) < 0) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') sep, & - trim(chk_list(lindex)) - sep = ', ' - end if - end do - write(6, *) trim(errmsg) - errmsg = '' - end if - end if - - end function check_list - logical function check_suite(test_suite) use test_host_ccpp_cap, only: ccpp_physics_suite_part_list use test_host_ccpp_cap, only: ccpp_physics_suite_variables + use test_utils, only: check_list ! Dummy argument type(suite_info), intent(in) :: test_suite @@ -225,6 +141,8 @@ subroutine test_host(retval, test_suites) use test_host_mod, only: num_time_steps use test_host_mod, only: init_data, compare_data use test_host_mod, only: ncols, pver + use test_host_data, only: num_consts, std_name_array, const_std_name + use test_host_data, only: check_constituent_indices use test_host_ccpp_cap, only: test_host_ccpp_deallocate_dynamic_constituents use test_host_ccpp_cap, only: test_host_ccpp_register_constituents use test_host_ccpp_cap, only: test_host_ccpp_is_scheme_constituent @@ -240,6 +158,7 @@ subroutine test_host(retval, test_suites) use test_host_ccpp_cap, only: ccpp_physics_suite_list use test_host_ccpp_cap, only: test_host_const_get_index use test_host_ccpp_cap, only: test_host_model_const_properties + use test_utils, only: check_list type(suite_info), intent(in) :: test_suites(:) logical, intent(out) :: retval @@ -255,6 +174,8 @@ subroutine test_host(retval, test_suites) logical :: const_log logical :: is_constituent logical :: has_default + integer :: test_scalar_const_index + integer :: test_const_indices(num_consts) character(len=128), allocatable :: suite_names(:) character(len=256) :: const_str character(len=512) :: errmsg @@ -359,7 +280,7 @@ subroutine test_host(retval, test_suites) call test_host_ccpp_register_constituents(host_constituents, & errmsg=errmsg, errflg=errflg) end if - ! Check the error + ! Check the error if (errflg == 0) then write(6, '(2a)') 'ERROR register_constituents: expected this error: ', & trim(expected_error) @@ -460,6 +381,16 @@ subroutine test_host(retval, test_suites) call check_errflg(subname//".index_dyn_const3", errflg, errmsg, & errflg_final) + ! Load up the test array indices + call test_host_const_get_index(const_std_name, test_scalar_const_index, errflg, errmsg) + call check_errflg(subname//"."//const_std_name, errflg, errmsg, & + errflg_final) + do sind = 1, num_consts + call test_host_const_get_index(std_name_array(sind), & + test_const_indices(sind), errflg, errmsg) + call check_errflg(subname//"."//std_name_array(sind), errflg, errmsg, & + errflg_final) + end do ! Stop tests here if the index checks failed, as all other tests will ! likely fail as well: @@ -1017,6 +948,12 @@ subroutine test_host(retval, test_suites) end if end do + ! Check indices + call check_constituent_indices(test_scalar_const_index, test_const_indices, & + errmsg, errflg) + call check_errflg(subname//" check suite indices", errflg, errmsg, & + errflg_final) + ! Loop over time steps do time_step = 1, num_time_steps ! Initialize the timestep @@ -1054,6 +991,11 @@ subroutine test_host(retval, test_suites) end do end do end do + ! Check indices + call check_constituent_indices(test_scalar_const_index, test_const_indices, & + errmsg, errflg) + call check_errflg(subname//" check suite indices", errflg, errmsg, & + errflg_final) do sind = 1, num_suites if (errflg == 0) then @@ -1109,75 +1051,3 @@ subroutine test_host(retval, test_suites) end subroutine test_host end module test_prog - - program test - use test_prog, only: test_host, suite_info, cm, cs - - implicit none - - character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(11) - character(len=cm), target :: test_outvars1(11) - character(len=cm), target :: test_reqvars1(15) - - type(suite_info) :: test_suites(1) - logical :: run_okay - - test_parts1 = (/ 'physics '/) - test_invars1 = (/ & - 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'surface_air_pressure ', & - 'temperature ', & - 'time_step_for_physics ', & - 'water_temperature_at_freezing ', & - 'ccpp_constituent_tendencies ', & - 'ccpp_constituents ', & - 'number_of_ccpp_constituents ', & - 'water_vapor_specific_humidity ' /) - test_outvars1 = (/ & - 'ccpp_error_message ', & - 'ccpp_error_code ', & - 'temperature ', & - 'water_vapor_specific_humidity ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'ccpp_constituent_tendencies ', & - 'ccpp_constituents ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & - 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'cloud_ice_dry_mixing_ratio ' /) - test_reqvars1 = (/ & - 'surface_air_pressure ', & - 'temperature ', & - 'time_step_for_physics ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'cloud_ice_dry_mixing_ratio ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & - 'water_temperature_at_freezing ', & - 'ccpp_constituent_tendencies ', & - 'ccpp_constituents ', & - 'number_of_ccpp_constituents ', & - 'water_vapor_specific_humidity ', & - 'ccpp_error_message ', & - 'ccpp_error_code ' /) - - ! Setup expected test suite info - test_suites(1)%suite_name = 'cld_suite' - test_suites(1)%suite_parts => test_parts1 - test_suites(1)%suite_input_vars => test_invars1 - test_suites(1)%suite_output_vars => test_outvars1 - test_suites(1)%suite_required_vars => test_reqvars1 - - call test_host(run_okay, test_suites) - - if (run_okay) then - STOP 0 - else - STOP -1 - end if - -end program test diff --git a/test/advection_test/test_host_data.F90 b/test/advection_test/test_host_data.F90 index ee33b66a..bbf0efdc 100644 --- a/test/advection_test/test_host_data.F90 +++ b/test/advection_test/test_host_data.F90 @@ -2,6 +2,8 @@ module test_host_data use ccpp_kinds, only: kind_phys + implicit none + !> \section arg_table_physics_state Argument Table !! \htmlinclude arg_table_physics_state.html type physics_state @@ -10,10 +12,63 @@ module test_host_data real(kind_phys), dimension(:,:,:), pointer :: q => NULL() ! constituent array end type physics_state - public allocate_physics_state + !> \section arg_table_test_host_data Argument Table + !! \htmlinclude arg_table_test_host_data.html + integer, public, parameter :: num_consts = 3 + character(len=32), public, parameter :: std_name_array(num_consts) = (/ & + 'specific_humidity ', & + 'cloud_liquid_dry_mixing_ratio', & + 'cloud_ice_dry_mixing_ratio ' /) + character(len=32), public, parameter :: const_std_name = std_name_array(1) + + integer :: const_inds(num_consts) = -1 ! test array access from suite + integer :: const_index = -1 ! test scalar access from suite + + public :: allocate_physics_state + public :: check_constituent_indices contains + subroutine check_constituent_indices(test_index, test_indices, errmsg, errflg) + ! Check constituent indices against what was found by suite + ! indices are passed in rather than looked up to avoid a dependency loop + ! Dummy arguments + integer, intent(in) :: test_index ! scalar const index from host + integer, intent(in) :: test_indices(:) ! array_test_indices from host + character(len=*), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Local variable + integer :: indx + integer :: emstrt + + errflg = 0 + errmsg = '' + if (test_index /= const_index) then + emstrt = len_trim(errmsg) + 1 + write(errmsg(emstrt:), '(2a,i0,a,i0)') 'const_index_check for ', & + const_std_name, test_index, ' /= ', const_index + errflg = errflg + 1 + end if + do indx = 1, num_consts + if (test_indices(indx) /= const_inds(indx)) then + emstrt = len_trim(errmsg) + 1 + if (len_trim(errmsg) > 0) then + write(errmsg(emstrt:), '(", ")') + emstrt = emstrt + 2 + end if + write(errmsg(emstrt:), '(2a,i0,a,i0)') 'const_indices_check for ', & + std_name_array(indx), test_indices(indx), ' /= ', const_inds(indx) + errflg = errflg + 1 + end if + end do + + ! Reset for next test + const_index = -1 + const_inds = -1 + + end subroutine check_constituent_indices + subroutine allocate_physics_state(cols, levels, constituents, state) integer, intent(in) :: cols integer, intent(in) :: levels diff --git a/test/advection_test/test_host_data.meta b/test/advection_test/test_host_data.meta index d256d2ec..a676f141 100644 --- a/test/advection_test/test_host_data.meta +++ b/test/advection_test/test_host_data.meta @@ -30,3 +30,41 @@ kind = kind_phys units = kg kg-1 dimensions = (horizontal_dimension, vertical_layer_dimension) + +[ccpp-table-properties] + name = test_host_data + type = module +[ccpp-arg-table] + name = test_host_data + type = module +[ num_consts ] + standard_name = banana_array_dim + long_name = Size of test_banana_name_array + units = 1 + dimensions = () + type = integer +[ std_name_array ] + standard_name = test_banana_name_array + type = character | kind = len=32 + units = count + dimensions = (banana_array_dim) + protected = true +[ const_std_name ] + standard_name = test_banana_name + type = character | kind = len=32 + units = 1 + dimensions = () + protected = true +[ const_inds ] + standard_name = test_banana_constituent_indices + long_name = Array of constituent indices + units = 1 + dimensions = (banana_array_dim) + protected = true + type = integer +[ const_index ] + standard_name = test_banana_constituent_index + long_name = Constituent index + units = 1 + dimensions = () + type = integer diff --git a/test/advection_test/test_host_mod.F90 b/test/advection_test/test_host_mod.F90 index 0ae75b3d..50826f17 100644 --- a/test/advection_test/test_host_mod.F90 +++ b/test/advection_test/test_host_mod.F90 @@ -10,7 +10,7 @@ module test_host_mod real(kind_phys), parameter :: tolerance = 1.0e-13_kind_phys !> \section arg_table_test_host_mod Argument Table - !! \htmlinclude arg_table_test_host_host.html + !! \htmlinclude arg_table_test_host_mod.html !! integer, parameter :: ncols = 10 integer, parameter :: pver = 5 diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py deleted file mode 100644 index 2a51a411..00000000 --- a/test/advection_test/test_reports.py +++ /dev/null @@ -1,184 +0,0 @@ -#! /usr/bin/env python3 -""" ------------------------------------------------------------------------ - Description: Test advection database report python interface - - Assumptions: - - Command line arguments: build_dir database_filepath - - Usage: python test_reports ------------------------------------------------------------------------ -""" -import sys -import os - -_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) -_SCRIPTS_DIR = os.path.abspath(os.path.join(_FRAMEWORK_DIR, "scripts")) - -if not os.path.exists(_SCRIPTS_DIR): - raise ImportError("Cannot find scripts directory") -# end if - -if ((sys.version_info[0] < 3) or - (sys.version_info[0] == 3) and (sys.version_info[1] < 8)): - raise Exception("Python 3.8 or greater required") -# end if - -sys.path.append(_SCRIPTS_DIR) -# pylint: disable=wrong-import-position -from ccpp_datafile import datatable_report, DatatableReport -# pylint: enable=wrong-import-position - -def usage(errmsg=None): - """Raise an exception with optional error message and usage message""" - emsg = "usage: {} " - if errmsg: - emsg = errmsg + '\n' + emsg - # end if - raise ValueError(emsg.format(sys.argv[0])) - -if len(sys.argv) != 3: - usage() -# end if - -_BUILD_DIR = os.path.abspath(sys.argv[1]) -_DATABASE = os.path.abspath(sys.argv[2]) -if not os.path.isdir(_BUILD_DIR): - _EMSG = " must be an existing build directory" - usage(_EMSG) -# end if -if (not os.path.exists(_DATABASE)) or (not os.path.isfile(_DATABASE)): - _EMSG = " must be an existing CCPP database file" - usage(_EMSG) -# end if - -# Check data -_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] -_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_cld_suite_cap.F90")] -_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), - os.path.join(_FRAMEWORK_DIR, "src", - "ccpp_constituent_prop_mod.F90"), - os.path.join(_FRAMEWORK_DIR, "src", "ccpp_hashable.F90"), - os.path.join(_FRAMEWORK_DIR, "src", "ccpp_hash_table.F90")] -_CCPP_FILES = _UTILITY_FILES + _HOST_FILES + _SUITE_FILES -_PROCESS_LIST = list() -_MODULE_LIST = ["cld_ice", "cld_liq", "apply_constituent_tendencies"] -_SUITE_LIST = ["cld_suite"] -_DYN_CONST_ROUTINES = ["cld_ice_dynamic_constituents", "cld_liq_dynamic_constituents"] -_REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", - "horizontal_loop_begin", "horizontal_loop_end", - "surface_air_pressure", "temperature", - "tendency_of_cloud_liquid_dry_mixing_ratio", - "time_step_for_physics", "water_temperature_at_freezing", - "water_vapor_specific_humidity", - "cloud_ice_dry_mixing_ratio", - "cloud_liquid_dry_mixing_ratio", - "ccpp_constituents", - "ccpp_constituent_tendencies", - "number_of_ccpp_constituents", - "dynamic_constituents_for_cld_ice", - "dynamic_constituents_for_cld_liq", - # Added by --debug option - "horizontal_dimension", - "vertical_layer_dimension"] -_INPUT_VARS_CLD = ["surface_air_pressure", "temperature", - "horizontal_loop_begin", "horizontal_loop_end", - "time_step_for_physics", "water_temperature_at_freezing", - "water_vapor_specific_humidity", - "cloud_ice_dry_mixing_ratio", - "cloud_liquid_dry_mixing_ratio", - "tendency_of_cloud_liquid_dry_mixing_ratio", - "ccpp_constituents", - "ccpp_constituent_tendencies", - "number_of_ccpp_constituents", - # Added by --debug option - "horizontal_dimension", - "vertical_layer_dimension"] -_OUTPUT_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", - "water_vapor_specific_humidity", "temperature", - "tendency_of_cloud_liquid_dry_mixing_ratio", - "cloud_ice_dry_mixing_ratio", - "ccpp_constituents", - "ccpp_constituent_tendencies", - "cloud_liquid_dry_mixing_ratio", - "dynamic_constituents_for_cld_ice", - "dynamic_constituents_for_cld_liq"] - -def fields_string(field_type, field_list, sep): - """Create an error string for field(s), . - is used to separate items in """ - indent = ' '*11 - if field_list: - if len(field_list) > 1: - field_str = "{} Fields: ".format(field_type) - else: - field_str = "{} Field: ".format(field_type) - # end if - fmsg = "\n{}{}{}".format(indent, field_str, sep.join(field_list)) - else: - fmsg = "" - # end if - return fmsg - -def check_datatable(database, report_type, check_list, sep=','): - """Run a database report and check the return string. - If an error is found, print an error message. - Return the number of errors""" - if sep is None: - sep = ',' - # end if - test_str = datatable_report(database, report_type, sep) - test_list = [x for x in test_str.split(sep) if x] - missing = list() - unexpected = list() - for item in check_list: - if item not in test_list: - missing.append(item) - # end if - # end for - for item in test_list: - if item not in check_list: - unexpected.append(item) - # end if - # end for - if missing or unexpected: - vmsg = "ERROR in {} datafile check:".format(report_type.action) - vmsg += fields_string("Missing", missing, sep) - vmsg += fields_string("Unexpected", unexpected, sep) - print(vmsg) - else: - print("{} report okay".format(report_type.action)) - # end if - return len(missing) + len(unexpected) - -NUM_ERRORS = 0 -print("Checking required files from python:") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("host_files"), - _HOST_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_files"), - _SUITE_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("utility_files"), - _UTILITY_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("ccpp_files"), - _CCPP_FILES) -print("\nChecking lists from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("process_list"), - _PROCESS_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("module_list"), - _MODULE_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), - _SUITE_LIST) -print("\nChecking variables for CLD suite from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="cld_suite"), - _REQUIRED_VARS_CLD) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="cld_suite"), - _INPUT_VARS_CLD) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", - value="cld_suite"), - _OUTPUT_VARS_CLD) - -sys.exit(NUM_ERRORS) diff --git a/test/capgen_test/.gitignore b/test/capgen_test/.gitignore deleted file mode 100644 index 378eac25..00000000 --- a/test/capgen_test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/test/capgen_test/CMakeLists.txt b/test/capgen_test/CMakeLists.txt index ccae4f08..7aa3b60c 100644 --- a/test/capgen_test/CMakeLists.txt +++ b/test/capgen_test/CMakeLists.txt @@ -1,188 +1,54 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -PROJECT(test_host) -ENABLE_LANGUAGE(Fortran) -include(CMakeForceCompiler) - -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) - -#------------------------------------------------------------------------------ -# -# Set where the CCPP Framework lives -# -#------------------------------------------------------------------------------ -get_filename_component(TEST_ROOT "${CMAKE_SOURCE_DIR}" DIRECTORY) -get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) #------------------------------------------------------------------------------ # # Create list of SCHEME_FILES, HOST_FILES, and SUITE_FILES # Paths should be relative to CMAKE_SOURCE_DIR (this file's directory) # #------------------------------------------------------------------------------ -LIST(APPEND SCHEME_FILES "temp_scheme_files.txt" "ddt_suite_files.txt") -LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") -LIST(APPEND SUITE_FILES "ddt_suite.xml" "temp_suite.xml") +set(SCHEME_FILES "setup_coeffs" "temp_set" "temp_adjust" "temp_calc_adjust") +set(SUITE_SCHEME_FILES "make_ddt" "environ_conditions") +set(HOST_FILES "test_host_data" "test_host_mod") +set(SUITE_FILES "ddt_suite.xml" "temp_suite.xml") + # HOST is the name of the executable we will build. # We assume there are files ${HOST}.meta and ${HOST}.F90 in CMAKE_SOURCE_DIR -SET(HOST "${CMAKE_PROJECT_NAME}") +set(HOST "test_host") -#------------------------------------------------------------------------------ -# -# End of project-specific input -# -#------------------------------------------------------------------------------ - -# By default, no verbose output -SET(VERBOSITY 0 CACHE STRING "Verbosity level of output (default: 0)") # By default, generated caps go in ccpp subdir -SET(CCPP_CAP_FILES "${CMAKE_BINARY_DIR}/ccpp" CACHE - STRING "Location of CCPP-generated cap files") - -SET(CCPP_FRAMEWORK ${CCPP_ROOT}/scripts) - -# Use rpaths on MacOSX -set(CMAKE_MACOSX_RPATH 1) - -#------------------------------------------------------------------------------ -# Set a default build type if none was specified -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - #message(STATUS "Setting build type to 'Debug' as none was specified.") - #set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" - "MinSizeRel" "RelWithDebInfo") -endif() - -ADD_COMPILE_OPTIONS(-O0) - -if (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") -# gfortran -# MESSAGE("gfortran being used.") - ADD_COMPILE_OPTIONS(-fcheck=all) - ADD_COMPILE_OPTIONS(-fbacktrace) - ADD_COMPILE_OPTIONS(-ffpe-trap=zero) - ADD_COMPILE_OPTIONS(-finit-real=nan) - ADD_COMPILE_OPTIONS(-ggdb) - ADD_COMPILE_OPTIONS(-ffree-line-length-none) - ADD_COMPILE_OPTIONS(-cpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "Intel") -# ifort -# MESSAGE("ifort being used.") - #ADD_COMPILE_OPTIONS(-check all) - ADD_COMPILE_OPTIONS(-fpe0) - ADD_COMPILE_OPTIONS(-warn) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-debug extended) - ADD_COMPILE_OPTIONS(-fpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "PGI") -# pgf90 -# MESSAGE("pgf90 being used.") - ADD_COMPILE_OPTIONS(-g) - ADD_COMPILE_OPTIONS(-Mipa=noconst) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-Mfree) - ADD_COMPILE_OPTIONS(-Mfptrap) - ADD_COMPILE_OPTIONS(-Mpreprocess) -else (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - message (WARNING "This program has only been compiled with gfortran, pgf90 and ifort. If another compiler is needed, the appropriate flags SHOULD be added in ${CMAKE_SOURCE_DIR}/CMakeLists.txt") -endif (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - -#------------------------------------------------------------------------------ -# CMake Modules -# Set the CMake module path -list(APPEND CMAKE_MODULE_PATH "${CCPP_FRAMEWORK}/cmake") -#------------------------------------------------------------------------------ -# Set OpenMP flags for C/C++/Fortran -if (OPENMP) - include(detect_openmp) - detect_openmp() - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") - message(STATUS "Enable OpenMP support for C/C++/Fortran compiler") -else(OPENMP) - message (STATUS "Disable OpenMP support for C/C++/Fortran compiler") -endif() - -# Create metadata and source file lists -FOREACH(FILE ${SCHEME_FILES}) - FILE(STRINGS ${FILE} FILENAMES) - LIST(APPEND SCHEME_FILENAMES ${FILENAMES}) -ENDFOREACH(FILE) -string(REPLACE ";" "," SCHEME_METADATA "${SCHEME_FILES}") - -FOREACH(FILE ${SCHEME_FILENAMES}) - # target_sources prefers absolute pathnames - string(REPLACE ".meta" ".F90" TEMP "${FILE}") - get_filename_component(ABS_PATH "${TEMP}" ABSOLUTE) - list(APPEND LIBRARY_LIST ${ABS_PATH}) -ENDFOREACH(FILE) - -FOREACH(FILE ${HOST_FILES}) - LIST(APPEND HOST_METADATA "${FILE}.meta") - # target_sources prefers absolute pathnames - get_filename_component(ABS_PATH "${FILE}.F90" ABSOLUTE) - LIST(APPEND HOST_SOURCE "${ABS_PATH}") -ENDFOREACH(FILE) -list(APPEND LIBRARY_LIST ${HOST_SOURCE}) -string(REPLACE ";" ".meta," HOST_METADATA "${HOST_FILES}") -set(HOST_METADATA "${HOST_METADATA}.meta,${HOST}.meta") - -string(REPLACE ";" "," SUITE_XML "${SUITE_FILES}") - -# Run ccpp_capgen -set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") -list(APPEND CAPGEN_CMD "--host-files") -list(APPEND CAPGEN_CMD "${HOST_METADATA}") -list(APPEND CAPGEN_CMD "--scheme-files") -list(APPEND CAPGEN_CMD "${SCHEME_METADATA}") -list(APPEND CAPGEN_CMD "--suites") -list(APPEND CAPGEN_CMD "${SUITE_XML}") -list(APPEND CAPGEN_CMD "--host-name") -list(APPEND CAPGEN_CMD "test_host") -list(APPEND CAPGEN_CMD "--output-root") -list(APPEND CAPGEN_CMD "${CCPP_CAP_FILES}") -while (VERBOSITY GREATER 0) - list(APPEND CAPGEN_CMD "--verbose") - MATH(EXPR VERBOSITY "${VERBOSITY} - 1") -endwhile () -list(APPEND CAPGEN_CMD "--debug") -string(REPLACE ";" " " CAPGEN_STRING "${CAPGEN_CMD}") -MESSAGE(STATUS "Running: ${CAPGEN_STRING}") -EXECUTE_PROCESS(COMMAND ${CAPGEN_CMD} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE CAPGEN_OUT ERROR_VARIABLE CAPGEN_OUT RESULT_VARIABLE RES) -MESSAGE(STATUS "${CAPGEN_OUT}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap generation completed") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap generation FAILED: result = ${RES}") -endif(RES EQUAL 0) - -# Retrieve the list of files from datatable.xml and set to CCPP_CAPS -set(DTABLE_CMD "${CCPP_FRAMEWORK}/ccpp_datafile.py") -list(APPEND DTABLE_CMD "${CCPP_CAP_FILES}/datatable.xml") -list(APPEND DTABLE_CMD "--ccpp-files") -list(APPEND DTABLE_CMD "--separator=\\;") -string(REPLACE ";" " " DTABLE_STRING "${DTABLE_CMD}") -MESSAGE(STATUS "Running: ${DTABLE_STRING}") -EXECUTE_PROCESS(COMMAND ${DTABLE_CMD} OUTPUT_VARIABLE CCPP_CAPS - RESULT_VARIABLE RES - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) -message(STATUS "CCPP_CAPS = ${CCPP_CAPS}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap files retrieved") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap file retrieval FAILED: result = ${RES}") -endif(RES EQUAL 0) -list(APPEND LIBRARY_LIST ${CCPP_CAPS}) -add_library(TESTLIB OBJECT ${LIBRARY_LIST}) -ADD_EXECUTABLE(${HOST} ${HOST}.F90 $) - -INCLUDE_DIRECTORIES(${CCPP_CAP_FILES}) - -set_target_properties(${HOST} PROPERTIES - COMPILE_FLAGS "${CMAKE_Fortran_FLAGS}" - LINK_FLAGS "${CMAKE_Fortran_FLAGS}") +set(CCPP_CAP_FILES "${CMAKE_CURRENT_BINARY_DIR}/ccpp") + +# Create lists for Fortran and meta data files from file names +list(TRANSFORM SCHEME_FILES APPEND ".F90" OUTPUT_VARIABLE SCHEME_FORTRAN_FILES) +list(TRANSFORM SCHEME_FILES APPEND ".meta" OUTPUT_VARIABLE SCHEME_META_FILES) +list(TRANSFORM SUITE_SCHEME_FILES APPEND ".F90" OUTPUT_VARIABLE SUITE_SCHEME_FORTRAN_FILES) +list(TRANSFORM SUITE_SCHEME_FILES APPEND ".meta" OUTPUT_VARIABLE SUITE_SCHEME_META_FILES) +list(TRANSFORM HOST_FILES APPEND ".F90" OUTPUT_VARIABLE CAPGEN_HOST_FORTRAN_FILES) +list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE CAPGEN_HOST_METADATA_FILES) + +list(APPEND CAPGEN_HOST_METADATA_FILES "${HOST}.meta") + +ccpp_capgen(CAPGEN_DEBUG ON + VERBOSITY ${CCPP_VERBOSITY} + HOSTFILES ${CAPGEN_HOST_METADATA_FILES} + SCHEMEFILES ${SCHEME_META_FILES} ${SUITE_SCHEME_META_FILES} + SUITES ${SUITE_FILES} + HOST_NAME ${HOST} + OUTPUT_ROOT "${CCPP_CAP_FILES}") + +# Retrieve the list of Fortran files required for test host from datatable.xml and set to CCPP_CAPS_LIST +ccpp_datafile(DATATABLE "${CCPP_CAP_FILES}/datatable.xml" + REPORT_NAME "--ccpp-files") + +# Create test host library +add_library(CAPGEN_TESTLIB OBJECT ${SCHEME_FORTRAN_FILES} + ${SUITE_SCHEME_FORTRAN_FILES} + ${CAPGEN_HOST_FORTRAN_FILES} + ${CCPP_CAPS_LIST}) + +# Setup test executable with needed dependencies +add_executable(capgen_host_integration test_capgen_host_integration.F90 ${HOST}.F90) +target_link_libraries(capgen_host_integration PRIVATE CAPGEN_TESTLIB test_utils) +target_include_directories(capgen_host_integration PRIVATE "$") + +# Add executable to be called with ctest +add_test(NAME ctest_capgen_host_integration COMMAND capgen_host_integration) diff --git a/test/capgen_test/README.md b/test/capgen_test/README.md index 127544e0..f989cbc0 100644 --- a/test/capgen_test/README.md +++ b/test/capgen_test/README.md @@ -1,6 +1,20 @@ -ccpp_capgen test -=========== +# Capgen Test -To build and run the ccpp_capgen test, run ./run_test -This script will build and run the test. -The exit code is zero (0) on PASS and non-zero on FAIL. +Contains tests for overall capgen capabilities such as: +- Multiple suites +- Multiple groups +- General DDT usage +- Dimensions with `ccpp_constant_one:N` and just `N` +- Non-standard dimensions (not just horizontal and vertical) (including integer dimensions) +- Variables that should be promoted to suite level + +## Building/Running + +To explicitly build/run the capgen test host, run: + +```bash +$ cmake -S -B -DCCPP_RUN_CAPGEN_TEST=ON +$ cd +$ make +$ ctest +``` diff --git a/test/capgen_test/capgen_test_reports.py b/test/capgen_test/capgen_test_reports.py new file mode 100644 index 00000000..b5eb60d0 --- /dev/null +++ b/test/capgen_test/capgen_test_reports.py @@ -0,0 +1,150 @@ +#! /usr/bin/env python3 +""" +----------------------------------------------------------------------- + Description: Test capgen database report python interface + + Assumptions: + + Command line arguments: build_dir database_filepath + + Usage: python test_reports +----------------------------------------------------------------------- +""" +import os +import unittest + +from test_stub import BaseTests + +_BUILD_DIR = os.path.join(os.path.abspath(os.environ['BUILD_DIR']), "test", "capgen_test") +_DATABASE = os.path.abspath(os.path.join(_BUILD_DIR, "ccpp", "datatable.xml")) + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) +_SCRIPTS_DIR = os.path.join(_FRAMEWORK_DIR, "scripts") +_SRC_DIR = os.path.join(_FRAMEWORK_DIR, "src") + +# Check data +_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] +_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] +_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), + os.path.join(_SRC_DIR, "ccpp_constituent_prop_mod.F90"), + os.path.join(_SRC_DIR, "ccpp_scheme_utils.F90"), + os.path.join(_SRC_DIR, "ccpp_hashable.F90"), + os.path.join(_SRC_DIR, "ccpp_hash_table.F90")] +_CCPP_FILES = _UTILITY_FILES + \ + [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] +_DEPENDENCIES = [os.path.join(_TEST_DIR, "adjust", "qux.F90"), + os.path.join(_TEST_DIR, "bar.F90"), + os.path.join(_TEST_DIR, "foo.F90")] +_PROCESS_LIST = ["setter=temp_set", "adjusting=temp_calc_adjust"] +_MODULE_LIST = ["environ_conditions", "make_ddt", "setup_coeffs", "temp_adjust", + "temp_calc_adjust", "temp_set"] +_SUITE_LIST = ["ddt_suite", "temp_suite"] +_INPUT_VARS_DDT = ["model_times", "number_of_model_times", + "horizontal_loop_begin", "horizontal_loop_end", + "surface_air_pressure", "horizontal_dimension"] +_OUTPUT_VARS_DDT = ["ccpp_error_code", "ccpp_error_message", "model_times", + "surface_air_pressure", "number_of_model_times"] +_REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT +_PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", + "horizontal_dimension", "vertical_layer_dimension", + "number_of_tracers", + "lower_bound_of_vertical_dimension_of_soil", + "upper_bound_of_vertical_dimension_of_soil", + "configuration_variable", + # Added for --debug + "index_of_water_vapor_specific_humidity", + "vertical_interface_dimension"] +_REQUIRED_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", + "potential_temperature", + "potential_temperature_at_interface", + "coefficients_for_interpolation", + "potential_temperature_increment", + "surface_air_pressure", "time_step_for_physics", + "water_vapor_specific_humidity", + "soil_levels", + "temperature_at_diagnostic_levels", + "array_variable_for_testing"] +_INPUT_VARS_TEMP = ["potential_temperature", + "potential_temperature_at_interface", + "coefficients_for_interpolation", + "potential_temperature_increment", + "surface_air_pressure", "time_step_for_physics", + "water_vapor_specific_humidity", + "soil_levels", + "temperature_at_diagnostic_levels", + "array_variable_for_testing"] +_OUTPUT_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", + "potential_temperature", + "potential_temperature_at_interface", + "coefficients_for_interpolation", + "surface_air_pressure", "water_vapor_specific_humidity", + "soil_levels", + "temperature_at_diagnostic_levels", + "array_variable_for_testing"] + + +class TestCapgenHostDataTables(unittest.TestCase, BaseTests.TestHostDataTables): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + + +class CommandLineCapgenHostDatafileRequiredFiles(unittest.TestCase, BaseTests.TestHostCommandLineDataFiles): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" + + +class TestCapgenDdtSuite(unittest.TestCase, BaseTests.TestSuite): + database = _DATABASE + required_vars = _REQUIRED_VARS_DDT + input_vars = _INPUT_VARS_DDT + output_vars = _OUTPUT_VARS_DDT + suite_name = "ddt_suite" + + +class CommandLineCapgenDdtSuite(unittest.TestCase, BaseTests.TestSuiteCommandLine): + database = _DATABASE + required_vars = _REQUIRED_VARS_DDT + input_vars = _INPUT_VARS_DDT + output_vars = _OUTPUT_VARS_DDT + suite_name = "ddt_suite" + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" + + +class TestCapgenTempSuite(unittest.TestCase, BaseTests.TestSuiteExcludeProtected): + database = _DATABASE + required_vars = _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP + input_vars = _INPUT_VARS_TEMP + _PROT_VARS_TEMP + required_vars_excluding_protected = _REQUIRED_VARS_TEMP + input_vars_excluding_protected = _INPUT_VARS_TEMP + output_vars = _OUTPUT_VARS_TEMP + suite_name = "temp_suite" + + +class CommandLineCapgenTempSuite(unittest.TestCase, BaseTests.TestSuiteExcludeProtectedCommandLine): + database = _DATABASE + required_vars = _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP + input_vars = _INPUT_VARS_TEMP + _PROT_VARS_TEMP + required_vars_excluding_protected = _REQUIRED_VARS_TEMP + input_vars_excluding_protected = _INPUT_VARS_TEMP + output_vars = _OUTPUT_VARS_TEMP + suite_name = "temp_suite" + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" diff --git a/test/capgen_test/ddt_suite_files.txt b/test/capgen_test/ddt_suite_files.txt deleted file mode 100644 index 7f96a84c..00000000 --- a/test/capgen_test/ddt_suite_files.txt +++ /dev/null @@ -1,2 +0,0 @@ -make_ddt.meta -environ_conditions.meta diff --git a/test/capgen_test/make_ddt.meta b/test/capgen_test/make_ddt.meta index b9dbd8a9..a252df09 100644 --- a/test/capgen_test/make_ddt.meta +++ b/test/capgen_test/make_ddt.meta @@ -12,7 +12,7 @@ [ vmr_array ] standard_name = array_of_volume_mixing_ratios units = ppmv - dimensions = (horizontal_loop_extent, number_of_chemical_species) + dimensions = (horizontal_dimension, number_of_chemical_species) type = real kind = kind_phys [ccpp-table-properties] diff --git a/test/capgen_test/run_test b/test/capgen_test/run_test deleted file mode 100755 index f0b71aa2..00000000 --- a/test/capgen_test/run_test +++ /dev/null @@ -1,284 +0,0 @@ -#! /bin/bash - -currdir="`pwd -P`" -scriptdir="$( cd $( dirname $0 ); pwd -P )" - -## -## Option default values -## -defdir="ct_build" -build_dir="${currdir}/${defdir}" -cleanup="PASS" # Other supported options are ALWAYS and NEVER -verbosity=0 - -## -## General syntax help function -## Usage: help -## -help () { - local hname="Usage: `basename ${0}`" - local hprefix="`echo ${hname} | tr '[!-~]' ' '`" - echo "${hname} [ --build-dir ] [ --cleanup ]" - echo "${hprefix} [ --verbosity <#> ]" - hprefix=" " - echo "" - echo "${hprefix} : Directory for building and running the test" - echo "${hprefix} default is /${defdir}" - echo "${hprefix} : Cleanup option is ALWAYS, NEVER, or PASS" - echo "${hprefix} default is PASS" - echo "${hprefix} verbosity: 0, 1, or 2" - echo "${hprefix} default is 0" - exit $1 -} - -## -## Error output function (should be handed a string) -## -perr() { - >&2 echo -e "\nERROR: ${@}\n" - exit 1 -} - -## -## Cleanup the build and test directory -## -docleanup() { - # We start off in the build directory - if [ "${build_dir}" == "${currdir}" ]; then - echo "WARNING: Cannot clean ${build_dir}" - else - cd ${currdir} - rm -rf ${build_dir} - fi -} - -## Process our input arguments -while [ $# -gt 0 ]; do - case $1 in - --h | -h | --help | -help) - help 0 - ;; - --build-dir) - if [ $# -lt 2 ]; then - perr "${1} requires a build directory" - fi - build_dir="${2}" - shift - ;; - --cleanup) - if [ $# -lt 2 ]; then - perr "${1} requies a cleanup option (ALWAYS, NEVER, PASS)" - fi - if [ "${2}" == "ALWAYS" -o "${2}" == "NEVER" -o "${2}" == "PASS" ]; then - cleanup="${2}" - else - perr "Allowed cleanup options: ALWAYS, NEVER, PASS" - fi - shift - ;; - --verbosity) - if [ $# -lt 2 ]; then - perr "${1} requires a verbosity value (0, 1, or 2)" - fi - if [ "${2}" == "0" -o "${2}" == "1" -o "${2}" == "2" ]; then - verbosity=$2 - else - perr "allowed verbosity levels are 0, 1, 2" - fi - shift - ;; - *) - perr "Unrecognized option, \"${1}\"" - ;; - esac - shift -done - -# Create the build directory, if necessary -if [ -d "${build_dir}" ]; then - # Always make sure build_dir is not in the test dir - if [ "$( cd ${build_dir}; pwd -P )" == "${currdir}" ]; then - build_dir="${build_dir}/${defdir}" - fi -else - mkdir -p ${build_dir} - res=$? - if [ $res -ne 0 ]; then - perr "Unable to create build directory, '${build_dir}'" - fi -fi -build_dir="$( cd ${build_dir}; pwd -P )" - -## framework is the CCPP Framework root dir -framework="$( cd $( dirname $( dirname ${scriptdir} ) ); pwd -P )" -frame_src="${framework}/src" - -## -## check strings for datafile command-list test -## NB: This has to be after build_dir is finalized -## -host_files="${build_dir}/ccpp/test_host_ccpp_cap.F90" -suite_files="${build_dir}/ccpp/ccpp_ddt_suite_cap.F90" -suite_files="${suite_files},${build_dir}/ccpp/ccpp_temp_suite_cap.F90" -utility_files="${build_dir}/ccpp/ccpp_kinds.F90" -utility_files="${utility_files},${frame_src}/ccpp_constituent_prop_mod.F90" -utility_files="${utility_files},${frame_src}/ccpp_hashable.F90" -utility_files="${utility_files},${frame_src}/ccpp_hash_table.F90" -ccpp_files="${utility_files}" -ccpp_files="${ccpp_files},${build_dir}/ccpp/test_host_ccpp_cap.F90" -ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_ddt_suite_cap.F90" -ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_temp_suite_cap.F90" -process_list="adjusting=temp_calc_adjust,setter=temp_set" -module_list="environ_conditions,make_ddt,setup_coeffs,temp_adjust,temp_calc_adjust,temp_set" -dependencies="${scriptdir}/adjust/qux.F90,${scriptdir}/bar.F90,${scriptdir}/foo.F90" -suite_list="ddt_suite;temp_suite" -required_vars_ddt="ccpp_error_code,ccpp_error_message,horizontal_dimension" -required_vars_ddt="${required_vars_ddt},horizontal_loop_begin" -required_vars_ddt="${required_vars_ddt},horizontal_loop_end" -required_vars_ddt="${required_vars_ddt},model_times" -required_vars_ddt="${required_vars_ddt},number_of_model_times" -required_vars_ddt="${required_vars_ddt},surface_air_pressure" -input_vars_ddt="horizontal_dimension" -input_vars_ddt="${input_vars_ddt},horizontal_loop_begin" -input_vars_ddt="${input_vars_ddt},horizontal_loop_end" -input_vars_ddt="${input_vars_ddt},model_times,number_of_model_times" -input_vars_ddt="${input_vars_ddt},surface_air_pressure" -output_vars_ddt="ccpp_error_code,ccpp_error_message" -output_vars_ddt="${output_vars_ddt},model_times,number_of_model_times,surface_air_pressure" -required_vars_temp="ccpp_error_code,ccpp_error_message" -required_vars_temp="${required_vars_temp},coefficients_for_interpolation" -required_vars_temp="${required_vars_temp},configuration_variable" -required_vars_temp="${required_vars_temp},horizontal_dimension" -required_vars_temp="${required_vars_temp},horizontal_loop_begin" -required_vars_temp="${required_vars_temp},horizontal_loop_end" -required_vars_temp="${required_vars_temp},index_of_water_vapor_specific_humidity" -required_vars_temp="${required_vars_temp},number_of_tracers" -required_vars_temp="${required_vars_temp},potential_temperature" -required_vars_temp="${required_vars_temp},potential_temperature_at_interface" -required_vars_temp="${required_vars_temp},potential_temperature_increment" -required_vars_temp="${required_vars_temp},surface_air_pressure" -required_vars_temp="${required_vars_temp},time_step_for_physics" -required_vars_temp="${required_vars_temp},vertical_interface_dimension" -required_vars_temp="${required_vars_temp},vertical_layer_dimension" -required_vars_temp="${required_vars_temp},water_vapor_specific_humidity" -input_vars_temp="coefficients_for_interpolation" -input_vars_temp="${input_vars_temp},configuration_variable" -input_vars_temp="${input_vars_temp},horizontal_dimension" -input_vars_temp="${input_vars_temp},horizontal_loop_begin" -input_vars_temp="${input_vars_temp},horizontal_loop_end" -input_vars_temp="${input_vars_temp},index_of_water_vapor_specific_humidity" -input_vars_temp="${input_vars_temp},number_of_tracers" -input_vars_temp="${input_vars_temp},potential_temperature" -input_vars_temp="${input_vars_temp},potential_temperature_at_interface" -input_vars_temp="${input_vars_temp},potential_temperature_increment" -input_vars_temp="${input_vars_temp},surface_air_pressure,time_step_for_physics" -input_vars_temp="${input_vars_temp},vertical_interface_dimension" -input_vars_temp="${input_vars_temp},vertical_layer_dimension" -input_vars_temp="${input_vars_temp},water_vapor_specific_humidity" -output_vars_temp="ccpp_error_code,ccpp_error_message" -output_vars_temp="${output_vars_temp},coefficients_for_interpolation" -output_vars_temp="${output_vars_temp},potential_temperature" -output_vars_temp="${output_vars_temp},potential_temperature_at_interface" -output_vars_temp="${output_vars_temp},surface_air_pressure" -output_vars_temp="${output_vars_temp},water_vapor_specific_humidity" - -## -## Run a database report and check the return string -## $1 is the report program file -## $2 is the database file -## $3 is the report string -## $4 is the check string -## $5+ are any optional arguments -## -check_datatable() { - local checkstr=${4} - local teststr - local prog=${1} - local database=${2} - local report=${3} - shift 4 - echo "Checking ${report} report" - teststr="`${prog} ${database} ${report} $@`" - if [ "${teststr}" != "${checkstr}" ]; then - perr "datatable check:\nExpected: '${checkstr}'\nGot: '${teststr}'" - fi -} - -# cd to the build directory -cd ${build_dir} -res=$? -if [ $res -ne 0 ]; then - perr "Unable to cd to build directory, '${build_dir}'" -fi -# Clean build directory -rm -rf * -res=$? -if [ $res -ne 0 ]; then - perr "Unable to clean build directory, '${build_dir}'" -fi -# Run CMake -opts="" -if [ $verbosity -gt 0 ]; then - opts="${opts} -DVERBOSITY=${verbosity}" -fi -# Run cmake -cmake ${scriptdir} ${opts} -res=$? -if [ $res -ne 0 ]; then - perr "CMake failed with exit code, ${res}" -fi -# Test the datafile user interface -report_prog="${framework}/scripts/ccpp_datafile.py" -datafile="${build_dir}/ccpp/datatable.xml" -echo "Running python interface tests" -python3 ${scriptdir}/test_reports.py ${build_dir} ${datafile} -res=$? -if [ $res -ne 0 ]; then - perr "python interface tests failed" -fi -echo "Running command line tests" -echo "Checking required files from command line:" -check_datatable ${report_prog} ${datafile} "--host-files" ${host_files} -check_datatable ${report_prog} ${datafile} "--suite-files" ${suite_files} -check_datatable ${report_prog} ${datafile} "--utility-files" ${utility_files} -check_datatable ${report_prog} ${datafile} "--ccpp-files" ${ccpp_files} -echo -e "\nChecking lists from command line" -check_datatable ${report_prog} ${datafile} "--process-list" ${process_list} -check_datatable ${report_prog} ${datafile} "--module-list" ${module_list} -check_datatable ${report_prog} ${datafile} "--dependencies" ${dependencies} -check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ - --sep ";" -echo -e "\nChecking variables for DDT suite from command line" -check_datatable ${report_prog} ${datafile} "--required-variables" \ - ${required_vars_ddt} "ddt_suite" -check_datatable ${report_prog} ${datafile} "--input-variables" \ - ${input_vars_ddt} "ddt_suite" -check_datatable ${report_prog} ${datafile} "--output-variables" \ - ${output_vars_ddt} "ddt_suite" -echo -e "\nChecking variables for temp suite from command line" -check_datatable ${report_prog} ${datafile} "--required-variables" \ - ${required_vars_temp} "temp_suite" -check_datatable ${report_prog} ${datafile} "--input-variables" \ - ${input_vars_temp} "temp_suite" -check_datatable ${report_prog} ${datafile} "--output-variables" \ - ${output_vars_temp} "temp_suite" -# Run make -make -res=$? -if [ $res -ne 0 ]; then - perr "make failed with exit code, ${res}" -fi -# Run test -./test_host -res=$? -if [ $res -ne 0 ]; then - perr "test_host failed with exit code, ${res}" -fi - -if [ "${cleanup}" == "ALWAYS" ]; then - docleanup -elif [ $res -eq 0 -a "${cleanup}" == "PASS" ]; then - docleanup -fi - -exit $res diff --git a/test/capgen_test/temp_scheme_files.txt b/test/capgen_test/temp_scheme_files.txt deleted file mode 100644 index 6c831539..00000000 --- a/test/capgen_test/temp_scheme_files.txt +++ /dev/null @@ -1,4 +0,0 @@ -setup_coeffs.meta -temp_set.meta -temp_adjust.meta -temp_calc_adjust.meta diff --git a/test/capgen_test/temp_set.F90 b/test/capgen_test/temp_set.F90 index a780433f..760fbf25 100644 --- a/test/capgen_test/temp_set.F90 +++ b/test/capgen_test/temp_set.F90 @@ -18,17 +18,20 @@ MODULE temp_set !> \section arg_table_temp_set_run Argument Table !! \htmlinclude arg_table_temp_set_run.html !! - SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & - to_promote, promote_pcnst, errmsg, errflg) + SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp_diag, temp, ps, & + to_promote, promote_pcnst, slev_lbound, soil_levs, var_array, errmsg, errflg) !---------------------------------------------------------------- IMPLICIT NONE !---------------------------------------------------------------- - integer, intent(in) :: ncol, lev + integer, intent(in) :: ncol, lev, slev_lbound REAL(kind_phys), intent(out) :: temp(:,:) real(kind_phys), intent(in) :: timestep real(kind_phys), intent(in) :: ps(:) REAL(kind_phys), INTENT(inout) :: temp_level(:, :) + real(kind_phys), intent(inout) :: temp_diag(:,:) + real(kind_phys), intent(inout) :: soil_levs(slev_lbound:) + real(kind_phys), intent(inout) :: var_array(:,:,:,:) real(kind_phys), intent(out) :: to_promote(:, :) real(kind_phys), intent(out) :: promote_pcnst(:) character(len=512), intent(out) :: errmsg @@ -38,6 +41,7 @@ SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & integer :: col_index integer :: lev_index + real(kind_phys) :: internal_scalar_var errmsg = '' errflg = 0 @@ -56,6 +60,12 @@ SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & end do end do + var_array(:,:,:,:) = 1._kind_phys + + ! + internal_scalar_var = soil_levs(slev_lbound) + internal_scalar_var = soil_levs(0) + END SUBROUTINE temp_set_run !> \section arg_table_temp_set_init Argument Table diff --git a/test/capgen_test/temp_set.meta b/test/capgen_test/temp_set.meta index b6c403ce..d709da1e 100644 --- a/test/capgen_test/temp_set.meta +++ b/test/capgen_test/temp_set.meta @@ -32,6 +32,13 @@ type = real kind = kind_phys intent = inout +[ temp_diag ] + standard_name = temperature_at_diagnostic_levels + units = K + dimensions = (horizontal_loop_extent, 6) + type = real + kind = kind_phys + intent = inout [ temp ] standard_name = potential_temperature units = K @@ -61,6 +68,28 @@ type = real kind = kind_phys intent = out +[ slev_lbound ] + standard_name = lower_bound_of_vertical_dimension_of_soil + type = integer + units = count + dimensions = () + intent = in +[ soil_levs ] + standard_name = soil_levels + long_name = soil levels + units = cm + dimensions = (lower_bound_of_vertical_dimension_of_soil:upper_bound_of_vertical_dimension_of_soil) + type = real + kind = kind_phys + intent = inout +[ var_array ] + standard_name = array_variable_for_testing + long_name = array variable for testing + units = none + dimensions = (horizontal_loop_extent,2,4,6) + type = real + kind = kind_phys + intent = inout [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/test_capgen_host_integration.F90 b/test/capgen_test/test_capgen_host_integration.F90 new file mode 100644 index 00000000..745e5678 --- /dev/null +++ b/test/capgen_test/test_capgen_host_integration.F90 @@ -0,0 +1,86 @@ +program test + use test_prog, only: test_host, suite_info, cm, cs + + implicit none + + character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & + 'physics2 ' /) + character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) + character(len=cm), target :: test_invars1(10) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & + 'time_step_for_physics ', & + 'array_variable_for_testing ' /) + character(len=cm), target :: test_outvars1(10) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'array_variable_for_testing ' /) + character(len=cm), target :: test_reqvars1(12) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'array_variable_for_testing ' /) + + character(len=cm), target :: test_invars2(3) = (/ & + 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ' /) + + character(len=cm), target :: test_outvars2(5) = (/ & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'model_times ', & + 'surface_air_pressure ', & + 'number_of_model_times ' /) + + character(len=cm), target :: test_reqvars2(5) = (/ & + 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) + type(suite_info) :: test_suites(2) + logical :: run_okay + + ! Setup expected test suite info + test_suites(1)%suite_name = 'temp_suite' + test_suites(1)%suite_parts => test_parts1 + test_suites(1)%suite_input_vars => test_invars1 + test_suites(1)%suite_output_vars => test_outvars1 + test_suites(1)%suite_required_vars => test_reqvars1 + test_suites(2)%suite_name = 'ddt_suite' + test_suites(2)%suite_parts => test_parts2 + test_suites(2)%suite_input_vars => test_invars2 + test_suites(2)%suite_output_vars => test_outvars2 + test_suites(2)%suite_required_vars => test_reqvars2 + + call test_host(run_okay, test_suites) + + if (run_okay) then + STOP 0 + else + STOP -1 + end if + +end program test diff --git a/test/capgen_test/test_host.F90 b/test/capgen_test/test_host.F90 index 8f716322..9ed3f47c 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -24,92 +24,10 @@ module test_prog CONTAINS - logical function check_list(test_list, chk_list, list_desc, suite_name) - ! Check a list () against its expected value () - - ! Dummy arguments - character(len=*), intent(in) :: test_list(:) - character(len=*), intent(in) :: chk_list(:) - character(len=*), intent(in) :: list_desc - character(len=*), optional, intent(in) :: suite_name - - ! Local variables - logical :: found - integer :: num_items - integer :: lindex, tindex - integer, allocatable :: check_unique(:) - character(len=2) :: sep - character(len=256) :: errmsg - - check_list = .true. - errmsg = '' - - ! Check the list size - num_items = size(chk_list) - if (size(test_list) /= num_items) then - write(errmsg, '(a,i0,2a)') 'ERROR: Found ', size(test_list), & - ' ', trim(list_desc) - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' for suite, ', & - trim(suite_name) - end if - write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items - write(6, *) trim(errmsg) - errmsg = '' - check_list = .false. - end if - - ! Now, check the list contents for 1-1 correspondence - if (check_list) then - allocate(check_unique(num_items)) - check_unique = -1 - do lindex = 1, num_items - found = .false. - do tindex = 1, num_items - if (trim(test_list(lindex)) == trim(chk_list(tindex))) then - check_unique(tindex) = lindex - found = .true. - exit - end if - end do - if (.not. found) then - check_list = .false. - write(errmsg, '(5a)') 'ERROR: ', trim(list_desc), ' item, ', & - trim(test_list(lindex)), ', was not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - write(6, *) trim(errmsg) - errmsg = '' - end if - end do - if (check_list .and. ANY(check_unique < 0)) then - check_list = .false. - write(errmsg, '(3a)') 'ERROR: The following ', trim(list_desc), & - ' items were not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - sep = '; ' - do lindex = 1, num_items - if (check_unique(lindex) < 0) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') sep, & - trim(chk_list(lindex)) - sep = ', ' - end if - end do - write(6, *) trim(errmsg) - errmsg = '' - end if - end if - - end function check_list - logical function check_suite(test_suite) use test_host_ccpp_cap, only: ccpp_physics_suite_part_list use test_host_ccpp_cap, only: ccpp_physics_suite_variables + use test_utils, only: check_list ! Dummy argument type(suite_info), intent(in) :: test_suite @@ -195,6 +113,7 @@ subroutine test_host(retval, test_suites) use test_host_ccpp_cap, only: test_host_ccpp_physics_finalize use test_host_ccpp_cap, only: ccpp_physics_suite_list use test_host_mod, only: init_data, compare_data, check_model_times + use test_utils, only: check_list type(suite_info), intent(in) :: test_suites(:) logical, intent(out) :: retval @@ -359,81 +278,3 @@ subroutine test_host(retval, test_suites) end subroutine test_host end module test_prog - - program test - use test_prog, only: test_host, suite_info, cm, cs - - implicit none - - character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & - 'physics2 ' /) - character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(7) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ' /) - character(len=cm), target :: test_outvars1(7) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - character(len=cm), target :: test_reqvars1(9) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - - character(len=cm), target :: test_invars2(3) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ' /) - - character(len=cm), target :: test_outvars2(5) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'model_times ', & - 'surface_air_pressure ', & - 'number_of_model_times ' /) - - character(len=cm), target :: test_reqvars2(5) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - type(suite_info) :: test_suites(2) - logical :: run_okay - - ! Setup expected test suite info - test_suites(1)%suite_name = 'temp_suite' - test_suites(1)%suite_parts => test_parts1 - test_suites(1)%suite_input_vars => test_invars1 - test_suites(1)%suite_output_vars => test_outvars1 - test_suites(1)%suite_required_vars => test_reqvars1 - test_suites(2)%suite_name = 'ddt_suite' - test_suites(2)%suite_parts => test_parts2 - test_suites(2)%suite_input_vars => test_invars2 - test_suites(2)%suite_output_vars => test_outvars2 - test_suites(2)%suite_required_vars => test_reqvars2 - - call test_host(run_okay, test_suites) - - if (run_okay) then - STOP 0 - else - STOP -1 - end if - -end program test diff --git a/test/capgen_test/test_host_data.F90 b/test/capgen_test/test_host_data.F90 index 7a651fca..1b0a45c1 100644 --- a/test/capgen_test/test_host_data.F90 +++ b/test/capgen_test/test_host_data.F90 @@ -2,28 +2,33 @@ module test_host_data use ccpp_kinds, only: kind_phys + implicit none + private + !> \section arg_table_physics_state Argument Table !! \htmlinclude arg_table_physics_state.html type physics_state real(kind_phys), dimension(:), allocatable :: & - ps ! surface pressure + ps, & ! surface pressure + soil_levs ! soil temperature (cm) real(kind_phys), dimension(:,:), allocatable :: & u, & ! zonal wind (m/s) v, & ! meridional wind (m/s) pmid ! midpoint pressure (Pa) - real(kind_phys), dimension(:,:,:),allocatable :: & q ! constituent mixing ratio (kg/kg moist or dry air depending on type) end type physics_state - public allocate_physics_state + public :: physics_state + public :: allocate_physics_state contains - subroutine allocate_physics_state(cols, levels, constituents, state) + subroutine allocate_physics_state(cols, levels, constituents, lbnd_slev, ubnd_slev, state) integer, intent(in) :: cols integer, intent(in) :: levels integer, intent(in) :: constituents + integer, intent(in) :: lbnd_slev, ubnd_slev type(physics_state), intent(out) :: state if (allocated(state%ps)) then @@ -46,6 +51,10 @@ subroutine allocate_physics_state(cols, levels, constituents, state) deallocate(state%q) end if allocate(state%q(cols, levels, constituents)) - + if (allocated(state%soil_levs)) then + deallocate(state%soil_levs) + end if + allocate(state%soil_levs(lbnd_slev:ubnd_slev)) + end subroutine allocate_physics_state end module test_host_data diff --git a/test/capgen_test/test_host_data.meta b/test/capgen_test/test_host_data.meta index df4b92b4..0e73c060 100644 --- a/test/capgen_test/test_host_data.meta +++ b/test/capgen_test/test_host_data.meta @@ -35,6 +35,13 @@ kind = kind_phys units = Pa dimensions = (horizontal_dimension, vertical_layer_dimension) +[ soil_levs ] + standard_name = soil_levels + long_name = soil levels + units = cm + dimensions = (lower_bound_of_vertical_dimension_of_soil:upper_bound_of_vertical_dimension_of_soil) + type = real + kind = kind_phys [ q ] standard_name = constituent_mixing_ratio state_variable = true @@ -42,7 +49,7 @@ kind = kind_phys units = kg kg-1 moist or dry air depending on type dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_tracers) -[ q(:,:,index_of_water_vapor_specific_humidity) ] +[ q(:,:,index_of_water_vapor_specific_HUMidity) ] standard_name = water_vapor_specific_humidity state_variable = true type = real diff --git a/test/capgen_test/test_host_mod.F90 b/test/capgen_test/test_host_mod.F90 index 2ce1fbd6..f2586a77 100644 --- a/test/capgen_test/test_host_mod.F90 +++ b/test/capgen_test/test_host_mod.F90 @@ -13,12 +13,17 @@ module test_host_mod integer, parameter :: pver = 5 integer, parameter :: pverP = 6 integer, parameter :: pcnst = 2 + integer, parameter :: slevs = 4 + integer, parameter :: slev_lbound = -3 + integer, parameter :: slev_ubound = 0 integer, parameter :: DiagDimStart = 2 integer, parameter :: index_qv = 1 logical, parameter :: config_var = .true. real(kind_phys), allocatable :: temp_midpoints(:,:) real(kind_phys) :: temp_interfaces(ncols, pverP) + real(kind_phys) :: temp_diag(ncols,6) real(kind_phys) :: coeffs(ncols) + real(kind_phys) :: var_array(ncols,2,4,6) real(kind_phys), dimension(DiagDimStart:ncols, DiagDimStart:pver) :: & diag1, & diag2 @@ -56,7 +61,7 @@ subroutine init_data() end do end do ! Allocate and initialize state - call allocate_physics_state(ncols, pver, pcnst, phys_state) + call allocate_physics_state(ncols, pver, pcnst, slev_lbound, slev_ubound, phys_state) do cind = 1, pcnst do lev = 1, pver offsize = ((cind - 1) * (ncols * pver)) + ((lev - 1) * ncols) diff --git a/test/capgen_test/test_host_mod.meta b/test/capgen_test/test_host_mod.meta index ae0abd05..08627af0 100644 --- a/test/capgen_test/test_host_mod.meta +++ b/test/capgen_test/test_host_mod.meta @@ -5,7 +5,7 @@ name = test_host_mod type = module [ index_qv ] - standard_name = index_of_water_vapor_specific_humidity + standard_name = index_of_water_vapor_specific_HUMidity units = index type = integer protected = True @@ -40,6 +40,24 @@ units = count protected = True dimensions = () +[ slevs ] + standard_name = vertical_dimension_of_soil + type = integer + units = count + protected = True + dimensions = () +[ slev_lbound] + standard_name = lower_bound_of_vertical_dimension_of_soil + type = integer + units = count + protected = True + dimensions = () +[ slev_ubound] + standard_name = upper_bound_of_vertical_dimension_of_soil + type = integer + units = count + protected = True + dimensions = () [ DiagDimStart ] standard_name = first_index_of_diag_fields type = integer @@ -56,6 +74,11 @@ units = K dimensions = (horizontal_dimension, vertical_interface_dimension) type = real | kind = kind_phys +[ temp_diag ] + standard_name = temperature_at_diagnostic_levels + units = K + dimensions = (horizontal_dimension, 6) + type = real | kind = kind_phys [ diag1 ] standard_name = diagnostic_stuff_type_1 long_name = This is just a test field @@ -102,3 +125,9 @@ units = none dimensions = (horizontal_dimension) type = real | kind = kind_phys +[ var_array ] + standard_name = array_variable_for_testing + long_name = array variable for testing + units = none + dimensions = (horizontal_dimension,2,4,6) + type = real | kind = kind_phys diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py deleted file mode 100644 index ed123013..00000000 --- a/test/capgen_test/test_reports.py +++ /dev/null @@ -1,198 +0,0 @@ -#! /usr/bin/env python3 -""" ------------------------------------------------------------------------ - Description: Test capgen database report python interface - - Assumptions: - - Command line arguments: build_dir database_filepath - - Usage: python test_reports ------------------------------------------------------------------------ -""" -import sys -import os - -_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) -_SCRIPTS_DIR = os.path.join(_FRAMEWORK_DIR, "scripts") -_SRC_DIR = os.path.join(_FRAMEWORK_DIR, "src") - -if not os.path.exists(_SCRIPTS_DIR): - raise ImportError("Cannot find scripts directory") -# end if - -if ((sys.version_info[0] < 3) or - (sys.version_info[0] == 3) and (sys.version_info[1] < 8)): - raise Exception("Python 3.8 or greater required") -# end if - -sys.path.append(_SCRIPTS_DIR) -# pylint: disable=wrong-import-position -from ccpp_datafile import datatable_report, DatatableReport -# pylint: enable=wrong-import-position - -def usage(errmsg=None): - """Raise an exception with optional error message and usage message""" - emsg = "usage: {} " - if errmsg: - emsg = errmsg + '\n' + emsg - # end if - raise ValueError(emsg.format(sys.argv[0])) - -if len(sys.argv) != 3: - usage() -# end if - -_BUILD_DIR = os.path.abspath(sys.argv[1]) -_DATABASE = os.path.abspath(sys.argv[2]) -if not os.path.isdir(_BUILD_DIR): - _EMSG = " must be an existing build directory" - usage(_EMSG) -# end if -if (not os.path.exists(_DATABASE)) or (not os.path.isfile(_DATABASE)): - _EMSG = " must be an existing CCPP database file" - usage(_EMSG) -# end if - -# Check data -_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] -_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] -_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), - os.path.join(_SRC_DIR, "ccpp_constituent_prop_mod.F90"), - os.path.join(_SRC_DIR, "ccpp_hashable.F90"), - os.path.join(_SRC_DIR, "ccpp_hash_table.F90")] -_CCPP_FILES = _UTILITY_FILES + \ - [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] -_PROCESS_LIST = ["setter=temp_set", "adjusting=temp_calc_adjust"] -_MODULE_LIST = ["environ_conditions", "make_ddt", "setup_coeffs", "temp_adjust", - "temp_calc_adjust", "temp_set"] -_SUITE_LIST = ["ddt_suite", "temp_suite"] -_INPUT_VARS_DDT = ["model_times", "number_of_model_times", - "horizontal_loop_begin", "horizontal_loop_end", - "surface_air_pressure", "horizontal_dimension"] -_OUTPUT_VARS_DDT = ["ccpp_error_code", "ccpp_error_message", "model_times", - "surface_air_pressure", "number_of_model_times"] -_REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT -_PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", - "horizontal_dimension", "vertical_layer_dimension", - "number_of_tracers", - "configuration_variable", - # Added for --debug - "index_of_water_vapor_specific_humidity", - "vertical_interface_dimension"] -_REQUIRED_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", - "potential_temperature", - "potential_temperature_at_interface", - "coefficients_for_interpolation", - "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] -_INPUT_VARS_TEMP = ["potential_temperature", - "potential_temperature_at_interface", - "coefficients_for_interpolation", - "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] -_OUTPUT_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", - "potential_temperature", - "potential_temperature_at_interface", - "coefficients_for_interpolation", - "surface_air_pressure", "water_vapor_specific_humidity"] - -def fields_string(field_type, field_list, sep): - """Create an error string for field(s), . - is used to separate items in """ - indent = ' '*11 - if field_list: - if len(field_list) > 1: - field_str = "{} Fields: ".format(field_type) - else: - field_str = "{} Field: ".format(field_type) - # end if - fmsg = "\n{}{}{}".format(indent, field_str, sep.join(field_list)) - else: - fmsg = "" - # end if - return fmsg - -def check_datatable(database, report_type, check_list, - sep=',', exclude_protected=False): - """Run a database report and check the return string. - If an error is found, print an error message. - Return the number of errors""" - if sep is None: - sep = ',' - # end if - test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) - test_list = [x for x in test_str.split(sep) if x] - missing = list() - unexpected = list() - for item in check_list: - if item not in test_list: - missing.append(item) - # end if - # end for - for item in test_list: - if item not in check_list: - unexpected.append(item) - # end if - # end for - if missing or unexpected: - vmsg = "ERROR in {} datafile check:".format(report_type.action) - vmsg += fields_string("Missing", missing, sep) - vmsg += fields_string("Unexpected", unexpected, sep) - print(vmsg) - else: - print("{} report okay".format(report_type.action)) - # end if - return len(missing) + len(unexpected) - -NUM_ERRORS = 0 -print("Checking required files from python:") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("host_files"), - _HOST_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_files"), - _SUITE_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("utility_files"), - _UTILITY_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("ccpp_files"), - _CCPP_FILES) -print("\nChecking lists from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("process_list"), - _PROCESS_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("module_list"), - _MODULE_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), - _SUITE_LIST) -print("\nChecking variables for DDT suite from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="ddt_suite"), - _REQUIRED_VARS_DDT) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="ddt_suite"), - _INPUT_VARS_DDT) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", - value="ddt_suite"), - _OUTPUT_VARS_DDT) -print("\nChecking variables for temp suite from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="temp_suite"), - _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="temp_suite"), - _REQUIRED_VARS_TEMP, exclude_protected=True) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="temp_suite"), - _INPUT_VARS_TEMP + _PROT_VARS_TEMP) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="temp_suite"), - _INPUT_VARS_TEMP, exclude_protected=True) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", - value="temp_suite"), - _OUTPUT_VARS_TEMP) - -sys.exit(NUM_ERRORS) diff --git a/test/ddthost_test/CMakeLists.txt b/test/ddthost_test/CMakeLists.txt index 5bbe196d..cc257619 100644 --- a/test/ddthost_test/CMakeLists.txt +++ b/test/ddthost_test/CMakeLists.txt @@ -1,191 +1,54 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.15) -PROJECT(test_host) -ENABLE_LANGUAGE(Fortran) - -include(CMakeForceCompiler) - -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) -#------------------------------------------------------------------------------ -# -# Set where the CCPP Framework lives -# -#------------------------------------------------------------------------------ -get_filename_component(TEST_ROOT "${CMAKE_SOURCE_DIR}" DIRECTORY) -get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) #------------------------------------------------------------------------------ # # Create list of SCHEME_FILES, HOST_FILES, and SUITE_FILES # Paths should be relative to CMAKE_SOURCE_DIR (this file's directory) # #------------------------------------------------------------------------------ -LIST(APPEND SCHEME_FILES "temp_scheme_files.txt" "ddt_suite_files.txt") -LIST(APPEND HOST_FILES "test_host_data" "test_host_mod" "host_ccpp_ddt") -LIST(APPEND SUITE_FILES "ddt_suite.xml" "temp_suite.xml") +set(SCHEME_FILES "setup_coeffs" "temp_set" "temp_adjust" "temp_calc_adjust") +set(SUITE_SCHEME_FILES "make_ddt" "environ_conditions") +set(HOST_FILES "test_host_data" "test_host_mod" "host_ccpp_ddt") +set(SUITE_FILES "ddt_suite.xml" "temp_suite.xml") # HOST is the name of the executable we will build. # We assume there are files ${HOST}.meta and ${HOST}.F90 in CMAKE_SOURCE_DIR -SET(HOST "${CMAKE_PROJECT_NAME}") +set(HOST "test_host") -#------------------------------------------------------------------------------ -# -# End of project-specific input -# -#------------------------------------------------------------------------------ - -# By default, no verbose output -SET(VERBOSITY 0 CACHE STRING "Verbosity level of output (default: 0)") # By default, generated caps go in ccpp subdir -SET(CCPP_CAP_FILES "${CMAKE_BINARY_DIR}/ccpp" CACHE - STRING "Location of CCPP-generated cap files") - -SET(CCPP_FRAMEWORK ${CCPP_ROOT}/scripts) - -# Use rpaths on MacOSX -set(CMAKE_MACOSX_RPATH 1) +set(CCPP_CAP_FILES "${CMAKE_CURRENT_BINARY_DIR}/ccpp") -#------------------------------------------------------------------------------ -# Set a default build type if none was specified -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - #message(STATUS "Setting build type to 'Debug' as none was specified.") - #set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" - "MinSizeRel" "RelWithDebInfo") -endif() - -ADD_COMPILE_OPTIONS(-O0) - -if (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") -# gfortran -# MESSAGE("gfortran being used.") - ADD_COMPILE_OPTIONS(-fcheck=all) - ADD_COMPILE_OPTIONS(-fbacktrace) - ADD_COMPILE_OPTIONS(-ffpe-trap=zero) - ADD_COMPILE_OPTIONS(-finit-real=nan) - ADD_COMPILE_OPTIONS(-ggdb) - ADD_COMPILE_OPTIONS(-ffree-line-length-none) - ADD_COMPILE_OPTIONS(-cpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "Intel") -# ifort -# MESSAGE("ifort being used.") - #ADD_COMPILE_OPTIONS(-check all) - ADD_COMPILE_OPTIONS(-fpe0) - ADD_COMPILE_OPTIONS(-warn) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-debug extended) - ADD_COMPILE_OPTIONS(-fpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "PGI") -# pgf90 -# MESSAGE("pgf90 being used.") - ADD_COMPILE_OPTIONS(-g) - ADD_COMPILE_OPTIONS(-Mipa=noconst) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-Mfree) - ADD_COMPILE_OPTIONS(-Mfptrap) - ADD_COMPILE_OPTIONS(-Mpreprocess) -else (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - message (WARNING "This program has only been compiled with gfortran, pgf90 and ifort. If another compiler is needed, the appropriate flags SHOULD be added in ${CMAKE_SOURCE_DIR}/CMakeLists.txt") -endif (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - -#------------------------------------------------------------------------------ -# CMake Modules -# Set the CMake module path -list(APPEND CMAKE_MODULE_PATH "${CCPP_FRAMEWORK}/cmake") -#------------------------------------------------------------------------------ -# Set OpenMP flags for C/C++/Fortran -if (OPENMP) - include(detect_openmp) - detect_openmp() - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") - message(STATUS "Enable OpenMP support for C/C++/Fortran compiler") -else(OPENMP) - message (STATUS "Disable OpenMP support for C/C++/Fortran compiler") -endif() +# Create lists for Fortran and meta data files from file names +list(TRANSFORM SCHEME_FILES APPEND ".F90" OUTPUT_VARIABLE SCHEME_FORTRAN_FILES) +list(TRANSFORM SCHEME_FILES APPEND ".meta" OUTPUT_VARIABLE SCHEME_META_FILES) +list(TRANSFORM SUITE_SCHEME_FILES APPEND ".F90" OUTPUT_VARIABLE SUITE_SCHEME_FORTRAN_FILES) +list(TRANSFORM SUITE_SCHEME_FILES APPEND ".meta" OUTPUT_VARIABLE SUITE_SCHEME_META_FILES) +list(TRANSFORM HOST_FILES APPEND ".F90" OUTPUT_VARIABLE DDT_HOST_FORTRAN_FILES) +list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE DDT_HOST_METADATA_FILES) -# Create metadata and source file lists -FOREACH(FILE ${SCHEME_FILES}) - FILE(STRINGS ${FILE} FILENAMES) - LIST(APPEND SCHEME_FILENAMES ${FILENAMES}) -ENDFOREACH(FILE) -string(REPLACE ";" "," SCHEME_METADATA "${SCHEME_FILES}") - -FOREACH(FILE ${SCHEME_FILENAMES}) - # target_sources prefers absolute pathnames - string(REPLACE ".meta" ".F90" TEMP "${FILE}") - get_filename_component(ABS_PATH "${TEMP}" ABSOLUTE) - list(APPEND LIBRARY_LIST ${ABS_PATH}) -ENDFOREACH(FILE) - -FOREACH(FILE ${HOST_FILES}) - LIST(APPEND HOST_METADATA "${FILE}.meta") - # target_sources prefers absolute pathnames - get_filename_component(ABS_PATH "${FILE}.F90" ABSOLUTE) - LIST(APPEND HOST_SOURCE "${ABS_PATH}") -ENDFOREACH(FILE) -list(APPEND LIBRARY_LIST ${HOST_SOURCE}) -string(REPLACE ";" ".meta," HOST_METADATA "${HOST_FILES}") -set(HOST_METADATA "${HOST_METADATA}.meta,${HOST}.meta") - -string(REPLACE ";" "," SUITE_XML "${SUITE_FILES}") +list(APPEND DDT_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen -set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") -list(APPEND CAPGEN_CMD "--host-files") -list(APPEND CAPGEN_CMD "${HOST_METADATA}") -list(APPEND CAPGEN_CMD "--scheme-files") -list(APPEND CAPGEN_CMD "${SCHEME_METADATA}") -list(APPEND CAPGEN_CMD "--suites") -list(APPEND CAPGEN_CMD "${SUITE_XML}") -list(APPEND CAPGEN_CMD "--host-name") -list(APPEND CAPGEN_CMD "test_host") -list(APPEND CAPGEN_CMD "--output-root") -list(APPEND CAPGEN_CMD "${CCPP_CAP_FILES}") -string(REPEAT "--verbose;" ${VERBOSITY} VERBOSE_REPEATED) -list(APPEND CAPGEN_CMD ${VERBOSE_REPEATED}) -list(APPEND CAPGEN_CMD "--debug") -string(REPLACE ";" " " CAPGEN_STRING "${CAPGEN_CMD}") -MESSAGE(STATUS "Running: ${CAPGEN_STRING}") -EXECUTE_PROCESS(COMMAND ${CAPGEN_CMD} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE CAPGEN_OUT - ERROR_VARIABLE CAPGEN_OUT - RESULT_VARIABLE RES) -MESSAGE(STATUS "${CAPGEN_OUT}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap generation completed") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap generation FAILED: result = ${RES}") -endif(RES EQUAL 0) - -# Retrieve the list of files from datatable.xml and set to CCPP_CAPS -set(DTABLE_CMD "${CCPP_FRAMEWORK}/ccpp_datafile.py") -list(APPEND DTABLE_CMD "${CCPP_CAP_FILES}/datatable.xml") -list(APPEND DTABLE_CMD "--ccpp-files") -list(APPEND DTABLE_CMD "--separator=\\;") -string(REPLACE ";" " " DTABLE_STRING "${DTABLE_CMD}") -MESSAGE(STATUS "Running: ${DTABLE_STRING}") -EXECUTE_PROCESS(COMMAND ${DTABLE_CMD} - OUTPUT_VARIABLE CCPP_CAPS - RESULT_VARIABLE RES - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_STRIP_TRAILING_WHITESPACE) -message(STATUS "CCPP_CAPS = ${CCPP_CAPS}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap files retrieved") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap file retrieval FAILED: result = ${RES}") -endif(RES EQUAL 0) -list(APPEND LIBRARY_LIST ${CCPP_CAPS}) -add_library(TESTLIB OBJECT ${LIBRARY_LIST}) -ADD_EXECUTABLE(${HOST} ${HOST}.F90 $) - -INCLUDE_DIRECTORIES(${CCPP_CAP_FILES}) - -set_target_properties(${HOST} PROPERTIES - COMPILE_FLAGS "${CMAKE_Fortran_FLAGS}" - LINK_FLAGS "${CMAKE_Fortran_FLAGS}") +ccpp_capgen(CAPGEN_DEBUG ON + VERBOSITY ${CCPP_VERBOSITY} + HOSTFILES ${DDT_HOST_METADATA_FILES} + SCHEMEFILES ${SCHEME_META_FILES} ${SUITE_SCHEME_META_FILES} + SUITES ${SUITE_FILES} + HOST_NAME ${HOST} + OUTPUT_ROOT "${CCPP_CAP_FILES}") + +# Retrieve the list of Fortran files required for test host from datatable.xml and set to CCPP_CAPS_LIST +ccpp_datafile(DATATABLE "${CCPP_CAP_FILES}/datatable.xml" + REPORT_NAME "--ccpp-files") + +# Create test host library +add_library(DDT_TESTLIB OBJECT ${SCHEME_FORTRAN_FILES} + ${SUITE_SCHEME_FORTRAN_FILES} + ${DDT_HOST_FORTRAN_FILES} + ${CCPP_CAPS_LIST}) + +# Setup test executable with needed dependencies +add_executable(ddt_host_integration test_ddt_host_integration.F90 ${HOST}.F90) +target_link_libraries(ddt_host_integration PRIVATE DDT_TESTLIB test_utils) +target_include_directories(ddt_host_integration PRIVATE "$") + +# Add executable to be called with ctest +add_test(NAME ctest_ddt_host_integration COMMAND ddt_host_integration) diff --git a/test/ddthost_test/README.md b/test/ddthost_test/README.md index 127544e0..292722dc 100644 --- a/test/ddthost_test/README.md +++ b/test/ddthost_test/README.md @@ -1,6 +1,16 @@ -ccpp_capgen test -=========== +# DDT Host Test -To build and run the ccpp_capgen test, run ./run_test -This script will build and run the test. -The exit code is zero (0) on PASS and non-zero on FAIL. +Contains tests to exercise more DDT functionality: +- Passing around and modifying a DDT +- Making DDT in host model & using it in CCPP-ized physics code + +## Building/Running + +To explicitly build/run the ddt test host, run: + +```bash +$ cmake -S -B -DCCPP_RUN_DDT_HOST_TEST=ON +$ cd +$ make +$ ctest +``` diff --git a/test/ddthost_test/ddt_suite_files.txt b/test/ddthost_test/ddt_suite_files.txt deleted file mode 100644 index 7f96a84c..00000000 --- a/test/ddthost_test/ddt_suite_files.txt +++ /dev/null @@ -1,2 +0,0 @@ -make_ddt.meta -environ_conditions.meta diff --git a/test/ddthost_test/ddthost_test_reports.py b/test/ddthost_test/ddthost_test_reports.py new file mode 100644 index 00000000..612cbbbf --- /dev/null +++ b/test/ddthost_test/ddthost_test_reports.py @@ -0,0 +1,139 @@ +#! /usr/bin/env python3 +""" +----------------------------------------------------------------------- + Description: Test DDT host database report python interface + + Assumptions: + + Command line arguments: build_dir database_filepath + + Usage: python test_reports +----------------------------------------------------------------------- +""" +import os +import unittest + +from test_stub import BaseTests + +_BUILD_DIR = os.path.join(os.path.abspath(os.environ['BUILD_DIR']), "test", "ddthost_test") +_DATABASE = os.path.abspath(os.path.join(_BUILD_DIR, "ccpp", "datatable.xml")) + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) +_SCRIPTS_DIR = os.path.join(_FRAMEWORK_DIR, "scripts") +_SRC_DIR = os.path.join(_FRAMEWORK_DIR, "src") + + +# Check data +_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] +_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] +_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), + os.path.join(_SRC_DIR, "ccpp_constituent_prop_mod.F90"), + os.path.join(_SRC_DIR, "ccpp_scheme_utils.F90"), + os.path.join(_SRC_DIR, "ccpp_hashable.F90"), + os.path.join(_SRC_DIR, "ccpp_hash_table.F90")] +_CCPP_FILES = _UTILITY_FILES + \ + [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] +_DEPENDENCIES = [os.path.join(_TEST_DIR, "adjust", "qux.F90"), + os.path.join(_TEST_DIR, "bar.F90"), + os.path.join(_TEST_DIR, "foo.F90")] +_PROCESS_LIST = ["setter=temp_set", "adjusting=temp_calc_adjust"] +_MODULE_LIST = ["environ_conditions", "make_ddt", "setup_coeffs", "temp_adjust", + "temp_calc_adjust", "temp_set"] +_SUITE_LIST = ["ddt_suite", "temp_suite"] +_INPUT_VARS_DDT = ["model_times", "number_of_model_times", + "horizontal_loop_begin", "horizontal_loop_end", + "surface_air_pressure", "horizontal_dimension", + "host_standard_ccpp_type"] +_OUTPUT_VARS_DDT = ["ccpp_error_code", "ccpp_error_message", "model_times", + "number_of_model_times", "surface_air_pressure"] +_REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT +_PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", + "horizontal_dimension", "vertical_layer_dimension", + "number_of_tracers", + # Added for --debug + "index_of_water_vapor_specific_humidity", + "vertical_interface_dimension"] +_REQUIRED_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", + "potential_temperature", + "potential_temperature_at_interface", + "coefficients_for_interpolation", + "potential_temperature_increment", + "surface_air_pressure", "time_step_for_physics", + "water_vapor_specific_humidity"] +_INPUT_VARS_TEMP = ["potential_temperature", + "potential_temperature_at_interface", + "coefficients_for_interpolation", + "potential_temperature_increment", + "surface_air_pressure", "time_step_for_physics", + "water_vapor_specific_humidity"] +_OUTPUT_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", + "potential_temperature", + "potential_temperature_at_interface", + "coefficients_for_interpolation", + "surface_air_pressure", "water_vapor_specific_humidity"] + +class TestDdtHostDataTables(unittest.TestCase, BaseTests.TestHostDataTables): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + + +class CommandLineDdtHostDatafileRequiredFiles(unittest.TestCase, BaseTests.TestHostCommandLineDataFiles): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" + + +class TestDdtSuite(unittest.TestCase, BaseTests.TestSuite): + database = _DATABASE + required_vars = _REQUIRED_VARS_DDT + input_vars = _INPUT_VARS_DDT + output_vars = _OUTPUT_VARS_DDT + suite_name = "ddt_suite" + + +class CommandLineDdtSuite(unittest.TestCase, BaseTests.TestSuiteCommandLine): + database = _DATABASE + required_vars = _REQUIRED_VARS_DDT + input_vars = _INPUT_VARS_DDT + output_vars = _OUTPUT_VARS_DDT + suite_name = "ddt_suite" + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" + + +class TestDdtTempSuite(unittest.TestCase, BaseTests.TestSuiteExcludeProtected): + database = _DATABASE + required_vars = _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP + input_vars = _INPUT_VARS_TEMP + _PROT_VARS_TEMP + required_vars_excluding_protected = _REQUIRED_VARS_TEMP + input_vars_excluding_protected = _INPUT_VARS_TEMP + output_vars = _OUTPUT_VARS_TEMP + suite_name = "temp_suite" + + +class CommandLineDdtTempSuite(unittest.TestCase, BaseTests.TestSuiteExcludeProtectedCommandLine): + database = _DATABASE + required_vars = _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP + input_vars = _INPUT_VARS_TEMP + _PROT_VARS_TEMP + required_vars_excluding_protected = _REQUIRED_VARS_TEMP + input_vars_excluding_protected = _INPUT_VARS_TEMP + output_vars = _OUTPUT_VARS_TEMP + suite_name = "temp_suite" + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" diff --git a/test/ddthost_test/make_ddt.F90 b/test/ddthost_test/make_ddt.F90 index e94aaff4..c9d0832b 100644 --- a/test/ddthost_test/make_ddt.F90 +++ b/test/ddthost_test/make_ddt.F90 @@ -69,10 +69,12 @@ end subroutine make_ddt_run !> \section arg_table_make_ddt_init Argument Table !! \htmlinclude arg_table_make_ddt_init.html !! - subroutine make_ddt_init(nbox, vmr, errmsg, errflg) + subroutine make_ddt_init(nbox, ccpp_info, vmr, errmsg, errflg) + use host_ccpp_ddt, only: ccpp_info_t ! Dummy arguments integer, intent(in) :: nbox + type(ccpp_info_t), intent(in) :: ccpp_info type(vmr_type), intent(out) :: vmr character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg diff --git a/test/ddthost_test/make_ddt.meta b/test/ddthost_test/make_ddt.meta index b9dbd8a9..4998e917 100644 --- a/test/ddthost_test/make_ddt.meta +++ b/test/ddthost_test/make_ddt.meta @@ -12,7 +12,7 @@ [ vmr_array ] standard_name = array_of_volume_mixing_ratios units = ppmv - dimensions = (horizontal_loop_extent, number_of_chemical_species) + dimensions = (horizontal_dimension, number_of_chemical_species) type = real kind = kind_phys [ccpp-table-properties] @@ -76,6 +76,11 @@ units = count dimensions = () intent = in +[ ccpp_info ] + standard_name = host_standard_ccpp_type + type = ccpp_info_t + dimensions = () + intent = in [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () diff --git a/test/ddthost_test/run_test b/test/ddthost_test/run_test deleted file mode 100755 index 65e73886..00000000 --- a/test/ddthost_test/run_test +++ /dev/null @@ -1,282 +0,0 @@ -#! /bin/bash - -currdir="`pwd -P`" -scriptdir="$( cd $( dirname $0 ); pwd -P )" - -## -## Option default values -## -defdir="ddt_build" -build_dir="${currdir}/${defdir}" -cleanup="PASS" # Other supported options are ALWAYS and NEVER -verbosity=2 - -## -## General syntax help function -## Usage: help -## -help () { - local hname="Usage: `basename ${0}`" - local hprefix="`echo ${hname} | tr '[!-~]' ' '`" - echo "${hname} [ --build-dir ] [ --cleanup ]" - echo "${hprefix} [ --verbosity <#> ]" - hprefix=" " - echo "" - echo "${hprefix} : Directory for building and running the test" - echo "${hprefix} default is /${defdir}" - echo "${hprefix} : Cleanup option is ALWAYS, NEVER, or PASS" - echo "${hprefix} default is PASS" - echo "${hprefix} verbosity: 0, 1, or 2 (default=${verbosity})" - echo "${hprefix} default is 0" - exit $1 -} - -## -## Error output function (should be handed a string) -## -perr() { - >&2 echo -e "\nERROR: ${@}\n" - exit 1 -} - -## -## Cleanup the build and test directory -## -docleanup() { - # We start off in the build directory - if [ "${build_dir}" == "${currdir}" ]; then - echo "WARNING: Cannot clean ${build_dir}" - else - cd ${currdir} - rm -rf ${build_dir} - fi -} - -## Process our input arguments -while [ $# -gt 0 ]; do - case $1 in - --h | -h | --help | -help) - help 0 - ;; - --build-dir) - if [ $# -lt 2 ]; then - perr "${1} requires a build directory" - fi - build_dir="${2}" - shift - ;; - --cleanup) - if [ $# -lt 2 ]; then - perr "${1} requies a cleanup option (ALWAYS, NEVER, PASS)" - fi - if [ "${2}" == "ALWAYS" -o "${2}" == "NEVER" -o "${2}" == "PASS" ]; then - cleanup="${2}" - else - perr "Allowed cleanup options: ALWAYS, NEVER, PASS" - fi - shift - ;; - --verbosity) - if [ $# -lt 2 ]; then - perr "${1} requires a verbosity value (0, 1, or 2)" - fi - if [ "${2}" == "0" -o "${2}" == "1" -o "${2}" == "2" ]; then - verbosity=$2 - else - perr "allowed verbosity levels are 0, 1, 2" - fi - shift - ;; - *) - perr "Unrecognized option, \"${1}\"" - ;; - esac - shift -done - -# Create the build directory, if necessary -if [ -d "${build_dir}" ]; then - # Always make sure build_dir is not in the test dir - if [ "$( cd ${build_dir}; pwd -P )" == "${currdir}" ]; then - build_dir="${build_dir}/${defdir}" - fi -else - mkdir -p ${build_dir} - res=$? - if [ $res -ne 0 ]; then - perr "Unable to create build directory, '${build_dir}'" - fi -fi -build_dir="$( cd ${build_dir}; pwd -P )" - -## framework is the CCPP Framework root dir -framework="$( cd $( dirname $( dirname ${scriptdir} ) ); pwd -P )" -frame_src="${framework}/src" - -## -## check strings for datafile command-list test -## NB: This has to be after build_dir is finalized -## -host_files="${build_dir}/ccpp/test_host_ccpp_cap.F90" -suite_files="${build_dir}/ccpp/ccpp_ddt_suite_cap.F90" -suite_files="${suite_files},${build_dir}/ccpp/ccpp_temp_suite_cap.F90" -utility_files="${build_dir}/ccpp/ccpp_kinds.F90" -utility_files="${utility_files},${frame_src}/ccpp_constituent_prop_mod.F90" -utility_files="${utility_files},${frame_src}/ccpp_hashable.F90" -utility_files="${utility_files},${frame_src}/ccpp_hash_table.F90" -ccpp_files="${utility_files}" -ccpp_files="${ccpp_files},${build_dir}/ccpp/test_host_ccpp_cap.F90" -ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_ddt_suite_cap.F90" -ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_temp_suite_cap.F90" -process_list="adjusting=temp_calc_adjust,setter=temp_set" -module_list="environ_conditions,make_ddt,setup_coeffs,temp_adjust,temp_calc_adjust,temp_set" -dependencies="${scriptdir}/adjust/qux.F90,${scriptdir}/bar.F90,${scriptdir}/foo.F90" -suite_list="ddt_suite;temp_suite" -required_vars_ddt="ccpp_error_code,ccpp_error_message,horizontal_dimension" -required_vars_ddt="${required_vars_ddt},horizontal_loop_begin" -required_vars_ddt="${required_vars_ddt},horizontal_loop_end" -required_vars_ddt="${required_vars_ddt},model_times" -required_vars_ddt="${required_vars_ddt},number_of_model_times" -required_vars_ddt="${required_vars_ddt},surface_air_pressure" -input_vars_ddt="horizontal_dimension" -input_vars_ddt="${input_vars_ddt},horizontal_loop_begin" -input_vars_ddt="${input_vars_ddt},horizontal_loop_end" -input_vars_ddt="${input_vars_ddt},model_times,number_of_model_times" -input_vars_ddt="${input_vars_ddt},surface_air_pressure" -output_vars_ddt="ccpp_error_code,ccpp_error_message" -output_vars_ddt="${output_vars_ddt},model_times,number_of_model_times,surface_air_pressure" -required_vars_temp="ccpp_error_code,ccpp_error_message" -required_vars_temp="${required_vars_temp},coefficients_for_interpolation" -required_vars_temp="${required_vars_temp},horizontal_dimension" -required_vars_temp="${required_vars_temp},horizontal_loop_begin" -required_vars_temp="${required_vars_temp},horizontal_loop_end" -required_vars_temp="${required_vars_temp},index_of_water_vapor_specific_humidity" -required_vars_temp="${required_vars_temp},number_of_tracers" -required_vars_temp="${required_vars_temp},potential_temperature" -required_vars_temp="${required_vars_temp},potential_temperature_at_interface" -required_vars_temp="${required_vars_temp},potential_temperature_increment" -required_vars_temp="${required_vars_temp},surface_air_pressure" -required_vars_temp="${required_vars_temp},time_step_for_physics" -required_vars_temp="${required_vars_temp},vertical_interface_dimension" -required_vars_temp="${required_vars_temp},vertical_layer_dimension" -required_vars_temp="${required_vars_temp},water_vapor_specific_humidity" -input_vars_temp="coefficients_for_interpolation" -input_vars_temp="${input_vars_temp},horizontal_dimension" -input_vars_temp="${input_vars_temp},horizontal_loop_begin" -input_vars_temp="${input_vars_temp},horizontal_loop_end" -input_vars_temp="${input_vars_temp},index_of_water_vapor_specific_humidity" -input_vars_temp="${input_vars_temp},number_of_tracers" -input_vars_temp="${input_vars_temp},potential_temperature" -input_vars_temp="${input_vars_temp},potential_temperature_at_interface" -input_vars_temp="${input_vars_temp},potential_temperature_increment" -input_vars_temp="${input_vars_temp},surface_air_pressure,time_step_for_physics" -input_vars_temp="${input_vars_temp},vertical_interface_dimension" -input_vars_temp="${input_vars_temp},vertical_layer_dimension" -input_vars_temp="${input_vars_temp},water_vapor_specific_humidity" -output_vars_temp="ccpp_error_code,ccpp_error_message" -output_vars_temp="${output_vars_temp},coefficients_for_interpolation" -output_vars_temp="${output_vars_temp},potential_temperature" -output_vars_temp="${output_vars_temp},potential_temperature_at_interface" -output_vars_temp="${output_vars_temp},surface_air_pressure" -output_vars_temp="${output_vars_temp},water_vapor_specific_humidity" - -## -## Run a database report and check the return string -## $1 is the report program file -## $2 is the database file -## $3 is the report string -## $4 is the check string -## $5+ are any optional arguments -## -check_datatable() { - local checkstr=${4} - local teststr - local prog=${1} - local database=${2} - local report=${3} - shift 4 - echo "Checking ${report} report" - teststr="`${prog} ${database} ${report} $@`" - if [ "${teststr}" != "${checkstr}" ]; then - perr "datatable check:\nExpected: '${checkstr}'\nGot: '${teststr}'" - fi -} - -# cd to the build directory -cd ${build_dir} -res=$? -if [ $res -ne 0 ]; then - perr "Unable to cd to build directory, '${build_dir}'" -fi -# Clean build directory -rm -rf * -res=$? -if [ $res -ne 0 ]; then - perr "Unable to clean build directory, '${build_dir}'" -fi -# Run CMake -opts="" -if [ $verbosity -gt 0 ]; then - opts="${opts} -DVERBOSITY=${verbosity}" -fi -# Run cmake -cmake ${scriptdir} ${opts} -res=$? -if [ $res -ne 0 ]; then - perr "CMake failed with exit code, ${res}" -fi -# Test the datafile user interface -report_prog="${framework}/scripts/ccpp_datafile.py" -datafile="${build_dir}/ccpp/datatable.xml" -echo "Running python interface tests" -python3 ${scriptdir}/test_reports.py ${build_dir} ${datafile} -res=$? -if [ $res -ne 0 ]; then - perr "python interface tests failed" -fi -echo "Running command line tests" -echo "Checking required files from command line:" -check_datatable ${report_prog} ${datafile} "--host-files" ${host_files} -check_datatable ${report_prog} ${datafile} "--suite-files" ${suite_files} -check_datatable ${report_prog} ${datafile} "--utility-files" ${utility_files} -check_datatable ${report_prog} ${datafile} "--ccpp-files" ${ccpp_files} -echo -e "\nChecking lists from command line" -check_datatable ${report_prog} ${datafile} "--process-list" ${process_list} -check_datatable ${report_prog} ${datafile} "--module-list" ${module_list} -check_datatable ${report_prog} ${datafile} "--dependencies" ${dependencies} -check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ - --sep ";" -echo -e "\nChecking variables for DDT suite from command line" -check_datatable ${report_prog} ${datafile} "--required-variables" \ - ${required_vars_ddt} "ddt_suite" -check_datatable ${report_prog} ${datafile} "--input-variables" \ - ${input_vars_ddt} "ddt_suite" -check_datatable ${report_prog} ${datafile} "--output-variables" \ - ${output_vars_ddt} "ddt_suite" -echo -e "\nChecking variables for temp suite from command line" -check_datatable ${report_prog} ${datafile} "--required-variables" \ - ${required_vars_temp} "temp_suite" -check_datatable ${report_prog} ${datafile} "--input-variables" \ - ${input_vars_temp} "temp_suite" -check_datatable ${report_prog} ${datafile} "--output-variables" \ - ${output_vars_temp} "temp_suite" -# Run make -make -res=$? -if [ $res -ne 0 ]; then - perr "make failed with exit code, ${res}" -fi -# Run test -./test_host -res=$? -if [ $res -ne 0 ]; then - perr "test_host failed with exit code, ${res}" -fi - -if [ "${cleanup}" == "ALWAYS" ]; then - docleanup -elif [ $res -eq 0 -a "${cleanup}" == "PASS" ]; then - docleanup -fi - -exit $res diff --git a/test/ddthost_test/temp_scheme_files.txt b/test/ddthost_test/temp_scheme_files.txt deleted file mode 100644 index 6c831539..00000000 --- a/test/ddthost_test/temp_scheme_files.txt +++ /dev/null @@ -1,4 +0,0 @@ -setup_coeffs.meta -temp_set.meta -temp_adjust.meta -temp_calc_adjust.meta diff --git a/test/ddthost_test/test_ddt_host_integration.F90 b/test/ddthost_test/test_ddt_host_integration.F90 new file mode 100644 index 00000000..23a0e53c --- /dev/null +++ b/test/ddthost_test/test_ddt_host_integration.F90 @@ -0,0 +1,79 @@ +program test + use test_prog, only: test_host, suite_info, cm, cs + + implicit none + + character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & + 'physics2 ' /) + character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) + character(len=cm), target :: test_invars1(7) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ' /) + character(len=cm), target :: test_outvars1(7) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) + character(len=cm), target :: test_reqvars1(9) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) + + character(len=cm), target :: test_invars2(4) = (/ & + 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ', & + 'host_standard_ccpp_type ' /) + + character(len=cm), target :: test_outvars2(5) = (/ & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'model_times ', & + 'surface_air_pressure ', & + 'number_of_model_times ' /) + + character(len=cm), target :: test_reqvars2(6) = (/ & + 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'host_standard_ccpp_type ' /) + type(suite_info) :: test_suites(2) + logical :: run_okay + + ! Setup expected test suite info + test_suites(1)%suite_name = 'temp_suite' + test_suites(1)%suite_parts => test_parts1 + test_suites(1)%suite_input_vars => test_invars1 + test_suites(1)%suite_output_vars => test_outvars1 + test_suites(1)%suite_required_vars => test_reqvars1 + test_suites(2)%suite_name = 'ddt_suite' + test_suites(2)%suite_parts => test_parts2 + test_suites(2)%suite_input_vars => test_invars2 + test_suites(2)%suite_output_vars => test_outvars2 + test_suites(2)%suite_required_vars => test_reqvars2 + + call test_host(run_okay, test_suites) + + if (run_okay) then + STOP 0 + else + STOP -1 + end if + +end program test diff --git a/test/ddthost_test/test_host.F90 b/test/ddthost_test/test_host.F90 index d060e59e..c8213e20 100644 --- a/test/ddthost_test/test_host.F90 +++ b/test/ddthost_test/test_host.F90 @@ -24,92 +24,10 @@ module test_prog contains - logical function check_list(test_list, chk_list, list_desc, suite_name) - ! Check a list () against its expected value () - - ! Dummy arguments - character(len=*), intent(in) :: test_list(:) - character(len=*), intent(in) :: chk_list(:) - character(len=*), intent(in) :: list_desc - character(len=*), optional, intent(in) :: suite_name - - ! Local variables - logical :: found - integer :: num_items - integer :: lindex, tindex - integer, allocatable :: check_unique(:) - character(len=2) :: sep - character(len=256) :: errmsg - - check_list = .true. - errmsg = '' - - ! Check the list size - num_items = size(chk_list) - if (size(test_list) /= num_items) then - write(errmsg, '(a,i0,2a)') 'ERROR: Found ', size(test_list), & - ' ', trim(list_desc) - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' for suite, ', & - trim(suite_name) - end if - write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items - write(6, *) trim(errmsg) - errmsg = '' - check_list = .false. - end if - - ! Now, check the list contents for 1-1 correspondence - if (check_list) then - allocate(check_unique(num_items)) - check_unique = -1 - do lindex = 1, num_items - found = .false. - do tindex = 1, num_items - if (trim(test_list(lindex)) == trim(chk_list(tindex))) then - check_unique(tindex) = lindex - found = .true. - exit - end if - end do - if (.not. found) then - check_list = .false. - write(errmsg, '(5a)') 'ERROR: ', trim(list_desc), ' item, ', & - trim(test_list(lindex)), ', was not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - write(6, *) trim(errmsg) - errmsg = '' - end if - end do - if (check_list .and. any(check_unique < 0)) then - check_list = .false. - write(errmsg, '(3a)') 'ERROR: The following ', trim(list_desc), & - ' items were not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - sep = '; ' - do lindex = 1, num_items - if (check_unique(lindex) < 0) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') sep, & - trim(chk_list(lindex)) - sep = ', ' - end if - end do - write(6, *) trim(errmsg) - errmsg = '' - end if - end if - - end function check_list - logical function check_suite(test_suite) use test_host_ccpp_cap, only: ccpp_physics_suite_part_list use test_host_ccpp_cap, only: ccpp_physics_suite_variables + use test_utils, only: check_list ! Dummy argument type(suite_info), intent(in) :: test_suite @@ -195,6 +113,7 @@ subroutine test_host(retval, test_suites) use test_host_ccpp_cap, only: test_host_ccpp_physics_finalize use test_host_ccpp_cap, only: ccpp_physics_suite_list use test_host_mod, only: init_data, compare_data, check_model_times + use test_utils, only: check_list type(suite_info), intent(in) :: test_suites(:) logical, intent(out) :: retval @@ -350,81 +269,3 @@ subroutine test_host(retval, test_suites) end subroutine test_host end module test_prog - - program test - use test_prog, only: test_host, suite_info, cm, cs - - implicit none - - character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & - 'physics2 ' /) - character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(7) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ' /) - character(len=cm), target :: test_outvars1(7) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - character(len=cm), target :: test_reqvars1(9) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - - character(len=cm), target :: test_invars2(3) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ' /) - - character(len=cm), target :: test_outvars2(5) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'model_times ', & - 'surface_air_pressure ', & - 'number_of_model_times ' /) - - character(len=cm), target :: test_reqvars2(5) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - type(suite_info) :: test_suites(2) - logical :: run_okay - - ! Setup expected test suite info - test_suites(1)%suite_name = 'temp_suite' - test_suites(1)%suite_parts => test_parts1 - test_suites(1)%suite_input_vars => test_invars1 - test_suites(1)%suite_output_vars => test_outvars1 - test_suites(1)%suite_required_vars => test_reqvars1 - test_suites(2)%suite_name = 'ddt_suite' - test_suites(2)%suite_parts => test_parts2 - test_suites(2)%suite_input_vars => test_invars2 - test_suites(2)%suite_output_vars => test_outvars2 - test_suites(2)%suite_required_vars => test_reqvars2 - - call test_host(run_okay, test_suites) - - if (run_okay) then - STOP 0 - else - STOP -1 - end if - -end program test diff --git a/test/ddthost_test/test_reports.py b/test/ddthost_test/test_reports.py deleted file mode 100644 index aae9098b..00000000 --- a/test/ddthost_test/test_reports.py +++ /dev/null @@ -1,178 +0,0 @@ -#! /usr/bin/env python3 -""" ------------------------------------------------------------------------ - Description: Test capgen database report python interface - - Assumptions: - - Command line arguments: build_dir database_filepath - - Usage: python test_reports ------------------------------------------------------------------------ -""" -import sys -import os - -_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) -_SCRIPTS_DIR = os.path.join(_FRAMEWORK_DIR, "scripts") -_SRC_DIR = os.path.join(_FRAMEWORK_DIR, "src") - -if not os.path.exists(_SCRIPTS_DIR): - raise ImportError("Cannot find scripts directory") -# end if - -sys.path.append(_SCRIPTS_DIR) -# pylint: disable=wrong-import-position -from ccpp_datafile import datatable_report, DatatableReport -# pylint: enable=wrong-import-position - -import argparse - -parser = argparse.ArgumentParser(description="Test capgen database report python interface") -parser.add_argument('build_dir') -parser.add_argument('database_filepath') -if len(sys.argv) > 3: - parser.error("Too many arguments") -# end if -args = parser.parse_args() -_BUILD_DIR = os.path.abspath(args.build_dir) -_DATABASE = os.path.abspath(args.database_filepath) -if not os.path.isdir(_BUILD_DIR): - parser.error(" must be an existing build directory") -# end if -if (not os.path.exists(_DATABASE)) or (not os.path.isfile(_DATABASE)): - parser.error(" must be an existing CCPP database file") -# end if - -# Check data -_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] -_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] -_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), - os.path.join(_SRC_DIR, "ccpp_constituent_prop_mod.F90"), - os.path.join(_SRC_DIR, "ccpp_hashable.F90"), - os.path.join(_SRC_DIR, "ccpp_hash_table.F90")] -_CCPP_FILES = _UTILITY_FILES + \ - [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_ddt_suite_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_temp_suite_cap.F90")] -_PROCESS_LIST = ["setter=temp_set", "adjusting=temp_calc_adjust"] -_MODULE_LIST = ["environ_conditions", "make_ddt", "setup_coeffs", "temp_adjust", - "temp_calc_adjust", "temp_set"] -_SUITE_LIST = ["ddt_suite", "temp_suite"] -_INPUT_VARS_DDT = ["model_times", "number_of_model_times", - "horizontal_loop_begin", "horizontal_loop_end", - "surface_air_pressure", "horizontal_dimension"] -_OUTPUT_VARS_DDT = ["ccpp_error_code", "ccpp_error_message", "model_times", - "number_of_model_times", "surface_air_pressure"] -_REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT -_PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", - "horizontal_dimension", "vertical_layer_dimension", - "number_of_tracers", - # Added for --debug - "index_of_water_vapor_specific_humidity", - "vertical_interface_dimension"] -_REQUIRED_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", - "potential_temperature", - "potential_temperature_at_interface", - "coefficients_for_interpolation", - "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] -_INPUT_VARS_TEMP = ["potential_temperature", - "potential_temperature_at_interface", - "coefficients_for_interpolation", - "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] -_OUTPUT_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", - "potential_temperature", - "potential_temperature_at_interface", - "coefficients_for_interpolation", - "surface_air_pressure", "water_vapor_specific_humidity"] - -def fields_string(field_type, field_list, sep): - """Create an error string for field(s), . - is used to separate items in """ - indent = ' '*11 - fmsg = "" - if field_list: - if len(field_list) > 1: - field_str = f"{field_type} Fields: " - else: - field_str = f"{field_type} Field: " - # end if - fmsg = f"\n{indent}{field_str}{sep.join(sorted(field_list))}" - # end if - return fmsg - -def check_datatable(database, report_type, check_list, - sep=',', exclude_protected=False): - """Run a database report and check the return string. - If an error is found, print an error message. - Return the number of errors""" - if sep is None: - sep = ',' - # end if - test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) - test_list = [x for x in test_str.split(sep) if x] - tests_run = set(test_list) - expected_tests = set(check_list) - missing = expected_tests - tests_run - unexpected = tests_run - expected_tests - if missing or unexpected: - vmsg = f"ERROR in {report_type.action} datafile check:" - vmsg += fields_string("Missing", missing, sep) - vmsg += fields_string("Unexpected", unexpected, sep) - print(vmsg) - else: - print(f"{report_type.action} report okay") - # end if - return len(missing) + len(unexpected) - -NUM_ERRORS = 0 -print("Checking required files from python:") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("host_files"), - _HOST_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_files"), - _SUITE_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("utility_files"), - _UTILITY_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("ccpp_files"), - _CCPP_FILES) -print("\nChecking lists from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("process_list"), - _PROCESS_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("module_list"), - _MODULE_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), - _SUITE_LIST) -print("\nChecking variables for DDT suite from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="ddt_suite"), - _REQUIRED_VARS_DDT) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="ddt_suite"), - _INPUT_VARS_DDT) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", - value="ddt_suite"), - _OUTPUT_VARS_DDT) -print("\nChecking variables for temp suite from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="temp_suite"), - _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="temp_suite"), - _REQUIRED_VARS_TEMP, exclude_protected=True) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="temp_suite"), - _INPUT_VARS_TEMP + _PROT_VARS_TEMP) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="temp_suite"), - _INPUT_VARS_TEMP, exclude_protected=True) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", - value="temp_suite"), - _OUTPUT_VARS_TEMP) - -sys.exit(NUM_ERRORS) diff --git a/test/run_fortran_tests.sh b/test/run_fortran_tests.sh deleted file mode 100755 index 942c0336..00000000 --- a/test/run_fortran_tests.sh +++ /dev/null @@ -1,66 +0,0 @@ -#! /bin/bash - -root=$( dirname $( cd $( dirname ${0}); pwd -P ) ) -test_dir=${root}/test - -perr() { - # Print error message ($2) on error ($1) - if [ ${1} -ne 0 ]; then - echo "ERROR: ${2}" - if [ $# -gt 2 ]; then - exit ${3} - else - exit 1 - fi - fi -} - - -cd ${test_dir} -perr $? "Cannot cd to test directory, '${test_dir}'" - -errcnt=0 - -# Run capgen test -./capgen_test/run_test -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "Failure running capgen test" -fi - -# Run advection test -./advection_test/run_test -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "Failure running advection test" -fi - -# Run DDT host variable test -./ddthost_test/run_test -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "Failure running ddthost test" -fi - -# Run var_compatibility test - ./var_compatibility_test/run_test - res=$? - errcnt=$((errcnt + res)) - if [ $res -ne 0 ]; then - echo "Failure running var_compatibility test" - fi - -if [ $errcnt -eq 0 ]; then - echo "All tests PASSed!" -else - if [ $errcnt -eq 1 ]; then - echo "${errcnt} test FAILed" - else - echo "${errcnt} tests FAILed" - fi - #Exit with non-zero exit code - exit 1 -fi diff --git a/test/test_fortran_to_metadata.sh b/test/test_fortran_to_metadata.sh new file mode 100755 index 00000000..adedaac6 --- /dev/null +++ b/test/test_fortran_to_metadata.sh @@ -0,0 +1,37 @@ +#! /bin/bash + +## Relevant directories and file paths +test_dir="$(cd $(dirname ${0}); pwd -P)" +script_dir="$(dirname ${test_dir})/scripts" +sample_files_dir="${test_dir}/unit_tests/sample_files" +f2m_script="${script_dir}/ccpp_fortran_to_metadata.py" +filename="test_fortran_to_metadata" +test_input="${sample_files_dir}/${filename}.F90" +tmp_dir="${test_dir}/unit_tests/tmp" +sample_meta="${sample_files_dir}/check_fortran_to_metadata.meta" + +# Run the script +opts="--ddt-names serling_t" +${f2m_script} --output-root "${tmp_dir}" ${opts} "${test_input}" +res=$? + +retval=0 +if [ ${res} -ne 0 ]; then + echo "FAIL: ccpp_fortran_to_metadata.py exited with error ${res}" + retval=${res} +elif [ ! -f "${tmp_dir}/${filename}.meta" ]; then + echo "FAIL: metadata file, '${tmp_dir}/${filename}.meta', not created" + retval=1 +else + cmp --quiet "${sample_meta}" "${tmp_dir}/${filename}.meta" + res=$? + if [ ${res} -ne 0 ]; then + echo "FAIL: Comparison with correct metadata file failed" + retval=${res} + else + echo "PASS" + # Cleanup + rm "${tmp_dir}/${filename}.meta" + fi +fi +exit ${retval} diff --git a/test/test_stub.py b/test/test_stub.py new file mode 100644 index 00000000..f54597c3 --- /dev/null +++ b/test/test_stub.py @@ -0,0 +1,162 @@ +from ccpp_datafile import datatable_report, DatatableReport +import subprocess + + +class BaseTests: + + + class TestHostDataTables: + _SEP = "," + + def test_host_files(self): + test_str = datatable_report(self.database, DatatableReport("host_files"), self._SEP) + self.assertSetEqual(set(self.host_files), set(test_str.split(self._SEP))) + + def test_suite_files(self): + test_str = datatable_report(self.database, DatatableReport("suite_files"), self._SEP) + self.assertSetEqual(set(self.suite_files), set(test_str.split(self._SEP))) + + def test_utility_files(self): + test_str = datatable_report(self.database, DatatableReport("utility_files"), self._SEP) + self.assertSetEqual(set(self.utility_files), set(test_str.split(self._SEP))) + + def test_ccpp_files(self): + test_str = datatable_report(self.database, DatatableReport("ccpp_files"), self._SEP) + self.assertSetEqual(set(self.ccpp_files), set(test_str.split(self._SEP))) + + def test_process_list(self): + test_str = datatable_report(self.database, DatatableReport("process_list"), self._SEP) + self.assertSetEqual(set(self.process_list), set(test_str.split(self._SEP))) + + def test_module_list(self): + test_str = datatable_report(self.database, DatatableReport("module_list"), self._SEP) + self.assertSetEqual(set(self.module_list), set(test_str.split(self._SEP))) + + def test_dependencies_list(self): + test_str = datatable_report(self.database, DatatableReport("dependencies"), self._SEP) + self.assertSetEqual(set(self.dependencies), set(test_str.split(self._SEP))) + + def test_suite_list(self): + test_str = datatable_report(self.database, DatatableReport("suite_list"), self._SEP) + self.assertSetEqual(set(self.suite_list), set(test_str.split(self._SEP))) + + + class TestHostCommandLineDataFiles: + _SEP = "," + + def test_host_files(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--host-files"], + capture_output=True, + text=True) + self.assertEqual(self._SEP.join(self.host_files), completedProcess.stdout.strip()) + + def test_suite_files(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--suite-files"], + capture_output=True, + text=True) + self.assertEqual(self._SEP.join(self.suite_files), completedProcess.stdout.strip()) + + def test_utility_files(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--utility-files"], + capture_output=True, + text=True) + self.assertEqual(self._SEP.join(self.utility_files), completedProcess.stdout.strip()) + + def test_ccpp_files(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--ccpp-files"], + capture_output=True, + text=True) + self.assertEqual(self._SEP.join(self.ccpp_files), completedProcess.stdout.strip()) + + def test_process_list(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--process-list"], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.process_list), actualOutput) + + def test_module_list(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--module-list"], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.module_list), actualOutput) + + def test_dependencies(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--dependencies"], + capture_output=True, + text=True) + self.assertEqual(self._SEP.join(self.dependencies), completedProcess.stdout.strip()) + + def test_suite_list(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--suite-list"], + capture_output=True, + text=True) + self.assertEqual(self._SEP.join(self.suite_list), completedProcess.stdout.strip()) + + + class TestSuite: + _SEP = "," + + def test_required_variables(self): + test_str = datatable_report(self.database, DatatableReport("required_variables", value=self.suite_name), self._SEP) + self.assertSetEqual(set(self.required_vars), set(test_str.split(self._SEP))) + + def test_input_variables(self): + test_str = datatable_report(self.database, DatatableReport("input_variables", value=self.suite_name), self._SEP) + self.assertSetEqual(set(self.input_vars), set(test_str.split(self._SEP))) + + def test_output_variables(self): + test_str = datatable_report(self.database, DatatableReport("output_variables", value=self.suite_name), self._SEP) + self.assertSetEqual(set(self.output_vars), set(test_str.split(self._SEP))) + + + class TestSuiteCommandLine: + _SEP = "," + + def test_required_variables(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--required-variables", self.suite_name], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.required_vars), actualOutput) + + def test_input_variables(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--input-variables", self.suite_name], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.input_vars), actualOutput) + + def test_output_variables(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--output-variables", self.suite_name], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.output_vars), actualOutput) + + + class TestSuiteExcludeProtected(TestSuite): + def test_required_variables_excluding_protected(self): + test_str = datatable_report(self.database, DatatableReport("required_variables", value="temp_suite"), self._SEP, exclude_protected=True) + self.assertSetEqual(set(self.required_vars_excluding_protected), set(test_str.split(self._SEP))) + + def test_input_variables_excluding_protected(self): + test_str = datatable_report(self.database, DatatableReport("input_variables", value="temp_suite"), self._SEP, exclude_protected=True) + self.assertSetEqual(set(self.input_vars_excluding_protected), set(test_str.split(self._SEP))) + + + class TestSuiteExcludeProtectedCommandLine(TestSuiteCommandLine): + def test_required_variables_excluding_protected(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--exclude-protected", "--required-variables", self.suite_name], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.required_vars_excluding_protected), actualOutput) + + def test_input_variables_excluding_protected(self): + completedProcess = subprocess.run([self.datafile_script, self.database, "--exclude-protected", "--input-variables", self.suite_name], + capture_output=True, + text=True) + actualOutput = {s.strip() for s in completedProcess.stdout.split(self._SEP)} + self.assertSetEqual(set(self.input_vars_excluding_protected), actualOutput) diff --git a/test/unit_tests/README.md b/test/unit_tests/README.md index cfd17c74..5a2353ee 100644 --- a/test/unit_tests/README.md +++ b/test/unit_tests/README.md @@ -1,45 +1,29 @@ -# How to build the test/capgen_test (on hera) +# How to run the python unit-tests ## Quick start: +To run all unit-tests: +```bash +$ export PYTHONPATH=/scripts:/scripts/parse_tools +$ pytest -v test/ ``` -cd test/capgen_test -mkdir build -cd build -cmake .. -make -./test_host -``` - -The command to run ccpp_capgen.py is: - -`/scripts/ccpp_capgen.py \ - --host-files test_host_data.meta,test_host_mod.meta,test_host.meta \ - --scheme-files temp_scheme_files.txt,ddt_suite_files.txt \ - --suites ddt_suite.xml,temp_suite.xml\ - --output-root /test/capgen_test/build/ccpp\ - --generate-host-cap` - -where `` is the path to your ccpp/framework directory. - -Modify a *meta* file in `capgen_test` and write a test that passes when fixed. -To run the unit tests: -``` -cd /test/unit_tests -python test_metadata_table.py +To run a specific unit tests: +```bash +$ cd /test/unit_tests +$ python test_metadata_table.py ``` For more verbose output: -``` -python test_metadata_table.py -v +```bash +$ python test_metadata_table.py -v ``` If you have `coverage` installed, to get test coverage: -``` -coverage run test_metadata_table.py -coverage report -m +```bash +$ coverage run test_metadata_table.py +$ coverage report -m ``` To check source code quality with pylint: -``` -cd -env PYTHONPATH=scripts:${PYTHONPATH} pylint --rcfile ./test/.pylintrc ./test/unit_tests/test_metadata_table.py -env PYTHONPATH=scripts:${PYTHONPATH} pylint --rcfile ./test/.pylintrc ./test/unit_tests/test_metadata_scheme_file.py +```bash +$ cd +$ env PYTHONPATH=scripts:${PYTHONPATH} pylint --rcfile ./test/.pylintrc ./test/unit_tests/test_metadata_table.py +$ env PYTHONPATH=scripts:${PYTHONPATH} pylint --rcfile ./test/.pylintrc ./test/unit_tests/test_metadata_scheme_file.py ``` diff --git a/test/unit_tests/sample_files/check_fortran_to_metadata.meta b/test/unit_tests/sample_files/check_fortran_to_metadata.meta new file mode 100644 index 00000000..7d84546b --- /dev/null +++ b/test/unit_tests/sample_files/check_fortran_to_metadata.meta @@ -0,0 +1,31 @@ +[ccpp-table-properties] + name = do_stuff + type = scheme + +[ccpp-arg-table] + name = do_stuff_run + type = scheme +[ const_props ] + standard_name = enter_standard_name_1 + units = enter_units + type = ccpp_constituent_prop_ptr_t + dimensions = (enter_standard_name_5:enter_standard_name_6) + intent = in +[ twilight_zone ] + standard_name = enter_standard_name_2 + units = enter_units + type = serling_t + dimensions = () + intent = inout +[ errmsg ] + standard_name = enter_standard_name_3 + units = enter_units + type = character | kind = len=512 + dimensions = () + intent = out +[ errflg ] + standard_name = enter_standard_name_4 + units = enter_units + type = integer + dimensions = () + intent = out diff --git a/test/unit_tests/sample_files/fortran_files/long_string_test.F90 b/test/unit_tests/sample_files/fortran_files/long_string_test.F90 new file mode 100644 index 00000000..2910fe53 --- /dev/null +++ b/test/unit_tests/sample_files/fortran_files/long_string_test.F90 @@ -0,0 +1,111 @@ +! +! This work (Common Community Physics Package Framework), identified by +! NOAA, NCAR, CU/CIRES, is free of known copyright restrictions and is +! placed in the public domain. +! +! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +! THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +!> +!! @brief Auto-generated Test of long string breaking for FortranWriter +!! +! +module long_string_test + + foo100 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + foo101 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' + + foo102 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901' + + foo103 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012' + + foo104 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123' + + foo105 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234' + + foo106 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345' + + foo107 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456' + + foo108 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567' + + foo109 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678' + + foo110 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + foo111 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' + + foo112 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901' + + foo113 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012' + + foo114 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123' + + foo115 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234' + + foo116 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345' + + foo117 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456' + + foo118 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567' + + foo119 = & + '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678' + + foo120 = & + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' + + foo121 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&' + foo122 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&1' + foo123 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&12' + foo124 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&123' + foo125 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&1234' + foo126 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&12345' + foo127 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&123456' + foo128 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&1234567' + foo129 = & + '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890& +&12345678' + +end module long_string_test diff --git a/test/unit_tests/sample_files/test_bad_var_property_name.meta b/test/unit_tests/sample_files/test_bad_var_property_name.meta index 619e7b83..a2009233 100644 --- a/test/unit_tests/sample_files/test_bad_var_property_name.meta +++ b/test/unit_tests/sample_files/test_bad_var_property_name.meta @@ -15,7 +15,7 @@ [ vmr_array ] standard_name = array_of_volume_mixing_ratios units = ppmv - dimensions = (horizontal_loop_extent, number_of_chemical_species) + dimensions = (horizontal_dimension, number_of_chemical_species) type = real kind = kind_phys diff --git a/test/unit_tests/sample_files/test_fortran_to_metadata.F90 b/test/unit_tests/sample_files/test_fortran_to_metadata.F90 new file mode 100644 index 00000000..ff4542c4 --- /dev/null +++ b/test/unit_tests/sample_files/test_fortran_to_metadata.F90 @@ -0,0 +1,28 @@ +module dme_adjust + + use ccpp_kinds, only: kind_phys + + implicit none + +contains +!=============================================================================== +!> \section arg_table_do_stuff_run Argument Table +!! \htmlinclude do_stuff_run.html +!! + subroutine do_stuff_run(const_props, twilight_zone, errmsg, errflg) + ! + ! Arguments + ! + type(ccpp_constituent_prop_ptr_t), intent(in) :: const_props(:) + type(serling_t), intent(inout) :: twilight_zone + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = ' ' + errflg = 0 + twilight_zone('adjust_set') + + end subroutine dme_adjust_run + +end module dme_adjust diff --git a/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta b/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta index b5cba0a2..0be34179 100644 --- a/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta +++ b/test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta @@ -14,7 +14,7 @@ [ vmr_array ] standard_name = array_of_volume_mixing_ratios units = ppmv - dimensions = (horizontal_loop_extent, number_of_chemical_species) + dimensions = (horizontal_dimension, number_of_chemical_species) type = real kind = kind_phys diff --git a/test/unit_tests/sample_host_files/mismatch_hdim_mod.F90 b/test/unit_tests/sample_host_files/mismatch_hdim_mod.F90 new file mode 100644 index 00000000..b3ebe52b --- /dev/null +++ b/test/unit_tests/sample_host_files/mismatch_hdim_mod.F90 @@ -0,0 +1,11 @@ +module mismatch_hdim_mod + + use ccpp_kinds, only: kind_phys + + !> \section arg_table_mismatch_hdim_mod Argument Table + !! \htmlinclude arg_table_mismatch_hdim_mod.html + real(kind_phys) :: ps1 + real(kind_phys), allocatable :: xbox(:,:) + real(kind_phys), allocatable :: switch(:,:) + +end module mismatch_hdim_mod diff --git a/test/unit_tests/sample_host_files/mismatch_hdim_mod.meta b/test/unit_tests/sample_host_files/mismatch_hdim_mod.meta new file mode 100644 index 00000000..24f6ba77 --- /dev/null +++ b/test/unit_tests/sample_host_files/mismatch_hdim_mod.meta @@ -0,0 +1,25 @@ +[ccpp-table-properties] + name = mismatch_hdim_mod + type = module +[ccpp-arg-table] + name = mismatch_hdim_mod + type = module +[ ps1 ] + standard_name = play_station + state_variable = true + type = real | kind = kind_phys + units = Pa + dimensions = () +[ xbox ] + standard_name = xbox + state_variable = true + type = real | kind = kind_phys + units = m s-1 + dimensions = (horizontal_loop_extent, vertical_layer_dimension) +[ switch ] + standard_name = nintendo_switch + long_name = Incompatible junk + state_variable = true + type = real | kind = kind_phys + units = m s-1 + dimensions = (horizontal_loop_being:horizontal_loop_end, vertical_layer_dimension) diff --git a/test/unit_tests/sample_scheme_files/mismatch_hdim.F90 b/test/unit_tests/sample_scheme_files/mismatch_hdim.F90 new file mode 100644 index 00000000..67680917 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/mismatch_hdim.F90 @@ -0,0 +1,48 @@ +! Test parameterization with no vertical level +! + +MODULE mismatch_hdim + + USE ccpp_kinds, ONLY: kind_phys + + IMPLICIT NONE + PRIVATE + + PUBLIC :: mismatch_hdim_init + PUBLIC :: mismatch_hdim_run + +CONTAINS + + !> \section arg_table_mismatch_hdim_run Argument Table + !! \htmlinclude arg_table_mismatch_hdim_run.html + !! + subroutine mismatch_hdim_run(tsfc, errmsg, errflg) + + real(kind_phys), intent(inout) :: tsfc(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + + tsfc = tsfc-1.0_kind_phys + + END SUBROUTINE mismatch_hdim_run + + !> \section arg_table_mismatch_hdim_init Argument Table + !! \htmlinclude arg_table_mismatch_hdim_init.html + !! + subroutine mismatch_hdim_init (tsfc, errmsg, errflg) + + real(kind_phys), intent(inout) :: tsfc(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + tsfc = tsfc+1.0_kind_phys + + errmsg = '' + errflg = 0 + + end subroutine mismatch_hdim_init + +END MODULE mismatch_hdim diff --git a/test/unit_tests/sample_scheme_files/mismatch_hdim.meta b/test/unit_tests/sample_scheme_files/mismatch_hdim.meta new file mode 100644 index 00000000..55d87fc3 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/mismatch_hdim.meta @@ -0,0 +1,55 @@ +[ccpp-table-properties] + name = mismatch_hdim + type = scheme + +######################################################################## +[ccpp-arg-table] + name = mismatch_hdim_run + type = scheme +[ tsfc ] + standard_name = temperature_at_surface + units = K + dimensions = (horizontal_dimension) + type = real + kind = kind_phys + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = mismatch_hdim_init + type = scheme +[ tsfc ] + standard_name = temperature_at_surface + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/unit_tests/sample_scheme_files/temp_adjust.F90 b/test/unit_tests/sample_scheme_files/temp_adjust.F90 index 4db1f2a8..7b1d0cbb 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.F90 +++ b/test/unit_tests/sample_scheme_files/temp_adjust.F90 @@ -17,7 +17,7 @@ MODULE temp_adjust !> \section arg_table_temp_adjust_register Argument Table !! \htmlinclude arg_table_temp_adjust_register.html !! - subroutine temp_adjust_register(config_var, dyn_const, errmsg, errflg) + subroutine temp_adjust_register(config_var, dyn_const, errflg, errmsg) logical, intent(in) :: config_var type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const character(len=512), intent(out) :: errmsg diff --git a/test/unit_tests/test_common.py b/test/unit_tests/test_common.py index fe5f852d..aa81ee8d 100755 --- a/test/unit_tests/test_common.py +++ b/test/unit_tests/test_common.py @@ -34,18 +34,6 @@ class CommonTestCase(unittest.TestCase): """Tests functionality of functions in common.py""" - def test_execute(self): - """Test execute() function""" - - # Input for successful test: ls command on this file - self.assertEqual(common.execute(f"ls {TEST_FILE}"),(0,f"{TEST_FILE}","")) - - # Input for failing test (no exception): exit 1 from a subshell - self.assertEqual(common.execute(f"(exit 1)",abort=False),(1,"",f"")) - - # Input for failing test (raise exception): exit 1 from a subshell - self.assertRaises(Exception,common.execute,f"(exit 1)",abort=True) - def test_split_var_name_and_array_reference(self): """Test split_var_name_and_array_reference() function""" @@ -59,18 +47,18 @@ def test_encode_container(self): typename = "COMPLEX" schemename = "testscheme" subroutinename = "testsubroutine" - self.assertEqual(common.encode_container(modulename),f"MODULE_{modulename}") - self.assertEqual(common.encode_container(modulename,typename),f"MODULE_{modulename} TYPE_{typename}") + self.assertEqual(common.encode_container(modulename),f"MODULE_{modulename.lower()}") + self.assertEqual(common.encode_container(modulename,typename),f"MODULE_{modulename.lower()} TYPE_{typename.lower()}") self.assertEqual(common.encode_container(modulename,schemename,subroutinename), - f"MODULE_{modulename} SCHEME_{schemename} SUBROUTINE_{subroutinename}") + f"MODULE_{modulename.lower()} SCHEME_{schemename.lower()} SUBROUTINE_{subroutinename.lower()}") self.assertRaises(Exception,common.encode_container,modulename,typename,schemename,subroutinename) self.assertRaises(Exception,common.encode_container) def test_decode_container(self): """Test decode_container() function""" - modulename = "ABCD1234" - typename = "COMPLEX" + modulename = "abcd1234" + typename = "complex" schemename = "testscheme" subroutinename = "testsubroutine" self.assertEqual(common.decode_container(f"MODULE_{modulename}"),f"MODULE {modulename}") diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py index 87e64baa..558db73d 100644 --- a/test/unit_tests/test_fortran_write.py +++ b/test/unit_tests/test_fortran_write.py @@ -122,6 +122,32 @@ def test_good_comments(self): amsg = f"{generate} does not match {compare}" self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) + def test_long_strings(self): + """Test breaking of long strings""" + # Setup + testname = "long_string_test" + compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") + generate = os.path.join(_TMP_DIR, f"{testname}.F90") + # Exercise + header = "Test of long string breaking for FortranWriter" + foostr = ''.join(['0123456789']*10) + nxtchr = ord('0') + with FortranWriter(generate, 'w', header, f"{testname}") as gen: + while len(foostr) < 130: + gen.write(f"foo{len(foostr)} = '{foostr}'", 1) + foostr += chr(nxtchr) + nxtchr += 1 + if nxtchr > ord('9'): + nxtchr = ord('0') + # end if + # end while + # end with + + # Check that file was generated + amsg = f"{generate} does not exist" + self.assertTrue(os.path.exists(generate), msg=amsg) + amsg = f"{generate} does not match {compare}" + self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) + if __name__ == "__main__": unittest.main() - diff --git a/test/unit_tests/test_metadata_host_file.py b/test/unit_tests/test_metadata_host_file.py index d01fda97..53e0a9fe 100644 --- a/test/unit_tests/test_metadata_host_file.py +++ b/test/unit_tests/test_metadata_host_file.py @@ -25,6 +25,8 @@ two DDT definitions - Correctly parse and match a simple module file with two DDT definitions and a data block + - "Test correct use of loop variables (horizontal + dimensions) in host metadata Assumptions: @@ -249,13 +251,30 @@ def test_module_with_two_ddts_and_extra_var(self): # Check error messages except_str = str(context.exception) emsgs = ["Variable mismatch in ddt2_t, variables missing from Fortran ddt.", - "No Fortran variable for bogus in ddt2_t", "2 errors found comparing"] for emsg in emsgs: self.assertTrue(emsg in except_str) # end for + def test_mismatch_hdim(self): + """Test correct use of loop variables (horizontal dimensions) + in host metadata.""" + # Setup + module_files = [os.path.join(self._sample_files_dir, "mismatch_hdim_mod.meta")] + # Exercise + hname = 'host_name_mismatch_hdim' + with self.assertRaises(CCPPError) as context: + _ = parse_host_model_files(module_files, hname, self._run_env) + # end with + # Check error messages + except_str = str(context.exception) + emsgs = ["Invalid horizontal dimension, 'horizontal_loop_extent'", + "Invalid horizontal dimension, 'horizontal_loop_end'"] + for emsg in emsgs: + self.assertTrue(emsg in except_str) + # end for + if __name__ == "__main__": unittest.main() diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index feab8acb..ef24742c 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -35,6 +35,9 @@ - Correctly interpret Fortran with preprocessor logic which affects the subroutine statement and/or the dummy argument statements resulting in incorrect Fortran + - Test correct use of loop variables (horizontal dimensions) + in scheme metadata. The allowed values depend on the phase + (run phase or not) Assumptions: @@ -212,15 +215,13 @@ def test_ccpp_notset_var_missing_in_meta(self): # Exercise with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify 3 correct error messages returned + # Verify 2 correct error messages returned emsg = "Variable mismatch in CCPPnotset_var_missing_in_meta_run, " + \ "variables missing from metadata header." self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errmsg in CCPPnotset_var_missing_in_meta_run" + emsg = "Fortran variable, bar, not in metadata" self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errflg in CCPPnotset_var_missing_in_meta_run" - self.assertTrue(emsg in str(context.exception)) - self.assertTrue("3 errors found comparing" in str(context.exception)) + self.assertTrue("2 errors found comparing" in str(context.exception)) def test_ccpp_eq1_var_missing_in_fort(self): """Test for correct detection of a variable that IS REMOVED the @@ -233,16 +234,14 @@ def test_ccpp_eq1_var_missing_in_fort(self): # Exercise with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify 3 correct error messages returned + # Verify 2 correct error messages returned emsg = "Variable mismatch in CCPPeq1_var_missing_in_fort_run, " + \ "variables missing from Fortran scheme." self.assertTrue(emsg in str(context.exception)) emsg = "Variable mismatch in CCPPeq1_var_missing_in_fort_run, " + \ "no Fortran variable bar." self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errmsg in CCPPeq1_var_missing_in_fort_run" - self.assertTrue(emsg in str(context.exception)) - self.assertTrue("3 errors found comparing" in str(context.exception)) + self.assertTrue("2 errors found comparing" in str(context.exception)) def test_ccpp_eq1_var_in_fort_meta(self): """Test positive case of a variable that IS PRESENT the @@ -302,16 +301,14 @@ def test_ccpp_gt1_var_in_fort_meta2(self): # Exercise with self.assertRaises(CCPPError) as context: _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify 3 correct error messages returned + # Verify 2 correct error messages returned emsg = "Variable mismatch in CCPPgt1_var_in_fort_meta_init, " + \ "variables missing from Fortran scheme." self.assertTrue(emsg in str(context.exception)) emsg = "Variable mismatch in CCPPgt1_var_in_fort_meta_init, " + \ "no Fortran variable bar." self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errmsg in CCPPgt1_var_in_fort_meta_init" - self.assertTrue(emsg in str(context.exception)) - self.assertTrue("3 errors found comparing" in str(context.exception)) + self.assertTrue("2 errors found comparing" in str(context.exception)) def test_ccpp_eq1_var_missing_in_meta(self): """Test correct detection of a variable that @@ -324,15 +321,13 @@ def test_ccpp_eq1_var_missing_in_meta(self): # Exercise with self.assertRaises(CCPPError) as context: _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify 3 correct error messages returned + # Verify 2 correct error messages returned emsg = "Variable mismatch in CCPPeq1_var_missing_in_meta_finalize, "+ \ "variables missing from metadata header." self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errmsg in CCPPeq1_var_missing_in_meta_finalize" + emsg = "Fortran variable, bar, not in metadata" self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errflg in CCPPeq1_var_missing_in_meta_finalize" - self.assertTrue(emsg in str(context.exception)) - self.assertTrue("3 errors found comparing" in str(context.exception)) + self.assertTrue("2 errors found comparing" in str(context.exception)) def test_scheme_ddt_only(self): """Test correct detection of a "scheme" file which contains only @@ -343,6 +338,20 @@ def test_scheme_ddt_only(self): scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) + def test_mismatch_hdim(self): + """Test correct use of loop variables (horizontal dimensions) + in scheme metadata. The allowed values depend on the phase + (run phase or not)""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, "mismatch_hdim.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + # Verify 2 correct error messages returned + emsg = "Invalid horizontal dimension, 'horizontal_dimension'" + self.assertTrue(emsg in str(context.exception)) + emsg = "Invalid horizontal dimension, 'horizontal_loop_extent'" + self.assertTrue(emsg in str(context.exception)) + if __name__ == "__main__": unittest.main() - diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index 94ed1e67..abf48b67 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -208,7 +208,7 @@ def test_bad_table_key(self): _ = parse_metadata_file(filename, known_ddts, self._DUMMY_RUN_ENV) #print("The exception is", context.exception) - emsg = "Invalid metadata table start property, 'something', at " + emsg = "Invalid metadata table start property, 'banana', at " self.assertTrue(emsg in str(context.exception)) def test_bad_line_split(self): diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index d0b5800e..7a2592b3 100755 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -428,27 +428,47 @@ def test_compatible_tendency_variable(self): self.assertFalse(compat.has_dim_transforms) self.assertFalse(compat.has_unit_transforms) + def test_compatible_tendency_variable_equivalent_units(self): + """Test that a given tendency variable is compatible with + its corresponding state variable""" + real_array1 = self._new_var('real_stdname1', 'V A', + ['horizontal_dimension', + 'vertical_layer_dimension'], + 'real', vkind='kind_phys') + real_array2 = self._new_var('tendency_of_real_stdname1', 'W s-1', + ['horizontal_dimension', + 'vertical_layer_dimension'], + 'real', vkind='kind_phys') + compat = real_array2.compatible(real_array1, self.__run_env, is_tend=True) + self.assertIsInstance(compat, VarCompatObj, + msg=self.__inst_emsg.format(type(compat))) + self.assertTrue(compat) + self.assertTrue(compat.compat) + self.assertEqual(compat.incompat_reason, '') + self.assertFalse(compat.has_kind_transforms) + self.assertFalse(compat.has_dim_transforms) + self.assertFalse(compat.has_unit_transforms) + def test_incompatible_tendency_variable(self): """Test that the correct error is returned when a given tendency variable has inconsistent units vs the state variable""" - real_array1 = self._new_var('real_stdname1', 'C', + real_array1 = self._new_var('real_stdname1', 'm', ['horizontal_dimension', 'vertical_layer_dimension'], 'real', vkind='kind_phys') - real_array2 = self._new_var('tendency_of_real_stdname1', 'C kg s-1', + real_array2 = self._new_var('tendency_of_real_stdname1', 'cm s-1', ['horizontal_dimension', 'vertical_layer_dimension'], 'real', vkind='kind_phys') compat = real_array2.compatible(real_array1, self.__run_env, is_tend=True) self.assertIsInstance(compat, VarCompatObj, msg=self.__inst_emsg.format(type(compat))) - #Verify correct error message returned - emsg = "\nMismatch tendency variable units 'C kg s-1' for variable 'tendency_of_real_stdname1'. No variable transforms supported for tendencies. Tendency units should be 'C s-1' to match state variable." + # Verify correct error message returned + emsg = "\nMismatch tendency variable units 'cm s-1' for variable 'tendency_of_real_stdname1'. No variable transforms supported for tendencies. Tendency units should be 'm s-1' to match state variable." self.assertEqual(compat.incompat_reason, emsg) self.assertFalse(compat.has_kind_transforms) self.assertFalse(compat.has_dim_transforms) self.assertFalse(compat.has_unit_transforms) - #Verify correct error message returned if __name__ == "__main__": diff --git a/test/utils/CMakeLists.txt b/test/utils/CMakeLists.txt new file mode 100644 index 00000000..dee888ca --- /dev/null +++ b/test/utils/CMakeLists.txt @@ -0,0 +1 @@ +add_library(test_utils STATIC test_utils.F90) diff --git a/test/utils/test_utils.F90 b/test/utils/test_utils.F90 new file mode 100644 index 00000000..088c347d --- /dev/null +++ b/test/utils/test_utils.F90 @@ -0,0 +1,88 @@ +module test_utils + + public :: check_list + +contains + logical function check_list(test_list, chk_list, list_desc, suite_name) + ! Check a list () against its expected value () + + ! Dummy arguments + character(len=*), intent(in) :: test_list(:) + character(len=*), intent(in) :: chk_list(:) + character(len=*), intent(in) :: list_desc + character(len=*), optional, intent(in) :: suite_name + + ! Local variables + logical :: found + integer :: num_items + integer :: lindex, tindex + integer, allocatable :: check_unique(:) + character(len=2) :: sep + character(len=256) :: errmsg + + check_list = .true. + errmsg = '' + + ! Check the list size + num_items = size(chk_list) + if (size(test_list) /= num_items) then + write(errmsg, '(a,i0,2a)') 'ERROR: Found ', size(test_list), & + ' ', trim(list_desc) + if (present(suite_name)) then + write(errmsg(len_trim(errmsg)+1:), '(2a)') ' for suite, ', & + trim(suite_name) + end if + write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items + write(6, *) trim(errmsg) + errmsg = '' + check_list = .false. + end if + + ! Now, check the list contents for 1-1 correspondence + if (check_list) then + allocate(check_unique(num_items)) + check_unique = -1 + do lindex = 1, num_items + found = .false. + do tindex = 1, num_items + if (trim(test_list(lindex)) == trim(chk_list(tindex))) then + check_unique(tindex) = lindex + found = .true. + exit + end if + end do + if (.not. found) then + check_list = .false. + write(errmsg, '(5a)') 'ERROR: ', trim(list_desc), ' item, ', & + trim(test_list(lindex)), ', was not found' + if (present(suite_name)) then + write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & + trim(suite_name) + end if + write(6, *) trim(errmsg) + errmsg = '' + end if + end do + if (check_list .and. any(check_unique < 0)) then + check_list = .false. + write(errmsg, '(3a)') 'ERROR: The following ', trim(list_desc), & + ' items were not found' + if (present(suite_name)) then + write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & + trim(suite_name) + end if + sep = '; ' + do lindex = 1, num_items + if (check_unique(lindex) < 0) then + write(errmsg(len_trim(errmsg)+1:), '(2a)') sep, & + trim(chk_list(lindex)) + sep = ', ' + end if + end do + write(6, *) trim(errmsg) + errmsg = '' + end if + end if + + end function check_list +end module test_utils diff --git a/test/var_compatibility_test/.gitignore b/test/var_compatibility_test/.gitignore deleted file mode 100644 index 378eac25..00000000 --- a/test/var_compatibility_test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/test/var_compatibility_test/CMakeLists.txt b/test/var_compatibility_test/CMakeLists.txt index 8cbd7e44..9204498d 100644 --- a/test/var_compatibility_test/CMakeLists.txt +++ b/test/var_compatibility_test/CMakeLists.txt @@ -1,188 +1,50 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -PROJECT(test_host) -ENABLE_LANGUAGE(Fortran) - -include(CMakeForceCompiler) - -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) -#------------------------------------------------------------------------------ -# -# Set where the CCPP Framework lives -# -#------------------------------------------------------------------------------ -get_filename_component(TEST_ROOT "${CMAKE_SOURCE_DIR}" DIRECTORY) -get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) #------------------------------------------------------------------------------ # # Create list of SCHEME_FILES, HOST_FILES, and SUITE_FILES # Paths should be relative to CMAKE_SOURCE_DIR (this file's directory) # #------------------------------------------------------------------------------ -LIST(APPEND SCHEME_FILES "var_compatibility_files.txt") -LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") -LIST(APPEND SUITE_FILES "var_compatibility_suite.xml") +set(SCHEME_FILES "effr_calc" "effr_diag" "effr_pre" "effr_post" "rad_lw" "rad_sw") +set(HOST_FILES "module_rad_ddt" "test_host_data" "test_host_mod") +set(SUITE_FILES "var_compatibility_suite.xml") # HOST is the name of the executable we will build. # We assume there are files ${HOST}.meta and ${HOST}.F90 in CMAKE_SOURCE_DIR -SET(HOST "${CMAKE_PROJECT_NAME}") +set(HOST "test_host") -#------------------------------------------------------------------------------ -# -# End of project-specific input -# -#------------------------------------------------------------------------------ - -# By default, no verbose output -SET(VERBOSITY 0 CACHE STRING "Verbosity level of output (default: 0)") # By default, generated caps go in ccpp subdir -SET(CCPP_CAP_FILES "${CMAKE_BINARY_DIR}/ccpp" CACHE - STRING "Location of CCPP-generated cap files") - -SET(CCPP_FRAMEWORK ${CCPP_ROOT}/scripts) - -# Use rpaths on MacOSX -set(CMAKE_MACOSX_RPATH 1) +set(CCPP_CAP_FILES "${CMAKE_CURRENT_BINARY_DIR}/ccpp") -#------------------------------------------------------------------------------ -# Set a default build type if none was specified -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - #message(STATUS "Setting build type to 'Debug' as none was specified.") - #set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - message(STATUS "Setting build type to 'Release' as none was specified.") - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" - "MinSizeRel" "RelWithDebInfo") -endif() - -ADD_COMPILE_OPTIONS(-O0) - -if (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") -# gfortran -# MESSAGE("gfortran being used.") - ADD_COMPILE_OPTIONS(-fcheck=all) - ADD_COMPILE_OPTIONS(-fbacktrace) - ADD_COMPILE_OPTIONS(-ffpe-trap=zero) - ADD_COMPILE_OPTIONS(-finit-real=nan) - ADD_COMPILE_OPTIONS(-ggdb) - ADD_COMPILE_OPTIONS(-ffree-line-length-none) - ADD_COMPILE_OPTIONS(-cpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "Intel") -# ifort -# MESSAGE("ifort being used.") - #ADD_COMPILE_OPTIONS(-check all) - ADD_COMPILE_OPTIONS(-fpe0) - ADD_COMPILE_OPTIONS(-warn) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-debug extended) - ADD_COMPILE_OPTIONS(-fpp) -elseif (${CMAKE_Fortran_COMPILER_ID} MATCHES "PGI") -# pgf90 -# MESSAGE("pgf90 being used.") - ADD_COMPILE_OPTIONS(-g) - ADD_COMPILE_OPTIONS(-Mipa=noconst) - ADD_COMPILE_OPTIONS(-traceback) - ADD_COMPILE_OPTIONS(-Mfree) - ADD_COMPILE_OPTIONS(-Mfptrap) - ADD_COMPILE_OPTIONS(-Mpreprocess) -else (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - message (WARNING "This program has only been compiled with gfortran, pgf90 and ifort. If another compiler is needed, the appropriate flags SHOULD be added in ${CMAKE_SOURCE_DIR}/CMakeLists.txt") -endif (${CMAKE_Fortran_COMPILER_ID} MATCHES "GNU") - -#------------------------------------------------------------------------------ -# CMake Modules -# Set the CMake module path -list(APPEND CMAKE_MODULE_PATH "${CCPP_FRAMEWORK}/cmake") -#------------------------------------------------------------------------------ -# Set OpenMP flags for C/C++/Fortran -if (OPENMP) - include(detect_openmp) - detect_openmp() - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") - set (CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${OpenMP_Fortran_FLAGS}") - message(STATUS "Enable OpenMP support for C/C++/Fortran compiler") -else(OPENMP) - message (STATUS "Disable OpenMP support for C/C++/Fortran compiler") -endif() +# Create lists for Fortran and meta data files from file names +list(TRANSFORM SCHEME_FILES APPEND ".F90" OUTPUT_VARIABLE SCHEME_FORTRAN_FILES) +list(TRANSFORM SCHEME_FILES APPEND ".meta" OUTPUT_VARIABLE SCHEME_META_FILES) +list(TRANSFORM HOST_FILES APPEND ".F90" OUTPUT_VARIABLE VAR_COMPATIBILITY_HOST_FORTRAN_FILES) +list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE VAR_COMPATIBILITY_HOST_METADATA_FILES) -# Create metadata and source file lists -FOREACH(FILE ${SCHEME_FILES}) - FILE(STRINGS ${FILE} FILENAMES) - LIST(APPEND SCHEME_FILENAMES ${FILENAMES}) -ENDFOREACH(FILE) -string(REPLACE ";" "," SCHEME_METADATA "${SCHEME_FILES}") - -FOREACH(FILE ${SCHEME_FILENAMES}) - # target_sources prefers absolute pathnames - string(REPLACE ".meta" ".F90" TEMP "${FILE}") - get_filename_component(ABS_PATH "${TEMP}" ABSOLUTE) - list(APPEND LIBRARY_LIST ${ABS_PATH}) -ENDFOREACH(FILE) - -FOREACH(FILE ${HOST_FILES}) - LIST(APPEND HOST_METADATA "${FILE}.meta") - # target_sources prefers absolute pathnames - get_filename_component(ABS_PATH "${FILE}.F90" ABSOLUTE) - LIST(APPEND HOST_SOURCE "${ABS_PATH}") -ENDFOREACH(FILE) -list(APPEND LIBRARY_LIST ${HOST_SOURCE}) -string(REPLACE ";" ".meta," HOST_METADATA "${HOST_FILES}") -set(HOST_METADATA "${HOST_METADATA}.meta,${HOST}.meta") - -string(REPLACE ";" "," SUITE_XML "${SUITE_FILES}") +list(APPEND VAR_COMPATIBILITY_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen -set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") -list(APPEND CAPGEN_CMD "--host-files") -list(APPEND CAPGEN_CMD "${HOST_METADATA}") -list(APPEND CAPGEN_CMD "--scheme-files") -list(APPEND CAPGEN_CMD "${SCHEME_METADATA}") -list(APPEND CAPGEN_CMD "--suites") -list(APPEND CAPGEN_CMD "${SUITE_XML}") -list(APPEND CAPGEN_CMD "--host-name") -list(APPEND CAPGEN_CMD "test_host") -list(APPEND CAPGEN_CMD "--output-root") -list(APPEND CAPGEN_CMD "${CCPP_CAP_FILES}") -while (VERBOSITY GREATER 0) - list(APPEND CAPGEN_CMD "--verbose") - MATH(EXPR VERBOSITY "${VERBOSITY} - 1") -endwhile () -list(APPEND CAPGEN_CMD "--debug") -string(REPLACE ";" " " CAPGEN_STRING "${CAPGEN_CMD}") -MESSAGE(STATUS "Running: ${CAPGEN_STRING}") -EXECUTE_PROCESS(COMMAND ${CAPGEN_CMD} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE CAPGEN_OUT ERROR_VARIABLE CAPGEN_OUT RESULT_VARIABLE RES) -MESSAGE(STATUS "${CAPGEN_OUT}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap generation completed") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap generation FAILED: result = ${RES}") -endif(RES EQUAL 0) - -# Retrieve the list of files from datatable.xml and set to CCPP_CAPS -set(DTABLE_CMD "${CCPP_FRAMEWORK}/ccpp_datafile.py") -list(APPEND DTABLE_CMD "${CCPP_CAP_FILES}/datatable.xml") -list(APPEND DTABLE_CMD "--ccpp-files") -list(APPEND DTABLE_CMD "--separator=\\;") -string(REPLACE ";" " " DTABLE_STRING "${DTABLE_CMD}") -MESSAGE(STATUS "Running: ${DTABLE_STRING}") -EXECUTE_PROCESS(COMMAND ${DTABLE_CMD} OUTPUT_VARIABLE CCPP_CAPS - RESULT_VARIABLE RES - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) -message(STATUS "CCPP_CAPS = ${CCPP_CAPS}") -if (RES EQUAL 0) - MESSAGE(STATUS "CCPP cap files retrieved") -else(RES EQUAL 0) - MESSAGE(FATAL_ERROR "CCPP cap file retrieval FAILED: result = ${RES}") -endif(RES EQUAL 0) -list(APPEND LIBRARY_LIST ${CCPP_CAPS}) -add_library(TESTLIB OBJECT ${LIBRARY_LIST}) -ADD_EXECUTABLE(${HOST} ${HOST}.F90 $) - -INCLUDE_DIRECTORIES(${CCPP_CAP_FILES}) - -set_target_properties(${HOST} PROPERTIES - COMPILE_FLAGS "${CMAKE_Fortran_FLAGS}" - LINK_FLAGS "${CMAKE_Fortran_FLAGS}") +ccpp_capgen(CAPGEN_DEBUG ON + VERBOSITY ${CCPP_VERBOSITY} + HOSTFILES ${VAR_COMPATIBILITY_HOST_METADATA_FILES} + SCHEMEFILES ${SCHEME_META_FILES} + SUITES ${SUITE_FILES} + HOST_NAME ${HOST} + OUTPUT_ROOT "${CCPP_CAP_FILES}") + +# Retrieve the list of Fortran files required for test host from datatable.xml and set to CCPP_CAPS_LIST +ccpp_datafile(DATATABLE "${CCPP_CAP_FILES}/datatable.xml" + REPORT_NAME "--ccpp-files") + +# Create test host library +add_library(VAR_COMPATIBILITY_TESTLIB OBJECT ${SCHEME_FORTRAN_FILES} + ${VAR_COMPATIBILITY_HOST_FORTRAN_FILES} + ${CCPP_CAPS_LIST}) + +# Setup test executable with needed dependencies +add_executable(var_compatibility_host_integration test_var_compatibility_integration.F90 ${HOST}.F90) +target_link_libraries(var_compatibility_host_integration PRIVATE VAR_COMPATIBILITY_TESTLIB test_utils) +target_include_directories(var_compatibility_host_integration PRIVATE "$") + +# Add executable to be called with ctest +add_test(NAME ctest_var_compatibility_host_integration COMMAND var_compatibility_host_integration) diff --git a/test/var_compatibility_test/README.md b/test/var_compatibility_test/README.md index 9b56ec9c..4e589cd6 100644 --- a/test/var_compatibility_test/README.md +++ b/test/var_compatibility_test/README.md @@ -1,6 +1,18 @@ -var_compatibility test -================ +# Variable Compatibility Test -To build and run the var_compatibility test, run ./run_test -This script will build and run the test. -The exit code is zero (0) on PASS and non-zero on FAIL. +Tests the variable compatibility object (`VarCompatObj`): +- Unit conversions (forward & reverse) +- Vertical array flipping (`top_at_one=true`) +- Kind conversions (`kind_phys <-> 8`) +- And various combinations thereof of the above cases + +## Building/Running + +To explicitly build/run the variable compatibility test host, run: + +```bash +$ cmake -S -B -DCCPP_RUN_VAR_COMPATIBILITY_TEST=ON +$ cd +$ make +$ ctest +``` diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 6dbbb722..0b626c16 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -8,16 +8,37 @@ module effr_calc implicit none private - public :: effr_calc_run + public :: effr_calc_run, effr_calc_init -contains + contains + !> \section arg_table_effr_calc_init Argument Table + !! \htmlinclude arg_table_effr_calc_init.html + !! + subroutine effr_calc_init(scheme_order, errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + integer, intent(inout) :: scheme_order + + errmsg = '' + errflg = 0 + + if (scheme_order .ne. 2) then + errflg = 1 + errmsg = 'ERROR: effr_calc_init() needs to be called second' + return + else + scheme_order = scheme_order + 1 + endif + + end subroutine effr_calc_init !> \section arg_table_effr_calc_run Argument Table !! \htmlinclude arg_table_effr_calc_run.html !! subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, ncg_in, nci_out, & effrl_inout, effri_out, effrs_inout, ncl_out, & - has_graupel, scalar_var, errmsg, errflg) + has_graupel, scalar_var, tke_inout, tke2_inout, & + errmsg, errflg) integer, intent(in) :: ncol integer, intent(in) :: nlev @@ -33,6 +54,9 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, ncg_in, nci_out, & character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg real(kind_phys), intent(out),optional :: ncl_out(:,:) + real(kind_phys), intent(inout) :: tke_inout + real(kind_phys), intent(inout) :: tke2_inout + !---------------------------------------------------------------- real(kind_phys), parameter :: re_qc_min = 2.5 ! microns @@ -52,7 +76,7 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, ncg_in, nci_out, & if (present(nci_out)) nci_out_local = nci_out effrl_inout = min(max(effrl_inout,re_qc_min),re_qc_max) if (present(effri_out)) effri_out = re_qi_avg - effrs_inout = effrs_inout + 10.0 ! in micrometer + effrs_inout = effrs_inout + (10.0 / 6.0) ! in micrometer scalar_var = 2.0 ! in km end subroutine effr_calc_run diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index 73c36ace..c3733f13 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -2,6 +2,33 @@ name = effr_calc type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_calc_init + type = scheme +[ scheme_order ] + standard_name = scheme_order_in_suite + long_name = scheme order in suite definition file + units = None + dimensions = () + type = integer + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +######################################################################## [ccpp-arg-table] name = effr_calc_run type = scheme @@ -103,6 +130,22 @@ type = real kind = kind_phys intent = inout +[ tke_inout ] + standard_name = turbulent_kinetic_energy + long_name = turbulent_kinetic_energy + units = m2 s-2 + dimensions = () + type = real + kind = kind_phys + intent = inout +[ tke2_inout ] + standard_name = turbulent_kinetic_energy2 + long_name = turbulent_kinetic_energy2 + units = m+2 s-2 + dimensions = () + type = real + kind = kind_phys + intent = inout [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/var_compatibility_test/effr_diag.F90 b/test/var_compatibility_test/effr_diag.F90 index 38b87c1a..409ff2f9 100644 --- a/test/var_compatibility_test/effr_diag.F90 +++ b/test/var_compatibility_test/effr_diag.F90 @@ -8,16 +8,38 @@ module effr_diag implicit none private - public :: effr_diag_run + public :: effr_diag_run, effr_diag_init contains + !> \section arg_table_effr_diag_init Argument Table + !! \htmlinclude arg_table_effr_diag_init.html + !! + subroutine effr_diag_init(scheme_order, errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + integer, intent(inout) :: scheme_order + + errmsg = '' + errflg = 0 + + if (scheme_order .ne. 4) then + errflg = 1 + errmsg = 'ERROR: effr_diag_init() needs to be called fourth' + return + else + scheme_order = scheme_order + 1 + endif + + end subroutine effr_diag_init + !> \section arg_table_effr_diag_run Argument Table !! \htmlinclude arg_table_effr_diag_run.html !! - subroutine effr_diag_run( effrr_in, errmsg, errflg) + subroutine effr_diag_run( effrr_in, scalar_var, errmsg, errflg) real(kind_phys), intent(in) :: effrr_in(:,:) + integer, intent(in) :: scalar_var character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -28,6 +50,10 @@ subroutine effr_diag_run( effrr_in, errmsg, errflg) call cmp_effr_diag(effrr_in, effrr_min, effrr_max) + if (scalar_var .ne. 380) then + errmsg = 'ERROR: effr_diag_run(): scalar_var should be 380' + errflg = 1 + endif end subroutine effr_diag_run subroutine cmp_effr_diag(effr, effr_min, effr_max) diff --git a/test/var_compatibility_test/effr_diag.meta b/test/var_compatibility_test/effr_diag.meta index ebb765f2..9e0e4fc2 100644 --- a/test/var_compatibility_test/effr_diag.meta +++ b/test/var_compatibility_test/effr_diag.meta @@ -2,6 +2,33 @@ name = effr_diag type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_diag_init + type = scheme +[ scheme_order ] + standard_name = scheme_order_in_suite + long_name = scheme order in suite definition file + units = None + dimensions = () + type = integer + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +######################################################################## [ccpp-arg-table] name = effr_diag_run type = scheme @@ -14,6 +41,13 @@ kind = kind_phys intent = in top_at_one = True +[ scalar_var ] + standard_name = scalar_variable_for_testing_c + long_name = unused scalar variable C + units = m + dimensions = () + type = integer + intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/var_compatibility_test/effr_post.F90 b/test/var_compatibility_test/effr_post.F90 index ca04c247..d42a574c 100644 --- a/test/var_compatibility_test/effr_post.F90 +++ b/test/var_compatibility_test/effr_post.F90 @@ -8,16 +8,38 @@ module effr_post implicit none private - public :: effr_post_run + public :: effr_post_run, effr_post_init contains + !> \section arg_table_effr_post_init Argument Table + !! \htmlinclude arg_table_effr_post_init.html + !! + subroutine effr_post_init(scheme_order, errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + integer, intent(inout) :: scheme_order + + errmsg = '' + errflg = 0 + + if (scheme_order .ne. 3) then + errflg = 1 + errmsg = 'ERROR: effr_post_init() needs to be called third' + return + else + scheme_order = scheme_order + 1 + endif + + end subroutine effr_post_init + !> \section arg_table_effr_post_run Argument Table !! \htmlinclude arg_table_effr_post_run.html !! - subroutine effr_post_run( effrr_inout, errmsg, errflg) + subroutine effr_post_run( effrr_inout, scalar_var, errmsg, errflg) real(kind_phys), intent(inout) :: effrr_inout(:,:) + real(kind_phys), intent(in) :: scalar_var character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -29,6 +51,11 @@ subroutine effr_post_run( effrr_inout, errmsg, errflg) ! Do some post-processing on effrr... effrr_inout(:,:) = effrr_inout(:,:)*1._kind_phys + if (scalar_var .ne. 1013.0) then + errmsg = 'ERROR: effr_post_run(): scalar_var should be 1013.0' + errflg = 1 + endif + end subroutine effr_post_run end module effr_post diff --git a/test/var_compatibility_test/effr_post.meta b/test/var_compatibility_test/effr_post.meta index d65be238..721582a6 100644 --- a/test/var_compatibility_test/effr_post.meta +++ b/test/var_compatibility_test/effr_post.meta @@ -2,6 +2,33 @@ name = effr_post type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_post_init + type = scheme +[ scheme_order ] + standard_name = scheme_order_in_suite + long_name = scheme order in suite definition file + units = None + dimensions = () + type = integer + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +######################################################################## [ccpp-arg-table] name = effr_post_run type = scheme @@ -13,6 +40,14 @@ type = real kind = kind_phys intent = inout +[ scalar_var ] + standard_name = scalar_variable_for_testing_b + long_name = unused scalar variable B + units = m + dimensions = () + type = real + kind = kind_phys + intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/var_compatibility_test/effr_pre.F90 b/test/var_compatibility_test/effr_pre.F90 index ba6ea2b9..17a3b187 100644 --- a/test/var_compatibility_test/effr_pre.F90 +++ b/test/var_compatibility_test/effr_pre.F90 @@ -1,23 +1,44 @@ !Test unit conversions for intent in, inout, out variables ! -module effr_pre +module mod_effr_pre use ccpp_kinds, only: kind_phys implicit none private - public :: effr_pre_run + public :: effr_pre_run, effr_pre_init contains + !> \section arg_table_effr_pre_init Argument Table + !! \htmlinclude arg_table_effr_pre_init.html + !! + subroutine effr_pre_init(scheme_order, errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + integer, intent(inout) :: scheme_order + + errmsg = '' + errflg = 0 + + if (scheme_order .ne. 1) then + errflg = 1 + errmsg = 'ERROR: effr_pre_init() needs to be called first' + return + else + scheme_order = scheme_order + 1 + endif + + end subroutine effr_pre_init !> \section arg_table_effr_pre_run Argument Table !! \htmlinclude arg_table_effr_pre_run.html !! - subroutine effr_pre_run( effrr_inout, errmsg, errflg) + subroutine effr_pre_run( effrr_inout, scalar_var, errmsg, errflg) real(kind_phys), intent(inout) :: effrr_inout(:,:) + real(kind_phys), intent(in) :: scalar_var character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -29,6 +50,11 @@ subroutine effr_pre_run( effrr_inout, errmsg, errflg) ! Do some pre-processing on effrr... effrr_inout(:,:) = effrr_inout(:,:)*1._kind_phys + if (scalar_var .ne. 273.15) then + errmsg = 'ERROR: effr_pre_run(): scalar_var should be 273.15' + errflg = 1 + endif + end subroutine effr_pre_run - - end module effr_pre + +end module mod_effr_pre diff --git a/test/var_compatibility_test/effr_pre.meta b/test/var_compatibility_test/effr_pre.meta index d6f67ec3..251b4175 100644 --- a/test/var_compatibility_test/effr_pre.meta +++ b/test/var_compatibility_test/effr_pre.meta @@ -1,7 +1,35 @@ [ccpp-table-properties] name = effr_pre type = scheme + module_name = mod_effr_pre dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_pre_init + type = scheme +[ scheme_order ] + standard_name = scheme_order_in_suite + long_name = scheme order in suite definition file + units = None + dimensions = () + type = integer + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +######################################################################## [ccpp-arg-table] name = effr_pre_run type = scheme @@ -13,6 +41,14 @@ type = real kind = kind_phys intent = inout +[ scalar_var ] + standard_name = scalar_variable_for_testing_a + long_name = unused scalar variable A + units = m + dimensions = () + type = real + kind = kind_phys + intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/var_compatibility_test/module_rad_ddt.F90 b/test/var_compatibility_test/module_rad_ddt.F90 new file mode 100644 index 00000000..21a1a0ec --- /dev/null +++ b/test/var_compatibility_test/module_rad_ddt.F90 @@ -0,0 +1,23 @@ +module mod_rad_ddt + USE ccpp_kinds, ONLY: kind_phys + implicit none + + public ty_rad_lw, ty_rad_sw + + !> \section arg_table_ty_rad_lw Argument Table + !! \htmlinclude arg_table_ty_rad_lw.html + !! + type ty_rad_lw + real(kind_phys) :: sfc_up_lw + real(kind_phys) :: sfc_down_lw + end type ty_rad_lw + + !> \section arg_table_ty_rad_sw Argument Table + !! \htmlinclude arg_table_ty_rad_sw.html + !! + type ty_rad_sw + real(kind_phys), pointer :: sfc_up_sw(:) => null() + real(kind_phys), pointer :: sfc_down_sw(:) => null() + end type ty_rad_sw + +end module mod_rad_ddt diff --git a/test/var_compatibility_test/module_rad_ddt.meta b/test/var_compatibility_test/module_rad_ddt.meta new file mode 100644 index 00000000..c4792547 --- /dev/null +++ b/test/var_compatibility_test/module_rad_ddt.meta @@ -0,0 +1,40 @@ +[ccpp-table-properties] + name = ty_rad_lw + type = ddt + dependencies = + module_name = mod_rad_ddt +[ccpp-arg-table] + name = ty_rad_lw + type = ddt +[ sfc_up_lw ] + standard_name = surface_upwelling_longwave_radiation_flux + units = W m2 + dimensions = () + type = real + kind = kind_phys +[ sfc_down_lw ] + standard_name = surface_downwelling_longwave_radiation_flux + units = W m2 + dimensions = () + type = real + kind = kind_phys + +[ccpp-table-properties] + name = ty_rad_sw + type = ddt + module_name = mod_rad_ddt +[ccpp-arg-table] + name = ty_rad_sw + type = ddt +[ sfc_up_sw ] + standard_name = surface_upwelling_shortwave_radiation_flux + units = W m2 + dimensions = (horizontal_dimension) + type = real + kind = kind_phys +[ sfc_down_sw ] + standard_name = surface_downwelling_shortwave_radiation_flux + units = W m2 + dimensions = (horizontal_dimension) + type = real + kind = kind_phys diff --git a/test/var_compatibility_test/rad_lw.F90 b/test/var_compatibility_test/rad_lw.F90 new file mode 100644 index 00000000..5859f8bf --- /dev/null +++ b/test/var_compatibility_test/rad_lw.F90 @@ -0,0 +1,35 @@ +module rad_lw + use ccpp_kinds, only: kind_phys + use mod_rad_ddt, only: ty_rad_lw + + implicit none + private + + public :: rad_lw_run + +contains + + !> \section arg_table_rad_lw_run Argument Table + !! \htmlinclude arg_table_rad_lw_run.html + !! + subroutine rad_lw_run(ncol, fluxLW, errmsg, errflg) + + integer, intent(in) :: ncol + type(ty_rad_lw), intent(inout) :: fluxLW(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Locals + integer :: icol + + errmsg = '' + errflg = 0 + + do icol=1,ncol + fluxLW(icol)%sfc_up_lw = 300._kind_phys + fluxLW(icol)%sfc_down_lw = 50._kind_phys + enddo + + end subroutine rad_lw_run + +end module rad_lw diff --git a/test/var_compatibility_test/rad_lw.meta b/test/var_compatibility_test/rad_lw.meta new file mode 100644 index 00000000..883edf1b --- /dev/null +++ b/test/var_compatibility_test/rad_lw.meta @@ -0,0 +1,35 @@ +[ccpp-table-properties] + name = rad_lw + type = scheme + dependencies = module_rad_ddt.F90 +[ccpp-arg-table] + name = rad_lw_run + type = scheme +[ ncol ] + standard_name = horizontal_loop_extent + type = integer + units = count + dimensions = () + intent = in +[fluxLW] + standard_name = longwave_radiation_fluxes + long_name = longwave radiation fluxes + units = W m-2 + dimensions = (horizontal_loop_extent) + type = ty_rad_lw + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/var_compatibility_test/rad_sw.F90 b/test/var_compatibility_test/rad_sw.F90 new file mode 100644 index 00000000..ddf35224 --- /dev/null +++ b/test/var_compatibility_test/rad_sw.F90 @@ -0,0 +1,35 @@ +module rad_sw + use ccpp_kinds, only: kind_phys + + implicit none + private + + public :: rad_sw_run + +contains + + !> \section arg_table_rad_sw_run Argument Table + !! \htmlinclude arg_table_rad_sw_run.html + !! + subroutine rad_sw_run(ncol, sfc_up_sw, sfc_down_sw, errmsg, errflg) + + integer, intent(in) :: ncol + real(kind_phys), intent(inout) :: sfc_up_sw(:) + real(kind_phys), intent(inout) :: sfc_down_sw(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Locals + integer :: icol + + errmsg = '' + errflg = 0 + + do icol=1,ncol + sfc_up_sw(icol) = 100._kind_phys + sfc_down_sw(icol) = 400._kind_phys + enddo + + end subroutine rad_sw_run + +end module rad_sw diff --git a/test/var_compatibility_test/rad_sw.meta b/test/var_compatibility_test/rad_sw.meta new file mode 100644 index 00000000..d88b9acc --- /dev/null +++ b/test/var_compatibility_test/rad_sw.meta @@ -0,0 +1,41 @@ +[ccpp-table-properties] + name = rad_sw + type = scheme +[ccpp-arg-table] + name = rad_sw_run + type = scheme +[ ncol ] + standard_name = horizontal_loop_extent + type = integer + units = count + dimensions = () + intent = in +[ sfc_up_sw ] + standard_name = surface_upwelling_shortwave_radiation_flux + units = W m2 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout +[ sfc_down_sw ] + standard_name = surface_downwelling_shortwave_radiation_flux + units = W m2 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test deleted file mode 100755 index 5a1d6b5c..00000000 --- a/test/var_compatibility_test/run_test +++ /dev/null @@ -1,261 +0,0 @@ -#! /bin/bash - -currdir="`pwd -P`" -scriptdir="$( cd $( dirname $0 ); pwd -P )" - -## -## Option default values -## -defdir="ct_build" -build_dir="${currdir}/${defdir}" -cleanup="PASS" # Other supported options are ALWAYS and NEVER -verbosity=0 - -## -## General syntax help function -## Usage: help -## -help () { - local hname="Usage: `basename ${0}`" - local hprefix="`echo ${hname} | tr '[!-~]' ' '`" - echo "${hname} [ --build-dir ] [ --cleanup ]" - echo "${hprefix} [ --verbosity <#> ]" - hprefix=" " - echo "" - echo "${hprefix} : Directory for building and running the test" - echo "${hprefix} default is /${defdir}" - echo "${hprefix} : Cleanup option is ALWAYS, NEVER, or PASS" - echo "${hprefix} default is PASS" - echo "${hprefix} verbosity: 0, 1, or 2" - echo "${hprefix} default is 0" - exit $1 -} - -## -## Error output function (should be handed a string) -## -perr() { - >&2 echo -e "\nERROR: ${@}\n" - exit 1 -} - -## -## Cleanup the build and test directory -## -docleanup() { - # We start off in the build directory - if [ "${build_dir}" == "${currdir}" ]; then - echo "WARNING: Cannot clean ${build_dir}" - else - cd ${currdir} - rm -rf ${build_dir} - fi -} - -## Process our input arguments -while [ $# -gt 0 ]; do - case $1 in - --h | -h | --help | -help) - help 0 - ;; - --build-dir) - if [ $# -lt 2 ]; then - perr "${1} requires a build directory" - fi - build_dir="${2}" - shift - ;; - --cleanup) - if [ $# -lt 2 ]; then - perr "${1} requies a cleanup option (ALWAYS, NEVER, PASS)" - fi - if [ "${2}" == "ALWAYS" -o "${2}" == "NEVER" -o "${2}" == "PASS" ]; then - cleanup="${2}" - else - perr "Allowed cleanup options: ALWAYS, NEVER, PASS" - fi - shift - ;; - --verbosity) - if [ $# -lt 2 ]; then - perr "${1} requires a verbosity value (0, 1, or 2)" - fi - if [ "${2}" == "0" -o "${2}" == "1" -o "${2}" == "2" ]; then - verbosity=$2 - else - perr "allowed verbosity levels are 0, 1, 2" - fi - shift - ;; - *) - perr "Unrecognized option, \"${1}\"" - ;; - esac - shift -done - -# Create the build directory, if necessary -if [ -d "${build_dir}" ]; then - # Always make sure build_dir is not in the test dir - if [ "$( cd ${build_dir}; pwd -P )" == "${currdir}" ]; then - build_dir="${build_dir}/${defdir}" - fi -else - mkdir -p ${build_dir} - res=$? - if [ $res -ne 0 ]; then - perr "Unable to create build directory, '${build_dir}'" - fi -fi -build_dir="$( cd ${build_dir}; pwd -P )" - -## framework is the CCPP Framework root dir -framework="$( cd $( dirname $( dirname ${scriptdir} ) ); pwd -P )" -frame_src="${framework}/src" - -## -## check strings for datafile command-list test -## NB: This has to be after build_dir is finalized -## -host_files="${build_dir}/ccpp/test_host_ccpp_cap.F90" -suite_files="${build_dir}/ccpp/ccpp_var_compatibility_suite_cap.F90" -utility_files="${build_dir}/ccpp/ccpp_kinds.F90" -utility_files="${utility_files},${frame_src}/ccpp_constituent_prop_mod.F90" -utility_files="${utility_files},${frame_src}/ccpp_hashable.F90" -utility_files="${utility_files},${frame_src}/ccpp_hash_table.F90" -ccpp_files="${utility_files}" -ccpp_files="${ccpp_files},${build_dir}/ccpp/test_host_ccpp_cap.F90" -ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_var_compatibility_suite_cap.F90" -#process_list="" -module_list="effr_calc,effr_diag,effr_post,effr_pre" -#dependencies="" -suite_list="var_compatibility_suite" -required_vars_var_compatibility="ccpp_error_code,ccpp_error_message" -required_vars_var_compatibility="${required_vars_var_compatibility},cloud_graupel_number_concentration" -required_vars_var_compatibility="${required_vars_var_compatibility},cloud_ice_number_concentration" -required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_graupel" -required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_ice_particle" -required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_liquid_water_particle" -required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_rain_particle" -required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" -required_vars_var_compatibility="${required_vars_var_compatibility},flag_indicating_cloud_microphysics_has_graupel" -required_vars_var_compatibility="${required_vars_var_compatibility},flag_indicating_cloud_microphysics_has_ice" -required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_dimension" -required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_loop_begin" -required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_loop_end" -required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing" -required_vars_var_compatibility="${required_vars_var_compatibility},vertical_layer_dimension" -input_vars_var_compatibility="cloud_graupel_number_concentration" -#input_vars_var_compatibility="${input_vars_var_compatibility},cloud_ice_number_concentration" -input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_graupel" -input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_liquid_water_particle" -input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_rain_particle" -input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" -input_vars_var_compatibility="${input_vars_var_compatibility},flag_indicating_cloud_microphysics_has_graupel" -input_vars_var_compatibility="${input_vars_var_compatibility},flag_indicating_cloud_microphysics_has_ice" -input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_dimension" -input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_loop_begin" -input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_loop_end" -input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing" -input_vars_var_compatibility="${input_vars_var_compatibility},vertical_layer_dimension" -output_vars_var_compatibility="ccpp_error_code,ccpp_error_message" -output_vars_var_compatibility="${output_vars_var_compatibility},cloud_ice_number_concentration" -output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_ice_particle" -output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_liquid_water_particle" -output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_rain_particle" -output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" -output_vars_var_compatibility="${output_vars_var_compatibility},scalar_variable_for_testing" - -## -## Run a database report and check the return string -## $1 is the report program file -## $2 is the database file -## $3 is the report string -## $4 is the check string -## $5+ are any optional arguments -## -check_datatable() { - local checkstr=${4} - local teststr - local prog=${1} - local database=${2} - local report=${3} - shift 4 - echo "Checking ${report} report" - teststr="`${prog} ${database} ${report} $@`" - if [ "${teststr}" != "${checkstr}" ]; then - perr "datatable check:\nExpected: '${checkstr}'\nGot: '${teststr}'" - fi -} - -# cd to the build directory -cd ${build_dir} -res=$? -if [ $res -ne 0 ]; then - perr "Unable to cd to build directory, '${build_dir}'" -fi -# Clean build directory -rm -rf * -res=$? -if [ $res -ne 0 ]; then - perr "Unable to clean build directory, '${build_dir}'" -fi -# Run CMake -opts="" -if [ $verbosity -gt 0 ]; then - opts="${opts} -DVERBOSITY=${verbosity}" -fi -# Run cmake -cmake ${scriptdir} ${opts} -res=$? -if [ $res -ne 0 ]; then - perr "CMake failed with exit code, ${res}" -fi -# Test the datafile user interface -report_prog="${framework}/scripts/ccpp_datafile.py" -datafile="${build_dir}/ccpp/datatable.xml" -echo "Running python interface tests" -python3 ${scriptdir}/test_reports.py ${build_dir} ${datafile} -res=$? -if [ $res -ne 0 ]; then - perr "python interface tests failed" -fi -echo "Running command line tests" -echo "Checking required files from command line:" -check_datatable ${report_prog} ${datafile} "--host-files" ${host_files} -check_datatable ${report_prog} ${datafile} "--suite-files" ${suite_files} -check_datatable ${report_prog} ${datafile} "--utility-files" ${utility_files} -check_datatable ${report_prog} ${datafile} "--ccpp-files" ${ccpp_files} -echo -e "\nChecking lists from command line" -#check_datatable ${report_prog} ${datafile} "--process-list" ${process_list} -check_datatable ${report_prog} ${datafile} "--module-list" ${module_list} -#check_datatable ${report_prog} ${datafile} "--dependencies" ${dependencies} -check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ - --sep ";" -echo -e "\nChecking variables for var_compatibility suite from command line" -check_datatable ${report_prog} ${datafile} "--required-variables" \ - ${required_vars_var_compatibility} "var_compatibility_suite" -check_datatable ${report_prog} ${datafile} "--input-variables" \ - ${input_vars_var_compatibility} "var_compatibility_suite" -check_datatable ${report_prog} ${datafile} "--output-variables" \ - ${output_vars_var_compatibility} "var_compatibility_suite" -# Run make -make -res=$? -if [ $res -ne 0 ]; then - perr "make failed with exit code, ${res}" -fi -# Run test -./test_host -res=$? -if [ $res -ne 0 ]; then - perr "test_host failed with exit code, ${res}" -fi - -if [ "${cleanup}" == "ALWAYS" ]; then - docleanup -elif [ $res -eq 0 -a "${cleanup}" == "PASS" ]; then - docleanup -fi - -exit $res diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 3bb50da4..f3a389e8 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -24,92 +24,10 @@ module test_prog CONTAINS - logical function check_list(test_list, chk_list, list_desc, suite_name) - ! Check a list () against its expected value () - - ! Dummy arguments - character(len=*), intent(in) :: test_list(:) - character(len=*), intent(in) :: chk_list(:) - character(len=*), intent(in) :: list_desc - character(len=*), optional, intent(in) :: suite_name - - ! Local variables - logical :: found - integer :: num_items - integer :: lindex, tindex - integer, allocatable :: check_unique(:) - character(len=2) :: sep - character(len=256) :: errmsg - - check_list = .true. - errmsg = '' - - ! Check the list size - num_items = size(chk_list) - if (size(test_list) /= num_items) then - write(errmsg, '(a,i0,2a)') 'ERROR: Found ', size(test_list), & - ' ', trim(list_desc) - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' for suite, ', & - trim(suite_name) - end if - write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items - write(6, *) trim(errmsg) - errmsg = '' - check_list = .false. - end if - - ! Now, check the list contents for 1-1 correspondence - if (check_list) then - allocate(check_unique(num_items)) - check_unique = -1 - do lindex = 1, num_items - found = .false. - do tindex = 1, num_items - if (trim(test_list(lindex)) == trim(chk_list(tindex))) then - check_unique(tindex) = lindex - found = .true. - exit - end if - end do - if (.not. found) then - check_list = .false. - write(errmsg, '(5a)') 'ERROR: ', trim(list_desc), ' item, ', & - trim(test_list(lindex)), ', was not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - write(6, *) trim(errmsg) - errmsg = '' - end if - end do - if (check_list .and. ANY(check_unique < 0)) then - check_list = .false. - write(errmsg, '(3a)') 'ERROR: The following ', trim(list_desc), & - ' items were not found' - if (present(suite_name)) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') ' in suite, ', & - trim(suite_name) - end if - sep = '; ' - do lindex = 1, num_items - if (check_unique(lindex) < 0) then - write(errmsg(len_trim(errmsg)+1:), '(2a)') sep, & - trim(chk_list(lindex)) - sep = ', ' - end if - end do - write(6, *) trim(errmsg) - errmsg = '' - end if - end if - - end function check_list - logical function check_suite(test_suite) use test_host_ccpp_cap, only: ccpp_physics_suite_part_list use test_host_ccpp_cap, only: ccpp_physics_suite_variables + use test_utils, only: check_list ! Dummy argument type(suite_info), intent(in) :: test_suite @@ -194,6 +112,7 @@ subroutine test_host(retval, test_suites) use test_host_ccpp_cap, only: test_host_ccpp_physics_finalize use test_host_ccpp_cap, only: ccpp_physics_suite_list use test_host_mod, only: init_data, compare_data + use test_utils, only: check_list type(suite_info), intent(in) :: test_suites(:) logical, intent(out) :: retval @@ -343,64 +262,3 @@ subroutine test_host(retval, test_suites) end subroutine test_host end module test_prog - - program test - use test_prog, only: test_host, suite_info, cm, cs - - implicit none - - character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - - character(len=cm), target :: test_invars1(8) = (/ & - 'effective_radius_of_stratiform_cloud_rain_particle ', & - 'effective_radius_of_stratiform_cloud_liquid_water_particle', & - 'effective_radius_of_stratiform_cloud_snow_particle ', & - 'effective_radius_of_stratiform_cloud_graupel ', & - 'cloud_graupel_number_concentration ', & - 'scalar_variable_for_testing ', & - 'flag_indicating_cloud_microphysics_has_graupel ', & - 'flag_indicating_cloud_microphysics_has_ice '/) - - character(len=cm), target :: test_outvars1(8) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'effective_radius_of_stratiform_cloud_ice_particle ', & - 'effective_radius_of_stratiform_cloud_liquid_water_particle', & - 'effective_radius_of_stratiform_cloud_rain_particle ', & - 'effective_radius_of_stratiform_cloud_snow_particle ', & - 'cloud_ice_number_concentration ', & - 'scalar_variable_for_testing ' /) - - character(len=cm), target :: test_reqvars1(12) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'effective_radius_of_stratiform_cloud_rain_particle ', & - 'effective_radius_of_stratiform_cloud_ice_particle ', & - 'effective_radius_of_stratiform_cloud_liquid_water_particle', & - 'effective_radius_of_stratiform_cloud_snow_particle ', & - 'effective_radius_of_stratiform_cloud_graupel ', & - 'cloud_graupel_number_concentration ', & - 'cloud_ice_number_concentration ', & - 'scalar_variable_for_testing ', & - 'flag_indicating_cloud_microphysics_has_graupel ', & - 'flag_indicating_cloud_microphysics_has_ice '/) - - type(suite_info) :: test_suites(1) - logical :: run_okay - - ! Setup expected test suite info - test_suites(1)%suite_name = 'var_compatibility_suite' - test_suites(1)%suite_parts => test_parts1 - test_suites(1)%suite_input_vars => test_invars1 - test_suites(1)%suite_output_vars => test_outvars1 - test_suites(1)%suite_required_vars => test_reqvars1 - - call test_host(run_okay, test_suites) - - if (run_okay) then - STOP 0 - else - STOP -1 - end if - -end program test diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 9d0ca306..c46bbfff 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -1,6 +1,10 @@ module test_host_data - use ccpp_kinds, only: kind_phys + use ccpp_kinds, only: kind_phys + use mod_rad_ddt, only: ty_rad_lw, ty_rad_sw + + implicit none + private !> \section arg_table_physics_state Argument Table !! \htmlinclude arg_table_physics_state.html @@ -13,9 +17,20 @@ module test_host_data ncg, & ! number concentration of cloud graupel nci ! number concentration of cloud ice real(kind_phys) :: scalar_var + type(ty_rad_lw), dimension(:), allocatable :: & + fluxLW ! Longwave radiation fluxes + type(ty_rad_sw) :: & + fluxSW ! Shortwave radiation fluxes + real(kind_phys) :: scalar_varA + real(kind_phys) :: scalar_varB + real(kind_phys) :: tke, tke2 + integer :: scalar_varC + integer :: scheme_order + integer :: num_subcycles end type physics_state - public allocate_physics_state + public :: physics_state + public :: allocate_physics_state contains @@ -62,6 +77,26 @@ subroutine allocate_physics_state(cols, levels, state, has_graupel, has_ice) allocate(state%nci(cols, levels)) endif + if (allocated(state%fluxLW)) then + deallocate(state%fluxLW) + end if + allocate(state%fluxLW(cols)) + + if (associated(state%fluxSW%sfc_up_sw)) then + nullify(state%fluxSW%sfc_up_sw) + end if + allocate(state%fluxSW%sfc_up_sw(cols)) + + if (associated(state%fluxSW%sfc_down_sw)) then + nullify(state%fluxSW%sfc_down_sw) + end if + allocate(state%fluxSW%sfc_down_sw(cols)) + + ! Initialize scheme counter. + state%scheme_order = 1 + ! Initialize subcycle counter. + state%num_subcycles = 3 + end subroutine allocate_physics_state end module test_host_data diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index d3bca89b..59a0fb4d 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -1,6 +1,7 @@ [ccpp-table-properties] name = physics_state type = ddt + dependencies = module_rad_ddt.F90 [ccpp-arg-table] name = physics_state type = ddt @@ -59,3 +60,69 @@ dimensions = () type = real kind = kind_phys +[ tke ] + standard_name = turbulent_kinetic_energy + long_name = turbulent_kinetic_energy + units = J kg-1 + dimensions = () + type = real + kind = kind_phys +[ tke2 ] + standard_name = turbulent_kinetic_energy2 + long_name = turbulent_kinetic_energy2 + units = m2 s-2 + dimensions = () + type = real + kind = kind_phys +[fluxSW] + standard_name = shortwave_radiation_fluxes + long_name = shortwave radiation fluxes + units = W m-2 + dimensions = () + type = ty_rad_sw +[fluxLW] + standard_name = longwave_radiation_fluxes + long_name = longwave radiation fluxes + units = W m-2 + dimensions = (horizontal_dimension) + type = ty_rad_lw +[scalar_varA] + standard_name = scalar_variable_for_testing_a + long_name = unused scalar variable A + units = m + dimensions = () + type = real + kind = kind_phys +[scalar_varB] + standard_name = scalar_variable_for_testing_b + long_name = unused scalar variable B + units = m + dimensions = () + type = real + kind = kind_phys +[scalar_varC] + standard_name = scalar_variable_for_testing_c + long_name = unused scalar variable C + units = m + dimensions = () + type = integer +[scheme_order] + standard_name = scheme_order_in_suite + long_name = scheme order in suite definition file + units = None + dimensions = () + type = integer +[num_subcycles] + standard_name = num_subcycles_for_effr + long_name = Number of times to subcycle the effr calculation + units = None + dimensions = () + type = integer + +[ccpp-table-properties] + name = test_host_data + type = module + dependencies = module_rad_ddt.F90 +[ccpp-arg-table] + name = test_host_data + type = module diff --git a/test/var_compatibility_test/test_host_mod.F90 b/test/var_compatibility_test/test_host_mod.F90 index ca1d2014..efaeb368 100644 --- a/test/var_compatibility_test/test_host_mod.F90 +++ b/test/var_compatibility_test/test_host_mod.F90 @@ -27,7 +27,10 @@ subroutine init_data() call allocate_physics_state(ncols, pver, phys_state, has_graupel, has_ice) phys_state%effrr = 1.0E-3 ! 1000 microns, in meter phys_state%effrl = 1.0E-4 ! 100 microns, in meter - phys_state%scalar_var = 1.0 ! in m + phys_state%scalar_var = 1.0 ! in m + phys_state%scalar_varA = 273.15 ! in K + phys_state%scalar_varB = 1013.0 ! in mb + phys_state%scalar_varC = 380 ! in ppmv effrs = 5.0E-4 ! 500 microns, in meter if (has_graupel) then phys_state%effrg = 2.5E-4 ! 250 microns, in meter @@ -37,6 +40,8 @@ subroutine init_data() phys_state%effri = 5.0E-5 ! 50 microns, in meter phys_state%nci = 80 endif + phys_state%tke = 10.0 !J kg-1 + phys_state%tke2 = 42.0 !J kg-1 end subroutine init_data @@ -47,7 +52,12 @@ logical function compare_data() real(kind_phys), parameter :: effri_expected = 7.5E-5 ! 75 microns, in meter real(kind_phys), parameter :: effrs_expected = 5.1E-4 ! 510 microns, in meter real(kind_phys), parameter :: scalar_expected = 2.0E3 ! 2 km, in meter + real(kind_phys), parameter :: tke_expected = 10.0 ! 10 J kg-1 real(kind_phys), parameter :: tolerance = 1.0E-6 ! used as scaling factor for expected value + real(kind_phys), parameter :: sfc_up_sw_expected = 100. ! W/m2 + real(kind_phys), parameter :: sfc_down_sw_expected = 400. ! W/m2 + real(kind_phys), parameter :: sfc_up_lw_expected = 300. ! W/m2 + real(kind_phys), parameter :: sfc_down_lw_expected = 50. ! W/m2 compare_data = .true. @@ -81,6 +91,36 @@ logical function compare_data() compare_data = .false. end if + if (abs( phys_state%tke - tke_expected) > tolerance*tke_expected) then + write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of tke from expected value exceeds tolerance: ', & + abs( phys_state%tke - tke_expected), ' > ', tolerance*tke_expected + compare_data = .false. + end if + + if (maxval(abs( phys_state%fluxSW%sfc_up_sw - sfc_up_sw_expected)) > tolerance*sfc_up_sw_expected) then + write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of sfc_up_sw from expected value exceeds tolerance: ', & + abs( phys_state%fluxSW%sfc_up_sw - sfc_up_sw_expected), ' > ', tolerance*sfc_up_sw_expected + compare_data = .false. + end if + + if (maxval(abs( phys_state%fluxSW%sfc_down_sw - sfc_down_sw_expected)) > tolerance*sfc_down_sw_expected) then + write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of sfc_down_sw from expected value exceeds tolerance: ', & + abs( phys_state%fluxSW%sfc_down_sw - sfc_down_sw_expected), ' > ', tolerance*sfc_down_sw_expected + compare_data = .false. + end if + + if (maxval(abs( phys_state%fluxLW%sfc_up_lw - sfc_up_lw_expected)) > tolerance*sfc_up_lw_expected) then + write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of sfc_up_lw from expected value exceeds tolerance: ', & + abs( phys_state%fluxLW%sfc_up_lw - sfc_up_lw_expected), ' > ', tolerance*sfc_up_lw_expected + compare_data = .false. + end if + + if (maxval(abs( phys_state%fluxLW%sfc_down_lw - sfc_down_lw_expected)) > tolerance*sfc_down_lw_expected) then + write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of sfc_down_lw from expected value exceeds tolerance: ', & + abs( phys_state%fluxLW%sfc_down_lw - sfc_down_lw_expected), ' > ', tolerance*sfc_down_lw_expected + compare_data = .false. + end if + end function compare_data end module test_host_mod diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py deleted file mode 100755 index 6f10fc6d..00000000 --- a/test/var_compatibility_test/test_reports.py +++ /dev/null @@ -1,162 +0,0 @@ -#! /usr/bin/env python3 -""" ------------------------------------------------------------------------ - Description: Test capgen database report python interface - - Assumptions: - - Command line arguments: build_dir database_filepath - - Usage: python test_reports ------------------------------------------------------------------------ -""" -import sys -import os - -_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) -_SCRIPTS_DIR = os.path.join(_FRAMEWORK_DIR, "scripts") -_SRC_DIR = os.path.join(_FRAMEWORK_DIR, "src") - -if not os.path.exists(_SCRIPTS_DIR): - raise ImportError("Cannot find scripts directory") -# end if - -if ((sys.version_info[0] < 3) or - (sys.version_info[0] == 3) and (sys.version_info[1] < 8)): - raise Exception("Python 3.8 or greater required") -# end if - -sys.path.append(_SCRIPTS_DIR) -# pylint: disable=wrong-import-position -from ccpp_datafile import datatable_report, DatatableReport -# pylint: enable=wrong-import-position - -def usage(errmsg=None): - """Raise an exception with optional error message and usage message""" - emsg = "usage: {} " - if errmsg: - emsg = errmsg + '\n' + emsg - # end if - raise ValueError(emsg.format(sys.argv[0])) - -if len(sys.argv) != 3: - usage() -# end if - -_BUILD_DIR = os.path.abspath(sys.argv[1]) -_DATABASE = os.path.abspath(sys.argv[2]) -if not os.path.isdir(_BUILD_DIR): - _EMSG = " must be an existing build directory" - usage(_EMSG) -# end if -if (not os.path.exists(_DATABASE)) or (not os.path.isfile(_DATABASE)): - _EMSG = " must be an existing CCPP database file" - usage(_EMSG) -# end if - -# Check data -_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] -_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_var_compatibility_suite_cap.F90")] -_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), - os.path.join(_SRC_DIR, "ccpp_constituent_prop_mod.F90"), - os.path.join(_SRC_DIR, "ccpp_hashable.F90"), - os.path.join(_SRC_DIR, "ccpp_hash_table.F90")] -_CCPP_FILES = _UTILITY_FILES + \ - [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), - os.path.join(_BUILD_DIR, "ccpp", "ccpp_var_compatibility_suite_cap.F90")] -_MODULE_LIST = ["effr_calc", "effr_diag", "effr_post", "effr_pre"] -_SUITE_LIST = ["var_compatibility_suite"] -_INPUT_VARS_VAR_ACTION = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", - "effective_radius_of_stratiform_cloud_liquid_water_particle", - "effective_radius_of_stratiform_cloud_rain_particle", - "effective_radius_of_stratiform_cloud_snow_particle", - "effective_radius_of_stratiform_cloud_graupel", - "cloud_graupel_number_concentration", - "scalar_variable_for_testing", - "flag_indicating_cloud_microphysics_has_graupel", - "flag_indicating_cloud_microphysics_has_ice"] -_OUTPUT_VARS_VAR_ACTION = ["ccpp_error_code", "ccpp_error_message", - "effective_radius_of_stratiform_cloud_ice_particle", - "effective_radius_of_stratiform_cloud_liquid_water_particle", - "effective_radius_of_stratiform_cloud_snow_particle", - "cloud_ice_number_concentration", - "effective_radius_of_stratiform_cloud_rain_particle", - "scalar_variable_for_testing"] -_REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION - -def fields_string(field_type, field_list, sep): - """Create an error string for field(s), . - is used to separate items in """ - indent = ' '*11 - if field_list: - if len(field_list) > 1: - field_str = "{} Fields: ".format(field_type) - else: - field_str = "{} Field: ".format(field_type) - # end if - fmsg = "\n{}{}{}".format(indent, field_str, sep.join(field_list)) - else: - fmsg = "" - # end if - return fmsg - -def check_datatable(database, report_type, check_list, - sep=',', exclude_protected=False): - """Run a database report and check the return string. - If an error is found, print an error message. - Return the number of errors""" - if sep is None: - sep = ',' - # end if - test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) - test_list = [x for x in test_str.split(sep) if x] - missing = list() - unexpected = list() - for item in check_list: - if item not in test_list: - missing.append(item) - # end if - # end for - for item in test_list: - if item not in check_list: - unexpected.append(item) - # end if - # end for - if missing or unexpected: - vmsg = "ERROR in {} datafile check:".format(report_type.action) - vmsg += fields_string("Missing", missing, sep) - vmsg += fields_string("Unexpected", unexpected, sep) - print(vmsg) - else: - print("{} report okay".format(report_type.action)) - # end if - return len(missing) + len(unexpected) - -NUM_ERRORS = 0 -print("Checking required files from python:") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("host_files"), - _HOST_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_files"), - _SUITE_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("utility_files"), - _UTILITY_FILES) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("ccpp_files"), - _CCPP_FILES) -print("\nChecking lists from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("module_list"), - _MODULE_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), - _SUITE_LIST) -print("\nChecking variables for var_compatibility suite from python") -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", - value="var_compatibility_suite"), - _REQUIRED_VARS_VAR_ACTION) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", - value="var_compatibility_suite"), - _INPUT_VARS_VAR_ACTION) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", - value="var_compatibility_suite"), - _OUTPUT_VARS_VAR_ACTION) - -sys.exit(NUM_ERRORS) diff --git a/test/var_compatibility_test/test_var_compatibility_integration.F90 b/test/var_compatibility_test/test_var_compatibility_integration.F90 new file mode 100644 index 00000000..1e081e10 --- /dev/null +++ b/test/var_compatibility_test/test_var_compatibility_integration.F90 @@ -0,0 +1,85 @@ +program test_var_compatibility_integration + use test_prog, only: test_host, suite_info, cm, cs + + implicit none + + character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) + + character(len=cm), target :: test_invars1(18) = (/ & + 'effective_radius_of_stratiform_cloud_rain_particle ', & + 'effective_radius_of_stratiform_cloud_liquid_water_particle', & + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'effective_radius_of_stratiform_cloud_graupel ', & + 'cloud_graupel_number_concentration ', & + 'scalar_variable_for_testing ', & + 'turbulent_kinetic_energy ', & + 'turbulent_kinetic_energy2 ', & + 'scalar_variable_for_testing_a ', & + 'scalar_variable_for_testing_b ', & + 'scalar_variable_for_testing_c ', & + 'scheme_order_in_suite ', & + 'num_subcycles_for_effr ', & + 'flag_indicating_cloud_microphysics_has_graupel ', & + 'flag_indicating_cloud_microphysics_has_ice ', & + 'surface_downwelling_shortwave_radiation_flux ', & + 'surface_upwelling_shortwave_radiation_flux ', & + 'longwave_radiation_fluxes '/) + + character(len=cm), target :: test_outvars1(14) = (/ & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'effective_radius_of_stratiform_cloud_ice_particle ', & + 'effective_radius_of_stratiform_cloud_liquid_water_particle', & + 'effective_radius_of_stratiform_cloud_rain_particle ', & + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'cloud_ice_number_concentration ', & + 'scalar_variable_for_testing ', & + 'scheme_order_in_suite ', & + 'surface_downwelling_shortwave_radiation_flux ', & + 'surface_upwelling_shortwave_radiation_flux ', & + 'turbulent_kinetic_energy ', & + 'turbulent_kinetic_energy2 ', & + 'longwave_radiation_fluxes '/) + + character(len=cm), target :: test_reqvars1(22) = (/ & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'effective_radius_of_stratiform_cloud_rain_particle ', & + 'effective_radius_of_stratiform_cloud_ice_particle ', & + 'effective_radius_of_stratiform_cloud_liquid_water_particle', & + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'effective_radius_of_stratiform_cloud_graupel ', & + 'cloud_graupel_number_concentration ', & + 'cloud_ice_number_concentration ', & + 'scalar_variable_for_testing ', & + 'turbulent_kinetic_energy ', & + 'turbulent_kinetic_energy2 ', & + 'scalar_variable_for_testing_a ', & + 'scalar_variable_for_testing_b ', & + 'scalar_variable_for_testing_c ', & + 'scheme_order_in_suite ', & + 'num_subcycles_for_effr ', & + 'flag_indicating_cloud_microphysics_has_graupel ', & + 'flag_indicating_cloud_microphysics_has_ice ', & + 'surface_downwelling_shortwave_radiation_flux ', & + 'surface_upwelling_shortwave_radiation_flux ', & + 'longwave_radiation_fluxes '/) + + type(suite_info) :: test_suites(1) + logical :: run_okay + + ! Setup expected test suite info + test_suites(1)%suite_name = 'var_compatibility_suite' + test_suites(1)%suite_parts => test_parts1 + test_suites(1)%suite_input_vars => test_invars1 + test_suites(1)%suite_output_vars => test_outvars1 + test_suites(1)%suite_required_vars => test_reqvars1 + + call test_host(run_okay, test_suites) + + if (run_okay) then + STOP 0 + else + STOP -1 + end if +end program test_var_compatibility_integration diff --git a/test/var_compatibility_test/var_compatibility_files.txt b/test/var_compatibility_test/var_compatibility_files.txt deleted file mode 100644 index 6d83c980..00000000 --- a/test/var_compatibility_test/var_compatibility_files.txt +++ /dev/null @@ -1,4 +0,0 @@ -effr_calc.meta -effr_diag.meta -effr_pre.meta -effr_post.meta diff --git a/test/var_compatibility_test/var_compatibility_suite.xml b/test/var_compatibility_test/var_compatibility_suite.xml index 5956a8bd..a168e2ef 100644 --- a/test/var_compatibility_test/var_compatibility_suite.xml +++ b/test/var_compatibility_test/var_compatibility_suite.xml @@ -2,9 +2,15 @@ - effr_pre - effr_calc - effr_post + + effr_pre + + effr_calc + + effr_post + effr_diag + rad_lw + rad_sw diff --git a/test/var_compatibility_test/var_compatibility_test_reports.py b/test/var_compatibility_test/var_compatibility_test_reports.py new file mode 100755 index 00000000..5a8bdb95 --- /dev/null +++ b/test/var_compatibility_test/var_compatibility_test_reports.py @@ -0,0 +1,116 @@ +#! /usr/bin/env python3 +""" +----------------------------------------------------------------------- + Description: Test capgen database report python interface + + Assumptions: + + Command line arguments: build_dir database_filepath + + Usage: python test_reports +----------------------------------------------------------------------- +""" +import os +import unittest + +from test_stub import BaseTests + +_BUILD_DIR = os.path.join(os.path.abspath(os.environ['BUILD_DIR']), "test", "var_compatibility_test") +_DATABASE = os.path.abspath(os.path.join(_BUILD_DIR, "ccpp", "datatable.xml")) + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_FRAMEWORK_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, os.pardir)) +_SCRIPTS_DIR = os.path.join(_FRAMEWORK_DIR, "scripts") +_SRC_DIR = os.path.join(_FRAMEWORK_DIR, "src") + +# Check data +_HOST_FILES = [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90")] +_SUITE_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_var_compatibility_suite_cap.F90")] +_UTILITY_FILES = [os.path.join(_BUILD_DIR, "ccpp", "ccpp_kinds.F90"), + os.path.join(_SRC_DIR, "ccpp_constituent_prop_mod.F90"), + os.path.join(_SRC_DIR, "ccpp_scheme_utils.F90"), + os.path.join(_SRC_DIR, "ccpp_hashable.F90"), + os.path.join(_SRC_DIR, "ccpp_hash_table.F90")] +_CCPP_FILES = _UTILITY_FILES + \ + [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), + os.path.join(_BUILD_DIR, "ccpp", "ccpp_var_compatibility_suite_cap.F90")] +_PROCESS_LIST = [""] +_MODULE_LIST = ["effr_calc", "effr_diag", "effr_post", "mod_effr_pre", "rad_lw", "rad_sw"] +_SUITE_LIST = ["var_compatibility_suite"] +_DEPENDENCIES = [ os.path.join(_TEST_DIR, "module_rad_ddt.F90")] +_INPUT_VARS_VAR_ACTION = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", + "effective_radius_of_stratiform_cloud_liquid_water_particle", + "effective_radius_of_stratiform_cloud_rain_particle", + "effective_radius_of_stratiform_cloud_snow_particle", + "effective_radius_of_stratiform_cloud_graupel", + "cloud_graupel_number_concentration", + "scalar_variable_for_testing", + "turbulent_kinetic_energy", + "turbulent_kinetic_energy2", + "scalar_variable_for_testing_a", + "scalar_variable_for_testing_b", + "scalar_variable_for_testing_c", + "scheme_order_in_suite", + "flag_indicating_cloud_microphysics_has_graupel", + "flag_indicating_cloud_microphysics_has_ice", + "surface_downwelling_shortwave_radiation_flux", + "surface_upwelling_shortwave_radiation_flux", + "longwave_radiation_fluxes", + "num_subcycles_for_effr"] +_OUTPUT_VARS_VAR_ACTION = ["ccpp_error_code", "ccpp_error_message", + "effective_radius_of_stratiform_cloud_ice_particle", + "effective_radius_of_stratiform_cloud_liquid_water_particle", + "effective_radius_of_stratiform_cloud_snow_particle", + "cloud_ice_number_concentration", + "effective_radius_of_stratiform_cloud_rain_particle", + "turbulent_kinetic_energy", + "turbulent_kinetic_energy2", + "scalar_variable_for_testing", + "scalar_variable_for_testing", + "surface_downwelling_shortwave_radiation_flux", + "surface_upwelling_shortwave_radiation_flux", + "longwave_radiation_fluxes", + "scheme_order_in_suite"] +_REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION + + +class TestVarCompatibilityHostDataTables(unittest.TestCase, BaseTests.TestHostDataTables): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + + +class CommandLineVarCompatibilityHostDatafileRequiredFiles(unittest.TestCase, BaseTests.TestHostCommandLineDataFiles): + database = _DATABASE + host_files = _HOST_FILES + suite_files = _SUITE_FILES + utility_files = _UTILITY_FILES + ccpp_files = _CCPP_FILES + process_list = _PROCESS_LIST + module_list = _MODULE_LIST + dependencies = _DEPENDENCIES + suite_list = _SUITE_LIST + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" + + +class TestCapgenDdtSuite(unittest.TestCase, BaseTests.TestSuite): + database = _DATABASE + required_vars = _REQUIRED_VARS_VAR_ACTION + input_vars = _INPUT_VARS_VAR_ACTION + output_vars = _OUTPUT_VARS_VAR_ACTION + suite_name = "var_compatibility_suite" + + +class CommandLineCapgenDdtSuite(unittest.TestCase, BaseTests.TestSuiteCommandLine): + database = _DATABASE + required_vars = _REQUIRED_VARS_VAR_ACTION + input_vars = _INPUT_VARS_VAR_ACTION + output_vars = _OUTPUT_VARS_VAR_ACTION + suite_name = "var_compatibility_suite" + datafile_script = f"{_SCRIPTS_DIR}/ccpp_datafile.py" diff --git a/test_prebuild/run_all_tests.sh b/test_prebuild/run_all_tests.sh index 2de74906..08e5910a 100755 --- a/test_prebuild/run_all_tests.sh +++ b/test_prebuild/run_all_tests.sh @@ -4,7 +4,8 @@ set -e echo "" && echo "Running unit_tests " && cd unit_tests && ./run_tests.sh && cd .. echo "" && echo "Running test_opt_arg " && cd test_opt_arg && ./run_test.sh && cd .. -echo "" && echo "Running test_blocked_data" && cd test_blocked_data && ./run_test.sh && cd .. +# No longer possible because of https://github.com/NCAR/ccpp-framework/pull/659 +#echo "" && echo "Running test_blocked_data" && cd test_blocked_data && ./run_test.sh && cd .. echo "" && echo "Running test_chunked_data" && cd test_chunked_data && ./run_test.sh && cd .. echo "" && echo "Running test_unit_conv" && cd test_unit_conv && ./run_test.sh && cd .. diff --git a/test_prebuild/test_track_variables/scheme_3.meta b/test_prebuild/test_track_variables/scheme_3.meta index eb1fe80b..f7d568dd 100644 --- a/test_prebuild/test_track_variables/scheme_3.meta +++ b/test_prebuild/test_track_variables/scheme_3.meta @@ -107,7 +107,7 @@ standard_name = air_pressure long_name = mean layer pressure units = Pa - dimensions = (horizontal_loop_extent,vertical_layer_dimension) + dimensions = (horizontal_dimension,vertical_layer_dimension) type = real kind = kind_phys intent = out @@ -115,7 +115,7 @@ standard_name = surface_air_pressure long_name = surface pressure units = Pa - dimensions = (horizontal_loop_extent) + dimensions = (horizontal_dimension) type = real kind = kind_phys intent = inout @@ -128,7 +128,7 @@ standard_name = air_pressure long_name = mean layer pressure units = Pa - dimensions = (horizontal_loop_extent,vertical_layer_dimension) + dimensions = (horizontal_dimension,vertical_layer_dimension) type = real kind = kind_phys intent = out @@ -136,7 +136,7 @@ standard_name = surface_air_pressure long_name = surface pressure units = Pa - dimensions = (horizontal_loop_extent) + dimensions = (horizontal_dimension) type = real kind = kind_phys intent = out diff --git a/test_prebuild/test_unit_conv/data.F90 b/test_prebuild/test_unit_conv/data.F90 index f53c107e..645a531b 100644 --- a/test_prebuild/test_unit_conv/data.F90 +++ b/test_prebuild/test_unit_conv/data.F90 @@ -10,13 +10,15 @@ module data private - public ncols, nspecies - public cdata, data_array, opt_array_flag + public ncols, ncolsrun, nspecies + public cdata, data_array, data_array2, opt_array_flag integer, parameter :: ncols = 4 + integer, parameter :: ncolsrun = ncols integer, parameter :: nspecies = 2 type(ccpp_t), target :: cdata real(kind_phys), dimension(1:ncols,1:nspecies) :: data_array + real(kind_phys), dimension(1:ncols) :: data_array2 logical :: opt_array_flag end module data diff --git a/test_prebuild/test_unit_conv/data.meta b/test_prebuild/test_unit_conv/data.meta index 895cd6c2..9c9ea5e7 100644 --- a/test_prebuild/test_unit_conv/data.meta +++ b/test_prebuild/test_unit_conv/data.meta @@ -12,6 +12,12 @@ dimensions = () type = ccpp_t [ncols] + standard_name = horizontal_dimension + long_name = horizontal dimension + units = count + dimensions = () + type = integer +[ncolsrun] standard_name = horizontal_loop_extent long_name = horizontal loop extent units = count @@ -27,14 +33,21 @@ standard_name = data_array_all_species long_name = data array in module units = m - dimensions = (horizontal_loop_extent, number_of_species) + dimensions = (horizontal_dimension, number_of_species) type = real kind = kind_phys [data_array(:,2)] standard_name = data_array long_name = data array in module units = m - dimensions = (horizontal_loop_extent) + dimensions = (horizontal_dimension) + type = real + kind = kind_phys +[data_array2] + standard_name = data_array2 + long_name = data array 2 in module + units = m2 s-2 + dimensions = (horizontal_dimension) type = real kind = kind_phys [opt_array_flag] @@ -47,7 +60,7 @@ standard_name = data_array_opt long_name = optional data array in km units = m - dimensions = (horizontal_loop_extent) + dimensions = (horizontal_dimension) type = real kind = kind_phys active = (flag_for_opt_array) diff --git a/test_prebuild/test_unit_conv/main.F90 b/test_prebuild/test_unit_conv/main.F90 index 3f7ee103..3eb6462e 100644 --- a/test_prebuild/test_unit_conv/main.F90 +++ b/test_prebuild/test_unit_conv/main.F90 @@ -4,7 +4,7 @@ program test_unit_conv use ccpp_types, only: ccpp_t use data, only: ncols, nspecies - use data, only: cdata, data_array, opt_array_flag + use data, only: cdata, data_array, data_array2, opt_array_flag use ccpp_static_api, only: ccpp_physics_init, & ccpp_physics_timestep_init, & @@ -30,6 +30,7 @@ program test_unit_conv cdata%thrd_cnt = 1 data_array = 1.0_8 + data_array2 = 42.0_8 opt_array_flag = .true. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/test_prebuild/test_unit_conv/unit_conv_scheme_1.F90 b/test_prebuild/test_unit_conv/unit_conv_scheme_1.F90 index d9488789..9ef178ff 100644 --- a/test_prebuild/test_unit_conv/unit_conv_scheme_1.F90 +++ b/test_prebuild/test_unit_conv/unit_conv_scheme_1.F90 @@ -13,16 +13,18 @@ module unit_conv_scheme_1 !! This is for unit testing only real(kind_phys), parameter :: target_value = 1.0_kind_phys + real(kind_phys), parameter :: target_value2 = 42.0_kind_phys contains !! \section arg_table_unit_conv_scheme_1_run Argument Table !! \htmlinclude unit_conv_scheme_1_run.html !! - subroutine unit_conv_scheme_1_run(data_array, data_array_opt, errmsg, errflg) + subroutine unit_conv_scheme_1_run(data_array, data_array2, data_array_opt, errmsg, errflg) character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg real(kind_phys), intent(inout) :: data_array(:) + real(kind_phys), intent(inout) :: data_array2(:) real(kind_phys), intent(inout), optional :: data_array_opt(:) ! Initialize CCPP error handling variables @@ -31,11 +33,19 @@ subroutine unit_conv_scheme_1_run(data_array, data_array_opt, errmsg, errflg) ! Check values in data array write(error_unit,'(a,e12.4)') 'In unit_conv_scheme_1_run: checking min/max values of data array to be approximately ', target_value if (minval(data_array)<0.99*target_value .or. maxval(data_array)>1.01*target_value) then - write(errmsg,'(3(a,e12.4),a)') "Error in unit_conv_scheme_1_run, expected values of approximately ", & + write(errmsg,'(3(a,e12.4),a)') "Error in unit_conv_scheme_1_run, expected values for data_array of approximately ", & target_value, " but got [ ", minval(data_array), " : ", maxval(data_array), " ]" errflg = 1 return end if + ! Check values in data array2 + write(error_unit,'(a,e12.4)') 'In unit_conv_scheme_1_run: checking min/max values of data array 2 to be approximately ', target_value2 + if (minval(data_array2)<0.99*target_value2 .or. maxval(data_array2)>1.01*target_value2) then + write(errmsg,'(3(a,e12.4),a)') "Error in unit_conv_scheme_1_run, expected values for data array 2 of approximately ", & + target_value2, " but got [ ", minval(data_array2), " : ", maxval(data_array2), " ]" + errflg = 1 + return + end if ! Check for presence of optional data array, then check its values write(error_unit,'(a)') 'In unit_conv_scheme_1_run: checking for presence of optional data array' if (.not. present(data_array_opt)) then diff --git a/test_prebuild/test_unit_conv/unit_conv_scheme_1.meta b/test_prebuild/test_unit_conv/unit_conv_scheme_1.meta index 91f22142..befb19bd 100644 --- a/test_prebuild/test_unit_conv/unit_conv_scheme_1.meta +++ b/test_prebuild/test_unit_conv/unit_conv_scheme_1.meta @@ -30,6 +30,14 @@ type = real kind = kind_phys intent = inout +[data_array2] + standard_name = data_array2 + long_name = data array in J kg-1 + units = J kg-1 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout [data_array_opt] standard_name = data_array_opt long_name = optional data array in m diff --git a/test_prebuild/test_unit_conv/unit_conv_scheme_2.F90 b/test_prebuild/test_unit_conv/unit_conv_scheme_2.F90 index 6b64402c..66f07d93 100644 --- a/test_prebuild/test_unit_conv/unit_conv_scheme_2.F90 +++ b/test_prebuild/test_unit_conv/unit_conv_scheme_2.F90 @@ -13,16 +13,18 @@ module unit_conv_scheme_2 !! This is for unit testing only real(kind_phys), parameter :: target_value = 1.0E-3_kind_phys + real(kind_phys), parameter :: target_value2 = 42.0_kind_phys contains !! \section arg_table_unit_conv_scheme_2_run Argument Table !! \htmlinclude unit_conv_scheme_2_run.html !! - subroutine unit_conv_scheme_2_run(data_array, data_array_opt, errmsg, errflg) + subroutine unit_conv_scheme_2_run(data_array, data_array2, data_array_opt, errmsg, errflg) character(len=*), intent(out) :: errmsg integer, intent(out) :: errflg real(kind_phys), intent(inout) :: data_array(:) + real(kind_phys), intent(inout) :: data_array2(:) real(kind_phys), intent(inout), optional :: data_array_opt(:) ! Initialize CCPP error handling variables @@ -36,6 +38,14 @@ subroutine unit_conv_scheme_2_run(data_array, data_array_opt, errmsg, errflg) errflg = 1 return end if + ! Check values in data array2 + write(error_unit,'(a,e12.4)') 'In unit_conv_scheme_2_run: checking min/max values of data array 2 to be approximately ', target_value2 + if (minval(data_array2)<0.99*target_value2 .or. maxval(data_array2)>1.01*target_value2) then + write(errmsg,'(3(a,e12.4),a)') "Error in unit_conv_scheme_2_run, expected values for data array 2 of approximately ", & + target_value2, " but got [ ", minval(data_array2), " : ", maxval(data_array2), " ]" + errflg = 1 + return + end if ! Check for presence of optional data array, then check its values write(error_unit,'(a)') 'In unit_conv_scheme_2_run: checking for presence of optional data array' if (.not. present(data_array_opt)) then diff --git a/test_prebuild/test_unit_conv/unit_conv_scheme_2.meta b/test_prebuild/test_unit_conv/unit_conv_scheme_2.meta index 534e3abe..68e4b063 100644 --- a/test_prebuild/test_unit_conv/unit_conv_scheme_2.meta +++ b/test_prebuild/test_unit_conv/unit_conv_scheme_2.meta @@ -30,6 +30,14 @@ type = real kind = kind_phys intent = inout +[data_array2] + standard_name = data_array2 + long_name = data array in m+2 s-2 + units = m+2 s-2 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout [data_array_opt] standard_name = data_array_opt long_name = optional data array in km