From ed3c91a29e424a8689d931b54f72949a1395db48 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Tue, 28 Jan 2025 23:39:12 +0000 Subject: [PATCH 01/20] Bug fix and add testing to demonstrate error --- scripts/ccpp_suite.py | 8 ++++---- test/var_compatibility_test/effr_calc.F90 | 15 +++++++++++++-- test/var_compatibility_test/effr_calc.meta | 20 ++++++++++++++++++++ test/var_compatibility_test/effr_diag.F90 | 14 +++++++++++++- test/var_compatibility_test/effr_diag.meta | 20 ++++++++++++++++++++ test/var_compatibility_test/effr_post.F90 | 14 +++++++++++++- test/var_compatibility_test/effr_post.meta | 20 ++++++++++++++++++++ test/var_compatibility_test/effr_pre.F90 | 13 ++++++++++++- test/var_compatibility_test/effr_pre.meta | 20 ++++++++++++++++++++ 9 files changed, 135 insertions(+), 9 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index b2d4c36e..b3225573 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_set = list() for group in self.groups: for scheme in group.schemes(): - scheme_set.add(scheme.name) + scheme_set.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_set: + if scheme_library[module]: scheme_entries = scheme_library[module] else: scheme_entries = no_scheme_entries diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 6dbbb722..34f9d1eb 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -8,10 +8,21 @@ 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(errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + end subroutine effr_calc_init + !> \section arg_table_effr_calc_run Argument Table !! \htmlinclude arg_table_effr_calc_run.html !! diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index 73c36ace..0530574f 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -2,6 +2,26 @@ name = effr_calc type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_calc_init + type = scheme +[ 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 diff --git a/test/var_compatibility_test/effr_diag.F90 b/test/var_compatibility_test/effr_diag.F90 index 38b87c1a..463b7be9 100644 --- a/test/var_compatibility_test/effr_diag.F90 +++ b/test/var_compatibility_test/effr_diag.F90 @@ -8,10 +8,22 @@ 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(errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + + end subroutine effr_diag_init + !> \section arg_table_effr_diag_run Argument Table !! \htmlinclude arg_table_effr_diag_run.html !! diff --git a/test/var_compatibility_test/effr_diag.meta b/test/var_compatibility_test/effr_diag.meta index ebb765f2..3a83e154 100644 --- a/test/var_compatibility_test/effr_diag.meta +++ b/test/var_compatibility_test/effr_diag.meta @@ -2,6 +2,26 @@ name = effr_diag type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_diag_init + type = scheme +[ 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 diff --git a/test/var_compatibility_test/effr_post.F90 b/test/var_compatibility_test/effr_post.F90 index ca04c247..c40b5df5 100644 --- a/test/var_compatibility_test/effr_post.F90 +++ b/test/var_compatibility_test/effr_post.F90 @@ -8,10 +8,22 @@ 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(errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + + end subroutine effr_post_init + !> \section arg_table_effr_post_run Argument Table !! \htmlinclude arg_table_effr_post_run.html !! diff --git a/test/var_compatibility_test/effr_post.meta b/test/var_compatibility_test/effr_post.meta index d65be238..2440a86e 100644 --- a/test/var_compatibility_test/effr_post.meta +++ b/test/var_compatibility_test/effr_post.meta @@ -2,6 +2,26 @@ name = effr_post type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_post_init + type = scheme +[ 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 diff --git a/test/var_compatibility_test/effr_pre.F90 b/test/var_compatibility_test/effr_pre.F90 index ba6ea2b9..c744f1fc 100644 --- a/test/var_compatibility_test/effr_pre.F90 +++ b/test/var_compatibility_test/effr_pre.F90 @@ -8,9 +8,20 @@ module effr_pre 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(errmsg, errflg) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + errmsg = '' + errflg = 0 + + end subroutine effr_pre_init !> \section arg_table_effr_pre_run Argument Table !! \htmlinclude arg_table_effr_pre_run.html diff --git a/test/var_compatibility_test/effr_pre.meta b/test/var_compatibility_test/effr_pre.meta index d6f67ec3..b24e8db6 100644 --- a/test/var_compatibility_test/effr_pre.meta +++ b/test/var_compatibility_test/effr_pre.meta @@ -2,6 +2,26 @@ name = effr_pre type = scheme dependencies = +######################################################################## +[ccpp-arg-table] + name = effr_pre_init + type = scheme +[ 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 From d73512a0a5b0dc330f5fa9f6092645e242662318 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Wed, 29 Jan 2025 19:57:07 +0000 Subject: [PATCH 02/20] Expand to test for correct scheme order in caps. --- test/var_compatibility_test/effr_calc.F90 | 13 +++++++++++-- test/var_compatibility_test/effr_calc.meta | 7 +++++++ test/var_compatibility_test/effr_diag.F90 | 11 ++++++++++- test/var_compatibility_test/effr_diag.meta | 7 +++++++ test/var_compatibility_test/effr_post.F90 | 11 ++++++++++- test/var_compatibility_test/effr_post.meta | 7 +++++++ test/var_compatibility_test/effr_pre.F90 | 11 ++++++++++- test/var_compatibility_test/effr_pre.meta | 7 +++++++ test/var_compatibility_test/run_test | 3 +++ test/var_compatibility_test/test_host.F90 | 12 ++++++++---- test/var_compatibility_test/test_host_data.F90 | 4 ++++ test/var_compatibility_test/test_host_data.meta | 6 ++++++ test/var_compatibility_test/test_reports.py | 4 +++- 13 files changed, 93 insertions(+), 10 deletions(-) diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 34f9d1eb..1b075a3c 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -14,13 +14,22 @@ module effr_calc !> \section arg_table_effr_calc_init Argument Table !! \htmlinclude arg_table_effr_calc_init.html !! - subroutine effr_calc_init(errmsg, errflg) + 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 diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index 0530574f..ec074f4d 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -6,6 +6,13 @@ [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 diff --git a/test/var_compatibility_test/effr_diag.F90 b/test/var_compatibility_test/effr_diag.F90 index 463b7be9..23993f5e 100644 --- a/test/var_compatibility_test/effr_diag.F90 +++ b/test/var_compatibility_test/effr_diag.F90 @@ -15,13 +15,22 @@ module effr_diag !> \section arg_table_effr_diag_init Argument Table !! \htmlinclude arg_table_effr_diag_init.html !! - subroutine effr_diag_init(errmsg, errflg) + 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 diff --git a/test/var_compatibility_test/effr_diag.meta b/test/var_compatibility_test/effr_diag.meta index 3a83e154..1855fd89 100644 --- a/test/var_compatibility_test/effr_diag.meta +++ b/test/var_compatibility_test/effr_diag.meta @@ -6,6 +6,13 @@ [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 diff --git a/test/var_compatibility_test/effr_post.F90 b/test/var_compatibility_test/effr_post.F90 index c40b5df5..17c63cc0 100644 --- a/test/var_compatibility_test/effr_post.F90 +++ b/test/var_compatibility_test/effr_post.F90 @@ -15,13 +15,22 @@ module effr_post !> \section arg_table_effr_post_init Argument Table !! \htmlinclude arg_table_effr_post_init.html !! - subroutine effr_post_init(errmsg, errflg) + 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 diff --git a/test/var_compatibility_test/effr_post.meta b/test/var_compatibility_test/effr_post.meta index 2440a86e..fd1d554e 100644 --- a/test/var_compatibility_test/effr_post.meta +++ b/test/var_compatibility_test/effr_post.meta @@ -6,6 +6,13 @@ [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 diff --git a/test/var_compatibility_test/effr_pre.F90 b/test/var_compatibility_test/effr_pre.F90 index c744f1fc..dcd3363a 100644 --- a/test/var_compatibility_test/effr_pre.F90 +++ b/test/var_compatibility_test/effr_pre.F90 @@ -14,13 +14,22 @@ module effr_pre !> \section arg_table_effr_pre_init Argument Table !! \htmlinclude arg_table_effr_pre_init.html !! - subroutine effr_pre_init(errmsg, errflg) + 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 diff --git a/test/var_compatibility_test/effr_pre.meta b/test/var_compatibility_test/effr_pre.meta index b24e8db6..2dc982e6 100644 --- a/test/var_compatibility_test/effr_pre.meta +++ b/test/var_compatibility_test/effr_pre.meta @@ -6,6 +6,13 @@ [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 diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index 5a1d6b5c..26e33a3a 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -144,6 +144,7 @@ required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_d 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},scheme_order_in_suite" 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" @@ -157,6 +158,7 @@ input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_dimensi 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},scheme_order_in_suite" 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" @@ -165,6 +167,7 @@ output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius 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" +output_vars_var_compatibility="${output_vars_var_compatibility},scheme_order_in_suite" ## ## Run a database report and check the return string diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 3bb50da4..721fc5f9 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,17 +351,18 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(8) = (/ & + character(len=cm), target :: test_invars1(9) = (/ & '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 ', & + 'scheme_order_in_suite ', & 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice '/) - character(len=cm), target :: test_outvars1(8) = (/ & + character(len=cm), target :: test_outvars1(9) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_ice_particle ', & @@ -369,9 +370,11 @@ program test 'effective_radius_of_stratiform_cloud_rain_particle ', & 'effective_radius_of_stratiform_cloud_snow_particle ', & 'cloud_ice_number_concentration ', & - 'scalar_variable_for_testing ' /) + 'scalar_variable_for_testing ', & + 'scheme_order_in_suite '/) + - character(len=cm), target :: test_reqvars1(12) = (/ & + character(len=cm), target :: test_reqvars1(13) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -382,6 +385,7 @@ program test 'cloud_graupel_number_concentration ', & 'cloud_ice_number_concentration ', & 'scalar_variable_for_testing ', & + 'scheme_order_in_suite ', & 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice '/) diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 9d0ca306..b6552e68 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -13,6 +13,7 @@ module test_host_data ncg, & ! number concentration of cloud graupel nci ! number concentration of cloud ice real(kind_phys) :: scalar_var + integer :: scheme_order end type physics_state public allocate_physics_state @@ -62,6 +63,9 @@ subroutine allocate_physics_state(cols, levels, state, has_graupel, has_ice) allocate(state%nci(cols, levels)) endif + ! Initialize scheme counter. + state%scheme_order = 1 + 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..db9d8b16 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -59,3 +59,9 @@ dimensions = () type = real kind = kind_phys +[scheme_order] + standard_name = scheme_order_in_suite + long_name = scheme order in suite definition file + units = None + dimensions = () + type = integer diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index 6f10fc6d..eb4a5a4b 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -74,6 +74,7 @@ def usage(errmsg=None): "effective_radius_of_stratiform_cloud_graupel", "cloud_graupel_number_concentration", "scalar_variable_for_testing", + "scheme_order_in_suite", "flag_indicating_cloud_microphysics_has_graupel", "flag_indicating_cloud_microphysics_has_ice"] _OUTPUT_VARS_VAR_ACTION = ["ccpp_error_code", "ccpp_error_message", @@ -82,7 +83,8 @@ def usage(errmsg=None): "effective_radius_of_stratiform_cloud_snow_particle", "cloud_ice_number_concentration", "effective_radius_of_stratiform_cloud_rain_particle", - "scalar_variable_for_testing"] + "scalar_variable_for_testing", + "scheme_order_in_suite"] _REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION def fields_string(field_type, field_list, sep): From f107e0cf4790a5388885cd27a047dc4d225eec6a Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 3 Feb 2025 15:58:17 +0000 Subject: [PATCH 03/20] Address reviewer comment --- scripts/ccpp_suite.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index b3225573..ab9d5552 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -426,14 +426,14 @@ 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 = list() + scheme_list = list() for group in self.groups: for scheme in group.schemes(): - scheme_set.append(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_set: + for module in scheme_list: if scheme_library[module]: scheme_entries = scheme_library[module] else: From aed2471bc480b5b3394f17c7be81d48dfa62874c Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 6 Feb 2025 11:01:58 -0700 Subject: [PATCH 04/20] ccpp_prebuild.py: remove execute function from common.py, clean files the Python way (#642) User interface changes?: No Closes https://github.com/NCAR/ccpp-framework/issues/641 --- scripts/ccpp_prebuild.py | 13 +++++++++---- scripts/common.py | 29 ----------------------------- test/unit_tests/test_common.py | 12 ------------ 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/scripts/ccpp_prebuild.py b/scripts/ccpp_prebuild.py index b8829b6f..95067328 100755 --- a/scripts/ccpp_prebuild.py +++ b/scripts/ccpp_prebuild.py @@ -13,7 +13,7 @@ import sys # CCPP framework imports -from common import encode_container, decode_container, decode_container_as_dict, execute +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 +157,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): diff --git a/scripts/common.py b/scripts/common.py index f5ba7f1d..3484a3c2 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).""" diff --git a/test/unit_tests/test_common.py b/test/unit_tests/test_common.py index fe5f852d..e95b8184 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""" From 6c494b26eee0cf9d58398313cf7e0ace43aa5255 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Wed, 12 Feb 2025 09:33:40 -0700 Subject: [PATCH 05/20] Capgen in SCM: Multiple instances of local_name in Group Cap (#631) Description: Allow for multiple instances of the same local_name being used in the Group cap for two situations: a) with different standard_names b) in different DDTs . User interface changes?: No Fixes: #629 Testing: Added to var_compatibility_test to exercise feature. This PR contains changes included in #630 --------- Co-authored-by: Dom Heinzeller --- scripts/metavar.py | 25 +++++++++++++++++++ scripts/suite_objects.py | 2 +- test/var_compatibility_test/effr_diag.F90 | 7 +++++- test/var_compatibility_test/effr_diag.meta | 7 ++++++ test/var_compatibility_test/effr_post.F90 | 8 +++++- test/var_compatibility_test/effr_post.meta | 8 ++++++ test/var_compatibility_test/effr_pre.F90 | 8 +++++- test/var_compatibility_test/effr_pre.meta | 8 ++++++ test/var_compatibility_test/run_test | 6 +++++ test/var_compatibility_test/test_host.F90 | 10 ++++++-- .../var_compatibility_test/test_host_data.F90 | 4 +++ .../test_host_data.meta | 20 +++++++++++++++ test/var_compatibility_test/test_host_mod.F90 | 5 +++- test/var_compatibility_test/test_reports.py | 3 +++ 14 files changed, 114 insertions(+), 7 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 35878bc4..66784e2e 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1677,12 +1677,37 @@ 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) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 4fac98b8..fb3a66c6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2273,7 +2273,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, diff --git a/test/var_compatibility_test/effr_diag.F90 b/test/var_compatibility_test/effr_diag.F90 index 23993f5e..409ff2f9 100644 --- a/test/var_compatibility_test/effr_diag.F90 +++ b/test/var_compatibility_test/effr_diag.F90 @@ -36,9 +36,10 @@ 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 !---------------------------------------------------------------- @@ -49,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 1855fd89..9e0e4fc2 100644 --- a/test/var_compatibility_test/effr_diag.meta +++ b/test/var_compatibility_test/effr_diag.meta @@ -41,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 17c63cc0..d42a574c 100644 --- a/test/var_compatibility_test/effr_post.F90 +++ b/test/var_compatibility_test/effr_post.F90 @@ -36,9 +36,10 @@ 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 !---------------------------------------------------------------- @@ -50,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 fd1d554e..721582a6 100644 --- a/test/var_compatibility_test/effr_post.meta +++ b/test/var_compatibility_test/effr_post.meta @@ -40,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 dcd3363a..51f1c373 100644 --- a/test/var_compatibility_test/effr_pre.F90 +++ b/test/var_compatibility_test/effr_pre.F90 @@ -35,9 +35,10 @@ 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 !---------------------------------------------------------------- @@ -49,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 diff --git a/test/var_compatibility_test/effr_pre.meta b/test/var_compatibility_test/effr_pre.meta index 2dc982e6..9c1fcf8e 100644 --- a/test/var_compatibility_test/effr_pre.meta +++ b/test/var_compatibility_test/effr_pre.meta @@ -40,6 +40,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/run_test b/test/var_compatibility_test/run_test index 26e33a3a..b2fa0f90 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -144,6 +144,9 @@ required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_d 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},scalar_variable_for_testing_a" +required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_b" +required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_c" required_vars_var_compatibility="${required_vars_var_compatibility},scheme_order_in_suite" required_vars_var_compatibility="${required_vars_var_compatibility},vertical_layer_dimension" input_vars_var_compatibility="cloud_graupel_number_concentration" @@ -158,6 +161,9 @@ input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_dimensi 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},scalar_variable_for_testing_a" +input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_b" +input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_c" input_vars_var_compatibility="${input_vars_var_compatibility},scheme_order_in_suite" input_vars_var_compatibility="${input_vars_var_compatibility},vertical_layer_dimension" output_vars_var_compatibility="ccpp_error_code,ccpp_error_message" diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 721fc5f9..14b80a60 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,13 +351,16 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(9) = (/ & + character(len=cm), target :: test_invars1(12) = (/ & '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 ', & + '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 '/) @@ -374,7 +377,7 @@ program test 'scheme_order_in_suite '/) - character(len=cm), target :: test_reqvars1(13) = (/ & + character(len=cm), target :: test_reqvars1(16) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -385,6 +388,9 @@ program test 'cloud_graupel_number_concentration ', & 'cloud_ice_number_concentration ', & 'scalar_variable_for_testing ', & + '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 '/) diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index b6552e68..6f351535 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -13,7 +13,11 @@ module test_host_data ncg, & ! number concentration of cloud graupel nci ! number concentration of cloud ice real(kind_phys) :: scalar_var + real(kind_phys) :: scalar_varA + real(kind_phys) :: scalar_varB + integer :: scalar_varC integer :: scheme_order + end type physics_state public allocate_physics_state diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index db9d8b16..ba4b2297 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -59,6 +59,26 @@ dimensions = () type = real kind = kind_phys +[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 diff --git a/test/var_compatibility_test/test_host_mod.F90 b/test/var_compatibility_test/test_host_mod.F90 index ca1d2014..51e36a83 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 diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index eb4a5a4b..47f8e33b 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -74,6 +74,9 @@ def usage(errmsg=None): "effective_radius_of_stratiform_cloud_graupel", "cloud_graupel_number_concentration", "scalar_variable_for_testing", + "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"] From dcb5ed50636e71ea1ef99408d5b39379e994eecf Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Tue, 25 Feb 2025 09:43:39 -0700 Subject: [PATCH 06/20] Capgen in SCM: Fix to allow for scheme subcycling. (#633) Overview This PR contains changes to fix scheme subcycling in Capgen and extends the var_compatability_test to exercise subcycling. UPDATE: Added bugfix for suite-part list ordering (see change to ccpp_suite.py). Description Create local group variable for subcycle indexing. Fix bug (`self.loop` -> `self._loop`) in the Subcycle write phase, and in ccpp_datafile. User interface changes?: No Fixes: #632 Fixes: #634 Testing: Added to var_compatibility_test to exercise feature. --------- Co-authored-by: Dom Heinzeller Co-authored-by: Steve Goldhaber --- scripts/ccpp_datafile.py | 2 +- scripts/ccpp_suite.py | 2 +- scripts/metavar.py | 8 +-- scripts/suite_objects.py | 61 +++++++++---------- test/var_compatibility_test/effr_calc.F90 | 4 +- test/var_compatibility_test/run_test | 2 + test/var_compatibility_test/test_host.F90 | 8 ++- .../var_compatibility_test/test_host_data.F90 | 3 + .../test_host_data.meta | 6 ++ test/var_compatibility_test/test_reports.py | 3 +- .../var_compatibility_suite.xml | 10 ++- 11 files changed, 62 insertions(+), 47 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 6bb5e537..605dc053 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -1096,7 +1096,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_suite.py b/scripts/ccpp_suite.py index 4440c193..a5cc8d9d 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -773,7 +773,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/metavar.py b/scripts/metavar.py index 66784e2e..8f283df6 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1677,7 +1677,7 @@ 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 + # 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. @@ -1709,10 +1709,8 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, # 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/suite_objects.py b/scripts/suite_objects.py index fb3a66c6..13d2c6a2 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} @@ -1461,7 +1461,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). @@ -1794,11 +1794,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 +1813,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 +1977,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 +2010,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 +2028,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 +2038,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 ############################################################################### @@ -2408,8 +2407,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: diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 1b075a3c..0bef2949 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -31,7 +31,7 @@ subroutine effr_calc_init(scheme_order, errmsg, errflg) endif end subroutine effr_calc_init - + !> \section arg_table_effr_calc_run Argument Table !! \htmlinclude arg_table_effr_calc_run.html !! @@ -72,7 +72,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/run_test b/test/var_compatibility_test/run_test index b2fa0f90..0eb0e7dd 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -143,6 +143,7 @@ required_vars_var_compatibility="${required_vars_var_compatibility},flag_indicat 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},num_subcycles_for_effr" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_a" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_b" @@ -160,6 +161,7 @@ input_vars_var_compatibility="${input_vars_var_compatibility},flag_indicating_cl 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},num_subcycles_for_effr" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_a" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_b" diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 14b80a60..5d7f2a4f 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,7 +351,7 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(12) = (/ & + character(len=cm), target :: test_invars1(13) = (/ & 'effective_radius_of_stratiform_cloud_rain_particle ', & 'effective_radius_of_stratiform_cloud_liquid_water_particle', & 'effective_radius_of_stratiform_cloud_snow_particle ', & @@ -362,6 +362,7 @@ program test '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 '/) @@ -375,9 +376,9 @@ program test 'cloud_ice_number_concentration ', & 'scalar_variable_for_testing ', & 'scheme_order_in_suite '/) - - character(len=cm), target :: test_reqvars1(16) = (/ & + + character(len=cm), target :: test_reqvars1(17) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -392,6 +393,7 @@ program test '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 '/) diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 6f351535..5754a093 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -17,6 +17,7 @@ module test_host_data real(kind_phys) :: scalar_varB integer :: scalar_varC integer :: scheme_order + integer :: num_subcycles end type physics_state @@ -69,6 +70,8 @@ subroutine allocate_physics_state(cols, levels, state, has_graupel, has_ice) ! Initialize scheme counter. state%scheme_order = 1 + ! Initialize subcycle counter. + state%num_subcycles = 3 end subroutine allocate_physics_state diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index ba4b2297..094f26d5 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -85,3 +85,9 @@ 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 diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index 47f8e33b..0eb7ef1c 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -79,7 +79,8 @@ def usage(errmsg=None): "scalar_variable_for_testing_c", "scheme_order_in_suite", "flag_indicating_cloud_microphysics_has_graupel", - "flag_indicating_cloud_microphysics_has_ice"] + "flag_indicating_cloud_microphysics_has_ice", + "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", diff --git a/test/var_compatibility_test/var_compatibility_suite.xml b/test/var_compatibility_test/var_compatibility_suite.xml index 5956a8bd..a5d4eb48 100644 --- a/test/var_compatibility_test/var_compatibility_suite.xml +++ b/test/var_compatibility_test/var_compatibility_suite.xml @@ -2,9 +2,13 @@ - effr_pre - effr_calc - effr_post + + effr_pre + + effr_calc + + effr_post + effr_diag From 7bc4fbbf34103cb01dd4bb0a07f5c401aef0e03d Mon Sep 17 00:00:00 2001 From: goldy <1588651+gold2718@users.noreply.github.com> Date: Mon, 3 Mar 2025 23:54:40 +0100 Subject: [PATCH 07/20] Constituent index lookup (#622) Implement constituent index lookup routines - Created a new CCPP source file, src/ccpp_scheme_utils.F90 - This file contains interfaces available to CCPP schemes - It contains a `private` pointer to the CCPP constituent object so many more interfaces could easily be created - Implemented two interface routines to find constituent indices from standard names - ccpp_constituent_index: Lookup index constituent by name - ccpp_constituent_indices: Lookup indices of consitutents by name - Add tests of this functionality into advection_test - Minor code cleanup User interface changes?: Yes - New routines available to CCPP schemes Addresses #597 Testing: test removed: None unit tests: PASS system tests: Added functionality to advection test, PASS manual testing: NA --------- Co-authored-by: Steve Goldhaber Co-authored-by: Steve Goldhaber --- scripts/ccpp_datafile.py | 2 + scripts/constituents.py | 3 + scripts/host_cap.py | 16 --- src/ccpp_constituent_prop_mod.F90 | 2 +- src/ccpp_scheme_utils.F90 | 121 ++++++++++++++++++ test/advection_test/CMakeLists.txt | 2 +- test/advection_test/cld_suite.xml | 1 + test/advection_test/cld_suite_files.txt | 1 + test/advection_test/const_indices.F90 | 94 ++++++++++++++ test/advection_test/const_indices.meta | 108 ++++++++++++++++ test/advection_test/run_test | 15 ++- test/advection_test/test_host.F90 | 39 +++++- test/advection_test/test_host_data.F90 | 57 ++++++++- test/advection_test/test_host_data.meta | 38 ++++++ test/advection_test/test_host_mod.F90 | 2 +- test/advection_test/test_reports.py | 14 +- test/capgen_test/run_test | 1 + test/capgen_test/test_host_data.F90 | 6 +- test/capgen_test/test_reports.py | 1 + test/ddthost_test/run_test | 1 + test/ddthost_test/test_reports.py | 1 + test/var_compatibility_test/run_test | 1 + .../var_compatibility_test/test_host_data.F90 | 6 +- test/var_compatibility_test/test_reports.py | 1 + 24 files changed, 502 insertions(+), 31 deletions(-) create mode 100644 src/ccpp_scheme_utils.F90 create mode 100644 test/advection_test/const_indices.F90 create mode 100644 test/advection_test/const_indices.meta diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 605dc053..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") 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/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/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/advection_test/CMakeLists.txt b/test/advection_test/CMakeLists.txt index c3f45190..5a9b546d 100644 --- a/test/advection_test/CMakeLists.txt +++ b/test/advection_test/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) PROJECT(test_host) ENABLE_LANGUAGE(Fortran) 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 index 301bb4ee..4ce40a53 100644 --- a/test/advection_test/cld_suite_files.txt +++ b/test/advection_test/cld_suite_files.txt @@ -1,3 +1,4 @@ cld_liq.meta cld_ice.meta apply_constituent_tendencies.meta +const_indices.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 index 5f9f8a8b..2301a4fc 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -122,13 +122,15 @@ 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},${fsrc}/ccpp_scheme_utils.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" +module_list="apply_constituent_tendencies,cld_ice,cld_liq,const_indices" dependencies="" suite_list="cld_suite" -required_vars="ccpp_constituent_tendencies,ccpp_constituents" +required_vars="banana_array_dim" +required_vars="${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" @@ -141,11 +143,15 @@ 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},test_banana_constituent_index" +required_vars="${required_vars},test_banana_constituent_indices" +required_vars="${required_vars},test_banana_name,test_banana_name_array" 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="banana_array_dim" +input_vars="${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" @@ -153,6 +159,7 @@ 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},test_banana_name,test_banana_name_array" input_vars="${input_vars},time_step_for_physics" input_vars="${input_vars},vertical_layer_dimension" input_vars="${input_vars},water_temperature_at_freezing" @@ -165,6 +172,8 @@ 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},test_banana_constituent_index" +output_vars="${output_vars},test_banana_constituent_indices" output_vars="${output_vars},water_vapor_specific_humidity" ## diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index c5a2121a..fa6c8e43 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -225,6 +225,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 @@ -255,6 +257,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 +363,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 +464,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 +1031,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 +1074,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 @@ -1116,15 +1141,16 @@ program test 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) + 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', & @@ -1147,8 +1173,11 @@ program test '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 ', & @@ -1161,6 +1190,8 @@ program test '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 ' /) 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 index 2a51a411..04294364 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -60,11 +60,13 @@ def usage(errmsg=None): _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 _PROCESS_LIST = list() -_MODULE_LIST = ["cld_ice", "cld_liq", "apply_constituent_tendencies"] +_MODULE_LIST = ["cld_ice", "cld_liq", "const_indices", "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", @@ -80,6 +82,10 @@ def usage(errmsg=None): "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"] @@ -93,6 +99,8 @@ def usage(errmsg=None): "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"] @@ -104,7 +112,9 @@ def usage(errmsg=None): "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"] def fields_string(field_type, field_list, sep): """Create an error string for field(s), . diff --git a/test/capgen_test/run_test b/test/capgen_test/run_test index f0b71aa2..5b0f4fe8 100755 --- a/test/capgen_test/run_test +++ b/test/capgen_test/run_test @@ -122,6 +122,7 @@ 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_scheme_utils.F90" utility_files="${utility_files},${frame_src}/ccpp_hashable.F90" utility_files="${utility_files},${frame_src}/ccpp_hash_table.F90" ccpp_files="${utility_files}" diff --git a/test/capgen_test/test_host_data.F90 b/test/capgen_test/test_host_data.F90 index 7a651fca..0c7cdea1 100644 --- a/test/capgen_test/test_host_data.F90 +++ b/test/capgen_test/test_host_data.F90 @@ -2,6 +2,9 @@ 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 @@ -16,7 +19,8 @@ module test_host_data 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 diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py index ed123013..499ab22d 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -61,6 +61,7 @@ def usage(errmsg=None): 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 + \ diff --git a/test/ddthost_test/run_test b/test/ddthost_test/run_test index 65e73886..98b934b4 100755 --- a/test/ddthost_test/run_test +++ b/test/ddthost_test/run_test @@ -122,6 +122,7 @@ 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_scheme_utils.F90" utility_files="${utility_files},${frame_src}/ccpp_hashable.F90" utility_files="${utility_files},${frame_src}/ccpp_hash_table.F90" ccpp_files="${utility_files}" diff --git a/test/ddthost_test/test_reports.py b/test/ddthost_test/test_reports.py index aae9098b..e2904a6e 100644 --- a/test/ddthost_test/test_reports.py +++ b/test/ddthost_test/test_reports.py @@ -51,6 +51,7 @@ 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 + \ diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index 0eb0e7dd..b604def7 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -121,6 +121,7 @@ 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_scheme_utils.F90" utility_files="${utility_files},${frame_src}/ccpp_hashable.F90" utility_files="${utility_files},${frame_src}/ccpp_hash_table.F90" ccpp_files="${utility_files}" diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 5754a093..2c5e3409 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -2,6 +2,9 @@ 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 @@ -21,7 +24,8 @@ module test_host_data end type physics_state - public allocate_physics_state + public :: physics_state + public :: allocate_physics_state contains diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index 0eb7ef1c..7519d456 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -60,6 +60,7 @@ def usage(errmsg=None): _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 + \ From 052dac2a8161874d8078f8b7733ce49914eef783 Mon Sep 17 00:00:00 2001 From: goldy <1588651+gold2718@users.noreply.github.com> Date: Tue, 4 Mar 2025 00:06:19 +0100 Subject: [PATCH 08/20] Add options for pre-registered DDT names (#644) Add options for pre-registered DDT names Add an option for pre-registered DDT names Add `ccpp_constituent_prop_ptr_t` as a default (pre-registered) DDT Add test for 'unknown' DDT variables User interface changes?: Yes Added a new option, `--ddt-names`, to allow users to simply add DDT names that allow generation of a CCPP metadata template from a Fortran source. Because it is an option, it is backwards compatible. Fixes: #643 ccpp_fortran_to_metadata.py errors out when given a file that has a constituent properties object Testing: test removed: None unit tests: NA system tests: NA manual testing: Added a manual test that tests this bug fix --------- Co-authored-by: Steve Goldhaber --- .github/workflows/capgen_unit_tests.yaml | 3 +- scripts/ccpp_fortran_to_metadata.py | 10 +++++ scripts/parse_tools/__init__.py | 4 +- scripts/parse_tools/parse_checkers.py | 2 +- test/test_fortran_to_metadata.sh | 37 +++++++++++++++++++ .../check_fortran_to_metadata.meta | 31 ++++++++++++++++ .../sample_files/test_fortran_to_metadata.F90 | 28 ++++++++++++++ 7 files changed, 112 insertions(+), 3 deletions(-) create mode 100755 test/test_fortran_to_metadata.sh create mode 100644 test/unit_tests/sample_files/check_fortran_to_metadata.meta create mode 100644 test/unit_tests/sample_files/test_fortran_to_metadata.F90 diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 820553af..4d871a52 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -18,4 +18,5 @@ jobs: run: sudo apt-get update && sudo apt-get install -y build-essential ${{matrix.fortran-compiler}} cmake python3 git libxml2-utils - name: Run unit tests run: cd test && ./run_fortran_tests.sh - + - name: Run Fortran to metadata test + run: cd test && ./test_fortran_to_metadata.sh 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/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..70d44c92 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -229,7 +229,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/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/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/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 From 5575301195b6bca1a6bc30a0b77d79dd44cf6c1f Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 22 Apr 2025 09:48:18 -0600 Subject: [PATCH 09/20] Update develop from main 2025/04/16 (#657) All in the title. This is the `CMakeLists.txt` update that was merged into main earlier today User interface changes?: No Fixes: Cleanup Testing: all tests pass with UFS and in CI test removed: n/a unit tests: pass system tests: pass manual testing: pass --------- Co-authored-by: Michael Kavulich Co-authored-by: Grant Firl --- src/CMakeLists.txt | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eaa78afe..4ff78a81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,29 +1,12 @@ +include(GNUInstallDirs) + #------------------------------------------------------------------------------ # Set the sources set(SOURCES_F90 ccpp_types.F90 ) -# Generate list of Fortran modules from defined sources -foreach(source_f90 ${SOURCES_F90}) - string(REGEX REPLACE ".F90" ".mod" module_f90 ${source_f90}) - list(APPEND MODULES_F90 ${CMAKE_CURRENT_BINARY_DIR}/${module_f90}) -endforeach() - -#------------------------------------------------------------------------------ -# Add the toplevel source directory to our include directoies (for .h) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - -# Add the toplevel binary directory to our include directoies (for .mod) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -# Set a cached variable containing the includes, so schemes can use them -set(${PACKAGE}_INCLUDE_DIRS - "${CMAKE_CURRENT_SOURCE_DIR}$${CMAKE_CURRENT_BINARY_DIR}" - CACHE FILEPATH "${PACKAGE} include directories") -set(${PACKAGE}_LIB_DIRS - "${CMAKE_CURRENT_BINARY_DIR}" - CACHE FILEPATH "${PACKAGE} library directories") +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}) #------------------------------------------------------------------------------ # Define the executable and what to link @@ -37,16 +20,17 @@ set_target_properties(ccpp_framework PROPERTIES VERSION ${PROJECT_VERSION} # Installation # target_include_directories(ccpp_framework PUBLIC - $ - $ + INTERFACE $ + $ ) + # Define where to install the library install(TARGETS ccpp_framework EXPORT ccpp_framework-targets ARCHIVE DESTINATION lib LIBRARY DESTINATION lib - RUNTIME DESTINATION lib + RUNTIME DESTINATION bin ) # Export our configuration @@ -55,5 +39,4 @@ install(EXPORT ccpp_framework-targets DESTINATION lib/cmake ) -# Define where to install the Fortran modules -install(FILES ${MODULES_F90} DESTINATION include) +install(DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) From c3223755ec6f78ffdf279e9cd2d61fe8a5c81471 Mon Sep 17 00:00:00 2001 From: goldy <1588651+gold2718@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:50:09 +0200 Subject: [PATCH 10/20] New implementation of differing module name (#646) New implementation of differing module name Implement a solution that allows a Fortran module name to differ from the filename. An optional module_name keyword is added to the ccpp-table-properties (MetadataTable) section that allows the user to specify a Fortran module name that is independent from the name of the enclosing file. --- scripts/ccpp_capgen.py | 23 +- scripts/ccpp_suite.py | 9 +- scripts/metadata_table.py | 285 +++++++++++------- scripts/parse_tools/parse_source.py | 4 + scripts/suite_objects.py | 7 +- test/ddthost_test/make_ddt.F90 | 4 +- test/ddthost_test/make_ddt.meta | 5 + test/ddthost_test/run_test | 2 + test/ddthost_test/test_host.F90 | 10 +- test/ddthost_test/test_reports.py | 3 +- test/unit_tests/test_metadata_table.py | 2 +- test/var_compatibility_test/CMakeLists.txt | 2 +- test/var_compatibility_test/effr_pre.F90 | 6 +- test/var_compatibility_test/effr_pre.meta | 1 + .../var_compatibility_test/module_rad_ddt.F90 | 23 ++ .../module_rad_ddt.meta | 40 +++ test/var_compatibility_test/rad_lw.F90 | 35 +++ test/var_compatibility_test/rad_lw.meta | 35 +++ test/var_compatibility_test/rad_sw.F90 | 35 +++ test/var_compatibility_test/rad_sw.meta | 35 +++ test/var_compatibility_test/run_test | 18 +- test/var_compatibility_test/test_host.F90 | 19 +- .../var_compatibility_test/test_host_data.F90 | 17 +- .../test_host_data.meta | 21 ++ test/var_compatibility_test/test_reports.py | 9 +- .../var_compatibility_files.txt | 3 + .../var_compatibility_suite.xml | 2 + 27 files changed, 505 insertions(+), 150 deletions(-) create mode 100644 test/var_compatibility_test/module_rad_ddt.F90 create mode 100644 test/var_compatibility_test/module_rad_ddt.meta create mode 100644 test/var_compatibility_test/rad_lw.F90 create mode 100644 test/var_compatibility_test/rad_lw.meta create mode 100644 test/var_compatibility_test/rad_sw.F90 create mode 100644 test/var_compatibility_test/rad_sw.meta diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 9eac0ecf..31421571 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 @@ -468,7 +468,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 +477,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 +524,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 +533,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 +637,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_suite.py b/scripts/ccpp_suite.py index a5cc8d9d..1182fd43 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -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) diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 942f8d81..bd388716 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: @@ -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 @@ -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 @@ -813,10 +884,6 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env, skip_ddt 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() - # end if # Initialize our ParseSource parent super().__init__(self.title, self.header_type, self.__pobj) # Read the variables @@ -883,8 +950,8 @@ 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: 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 13d2c6a2..5ac21138 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2482,12 +2482,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/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..4f5c9a00 100644 --- a/test/ddthost_test/make_ddt.meta +++ b/test/ddthost_test/make_ddt.meta @@ -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 index 98b934b4..964bface 100755 --- a/test/ddthost_test/run_test +++ b/test/ddthost_test/run_test @@ -136,12 +136,14 @@ 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},host_standard_ccpp_type" 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},host_standard_ccpp_type" 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" diff --git a/test/ddthost_test/test_host.F90 b/test/ddthost_test/test_host.F90 index d060e59e..12c4aeb0 100644 --- a/test/ddthost_test/test_host.F90 +++ b/test/ddthost_test/test_host.F90 @@ -386,10 +386,11 @@ program test 'ccpp_error_code ', & 'ccpp_error_message ' /) - character(len=cm), target :: test_invars2(3) = (/ & + character(len=cm), target :: test_invars2(4) = (/ & 'model_times ', & 'number_of_model_times ', & - 'surface_air_pressure ' /) + 'surface_air_pressure ', & + 'host_standard_ccpp_type ' /) character(len=cm), target :: test_outvars2(5) = (/ & 'ccpp_error_code ', & @@ -398,12 +399,13 @@ program test 'surface_air_pressure ', & 'number_of_model_times ' /) - character(len=cm), target :: test_reqvars2(5) = (/ & + character(len=cm), target :: test_reqvars2(6) = (/ & 'model_times ', & 'number_of_model_times ', & 'surface_air_pressure ', & 'ccpp_error_code ', & - 'ccpp_error_message ' /) + 'ccpp_error_message ', & + 'host_standard_ccpp_type ' /) type(suite_info) :: test_suites(2) logical :: run_okay diff --git a/test/ddthost_test/test_reports.py b/test/ddthost_test/test_reports.py index e2904a6e..6fec8be4 100644 --- a/test/ddthost_test/test_reports.py +++ b/test/ddthost_test/test_reports.py @@ -64,7 +64,8 @@ _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"] + "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 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/var_compatibility_test/CMakeLists.txt b/test/var_compatibility_test/CMakeLists.txt index 8cbd7e44..34734c06 100644 --- a/test/var_compatibility_test/CMakeLists.txt +++ b/test/var_compatibility_test/CMakeLists.txt @@ -20,7 +20,7 @@ get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) # #------------------------------------------------------------------------------ LIST(APPEND SCHEME_FILES "var_compatibility_files.txt") -LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") +LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") LIST(APPEND 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 diff --git a/test/var_compatibility_test/effr_pre.F90 b/test/var_compatibility_test/effr_pre.F90 index 51f1c373..17a3b187 100644 --- a/test/var_compatibility_test/effr_pre.F90 +++ b/test/var_compatibility_test/effr_pre.F90 @@ -1,7 +1,7 @@ !Test unit conversions for intent in, inout, out variables ! -module effr_pre +module mod_effr_pre use ccpp_kinds, only: kind_phys @@ -56,5 +56,5 @@ subroutine effr_pre_run( effrr_inout, scalar_var, errmsg, errflg) 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 9c1fcf8e..251b4175 100644 --- a/test/var_compatibility_test/effr_pre.meta +++ b/test/var_compatibility_test/effr_pre.meta @@ -1,6 +1,7 @@ [ccpp-table-properties] name = effr_pre type = scheme + module_name = mod_effr_pre dependencies = ######################################################################## [ccpp-arg-table] 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..c7986a6c --- /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) :: sfc_up_sw + real(kind_phys) :: sfc_down_sw + 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..4576c151 --- /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 = () + type = real + kind = kind_phys +[ sfc_down_sw ] + standard_name = surface_downwelling_shortwave_radiation_flux + units = W m2 + dimensions = () + 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..a0f22af9 --- /dev/null +++ b/test/var_compatibility_test/rad_sw.F90 @@ -0,0 +1,35 @@ +module rad_sw + use ccpp_kinds, only: kind_phys + use mod_rad_ddt, only: ty_rad_sw + + 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, fluxSW, errmsg, errflg) + + integer, intent(in) :: ncol + type(ty_rad_sw), intent(inout) :: fluxSW(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Locals + integer :: icol + + errmsg = '' + errflg = 0 + + do icol=1,ncol + fluxSW(icol)%sfc_up_sw = 100._kind_phys + fluxSW(icol)%sfc_down_sw = 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..81f2d583 --- /dev/null +++ b/test/var_compatibility_test/rad_sw.meta @@ -0,0 +1,35 @@ +[ccpp-table-properties] + name = rad_sw + type = scheme + dependencies = module_rad_ddt.F90 +[ccpp-arg-table] + name = rad_sw_run + type = scheme +[ ncol ] + standard_name = horizontal_loop_extent + type = integer + units = count + dimensions = () + intent = in +[fluxSW] + standard_name = shortwave_radiation_fluxes + long_name = shortwave radiation fluxes + units = W m-2 + dimensions = (horizontal_loop_extent) + type = ty_rad_sw + 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 index b604def7..be8f1f6c 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -1,12 +1,12 @@ #! /bin/bash -currdir="`pwd -P`" +currdir="$(pwd -P)" scriptdir="$( cd $( dirname $0 ); pwd -P )" ## ## Option default values ## -defdir="ct_build" +defdir="vc_build" build_dir="${currdir}/${defdir}" cleanup="PASS" # Other supported options are ALWAYS and NEVER verbosity=0 @@ -128,8 +128,8 @@ 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="" +module_list="effr_calc,effr_diag,effr_post,mod_effr_pre,rad_lw,rad_sw" +dependencies="${scriptdir}/module_rad_ddt.F90" 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" @@ -144,12 +144,14 @@ required_vars_var_compatibility="${required_vars_var_compatibility},flag_indicat 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},longwave_radiation_fluxes" required_vars_var_compatibility="${required_vars_var_compatibility},num_subcycles_for_effr" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_a" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_b" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_c" required_vars_var_compatibility="${required_vars_var_compatibility},scheme_order_in_suite" +required_vars_var_compatibility="${required_vars_var_compatibility},shortwave_radiation_fluxes" 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" @@ -162,12 +164,14 @@ input_vars_var_compatibility="${input_vars_var_compatibility},flag_indicating_cl 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},longwave_radiation_fluxes" input_vars_var_compatibility="${input_vars_var_compatibility},num_subcycles_for_effr" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_a" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_b" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_c" input_vars_var_compatibility="${input_vars_var_compatibility},scheme_order_in_suite" +input_vars_var_compatibility="${input_vars_var_compatibility},shortwave_radiation_fluxes" 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" @@ -175,8 +179,10 @@ output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius 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},longwave_radiation_fluxes" output_vars_var_compatibility="${output_vars_var_compatibility},scalar_variable_for_testing" output_vars_var_compatibility="${output_vars_var_compatibility},scheme_order_in_suite" +output_vars_var_compatibility="${output_vars_var_compatibility},shortwave_radiation_fluxes" ## ## Run a database report and check the return string @@ -239,9 +245,9 @@ 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} "--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} "--dependencies" ${dependencies} check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ --sep ";" echo -e "\nChecking variables for var_compatibility suite from command line" diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 5d7f2a4f..2ff05eb7 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,7 +351,7 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(13) = (/ & + character(len=cm), target :: test_invars1(15) = (/ & 'effective_radius_of_stratiform_cloud_rain_particle ', & 'effective_radius_of_stratiform_cloud_liquid_water_particle', & 'effective_radius_of_stratiform_cloud_snow_particle ', & @@ -364,9 +364,11 @@ program test 'scheme_order_in_suite ', & 'num_subcycles_for_effr ', & 'flag_indicating_cloud_microphysics_has_graupel ', & - 'flag_indicating_cloud_microphysics_has_ice '/) + 'flag_indicating_cloud_microphysics_has_ice ', & + 'shortwave_radiation_fluxes ', & + 'longwave_radiation_fluxes '/) - character(len=cm), target :: test_outvars1(9) = (/ & + character(len=cm), target :: test_outvars1(11) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_ice_particle ', & @@ -375,10 +377,11 @@ program test 'effective_radius_of_stratiform_cloud_snow_particle ', & 'cloud_ice_number_concentration ', & 'scalar_variable_for_testing ', & - 'scheme_order_in_suite '/) - + 'scheme_order_in_suite ', & + 'shortwave_radiation_fluxes ', & + 'longwave_radiation_fluxes '/) - character(len=cm), target :: test_reqvars1(17) = (/ & + character(len=cm), target :: test_reqvars1(19) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -395,7 +398,9 @@ program test 'scheme_order_in_suite ', & 'num_subcycles_for_effr ', & 'flag_indicating_cloud_microphysics_has_graupel ', & - 'flag_indicating_cloud_microphysics_has_ice '/) + 'flag_indicating_cloud_microphysics_has_ice ', & + 'shortwave_radiation_fluxes ', & + 'longwave_radiation_fluxes '/) type(suite_info) :: test_suites(1) logical :: run_okay diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 2c5e3409..964c88e7 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -1,6 +1,7 @@ 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 @@ -15,6 +16,10 @@ module test_host_data effrg, & ! effective radius of cloud graupel ncg, & ! number concentration of cloud graupel nci ! number concentration of cloud ice + type(ty_rad_lw), dimension(:), allocatable :: & + fluxLW ! Longwave radiation fluxes + type(ty_rad_sw), dimension(:), allocatable :: & + fluxSW ! Shortwave radiation fluxes real(kind_phys) :: scalar_var real(kind_phys) :: scalar_varA real(kind_phys) :: scalar_varB @@ -72,6 +77,16 @@ 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 (allocated(state%fluxSW)) then + deallocate(state%fluxSW) + end if + allocate(state%fluxSW(cols)) + ! Initialize scheme counter. state%scheme_order = 1 ! Initialize subcycle counter. diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index 094f26d5..b507f11e 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,6 +60,18 @@ dimensions = () type = real kind = kind_phys +[fluxSW] + standard_name = shortwave_radiation_fluxes + long_name = shortwave radiation fluxes + units = W m-2 + dimensions = (horizontal_dimension) + 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 @@ -91,3 +104,11 @@ 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_reports.py b/test/var_compatibility_test/test_reports.py index 7519d456..4fd2863f 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -66,8 +66,9 @@ def usage(errmsg=None): _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"] +_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", @@ -81,6 +82,8 @@ def usage(errmsg=None): "scheme_order_in_suite", "flag_indicating_cloud_microphysics_has_graupel", "flag_indicating_cloud_microphysics_has_ice", + "shortwave_radiation_fluxes", + "longwave_radiation_fluxes", "num_subcycles_for_effr"] _OUTPUT_VARS_VAR_ACTION = ["ccpp_error_code", "ccpp_error_message", "effective_radius_of_stratiform_cloud_ice_particle", @@ -89,6 +92,8 @@ def usage(errmsg=None): "cloud_ice_number_concentration", "effective_radius_of_stratiform_cloud_rain_particle", "scalar_variable_for_testing", + "shortwave_radiation_fluxes", + "longwave_radiation_fluxes", "scheme_order_in_suite"] _REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION @@ -155,6 +160,8 @@ def check_datatable(database, report_type, check_list, _MODULE_LIST) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), _SUITE_LIST) +NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("dependencies"), + _DEPENDENCIES) print("\nChecking variables for var_compatibility suite from python") NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", value="var_compatibility_suite"), diff --git a/test/var_compatibility_test/var_compatibility_files.txt b/test/var_compatibility_test/var_compatibility_files.txt index 6d83c980..71df1054 100644 --- a/test/var_compatibility_test/var_compatibility_files.txt +++ b/test/var_compatibility_test/var_compatibility_files.txt @@ -1,4 +1,7 @@ +module_rad_ddt.meta effr_calc.meta effr_diag.meta effr_pre.meta effr_post.meta +rad_lw.meta +rad_sw.meta diff --git a/test/var_compatibility_test/var_compatibility_suite.xml b/test/var_compatibility_test/var_compatibility_suite.xml index a5d4eb48..a168e2ef 100644 --- a/test/var_compatibility_test/var_compatibility_suite.xml +++ b/test/var_compatibility_test/var_compatibility_suite.xml @@ -10,5 +10,7 @@ effr_post effr_diag + rad_lw + rad_sw From 654b3c5b0240aa8630f0b20875a209e24d1dc2de Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 1 May 2025 10:49:48 -0600 Subject: [PATCH 11/20] Support equivalent and identical units in capgen and ccpp-prebuild (#656) This PR adds logic to support equivalent and identical units in both capgen and ccpp-prebuild. 1. Support for identical units (`m2 s-2` is identical to `m+2 s-2`). Internally, both `capgen` and `ccpp-prebuild` convert the former to the latter. For `capgen`, this happens deep down in the unit conversion logic. If `capgen` finds that two units are identical after parsing them, it returns a `(None, None)` unit conversion that tells the VarCompatibilityObject that the variables are equivalent w.r.t. units. For `ccpp-prebuild`, this happens when translating `capgen` metadata to `ccpp-prebuild` metadata. The outcome is the same. 2. Support for equivalent units (`m2 s-2` is equivalent to `J kg-1`). This is handled differently for the two code generators. While `capgen` skips the unit conversion step (VarCompatObj says variables are equivalent, hence no conversion required), `ccpp-prebuild` uses an identity transformation `{var} = {var}`. As a result, the `units` metadata can now be written as `m2 s-2` or `m+2 s-2`. Both are acceptable and dealt with by the two code generators. The `ccpp-prebuild` implementation is simpler than the `capgen` implementation, because of the interface between the `capgen`-provided metadata from the parser and the metadata used by `ccpp-prebuild`, and also because the `ccpp-prebuild` implementation is more of a workaround (yes, prebuild needs to go). User interface changes?: No Fixes #570 Testing: test removed: none unit tests: all pass, including doc tests; added several doc tests for the new capabilities system tests: all pass; extended `capgen` and `ccpp-prebuild` tests to cover the new features manual testing: ran all unit and system tests on my laptop --------- Co-authored-by: Dustin Swales Co-authored-by: Michael Kavulich Co-authored-by: Test User --- scripts/common.py | 15 ++++ scripts/conversion_tools/unit_conversion.py | 20 +++++ scripts/metadata_parser.py | 8 +- scripts/parse_tools/parse_checkers.py | 7 +- scripts/var_props.py | 80 +++++++++++++++---- test/unit_tests/test_var_transforms.py | 30 +++++-- test/var_compatibility_test/effr_calc.F90 | 6 +- test/var_compatibility_test/effr_calc.meta | 16 ++++ test/var_compatibility_test/run_test | 7 +- test/var_compatibility_test/test_host.F90 | 16 ++-- .../var_compatibility_test/test_host_data.F90 | 2 +- .../test_host_data.meta | 14 ++++ test/var_compatibility_test/test_host_mod.F90 | 8 ++ test/var_compatibility_test/test_reports.py | 5 ++ test_prebuild/test_unit_conv/data.F90 | 3 +- test_prebuild/test_unit_conv/data.meta | 7 ++ test_prebuild/test_unit_conv/main.F90 | 3 +- .../test_unit_conv/unit_conv_scheme_1.F90 | 14 +++- .../test_unit_conv/unit_conv_scheme_1.meta | 8 ++ .../test_unit_conv/unit_conv_scheme_2.F90 | 12 ++- .../test_unit_conv/unit_conv_scheme_2.meta | 8 ++ 21 files changed, 253 insertions(+), 36 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index 3484a3c2..84520222 100755 --- a/scripts/common.py +++ b/scripts/common.py @@ -148,6 +148,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. diff --git a/scripts/conversion_tools/unit_conversion.py b/scripts/conversion_tools/unit_conversion.py index b9afa144..7fbf41a2 100755 --- a/scripts/conversion_tools/unit_conversion.py +++ b/scripts/conversion_tools/unit_conversion.py @@ -176,3 +176,23 @@ 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}' diff --git a/scripts/metadata_parser.py b/scripts/metadata_parser.py index d1277ebe..e8c3f987 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,9 +231,14 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub #kind = new_var.get_prop_value('kind') # *DH 20210812 + # 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, long_name = new_var.get_prop_value('long_name') + legacy_note, - units = new_var.get_prop_value('units'), + units = units, local_name = new_var.get_prop_value('local_name'), type = new_var.get_prop_value('type').lower(), dimensions = dimensions, diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 70d44c92..101bae3d 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) diff --git a/scripts/var_props.py b/scripts/var_props.py index fcbed74c..52800ba4 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,14 @@ 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('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 +1222,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 +1245,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 +1394,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 +1506,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/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/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 0bef2949..0b626c16 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -37,7 +37,8 @@ end subroutine effr_calc_init !! 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 @@ -53,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 diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index ec074f4d..c3733f13 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -130,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/run_test b/test/var_compatibility_test/run_test index be8f1f6c..83a1f07e 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -152,9 +152,10 @@ required_vars_var_compatibility="${required_vars_var_compatibility},scalar_varia required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_c" required_vars_var_compatibility="${required_vars_var_compatibility},scheme_order_in_suite" required_vars_var_compatibility="${required_vars_var_compatibility},shortwave_radiation_fluxes" +required_vars_var_compatibility="${required_vars_var_compatibility},turbulent_kinetic_energy" +required_vars_var_compatibility="${required_vars_var_compatibility},turbulent_kinetic_energy2" 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" @@ -172,6 +173,8 @@ input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_fo input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_c" input_vars_var_compatibility="${input_vars_var_compatibility},scheme_order_in_suite" input_vars_var_compatibility="${input_vars_var_compatibility},shortwave_radiation_fluxes" +input_vars_var_compatibility="${input_vars_var_compatibility},turbulent_kinetic_energy" +input_vars_var_compatibility="${input_vars_var_compatibility},turbulent_kinetic_energy2" 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" @@ -183,6 +186,8 @@ output_vars_var_compatibility="${output_vars_var_compatibility},longwave_radiati output_vars_var_compatibility="${output_vars_var_compatibility},scalar_variable_for_testing" output_vars_var_compatibility="${output_vars_var_compatibility},scheme_order_in_suite" output_vars_var_compatibility="${output_vars_var_compatibility},shortwave_radiation_fluxes" +output_vars_var_compatibility="${output_vars_var_compatibility},turbulent_kinetic_energy" +output_vars_var_compatibility="${output_vars_var_compatibility},turbulent_kinetic_energy2" ## ## Run a database report and check the return string diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 2ff05eb7..65d06827 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,13 +351,15 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(15) = (/ & + character(len=cm), target :: test_invars1(17) = (/ & '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 ', & @@ -368,7 +370,7 @@ program test 'shortwave_radiation_fluxes ', & 'longwave_radiation_fluxes '/) - character(len=cm), target :: test_outvars1(11) = (/ & + character(len=cm), target :: test_outvars1(13) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_ice_particle ', & @@ -378,10 +380,12 @@ program test 'cloud_ice_number_concentration ', & 'scalar_variable_for_testing ', & 'scheme_order_in_suite ', & + 'turbulent_kinetic_energy ', & + 'turbulent_kinetic_energy2 ', & 'shortwave_radiation_fluxes ', & - 'longwave_radiation_fluxes '/) + 'longwave_radiation_fluxes '/) - character(len=cm), target :: test_reqvars1(19) = (/ & + character(len=cm), target :: test_reqvars1(21) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -392,6 +396,8 @@ program test '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 ', & @@ -400,7 +406,7 @@ program test 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice ', & 'shortwave_radiation_fluxes ', & - 'longwave_radiation_fluxes '/) + 'longwave_radiation_fluxes '/) type(suite_info) :: test_suites(1) logical :: run_okay diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 964c88e7..32b7821f 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -23,10 +23,10 @@ module test_host_data real(kind_phys) :: scalar_var 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 :: physics_state diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index b507f11e..b931a6c1 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -60,6 +60,20 @@ 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 diff --git a/test/var_compatibility_test/test_host_mod.F90 b/test/var_compatibility_test/test_host_mod.F90 index 51e36a83..5cd4846a 100644 --- a/test/var_compatibility_test/test_host_mod.F90 +++ b/test/var_compatibility_test/test_host_mod.F90 @@ -40,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 @@ -50,6 +52,7 @@ 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 compare_data = .true. @@ -84,6 +87,11 @@ 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 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 index 4fd2863f..03202a7b 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -76,6 +76,8 @@ def usage(errmsg=None): "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", @@ -91,6 +93,9 @@ def usage(errmsg=None): "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", "shortwave_radiation_fluxes", "longwave_radiation_fluxes", diff --git a/test_prebuild/test_unit_conv/data.F90 b/test_prebuild/test_unit_conv/data.F90 index f53c107e..c926122b 100644 --- a/test_prebuild/test_unit_conv/data.F90 +++ b/test_prebuild/test_unit_conv/data.F90 @@ -11,12 +11,13 @@ module data private public ncols, nspecies - public cdata, data_array, opt_array_flag + public cdata, data_array, data_array2, opt_array_flag integer, parameter :: ncols = 4 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..e709e4fc 100644 --- a/test_prebuild/test_unit_conv/data.meta +++ b/test_prebuild/test_unit_conv/data.meta @@ -37,6 +37,13 @@ dimensions = (horizontal_loop_extent) type = real kind = kind_phys +[data_array2] + standard_name = data_array2 + long_name = data array 2 in module + units = m2 s-2 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys [opt_array_flag] standard_name = flag_for_opt_array long_name = flag for passing optional data 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 From 7b264208c50122547f1fdebd0d7a41d0087c2875 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Wed, 14 May 2025 08:41:48 -0600 Subject: [PATCH 12/20] DEBUG refinements (#652) This PR contains changes for the DEBUG tests. Add functionality within the DEBUG checks to: - Allow case-insensitivity for standard_names within dimensions. - Permit numerical dimensions in metadata. - Skip certain debug checks for arrays with non-standard lower-bounds - Remove tests for character and type arrays (not working) - Capgen tests updated to test new capabilities. User interface changes?: No Fixes: #649 #650 #651 #652 --------- Co-authored-by: Dom Heinzeller --- scripts/metavar.py | 2 +- scripts/suite_objects.py | 129 ++++++++++++++++++++------- test/capgen_test/run_test | 22 ++++- test/capgen_test/temp_set.F90 | 16 +++- test/capgen_test/temp_set.meta | 29 ++++++ test/capgen_test/test_host.F90 | 21 +++-- test/capgen_test/test_host_data.F90 | 13 ++- test/capgen_test/test_host_data.meta | 9 +- test/capgen_test/test_host_mod.F90 | 7 +- test/capgen_test/test_host_mod.meta | 31 ++++++- test/capgen_test/test_reports.py | 17 +++- 11 files changed, 239 insertions(+), 57 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 8f283df6..1f320de4 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -708,7 +708,7 @@ def call_string(self, var_dict, loop_vars=None): lname = "" for item in dim.split(':'): if item: - dvar = var_dict.find_variable(standard_name=item, + dvar = var_dict.find_variable(standard_name=item.lower(), any_scope=False) if dvar is None: try: diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5ac21138..7100d479 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -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, @@ -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.""" diff --git a/test/capgen_test/run_test b/test/capgen_test/run_test index 5b0f4fe8..99ffc6a2 100755 --- a/test/capgen_test/run_test +++ b/test/capgen_test/run_test @@ -146,41 +146,55 @@ 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="array_variable_for_testing" +required_vars_temp="${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},lower_bound_of_vertical_dimension_of_soil" 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},soil_levels" required_vars_temp="${required_vars_temp},surface_air_pressure" +required_vars_temp="${required_vars_temp},temperature_at_diagnostic_levels" required_vars_temp="${required_vars_temp},time_step_for_physics" +required_vars_temp="${required_vars_temp},upper_bound_of_vertical_dimension_of_soil" 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="array_variable_for_testing" +input_vars_temp="${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},lower_bound_of_vertical_dimension_of_soil" 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},soil_levels" +input_vars_temp="${input_vars_temp},surface_air_pressure" +input_vars_temp="${input_vars_temp},temperature_at_diagnostic_levels" +input_vars_temp="${input_vars_temp},time_step_for_physics" +input_vars_temp="${input_vars_temp},upper_bound_of_vertical_dimension_of_soil" 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="array_variable_for_testing" +output_vars_temp="${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},soil_levels" output_vars_temp="${output_vars_temp},surface_air_pressure" +output_vars_temp="${output_vars_temp},temperature_at_diagnostic_levels" output_vars_temp="${output_vars_temp},water_vapor_specific_humidity" ## 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..adfe1532 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_dimension,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_host.F90 b/test/capgen_test/test_host.F90 index 8f716322..fd31b145 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -368,23 +368,29 @@ program test 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) = (/ & + 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 ', & - 'time_step_for_physics ' /) - character(len=cm), target :: test_outvars1(7) = (/ & + '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 ' /) - character(len=cm), target :: test_reqvars1(9) = (/ & + 'ccpp_error_message ', & + 'array_variable_for_testing ' /) + character(len=cm), target :: test_reqvars1(12) = (/ & 'potential_temperature ', & 'potential_temperature_at_interface ', & 'coefficients_for_interpolation ', & @@ -392,8 +398,11 @@ program test 'water_vapor_specific_humidity ', & 'potential_temperature_increment ', & 'time_step_for_physics ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & 'ccpp_error_code ', & - 'ccpp_error_message ' /) + 'ccpp_error_message ', & + 'array_variable_for_testing ' /) character(len=cm), target :: test_invars2(3) = (/ & 'model_times ', & diff --git a/test/capgen_test/test_host_data.F90 b/test/capgen_test/test_host_data.F90 index 0c7cdea1..1b0a45c1 100644 --- a/test/capgen_test/test_host_data.F90 +++ b/test/capgen_test/test_host_data.F90 @@ -9,12 +9,12 @@ module test_host_data !! \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 @@ -24,10 +24,11 @@ module test_host_data 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 @@ -50,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 index 499ab22d..fb38a4dc 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -81,6 +81,8 @@ def usage(errmsg=None): _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", @@ -91,18 +93,27 @@ def usage(errmsg=None): "coefficients_for_interpolation", "potential_temperature_increment", "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] + "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"] + "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"] + "surface_air_pressure", "water_vapor_specific_humidity", + "soil_levels", + "temperature_at_diagnostic_levels", + "array_variable_for_testing"] def fields_string(field_type, field_list, sep): """Create an error string for field(s), . From 4d4dc787925493bfc6ee97f7f98ef2436b3a1b45 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 22 May 2025 11:02:42 -0600 Subject: [PATCH 13/20] Detect invalid horizontal dimensions (loop variables) in metadata for host and schemes (#659) Detect invalid horizontal dimensions (loop variables) in metadata for host and schemes Detect invalid horizontal dimensions (loop variables) in metadata for host and schemes. For schemes, `horizontal_dimension` is invalid in the `run` phase, all other options (`horizontal_loop_extent`, `horizontal_loop_begin`, `horizontal_loop_end`) are only valid in the `run` phase. For host models, only `horizontal_dimension` is valid. Tests are added for both scheme and host metadata, and existing tests are fixed, namely bad horizontal dimensions in: - `test/unit_tests/sample_files/test_bad_var_property_name.meta` - `test/unit_tests/sample_files/test_multi_ccpp_arg_tables.meta` - `test/capgen_test/make_ddt.meta` - `test/capgen_test/temp_set.meta` - `test/ddthost_test/make_ddt.meta` - `test_prebuild/test_tracked_data/scheme3.meta` - `test_prebuild/test_unit_conv/data.*` Further: I commented out test for blocked data structures in `test_prebuild/run_all_tests.sh`; this test can no longer be run, since blocked data structures required the host model to define the horizontal dimension for blocked data as `horizontal_loop_extent`. This test and all code related to supporting blocked data structures in `ccpp_prebuild.py` will be removed in a follow-up PR (out of scope of this PR). User interface changes?: No Fixes https://github.com/NCAR/ccpp-framework/issues/521 Testing: **all tests pass** tests **added**: test that invalid horizontal dimensions are detected correctly for host and scheme metadata tests **removed**: commented out blocked data tests for `ccpp_prebuild` unit tests: system tests: manual testing: --------- Co-authored-by: Test User --- .github/workflows/prebuild.yaml | 17 +++--- scripts/metadata_table.py | 18 ++++++ test/capgen_test/make_ddt.meta | 2 +- test/capgen_test/temp_set.meta | 2 +- test/ddthost_test/make_ddt.meta | 2 +- .../test_bad_var_property_name.meta | 2 +- .../test_multi_ccpp_arg_tables.meta | 2 +- .../sample_host_files/mismatch_hdim_mod.F90 | 11 ++++ .../sample_host_files/mismatch_hdim_mod.meta | 25 +++++++++ .../sample_scheme_files/mismatch_hdim.F90 | 48 ++++++++++++++++ .../sample_scheme_files/mismatch_hdim.meta | 55 +++++++++++++++++++ test/unit_tests/test_metadata_host_file.py | 21 ++++++- test/unit_tests/test_metadata_scheme_file.py | 18 ++++++ test_prebuild/run_all_tests.sh | 3 +- .../test_track_variables/scheme_3.meta | 8 +-- test_prebuild/test_unit_conv/data.F90 | 3 +- test_prebuild/test_unit_conv/data.meta | 14 +++-- 17 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 test/unit_tests/sample_host_files/mismatch_hdim_mod.F90 create mode 100644 test/unit_tests/sample_host_files/mismatch_hdim_mod.meta create mode 100644 test/unit_tests/sample_scheme_files/mismatch_hdim.F90 create mode 100644 test/unit_tests/sample_scheme_files/mismatch_hdim.meta 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/scripts/metadata_table.py b/scripts/metadata_table.py index bd388716..8b0cfc18 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -993,8 +993,26 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): pval = [] for dim in porig: if ':' in 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) 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)) # end if 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/temp_set.meta b/test/capgen_test/temp_set.meta index adfe1532..d709da1e 100644 --- a/test/capgen_test/temp_set.meta +++ b/test/capgen_test/temp_set.meta @@ -86,7 +86,7 @@ standard_name = array_variable_for_testing long_name = array variable for testing units = none - dimensions = (horizontal_dimension,2,4,6) + dimensions = (horizontal_loop_extent,2,4,6) type = real kind = kind_phys intent = inout diff --git a/test/ddthost_test/make_ddt.meta b/test/ddthost_test/make_ddt.meta index 4f5c9a00..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] 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_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/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..5ad7e305 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: @@ -343,6 +346,21 @@ 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_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 c926122b..645a531b 100644 --- a/test_prebuild/test_unit_conv/data.F90 +++ b/test_prebuild/test_unit_conv/data.F90 @@ -10,10 +10,11 @@ module data private - public ncols, nspecies + 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 diff --git a/test_prebuild/test_unit_conv/data.meta b/test_prebuild/test_unit_conv/data.meta index e709e4fc..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,21 +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_loop_extent) + dimensions = (horizontal_dimension) type = real kind = kind_phys [opt_array_flag] @@ -54,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) From eae6c3d8e2ae3283547910047e2e2643dddf815c Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Thu, 22 May 2025 11:16:09 -0600 Subject: [PATCH 14/20] Capgen in SCM: DDT subfields into Group Caps (#640) ## Summary Allow for sub components of a DDT to be passed into the Group caps from the Host cap . ## Description This PR adds recursive searching for DDTs when creating the call strings to the Group caps. This preserves the full reference in the call string. Snippet from `test_host_ccpp_cap.F90` before the change to ddt_library.py: `sfc_up_sw=phys_state%sfc_up_sw(col_start:col_end)` After, with full reference in call string: `sfc_up_sw=phys_state%fluxSW%sfc_up_sw(col_start:col_end)` User interface changes?: No Fixes: #639 This is built on #646 Testing: New testing added to pass subfields of DDT into scheme. --------- Co-authored-by: Steve Goldhaber Co-authored-by: Dom Heinzeller --- scripts/ddt_library.py | 11 +++++-- test/var_compatibility_test/CMakeLists.txt | 2 +- .../var_compatibility_test/module_rad_ddt.F90 | 4 +-- .../module_rad_ddt.meta | 4 +-- test/var_compatibility_test/rad_sw.F90 | 10 +++---- test/var_compatibility_test/rad_sw.meta | 18 ++++++++---- test/var_compatibility_test/run_test | 9 ++++-- test/var_compatibility_test/test_host.F90 | 17 ++++++----- .../var_compatibility_test/test_host_data.F90 | 15 ++++++---- .../test_host_data.meta | 2 +- test/var_compatibility_test/test_host_mod.F90 | 29 +++++++++++++++++++ test/var_compatibility_test/test_reports.py | 6 ++-- 12 files changed, 90 insertions(+), 37 deletions(-) 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/test/var_compatibility_test/CMakeLists.txt b/test/var_compatibility_test/CMakeLists.txt index 34734c06..e25f3fda 100644 --- a/test/var_compatibility_test/CMakeLists.txt +++ b/test/var_compatibility_test/CMakeLists.txt @@ -20,7 +20,7 @@ get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) # #------------------------------------------------------------------------------ LIST(APPEND SCHEME_FILES "var_compatibility_files.txt") -LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") +LIST(APPEND HOST_FILES "module_rad_ddt" "test_host_data" "test_host_mod") LIST(APPEND 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 diff --git a/test/var_compatibility_test/module_rad_ddt.F90 b/test/var_compatibility_test/module_rad_ddt.F90 index c7986a6c..21a1a0ec 100644 --- a/test/var_compatibility_test/module_rad_ddt.F90 +++ b/test/var_compatibility_test/module_rad_ddt.F90 @@ -16,8 +16,8 @@ module mod_rad_ddt !! \htmlinclude arg_table_ty_rad_sw.html !! type ty_rad_sw - real(kind_phys) :: sfc_up_sw - real(kind_phys) :: sfc_down_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 index 4576c151..c4792547 100644 --- a/test/var_compatibility_test/module_rad_ddt.meta +++ b/test/var_compatibility_test/module_rad_ddt.meta @@ -29,12 +29,12 @@ [ sfc_up_sw ] standard_name = surface_upwelling_shortwave_radiation_flux units = W m2 - dimensions = () + dimensions = (horizontal_dimension) type = real kind = kind_phys [ sfc_down_sw ] standard_name = surface_downwelling_shortwave_radiation_flux units = W m2 - dimensions = () + dimensions = (horizontal_dimension) type = real kind = kind_phys diff --git a/test/var_compatibility_test/rad_sw.F90 b/test/var_compatibility_test/rad_sw.F90 index a0f22af9..ddf35224 100644 --- a/test/var_compatibility_test/rad_sw.F90 +++ b/test/var_compatibility_test/rad_sw.F90 @@ -1,6 +1,5 @@ module rad_sw use ccpp_kinds, only: kind_phys - use mod_rad_ddt, only: ty_rad_sw implicit none private @@ -12,10 +11,11 @@ module rad_sw !> \section arg_table_rad_sw_run Argument Table !! \htmlinclude arg_table_rad_sw_run.html !! - subroutine rad_sw_run(ncol, fluxSW, errmsg, errflg) + subroutine rad_sw_run(ncol, sfc_up_sw, sfc_down_sw, errmsg, errflg) integer, intent(in) :: ncol - type(ty_rad_sw), intent(inout) :: fluxSW(:) + 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 @@ -26,8 +26,8 @@ subroutine rad_sw_run(ncol, fluxSW, errmsg, errflg) errflg = 0 do icol=1,ncol - fluxSW(icol)%sfc_up_sw = 100._kind_phys - fluxSW(icol)%sfc_down_sw = 400._kind_phys + sfc_up_sw(icol) = 100._kind_phys + sfc_down_sw(icol) = 400._kind_phys enddo end subroutine rad_sw_run diff --git a/test/var_compatibility_test/rad_sw.meta b/test/var_compatibility_test/rad_sw.meta index 81f2d583..d88b9acc 100644 --- a/test/var_compatibility_test/rad_sw.meta +++ b/test/var_compatibility_test/rad_sw.meta @@ -1,7 +1,6 @@ [ccpp-table-properties] name = rad_sw type = scheme - dependencies = module_rad_ddt.F90 [ccpp-arg-table] name = rad_sw_run type = scheme @@ -11,12 +10,19 @@ units = count dimensions = () intent = in -[fluxSW] - standard_name = shortwave_radiation_fluxes - long_name = shortwave radiation fluxes - units = W m-2 +[ sfc_up_sw ] + standard_name = surface_upwelling_shortwave_radiation_flux + units = W m2 dimensions = (horizontal_loop_extent) - type = ty_rad_sw + 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 diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index 83a1f07e..fb70bde1 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -151,7 +151,8 @@ required_vars_var_compatibility="${required_vars_var_compatibility},scalar_varia required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_b" required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_c" required_vars_var_compatibility="${required_vars_var_compatibility},scheme_order_in_suite" -required_vars_var_compatibility="${required_vars_var_compatibility},shortwave_radiation_fluxes" +required_vars_var_compatibility="${required_vars_var_compatibility},surface_downwelling_shortwave_radiation_flux" +required_vars_var_compatibility="${required_vars_var_compatibility},surface_upwelling_shortwave_radiation_flux" required_vars_var_compatibility="${required_vars_var_compatibility},turbulent_kinetic_energy" required_vars_var_compatibility="${required_vars_var_compatibility},turbulent_kinetic_energy2" required_vars_var_compatibility="${required_vars_var_compatibility},vertical_layer_dimension" @@ -172,7 +173,8 @@ input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_fo input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_b" input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_c" input_vars_var_compatibility="${input_vars_var_compatibility},scheme_order_in_suite" -input_vars_var_compatibility="${input_vars_var_compatibility},shortwave_radiation_fluxes" +input_vars_var_compatibility="${input_vars_var_compatibility},surface_downwelling_shortwave_radiation_flux" +input_vars_var_compatibility="${input_vars_var_compatibility},surface_upwelling_shortwave_radiation_flux" input_vars_var_compatibility="${input_vars_var_compatibility},turbulent_kinetic_energy" input_vars_var_compatibility="${input_vars_var_compatibility},turbulent_kinetic_energy2" input_vars_var_compatibility="${input_vars_var_compatibility},vertical_layer_dimension" @@ -185,7 +187,8 @@ output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius output_vars_var_compatibility="${output_vars_var_compatibility},longwave_radiation_fluxes" output_vars_var_compatibility="${output_vars_var_compatibility},scalar_variable_for_testing" output_vars_var_compatibility="${output_vars_var_compatibility},scheme_order_in_suite" -output_vars_var_compatibility="${output_vars_var_compatibility},shortwave_radiation_fluxes" +output_vars_var_compatibility="${output_vars_var_compatibility},surface_downwelling_shortwave_radiation_flux" +output_vars_var_compatibility="${output_vars_var_compatibility},surface_upwelling_shortwave_radiation_flux" output_vars_var_compatibility="${output_vars_var_compatibility},turbulent_kinetic_energy" output_vars_var_compatibility="${output_vars_var_compatibility},turbulent_kinetic_energy2" diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 65d06827..bbe7b373 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,7 +351,7 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(17) = (/ & + 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 ', & @@ -367,10 +367,11 @@ program test 'num_subcycles_for_effr ', & 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice ', & - 'shortwave_radiation_fluxes ', & + 'surface_downwelling_shortwave_radiation_flux ', & + 'surface_upwelling_shortwave_radiation_flux ', & 'longwave_radiation_fluxes '/) - character(len=cm), target :: test_outvars1(13) = (/ & + character(len=cm), target :: test_outvars1(14) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_ice_particle ', & @@ -380,12 +381,13 @@ program test '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 ', & - 'shortwave_radiation_fluxes ', & - 'longwave_radiation_fluxes '/) + 'longwave_radiation_fluxes '/) - character(len=cm), target :: test_reqvars1(21) = (/ & + character(len=cm), target :: test_reqvars1(22) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -405,7 +407,8 @@ program test 'num_subcycles_for_effr ', & 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice ', & - 'shortwave_radiation_fluxes ', & + 'surface_downwelling_shortwave_radiation_flux ', & + 'surface_upwelling_shortwave_radiation_flux ', & 'longwave_radiation_fluxes '/) type(suite_info) :: test_suites(1) diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index 32b7821f..c46bbfff 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -16,11 +16,11 @@ module test_host_data effrg, & ! effective radius of cloud graupel 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), dimension(:), allocatable :: & + type(ty_rad_sw) :: & fluxSW ! Shortwave radiation fluxes - real(kind_phys) :: scalar_var real(kind_phys) :: scalar_varA real(kind_phys) :: scalar_varB real(kind_phys) :: tke, tke2 @@ -82,10 +82,15 @@ subroutine allocate_physics_state(cols, levels, state, has_graupel, has_ice) end if allocate(state%fluxLW(cols)) - if (allocated(state%fluxSW)) then - deallocate(state%fluxSW) + 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(cols)) + allocate(state%fluxSW%sfc_down_sw(cols)) ! Initialize scheme counter. state%scheme_order = 1 diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index b931a6c1..59a0fb4d 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -78,7 +78,7 @@ standard_name = shortwave_radiation_fluxes long_name = shortwave radiation fluxes units = W m-2 - dimensions = (horizontal_dimension) + dimensions = () type = ty_rad_sw [fluxLW] standard_name = longwave_radiation_fluxes diff --git a/test/var_compatibility_test/test_host_mod.F90 b/test/var_compatibility_test/test_host_mod.F90 index 5cd4846a..efaeb368 100644 --- a/test/var_compatibility_test/test_host_mod.F90 +++ b/test/var_compatibility_test/test_host_mod.F90 @@ -54,6 +54,10 @@ logical function compare_data() 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. @@ -92,6 +96,31 @@ logical function compare_data() 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 index 03202a7b..4bf0d6bb 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -84,7 +84,8 @@ def usage(errmsg=None): "scheme_order_in_suite", "flag_indicating_cloud_microphysics_has_graupel", "flag_indicating_cloud_microphysics_has_ice", - "shortwave_radiation_fluxes", + "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", @@ -97,7 +98,8 @@ def usage(errmsg=None): "turbulent_kinetic_energy2", "scalar_variable_for_testing", "scalar_variable_for_testing", - "shortwave_radiation_fluxes", + "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 From 9254784f55757cdc8386509fedc7e6fdd02517ab Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 3 Jun 2025 16:50:30 -0600 Subject: [PATCH 15/20] Remove case sensitivity for variable dimensions (#660) Updates metadata_table.py parsing to lowercase the following fields: * local name * standard name * dimensions Context: I ran into an issue where dimension variable standard names were not being found if they contain >=1 upper case letter. To reproduce, modify any dimension in any of the capgen tests to be mixed case. The test run will fail with this message: `Could not find dimension...` User interface changes?: No --------- Co-authored-by: Courtney Peverley --- scripts/metadata_table.py | 10 +++++++--- scripts/metavar.py | 2 +- test/advection_test/cld_liq.meta | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 8b0cfc18..30b6aa76 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -921,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 @@ -1003,7 +1003,7 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): var_ok = False # end if # end for - pval.append(dim) + pval.append(dim.lower()) else: dim_ok = VarDictionary.loop_var_okay(standard_name=dim, is_run_phase=self.__section_title.endswith("_run")) @@ -1014,10 +1014,14 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=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 1f320de4..8f283df6 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -708,7 +708,7 @@ def call_string(self, var_dict, loop_vars=None): lname = "" for item in dim.split(':'): if item: - dvar = var_dict.find_variable(standard_name=item.lower(), + dvar = var_dict.find_variable(standard_name=item, any_scope=False) if dvar is None: try: 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 From 981eeae8b90d122a65fa899064902de867f665fc Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 9 Jul 2025 17:45:57 -0600 Subject: [PATCH 16/20] Throw clearer error message when integer dimension indices are present; remove known ddt case sensitivity (#662) Add error handling for integer dimension indices; remove case sensitivity for known DDTs **Description** - Updates to scripts/parse_tools/parse_checkers.py to append additional information to the error message when numerical indices are supplied for dimensions - In scripts/metadata_table.py, remove case sensitivity for known DDTs - Also fixes issue when a line is exactly equal to the max fortran line length and does not need to be continued. User interface changes?: No Closes #661 - numerical dimension indices error in capgen Closes #664 - long standard name bug Testing: test removed: n/a unit tests: all PASS, added two new doctests system tests: manual testing: --------- Co-authored-by: Steve Goldhaber --- scripts/fortran_tools/fortran_write.py | 6 + scripts/metadata_table.py | 10 +- scripts/parse_tools/parse_checkers.py | 30 ++++- .../fortran_files/long_string_test.F90 | 111 ++++++++++++++++++ test/unit_tests/test_fortran_write.py | 28 ++++- 5 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 test/unit_tests/sample_files/fortran_files/long_string_test.F90 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/metadata_table.py b/scripts/metadata_table.py index 30b6aa76..959866a9 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -205,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 {}:{}' @@ -540,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 = [] @@ -882,7 +882,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, module_name, self.title, start_ctx)) # end if if self.header_type == "ddt": - known_ddts.append(self.title) + known_ddts.append(self.title.lower()) # end if # Initialize our ParseSource parent super().__init__(self.title, self.header_type, self.__pobj) @@ -954,11 +954,11 @@ def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): 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) diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 101bae3d..5aaaaeab 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -101,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)) @@ -123,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, @@ -147,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 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/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() - From 74b2f229b7d8fce5b3cc2b932bb95672f280cfdc Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:07:15 -0400 Subject: [PATCH 17/20] Testing refactor (#612) Refactoring of testing infrastructure Changes include: - Refactored python tests to leverage `unittest` framework. - Replaced shell tests with equivalent wrapper from Python. - Reduces value duplication from fortran/shell/python files to just fortran and python file. - Adds re-useable CMake infrastructure for capgen, advection, ddthost, and var_compatability tests. - Adds cmake functions to wrap calls to ccpp_capgen.py and ccpp_datafile.py. - Enabling re-use of openmp and mpi configuration for both library and tests. - Removing PGI compiler options and adding ifx support. - Replaces manually calling fortran tests through run_test with ctest - Removed a few un-used files - Adds missing `dependencies` and `--exclude-required` tests. - Removed explicit optimization flag setting and relying on default CMake behavior from `CMAKE_BUILD_TYPE`. - Removed `-DDEBUG` flag as generated code requires a `--debug` flag to insert debug logic explicitly. - Updated test/unit-test docs to reflect current build/run process. - Added docs documentation to demonstrate building of docs. User interface changes?: No Fixes: None Resolves https://github.com/NCAR/ccpp-framework/issues/514 Testing: test removed: None unit tests: system tests: manual testing: --------- Co-authored-by: Dom Heinzeller Co-authored-by: Courtney Peverley --- .github/workflows/capgen_unit_tests.yaml | 34 +- .gitignore | 2 + .travis.yml | 28 -- CMakeLists.txt | 107 +++++-- cmake/ccpp_capgen.cmake | 129 ++++++++ doc/CMakeLists.txt | 28 +- doc/README.md | 11 + src/CMakeLists.txt | 9 +- test/CMakeLists.txt | 14 + test/README.md | 75 +++++ test/advection_test/CMakeLists.txt | 272 +++------------- test/advection_test/README.md | 21 ++ test/advection_test/advection_test_reports.py | 127 ++++++++ test/advection_test/cld_suite_files.txt | 4 - test/advection_test/cld_suite_files_error.txt | 3 - test/advection_test/run_test | 271 ---------------- .../test_advection_host_integration.F90 | 77 +++++ test/advection_test/test_host.F90 | 165 +--------- test/advection_test/test_reports.py | 194 ------------ test/capgen_test/.gitignore | 1 - test/capgen_test/CMakeLists.txt | 220 +++---------- test/capgen_test/README.md | 24 +- test/capgen_test/capgen_test_reports.py | 150 +++++++++ test/capgen_test/ddt_suite_files.txt | 2 - test/capgen_test/run_test | 299 ------------------ test/capgen_test/temp_scheme_files.txt | 4 - .../test_capgen_host_integration.F90 | 86 +++++ test/capgen_test/test_host.F90 | 172 +--------- test/capgen_test/test_reports.py | 210 ------------ test/ddthost_test/CMakeLists.txt | 215 +++---------- test/ddthost_test/README.md | 20 +- test/ddthost_test/ddt_suite_files.txt | 2 - test/ddthost_test/ddthost_test_reports.py | 139 ++++++++ test/ddthost_test/run_test | 285 ----------------- test/ddthost_test/temp_scheme_files.txt | 4 - .../test_ddt_host_integration.F90 | 79 +++++ test/ddthost_test/test_host.F90 | 165 +--------- test/ddthost_test/test_reports.py | 180 ----------- test/run_fortran_tests.sh | 66 ---- test/test_stub.py | 162 ++++++++++ test/unit_tests/README.md | 52 ++- test/utils/CMakeLists.txt | 1 + test/utils/test_utils.F90 | 88 ++++++ test/var_compatibility_test/.gitignore | 1 - test/var_compatibility_test/CMakeLists.txt | 208 ++---------- test/var_compatibility_test/README.md | 22 +- test/var_compatibility_test/run_test | 287 ----------------- test/var_compatibility_test/test_host.F90 | 172 +--------- test/var_compatibility_test/test_reports.py | 183 ----------- .../test_var_compatibility_integration.F90 | 85 +++++ .../var_compatibility_files.txt | 7 - .../var_compatibility_test_reports.py | 116 +++++++ 52 files changed, 1739 insertions(+), 3539 deletions(-) delete mode 100644 .travis.yml create mode 100644 cmake/ccpp_capgen.cmake create mode 100644 doc/README.md create mode 100644 test/CMakeLists.txt create mode 100644 test/README.md create mode 100644 test/advection_test/README.md create mode 100644 test/advection_test/advection_test_reports.py delete mode 100644 test/advection_test/cld_suite_files.txt delete mode 100644 test/advection_test/cld_suite_files_error.txt delete mode 100755 test/advection_test/run_test create mode 100644 test/advection_test/test_advection_host_integration.F90 delete mode 100644 test/advection_test/test_reports.py delete mode 100644 test/capgen_test/.gitignore create mode 100644 test/capgen_test/capgen_test_reports.py delete mode 100644 test/capgen_test/ddt_suite_files.txt delete mode 100755 test/capgen_test/run_test delete mode 100644 test/capgen_test/temp_scheme_files.txt create mode 100644 test/capgen_test/test_capgen_host_integration.F90 delete mode 100644 test/capgen_test/test_reports.py delete mode 100644 test/ddthost_test/ddt_suite_files.txt create mode 100644 test/ddthost_test/ddthost_test_reports.py delete mode 100755 test/ddthost_test/run_test delete mode 100644 test/ddthost_test/temp_scheme_files.txt create mode 100644 test/ddthost_test/test_ddt_host_integration.F90 delete mode 100644 test/ddthost_test/test_reports.py delete mode 100755 test/run_fortran_tests.sh create mode 100644 test/test_stub.py create mode 100644 test/utils/CMakeLists.txt create mode 100644 test/utils/test_utils.F90 delete mode 100644 test/var_compatibility_test/.gitignore delete mode 100755 test/var_compatibility_test/run_test delete mode 100755 test/var_compatibility_test/test_reports.py create mode 100644 test/var_compatibility_test/test_var_compatibility_integration.F90 delete mode 100644 test/var_compatibility_test/var_compatibility_files.txt create mode 100755 test/var_compatibility_test/var_compatibility_test_reports.py diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 4d871a52..7247100f 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -15,8 +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/.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/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/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 5a9b546d..0f1be200 100644 --- a/test/advection_test/CMakeLists.txt +++ b/test/advection_test/CMakeLists.txt @@ -1,233 +1,57 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.10) -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_suite_files.txt b/test/advection_test/cld_suite_files.txt deleted file mode 100644 index 4ce40a53..00000000 --- a/test/advection_test/cld_suite_files.txt +++ /dev/null @@ -1,4 +0,0 @@ -cld_liq.meta -cld_ice.meta -apply_constituent_tendencies.meta -const_indices.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/run_test b/test/advection_test/run_test deleted file mode 100755 index 2301a4fc..00000000 --- a/test/advection_test/run_test +++ /dev/null @@ -1,271 +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},${fsrc}/ccpp_scheme_utils.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,const_indices" -dependencies="" -suite_list="cld_suite" -required_vars="banana_array_dim" -required_vars="${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},test_banana_constituent_index" -required_vars="${required_vars},test_banana_constituent_indices" -required_vars="${required_vars},test_banana_name,test_banana_name_array" -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="banana_array_dim" -input_vars="${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},test_banana_name,test_banana_name_array" -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},test_banana_constituent_index" -output_vars="${output_vars},test_banana_constituent_indices" -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 fa6c8e43..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 @@ -242,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 @@ -1134,81 +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(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_reports.py b/test/advection_test/test_reports.py deleted file mode 100644 index 04294364..00000000 --- a/test/advection_test/test_reports.py +++ /dev/null @@ -1,194 +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_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 -_PROCESS_LIST = list() -_MODULE_LIST = ["cld_ice", "cld_liq", "const_indices", "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", - "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", - "test_banana_constituent_indices", - "test_banana_constituent_index"] - -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/run_test b/test/capgen_test/run_test deleted file mode 100755 index 99ffc6a2..00000000 --- a/test/capgen_test/run_test +++ /dev/null @@ -1,299 +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_scheme_utils.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="array_variable_for_testing" -required_vars_temp="${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},lower_bound_of_vertical_dimension_of_soil" -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},soil_levels" -required_vars_temp="${required_vars_temp},surface_air_pressure" -required_vars_temp="${required_vars_temp},temperature_at_diagnostic_levels" -required_vars_temp="${required_vars_temp},time_step_for_physics" -required_vars_temp="${required_vars_temp},upper_bound_of_vertical_dimension_of_soil" -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="array_variable_for_testing" -input_vars_temp="${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},lower_bound_of_vertical_dimension_of_soil" -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},soil_levels" -input_vars_temp="${input_vars_temp},surface_air_pressure" -input_vars_temp="${input_vars_temp},temperature_at_diagnostic_levels" -input_vars_temp="${input_vars_temp},time_step_for_physics" -input_vars_temp="${input_vars_temp},upper_bound_of_vertical_dimension_of_soil" -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="array_variable_for_testing" -output_vars_temp="${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},soil_levels" -output_vars_temp="${output_vars_temp},surface_air_pressure" -output_vars_temp="${output_vars_temp},temperature_at_diagnostic_levels" -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/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 fd31b145..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,90 +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(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_reports.py b/test/capgen_test/test_reports.py deleted file mode 100644 index fb38a4dc..00000000 --- a/test/capgen_test/test_reports.py +++ /dev/null @@ -1,210 +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_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")] -_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"] - -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/run_test b/test/ddthost_test/run_test deleted file mode 100755 index 964bface..00000000 --- a/test/ddthost_test/run_test +++ /dev/null @@ -1,285 +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_scheme_utils.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},host_standard_ccpp_type" -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},host_standard_ccpp_type" -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 12c4aeb0..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,83 +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(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_reports.py b/test/ddthost_test/test_reports.py deleted file mode 100644 index 6fec8be4..00000000 --- a/test/ddthost_test/test_reports.py +++ /dev/null @@ -1,180 +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_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")] -_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"] - -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_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/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 e25f3fda..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 "module_rad_ddt" "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/run_test b/test/var_compatibility_test/run_test deleted file mode 100755 index fb70bde1..00000000 --- a/test/var_compatibility_test/run_test +++ /dev/null @@ -1,287 +0,0 @@ -#! /bin/bash - -currdir="$(pwd -P)" -scriptdir="$( cd $( dirname $0 ); pwd -P )" - -## -## Option default values -## -defdir="vc_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_scheme_utils.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,mod_effr_pre,rad_lw,rad_sw" -dependencies="${scriptdir}/module_rad_ddt.F90" -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},longwave_radiation_fluxes" -required_vars_var_compatibility="${required_vars_var_compatibility},num_subcycles_for_effr" -required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing" -required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_a" -required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_b" -required_vars_var_compatibility="${required_vars_var_compatibility},scalar_variable_for_testing_c" -required_vars_var_compatibility="${required_vars_var_compatibility},scheme_order_in_suite" -required_vars_var_compatibility="${required_vars_var_compatibility},surface_downwelling_shortwave_radiation_flux" -required_vars_var_compatibility="${required_vars_var_compatibility},surface_upwelling_shortwave_radiation_flux" -required_vars_var_compatibility="${required_vars_var_compatibility},turbulent_kinetic_energy" -required_vars_var_compatibility="${required_vars_var_compatibility},turbulent_kinetic_energy2" -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},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},longwave_radiation_fluxes" -input_vars_var_compatibility="${input_vars_var_compatibility},num_subcycles_for_effr" -input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing" -input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_a" -input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_b" -input_vars_var_compatibility="${input_vars_var_compatibility},scalar_variable_for_testing_c" -input_vars_var_compatibility="${input_vars_var_compatibility},scheme_order_in_suite" -input_vars_var_compatibility="${input_vars_var_compatibility},surface_downwelling_shortwave_radiation_flux" -input_vars_var_compatibility="${input_vars_var_compatibility},surface_upwelling_shortwave_radiation_flux" -input_vars_var_compatibility="${input_vars_var_compatibility},turbulent_kinetic_energy" -input_vars_var_compatibility="${input_vars_var_compatibility},turbulent_kinetic_energy2" -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},longwave_radiation_fluxes" -output_vars_var_compatibility="${output_vars_var_compatibility},scalar_variable_for_testing" -output_vars_var_compatibility="${output_vars_var_compatibility},scheme_order_in_suite" -output_vars_var_compatibility="${output_vars_var_compatibility},surface_downwelling_shortwave_radiation_flux" -output_vars_var_compatibility="${output_vars_var_compatibility},surface_upwelling_shortwave_radiation_flux" -output_vars_var_compatibility="${output_vars_var_compatibility},turbulent_kinetic_energy" -output_vars_var_compatibility="${output_vars_var_compatibility},turbulent_kinetic_energy2" - -## -## 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 bbe7b373..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,90 +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(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 diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py deleted file mode 100755 index 4bf0d6bb..00000000 --- a/test/var_compatibility_test/test_reports.py +++ /dev/null @@ -1,183 +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_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")] -_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 - -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) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("dependencies"), - _DEPENDENCIES) -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 71df1054..00000000 --- a/test/var_compatibility_test/var_compatibility_files.txt +++ /dev/null @@ -1,7 +0,0 @@ -module_rad_ddt.meta -effr_calc.meta -effr_diag.meta -effr_pre.meta -effr_post.meta -rad_lw.meta -rad_sw.meta 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" From 9a40b0f61d7dafa824685097f8eef6b4be966784 Mon Sep 17 00:00:00 2001 From: goldy <1588651+gold2718@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:09:36 +0200 Subject: [PATCH 18/20] Allow different ordering in Fortran scheme and associated metadata (#666) Allow flexible Fortran scheme argument ordering In checking compatibility between Fortran and metadata, schemes were required to have arguments in the same order. This PR removes that restriction. User interface changes?: No Fixes: #665 Testing: unit tests: Modified one test Fortran file to use a different argument ordering in the Fortran scheme. system tests: manual testing: manually ran unit tests Co-authored-by: Steve Goldhaber --- scripts/ccpp_capgen.py | 13 ++++++++- .../sample_scheme_files/temp_adjust.F90 | 2 +- test/unit_tests/test_metadata_scheme_file.py | 29 +++++++------------ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 31421571..a0b1e032 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -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') 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_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index 5ad7e305..ef24742c 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -215,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" - self.assertTrue(emsg in str(context.exception)) - emsg = "Out of order argument, errflg in CCPPnotset_var_missing_in_meta_run" + emsg = "Fortran variable, bar, not in metadata" 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 @@ -236,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 @@ -305,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 @@ -327,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 @@ -363,4 +355,3 @@ def test_mismatch_hdim(self): if __name__ == "__main__": unittest.main() - From 07b356dc1e78c6918ed3787cb77c0ab04aef2976 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Wed, 6 Aug 2025 14:16:41 -0400 Subject: [PATCH 19/20] Make N m-2 equivalent to Pa (#670) **Makes N m-2 equivalent to Pa.** Simple change to `unit_conversion.py` to add N m-2 equivalent to Pa and vice-versa. User interface changes?: No --- scripts/conversion_tools/unit_conversion.py | 8 ++++++++ scripts/var_props.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/scripts/conversion_tools/unit_conversion.py b/scripts/conversion_tools/unit_conversion.py index 7fbf41a2..25a44cab 100755 --- a/scripts/conversion_tools/unit_conversion.py +++ b/scripts/conversion_tools/unit_conversion.py @@ -196,3 +196,11 @@ def V_A__to__W(): 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/var_props.py b/scripts/var_props.py index 52800ba4..a53e8b68 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -1201,6 +1201,8 @@ def _get_unit_convstrs(self, var1_units, var2_units): ('{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') From 4ae528b875487db284a9894b55797de8f8206271 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 21 Aug 2025 12:06:20 -0600 Subject: [PATCH 20/20] Bug fixes for ccpp_prebuild to work with partially case-insensitive capgen parser (#669) **Bug fixes for ccpp_prebuild to work with partially case-insensitive capgen parser** These updates are needed to make `ccpp_prebuild.py` work with the recent, partially complete case-insensitive `capgen` parser. I tested this with NEPTUNE in a rather complicated way - pulling develop into the branch neptune uses (that is based on main), then creating the bug fixes there, then cherry-picking them so that we can merge them into develop here. Hopefully, by the time this comes all back to NEPTUNE it will still work :-) This PR needs to be merged into develop, then #668 must be updated before it can be merged into main. User interface changes?: no - but prebuild is now case-insensitive Fixes: no separate issue created, see discussion in #668 Testing: test removed: none unit tests: all pass system tests: all pass manual testing: full regression testing with NEPTUNE passed --- scripts/ccpp_prebuild.py | 13 ++++--- scripts/ccpp_track_variables.py | 3 +- scripts/common.py | 55 ++++++++++++++++++++++++++--- scripts/metadata_parser.py | 6 ++-- scripts/mkstatic.py | 61 +++++++++++++++++++-------------- test/unit_tests/test_common.py | 10 +++--- 6 files changed, 105 insertions(+), 43 deletions(-) diff --git a/scripts/ccpp_prebuild.py b/scripts/ccpp_prebuild.py index 95067328..c6198e27 100755 --- a/scripts/ccpp_prebuild.py +++ b/scripts/ccpp_prebuild.py @@ -13,6 +13,7 @@ import sys # CCPP framework imports +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 @@ -361,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 @@ -401,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) @@ -749,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_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 84520222..234e2521 100755 --- a/scripts/common.py +++ b/scripts/common.py @@ -101,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 @@ -184,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/metadata_parser.py b/scripts/metadata_parser.py index e8c3f987..31078b64 100755 --- a/scripts/metadata_parser.py +++ b/scripts/metadata_parser.py @@ -236,12 +236,12 @@ def read_new_metadata(filename, module_name, table_name, scheme_name = None, sub # 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, + var = Var(standard_name = standard_name.lower(), long_name = new_var.get_prop_value('long_name') + legacy_note, units = units, - local_name = new_var.get_prop_value('local_name'), + 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/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/test/unit_tests/test_common.py b/test/unit_tests/test_common.py index e95b8184..aa81ee8d 100755 --- a/test/unit_tests/test_common.py +++ b/test/unit_tests/test_common.py @@ -47,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}")