From 43031fe8d7cb30ae9bdb8297507afc64c2ee9e80 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 21 Aug 2023 13:35:15 -0600 Subject: [PATCH 01/12] initial stab at testing-only PR --- .github/workflows/capgen_unit_tests.yaml | 22 ++ test/advection_test/cld_ice.F90 | 12 +- test/advection_test/cld_ice.meta | 4 +- test/advection_test/cld_liq.F90 | 12 +- test/advection_test/cld_liq.meta | 4 +- test/run_tests.sh | 15 +- .../fortran_files/comments_test.F90 | 33 ++ .../fortran_files/linebreak_test.F90 | 39 +++ .../sample_host_files/data1_mod.F90 | 11 + .../sample_host_files/data1_mod.meta | 25 ++ test/unit_tests/test_fortran_write.py | 126 +++++++ test/unit_tests/test_metadata_host_file.py | 260 ++++++++++++++ test/unit_tests/test_metadata_scheme_file.py | 329 +++++++++++------- test/unit_tests/test_metadata_table.py | 2 +- 14 files changed, 737 insertions(+), 157 deletions(-) create mode 100644 .github/workflows/capgen_unit_tests.yaml create mode 100644 test/unit_tests/sample_files/fortran_files/comments_test.F90 create mode 100644 test/unit_tests/sample_files/fortran_files/linebreak_test.F90 create mode 100644 test/unit_tests/sample_host_files/data1_mod.F90 create mode 100644 test/unit_tests/sample_host_files/data1_mod.meta create mode 100644 test/unit_tests/test_fortran_write.py create mode 100644 test/unit_tests/test_metadata_host_file.py diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml new file mode 100644 index 00000000..eb574f2d --- /dev/null +++ b/.github/workflows/capgen_unit_tests.yaml @@ -0,0 +1,22 @@ +name: Capgen Unit Tests + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + #Trigger workflow on push to any branch or branch heirarchy: + - '**' + +jobs: + unit_tests: + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: update repos and install dependencies + run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git + - name: Run unit tests + run: cd test && ./run_tests.sh + diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index 5a91282f..9c1e769a 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -19,7 +19,7 @@ MODULE cld_ice !> \section arg_table_cld_ice_run Argument Table !! \htmlinclude arg_table_cld_ice_run.html !! - subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice, & + subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice_array, & errmsg, errflg) integer, intent(in) :: ncol @@ -27,7 +27,7 @@ subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice, & real(kind_phys), intent(inout) :: temp(:,:) real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) - REAL(kind_phys), intent(inout) :: cld_ice(:,:) + REAL(kind_phys), intent(inout) :: cld_ice_array(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -44,7 +44,7 @@ subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice, & do ilev = 1, size(temp, 2) if (temp(icol, ilev) < tcld) then frz = MAX(qv(icol, ilev) - 0.5_kind_phys, 0.0_kind_phys) - cld_ice(icol, ilev) = cld_ice(icol, ilev) + frz + cld_ice_array(icol, ilev) = cld_ice_array(icol, ilev) + frz qv(icol, ilev) = qv(icol, ilev) - frz if (frz > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + 1.0_kind_phys @@ -58,16 +58,16 @@ END SUBROUTINE cld_ice_run !> \section arg_table_cld_ice_init Argument Table !! \htmlinclude arg_table_cld_ice_init.html !! - subroutine cld_ice_init(tfreeze, cld_ice, errmsg, errflg) + subroutine cld_ice_init(tfreeze, cld_ice_array, errmsg, errflg) real(kind_phys), intent(in) :: tfreeze - real(kind_phys), intent(inout) :: cld_ice(:,:) + real(kind_phys), intent(inout) :: cld_ice_array(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg errmsg = '' errflg = 0 - cld_ice = 0.0_kind_phys + cld_ice_array = 0.0_kind_phys tcld = tfreeze - 20.0_kind_phys end subroutine cld_ice_init diff --git a/test/advection_test/cld_ice.meta b/test/advection_test/cld_ice.meta index e4a961ab..f66888e0 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -41,7 +41,7 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in -[ cld_ice ] +[ cld_ice_array ] standard_name = cloud_ice_dry_mixing_ratio advected = .true. units = kg kg-1 @@ -73,7 +73,7 @@ dimensions = () type = real | kind = kind_phys intent = in -[ cld_ice ] +[ cld_ice_array ] standard_name = cloud_ice_dry_mixing_ratio advected = .true. units = kg kg-1 diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 2e1e5a57..1e6940e9 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -16,7 +16,7 @@ MODULE cld_liq !> \section arg_table_cld_liq_run Argument Table !! \htmlinclude arg_table_cld_liq_run.html !! - subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq, & + subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & errmsg, errflg) integer, intent(in) :: ncol @@ -25,7 +25,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq, & real(kind_phys), intent(inout) :: temp(:,:) real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) - REAL(kind_phys), intent(inout) :: cld_liq(:,:) + REAL(kind_phys), intent(inout) :: cld_liq_array(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -43,7 +43,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq, & if ( (qv(icol, ilev) > 0.0_kind_phys) .and. & (temp(icol, ilev) <= tcld)) then cond = MIN(qv(icol, ilev), 0.1_kind_phys) - cld_liq(icol, ilev) = cld_liq(icol, ilev) + cond + cld_liq_array(icol, ilev) = cld_liq_array(icol, ilev) + cond qv(icol, ilev) = qv(icol, ilev) - cond if (cond > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + (cond * 5.0_kind_phys) @@ -57,10 +57,10 @@ END SUBROUTINE cld_liq_run !> \section arg_table_cld_liq_init Argument Table !! \htmlinclude arg_table_cld_liq_init.html !! - subroutine cld_liq_init(tfreeze, cld_liq, tcld, errmsg, errflg) + subroutine cld_liq_init(tfreeze, cld_liq_array, tcld, errmsg, errflg) real(kind_phys), intent(in) :: tfreeze - real(kind_phys), intent(out) :: cld_liq(:,:) + real(kind_phys), intent(out) :: cld_liq_array(:,:) real(kind_phys), intent(out) :: tcld character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -69,7 +69,7 @@ subroutine cld_liq_init(tfreeze, cld_liq, tcld, errmsg, errflg) errmsg = '' errflg = 0 - cld_liq = 0.0_kind_phys + cld_liq_array = 0.0_kind_phys tcld = tfreeze - 20.0_kind_phys end subroutine cld_liq_init diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 1186d741..4c87f4b7 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -47,7 +47,7 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in -[ cld_liq ] +[ cld_liq_array ] standard_name = cloud_liquid_dry_mixing_ratio advected = .true. units = kg kg-1 @@ -79,7 +79,7 @@ dimensions = () type = real | kind = kind_phys intent = in -[ cld_liq ] +[ cld_liq_array ] standard_name = cloud_liquid_dry_mixing_ratio advected = .true. units = kg kg-1 diff --git a/test/run_tests.sh b/test/run_tests.sh index 97001d30..04ce5e8c 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -38,12 +38,14 @@ if [ $res -ne 0 ]; then fi # Run var_action test -./var_action_test/run_test -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "Failure running var_action test" -fi +# TODO: Re-enable after feature fully implemented. +# ./var_action_test/run_test +# res=$? +# errcnt=$((errcnt + res)) +# if [ $res -ne 0 ]; then +# echo "Failure running var_action test" +# fi +echo "Skipping var_action_test/run_test until feature is fully implemented" # Run doctests ./run_doctest.sh @@ -67,4 +69,5 @@ if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else echo "${errcnt} tests FAILed" + return 1 fi diff --git a/test/unit_tests/sample_files/fortran_files/comments_test.F90 b/test/unit_tests/sample_files/fortran_files/comments_test.F90 new file mode 100644 index 00000000..d4820a36 --- /dev/null +++ b/test/unit_tests/sample_files/fortran_files/comments_test.F90 @@ -0,0 +1,33 @@ +! +! 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 comment writing for FortranWriter +!! +! +module comments_test + +! We can write comments in the module header + ! We can write indented comments in the header + integer :: foo ! Comment at end of line works + integer :: bar ! + ! xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ! + integer :: baz ! + ! yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + ! yyyyy + +CONTAINS + ! We can write comments in the module body + +end module comments_test diff --git a/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 b/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 new file mode 100644 index 00000000..4f89441f --- /dev/null +++ b/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 @@ -0,0 +1,39 @@ +! +! 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 line breaking for FortranWriter +!! +! +module linebreak_test + + character(len=7) :: data = (/ name000, name001, name002, name003, name004, name005, name006, & + name007, name008, name009, name010, name011, name012, name013, name014, name015, & + name016, name017, name018, name019, name020, name021, name022, name023, name024, & + name025, name026, name027, name028, name029, name030, name031, name032, name033, & + name034, name035, name036, name037, name038, name039, name040, name041, name042, & + name043, name044, name045, name046, name047, name048, name049, name050, name051, & + name052, name053, name054, name055, name056, name057, name058, name059, name060, & + name061, name062, name063, name064, name065, name066, name067, name068, name069, & + name070, name071, name072, name073, name074, name075, name076, name077, name078, & + name079, name080, name081, name082, name083, name084, name085, name086, name087, & + name088, name089, name090, name091, name092, name093, name094, name095, name096, & + name097, name098, name099 /) + +CONTAINS + call & + endrun('Cannot read columns_on_task from file'// & + ', columns_on_task has no horizontal dimension; columns_on_task is a protected variable') + + +end module linebreak_test diff --git a/test/unit_tests/sample_host_files/data1_mod.F90 b/test/unit_tests/sample_host_files/data1_mod.F90 new file mode 100644 index 00000000..b85db315 --- /dev/null +++ b/test/unit_tests/sample_host_files/data1_mod.F90 @@ -0,0 +1,11 @@ +module data1_mod + + use ccpp_kinds, only: kind_phys + + !> \section arg_table_data1_mod Argument Table + !! \htmlinclude arg_table_data1_mod.html + real(kind_phys) :: ps1 + real(kind_phys), allocatable :: xbox(:,:) + real(kind_phys), allocatable :: switch(:,:) + +end module data1_mod diff --git a/test/unit_tests/sample_host_files/data1_mod.meta b/test/unit_tests/sample_host_files/data1_mod.meta new file mode 100644 index 00000000..37e2de96 --- /dev/null +++ b/test/unit_tests/sample_host_files/data1_mod.meta @@ -0,0 +1,25 @@ +[ccpp-table-properties] + name = data1_mod + type = module +[ccpp-arg-table] + name = data1_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_dimension, 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_dimension, vertical_layer_dimension) diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py new file mode 100644 index 00000000..fdc00085 --- /dev/null +++ b/test/unit_tests/test_fortran_write.py @@ -0,0 +1,126 @@ +#! /usr/bin/env python3 +""" +----------------------------------------------------------------------- + Description: Contains unit tests for FortranWriter + in scripts file fortran/fortran_write.py + + Assumptions: + + Command line arguments: none + + Usage: python3 test_fortran_write.py # run the unit tests +----------------------------------------------------------------------- +""" + +import filecmp +import glob +import os +import sys +import unittest + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_SCRIPTS_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, + os.pardir, "scripts")) +_SAMPLE_FILES_DIR = os.path.join(_TEST_DIR, "sample_files", "fortran_files") +_PRE_TMP_DIR = os.path.join(_TEST_DIR, "tmp") +_TMP_DIR = os.path.join(_PRE_TMP_DIR, "fortran_files") + +if not os.path.exists(_SCRIPTS_DIR): + raise ImportError(f"Cannot find scripts directory, {_SCRIPTS_DIR}") + +sys.path.append(_SCRIPTS_DIR) + +# pylint: disable=wrong-import-position +from fortran_tools import FortranWriter +# pylint: enable=wrong-import-position + +############################################################################### +def remove_files(file_list): +############################################################################### + """Remove files in if they exist""" + if isinstance(file_list, str): + file_list = [file_list] + # end if + for fpath in file_list: + if os.path.exists(fpath): + os.remove(fpath) + # End if + # End for + +class MetadataTableTestCase(unittest.TestCase): + + """Tests for `FortranWriter`.""" + + @classmethod + def setUpClass(cls): + """Clean output directory (tmp) before running tests""" + #Does "tmp" directory exist? If not then create it: + if not os.path.exists(_PRE_TMP_DIR): + os.mkdir(_PRE_TMP_DIR) + # Ensure the "sample_files/fortran_files" directory exists and is empty + if os.path.exists(_TMP_DIR): + # Clear out all files: + remove_files(glob.iglob(os.path.join(_TMP_DIR, '*.*'))) + else: + os.makedirs(_TMP_DIR) + # end if + + #Run inherited setup method: + super().setUpClass() + + def test_line_breaking(self): + """Test that FortranWriter correctly breaks long lines""" + # Setup + testname = "linebreak_test" + compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") + generate = os.path.join(_TMP_DIR, f"{testname}.F90") + # Exercise + header = "Test of line breaking for FortranWriter" + with FortranWriter(generate, 'w', header, f"{testname}") as gen: + # Test long declaration + data_items = ', '.join([f"name{x:03}" for x in range(100)]) + gen.write(f"character(len=7) :: data = (/ {data_items} /)", 1) + gen.end_module_header() + # Test long code lines + line_items = ["call endrun('Cannot read columns_on_task from ", + "file'//', columns_on_task has no horizontal ", + "dimension; columns_on_task is a ", + "protected variable')"] + gen.write(f"{''.join(line_items)}", 2) + # 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) + + def test_good_comments(self): + """Test that comments are written and broken correctly.""" + # Setup + testname = "comments_test" + compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") + generate = os.path.join(_TMP_DIR, f"{testname}.F90") + # Exercise + header = "Test of comment writing for FortranWriter" + with FortranWriter(generate, 'w', header, f"{testname}") as gen: + gen.comment("We can write comments in the module header", 0) + gen.comment("We can write indented comments in the header", 1) + gen.write("integer :: foo ! Comment at end of line works", 1) + # Test long comments at end of line + gen.write(f"integer :: bar ! {'x'*100}", 1) + gen.write(f"integer :: baz ! {'y'*130}", 1) + gen.end_module_header() + # Test comment line in body + gen.comment("We can write comments in the module body", 1) + + # end with + + # Check that file was generated + amsg = f"{generate} does not exist" + self.assertTrue(os.path.exists(generate), msg=amsg) + amsg = f"{generate} does not match {compare}" + self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit_tests/test_metadata_host_file.py b/test/unit_tests/test_metadata_host_file.py new file mode 100644 index 00000000..cc5ba7b5 --- /dev/null +++ b/test/unit_tests/test_metadata_host_file.py @@ -0,0 +1,260 @@ +#! /usr/bin/env python3 + +""" +----------------------------------------------------------------------- + Description: capgen needs to compare a metadata header against the + associated CCPP Fortran interface routine. This set of + tests is testing the parse_host_model_files function in + ccpp_capgen.py which performs the operations in the first + bullet below. Each test calls this function. + + * This script contains unit tests that do the following: + 1) Read one or more metadata files (to collect + the metadata headers) + 2) Read the associated CCPP Fortran host file(s) (to + collect Fortran interfaces) + 3) Create a CCPP host model object + 3) Test the properties of the CCPP host model object + + * Tests include: + - Correctly parse and match a simple module file with + data fields (a data block) + - Correctly parse and match a simple module file with + a DDT definition + - Correctly parse and match a simple module file with + two DDT definitions + - Correctly parse and match a simple module file with + two DDT definitions and a data block + + Assumptions: + + Command line arguments: none + + Usage: python3 test_metadata_host_file.py # run the unit tests +----------------------------------------------------------------------- +""" +import sys +import os +import logging +import unittest + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_SCRIPTS_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, + os.pardir, "scripts")) +if not os.path.exists(_SCRIPTS_DIR): + raise ImportError("Cannot find scripts directory") + +sys.path.append(_SCRIPTS_DIR) + +# pylint: disable=wrong-import-position +from ccpp_capgen import parse_host_model_files +from framework_env import CCPPFrameworkEnv +from parse_tools import CCPPError +# pylint: enable=wrong-import-position + +class MetadataHeaderTestCase(unittest.TestCase): + """Unit tests for parse_host_model_files""" + + def setUp(self): + """Setup important directories and logging""" + self._sample_files_dir = os.path.join(_TEST_DIR, "sample_host_files") + logger = logging.getLogger(self.__class__.__name__) + self._run_env = CCPPFrameworkEnv(logger, ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) + + def test_module_with_data(self): + """Test that a module containing a data block is parsed and matched + correctly.""" + # Setup + module_files = [os.path.join(self._sample_files_dir, "data1_mod.meta")] + # Exercise + hname = 'host_name_data1' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), 1) + # Verify header titles + self.assertTrue('data1_mod' in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 3) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('play_station' in std_names) + self.assertTrue('xbox' in std_names) + self.assertTrue('nintendo_switch' in std_names) + + def test_module_with_one_ddt(self): + """Test that a module containing a DDT definition is parsed and matched + correctly.""" + # Setup + ddt_name = 'ddt1_t' + module_files = [os.path.join(self._sample_files_dir, "ddt1.meta")] + # Exercise + hname = 'host_name_ddt1' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), 1) + # Verify header titles + self.assertTrue(ddt_name in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 0) + # Verify that the DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), 2) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('ddt_var_array_dimension' in std_names) + self.assertTrue('vars_array' in std_names) + + def test_module_with_two_ddts(self): + """Test that a module containing two DDT definitions is parsed and + matched correctly.""" + # Setup + ddt_names = ['ddt1_t', 'ddt2_t'] + ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] + + module_files = [os.path.join(self._sample_files_dir, "ddt2.meta")] + # Exercise + hname = 'host_name_ddt2' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), len(ddt_names)) + # Verify header titles + for ddt_name in ddt_names: + self.assertTrue(ddt_name in module_headers) + # end for + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 0) + # Verify that each DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + for index, ddt_name in enumerate(ddt_names): + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), len(ddt_vars[index])) + std_names = [x.get_prop_value('standard_name') for x in vlist] + for sname in ddt_vars[index]: + self.assertTrue(sname in std_names) + # end for + # end for + + def test_module_with_two_ddts_and_data(self): + """Test that a module containing two DDT definitions and a block of + module data is parsed and matched correctly.""" + # Setup + ddt_names = ['ddt1_t', 'ddt2_t'] + ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] + + module_files = [os.path.join(self._sample_files_dir, + "ddt_data1_mod.meta")] + # Exercise + hname = 'host_name_ddt_data' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), len(ddt_names) + 1) + # Verify header titles + for ddt_name in ddt_names: + self.assertTrue(ddt_name in module_headers) + # end for + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 3) + # Verify that each DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + for index, ddt_name in enumerate(ddt_names): + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), len(ddt_vars[index])) + std_names = [x.get_prop_value('standard_name') for x in vlist] + for sname in ddt_vars[index]: + self.assertTrue(sname in std_names) + # end for + # end for + # Verify header titles + self.assertTrue('ddt_data1_mod' in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 3) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('play_station' in std_names) + self.assertTrue('xbox' in std_names) + self.assertTrue('nintendo_switch' in std_names) + + def test_module_with_one_ddt_plus_undoc(self): + """Test that a module containing a one documented DDT definition + (i.e., with metadata) and one DDT without (i.e., no metadata) + is parsed and matched correctly.""" + # Setup + ddt_name = 'ddt2_t' + module_files = [os.path.join(self._sample_files_dir, "ddt1_plus.meta")] + # Exercise + hname = 'host_name_ddt1_plus' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), 1) + # Verify header titles + self.assertTrue(ddt_name in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 0) + # Verify that the DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), 2) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('ddt_var_array_dimension' in std_names) + self.assertTrue('vars_array' in std_names) + + def test_module_with_two_ddts_and_extra_var(self): + """Test that a module containing two DDT definitions is parsed and + a useful error message is produced if the DDT metadata has an + extra variable.""" + # Setup + ddt_names = ['ddt1_t', 'ddt2_t'] + ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] + + module_files = [os.path.join(self._sample_files_dir, + "ddt2_extra_var.meta")] + # Exercise + hname = 'host_name_ddt_extra_var' + with self.assertRaises(CCPPError) as context: + host_model = parse_host_model_files(module_files, hname, + self._run_env) + # end with + # 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 + +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 52f62aa1..9598406d 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -40,7 +40,7 @@ Command line arguments: none - Usage: python test_metadata_scheme_file.py # run the unit tests + Usage: python3 test_metadata_scheme_file.py # run the unit tests ----------------------------------------------------------------------- """ import sys @@ -59,6 +59,7 @@ # pylint: disable=wrong-import-position from ccpp_capgen import parse_scheme_files from framework_env import CCPPFrameworkEnv +from parse_tools import CCPPError # pylint: enable=wrong-import-position class MetadataHeaderTestCase(unittest.TestCase): @@ -67,6 +68,7 @@ class MetadataHeaderTestCase(unittest.TestCase): def setUp(self): """Setup important directories and logging""" self._sample_files_dir = os.path.join(_TEST_DIR, "sample_scheme_files") + self._host_files_dir = os.path.join(_TEST_DIR, "sample_host_files") logger = logging.getLogger(self.__class__.__name__) self._run_env = CCPPFrameworkEnv(logger, ndict={'host_files':'', 'scheme_files':'', @@ -85,203 +87,262 @@ def setUp(self): 'CCPP=2'}) def test_good_scheme_file(self): - """Test that good metadata file matches the Fortran, with routines in the same order """ - #Setup + """Test that good metadata file matches the Fortran, + with routines in the same order """ + # Setup scheme_files = [os.path.join(self._sample_files_dir, "temp_adjust.meta")] - #Exercise + # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env) - #Verify size of returned list equals number of scheme headers in the test file - # and that header (subroutine) names are 'temp_adjust_[init,run,finalize]' + # Verify size of returned list equals number of scheme headers + # in the test file and that header (subroutine) names are + # 'temp_adjust_[init,run,finalize]' self.assertEqual(len(scheme_headers), 3) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] self.assertTrue('temp_adjust_init' in titles) self.assertTrue('temp_adjust_run' in titles) self.assertTrue('temp_adjust_finalize' in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) self.assertTrue('temp_adjust' in table_dict) def test_reordered_scheme_file(self): - """Test that metadata file matches the Fortran when the routines are not in the same order """ - #Setup + """Test that metadata file matches the Fortran when the + routines are not in the same order """ + # Setup scheme_files = [os.path.join(self._sample_files_dir, "reorder.meta")] - #Exercise + # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env) - #Verify size of returned list equals number of scheme headers in the test file - # and that header (subroutine) names are 'reorder_[init,run,finalize]' + # Verify size of returned list equals number of scheme headers + # in the test file and that header (subroutine) names are + # 'reorder_[init,run,finalize]' self.assertEqual(len(scheme_headers), 3) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] self.assertTrue('reorder_init' in titles) self.assertTrue('reorder_run' in titles) self.assertTrue('reorder_finalize' in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) self.assertTrue('reorder' in table_dict) def test_missing_metadata_header(self): - """Test that a missing metadata header (aka arg table) is corretly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "missing_arg_table.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that a missing metadata header (aka arg table) is + corretly detected """ + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "missing_arg_table.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned + # Verify correct error message returned emsg = "No matching metadata header found for missing_arg_table_run in" self.assertTrue(emsg in str(context.exception)) def test_missing_fortran_header(self): """Test that a missing fortran header is corretly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "missing_fort_header.meta")] - #Exercise - with self.assertRaises(Exception) as context: + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "missing_fort_header.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned + # Verify correct error message returned emsg = "No matching Fortran routine found for missing_fort_header_run in" self.assertTrue(emsg in str(context.exception)) def test_mismatch_intent(self): - """Test that differing intent, kind, rank, and type between metadata and fortran is corretly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "mismatch_intent.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that differing intent, kind, rank, and type between + metadata and fortran is corretly detected """ + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "mismatch_intent.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify 4 correct error messages returned - self.assertTrue('intent mismatch (in != inout) in mismatch_intent_run, at' in str(context.exception)) - self.assertTrue('kind mismatch (kind_fizz != kind_phys) in mismatch_intent_run, at' in str(context.exception)) - self.assertTrue('rank mismatch in mismatch_intent_run/potential_temperature (0 != 1), at' in str(context.exception)) - self.assertTrue('type mismatch (integer != real) in mismatch_intent_run, at' in str(context.exception)) - self.assertTrue('4 errors found comparing' in str(context.exception)) + # Verify 4 correct error messages returned + emsg = "intent mismatch (in != inout) in mismatch_intent_run, at" + self.assertTrue(emsg in str(context.exception)) + emsg = "kind mismatch (kind_fizz != kind_phys) in mismatch_intent_run, at" + self.assertTrue(emsg in str(context.exception)) + emsg = "rank mismatch in mismatch_intent_run/potential_temperature (0 != 1), at" + self.assertTrue(emsg in str(context.exception)) + emsg = "type mismatch (integer != real) in mismatch_intent_run, at" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("4 errors found comparing" in str(context.exception)) def test_invalid_subr_stmnt(self): - """Test that invalid Fortran subroutine statements are correctly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "invalid_subr_stmnt.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that invalid Fortran subroutine statements are correctly + detected """ + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "invalid_subr_stmnt.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned - self.assertTrue("Invalid dummy argument, 'errmsg', at" in str(context.exception)) + # Verify correct error message returned + self.assertTrue("Invalid dummy argument, 'errmsg', at" + in str(context.exception)) def test_invalid_dummy_arg(self): - """Test that invalid dummy argument statements are correctly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "invalid_dummy_arg.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that invalid dummy argument statements are correctly detected""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "invalid_dummy_arg.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned - self.assertTrue("Invalid dummy argument, 'woohoo', at" in str(context.exception)) + # Verify correct error message returned + emsg = "Invalid dummy argument, 'woohoo', at" + self.assertTrue(emsg in str(context.exception)) -# pylint: disable=invalid-name - def test_CCPPnotset_var_missing_in_meta(self): - """Test for correct detection of a variable that REMAINS in the subroutine argument list - (due to an undefined pre-processor directive: #ifndef CCPP), BUT IS NOT PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPnotset_var_missing_in_meta.meta")] - #Exercise - with self.assertRaises(Exception) as context: + def test_ccpp_notset_var_missing_in_meta(self): + """Test for correct detection of a variable that REMAINS in the + subroutine argument list + (due to an undefined pre-processor directive: #ifndef CCPP), + BUT IS NOT PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPnotset_var_missing_in_meta.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPnotset_var_missing_in_meta_run, variables missing from metadata header.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPnotset_var_missing_in_meta_run' in str(context.exception)) - self.assertTrue('Out of order argument, errflg in CCPPnotset_var_missing_in_meta_run' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + # Verify 3 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" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("3 errors found comparing" in str(context.exception)) - def test_CCPPeq1_var_missing_in_fort(self): - """Test for correct detection of a variable that IS REMOVED the subroutine argument list - (due to a pre-processor directive: #ifndef CCPP), but IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_missing_in_fort.meta")] - #Exercise - with self.assertRaises(Exception) as context: + def test_ccpp_eq1_var_missing_in_fort(self): + """Test for correct detection of a variable that IS REMOVED the + subroutine argument list + (due to a pre-processor directive: #ifndef CCPP), + but IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPeq1_var_missing_in_fort.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_fort_run, variables missing from Fortran scheme.' - in str(context.exception)) - self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_fort_run, no Fortran variable bar.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPeq1_var_missing_in_fort_run' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + # Verify 3 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)) - def test_CCPPeq1_var_in_fort_meta(self): - """Test positive case of a variable that IS PRESENT the subroutine argument list - (due to a pre-processor directive: #ifdef CCPP), and IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_in_fort_meta.meta")] - #Exercise + def test_ccpp_eq1_var_in_fort_meta(self): + """Test positive case of a variable that IS PRESENT the + subroutine argument list + (due to a pre-processor directive: #ifdef CCPP), + and IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPeq1_var_in_fort_meta.meta")] + # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify size of returned list equals number of scheme headers in the test file (1) - # and that header (subroutine) name is 'CCPPeq1_var_in_fort_meta_run' + # Verify size of returned list equals number of scheme headers in + # the test file (1) and that header (subroutine) name is + # 'CCPPeq1_var_in_fort_meta_run' self.assertEqual(len(scheme_headers), 1) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] - self.assertTrue('CCPPeq1_var_in_fort_meta_run' in titles) + self.assertTrue("CCPPeq1_var_in_fort_meta_run" in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) - self.assertTrue('CCPPeq1_var_in_fort_meta' in table_dict) + self.assertTrue("CCPPeq1_var_in_fort_meta" in table_dict) - def test_CCPPgt1_var_in_fort_meta(self): - """Test positive case of a variable that IS PRESENT the subroutine argument list - (due to a pre-processor directive: #if CCPP > 1), and IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPgt1_var_in_fort_meta.meta")] - #Exercise + def test_ccpp_gt1_var_in_fort_meta(self): + """Test positive case of a variable that IS PRESENT the + subroutine argument list + (due to a pre-processor directive: #if CCPP > 1), + and IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPgt1_var_in_fort_meta.meta")] + # Exercise # Set CCPP directive to > 1 scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp2) - #Verify size of returned list equals number of scheme headers in the test file (1) - # and that header (subroutine) name is 'CCPPgt1_var_in_fort_meta_init' + # Verify size of returned list equals number of scheme headers + # in the test file (1) and that header (subroutine) name is + # 'CCPPgt1_var_in_fort_meta_init' self.assertEqual(len(scheme_headers), 1) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] - self.assertTrue('CCPPgt1_var_in_fort_meta_init' in titles) + self.assertTrue("CCPPgt1_var_in_fort_meta_init" in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) - self.assertTrue('CCPPgt1_var_in_fort_meta' in table_dict) + self.assertTrue("CCPPgt1_var_in_fort_meta" in table_dict) - def test_CCPPgt1_var_in_fort_meta2(self): - """Test correct detection of a variable that IS NOT PRESENT the subroutine argument list - (due to a pre-processor directive: #if CCPP > 1), but IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPgt1_var_in_fort_meta.meta")] - #Exercise - with self.assertRaises(Exception) as context: - parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPgt1_var_in_fort_meta_init, variables missing from Fortran scheme.' - in str(context.exception)) - self.assertTrue('Variable mismatch in CCPPgt1_var_in_fort_meta_init, no Fortran variable bar.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPgt1_var_in_fort_meta_init' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + def test_ccpp_gt1_var_in_fort_meta2(self): + """Test correct detection of a variable that + IS NOT PRESENT the subroutine argument list + (due to a pre-processor directive: #if CCPP > 1), + but IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPgt1_var_in_fort_meta.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + # Verify 3 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)) - def test_CCPPeq1_var_missing_in_meta(self): - """Test correct detection of a variable that IS PRESENT the subroutine argument list - (due to a pre-processor directive: #ifdef CCPP), and IS NOT PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_missing_in_meta.meta")] - #Exercise - with self.assertRaises(Exception) as context: - parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_meta_finalize, variables missing from metadata header.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPeq1_var_missing_in_meta_finalize' in str(context.exception)) - self.assertTrue('Out of order argument, errflg in CCPPeq1_var_missing_in_meta_finalize' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + def test_ccpp_eq1_var_missing_in_meta(self): + """Test correct detection of a variable that + IS PRESENT the subroutine argument list + (due to a pre-processor directive: #ifdef CCPP), + and IS NOT PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPeq1_var_missing_in_meta.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + # Verify 3 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" + 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)) -# pylint: enable=invalid-name + def test_scheme_ddt_only(self): + """Test correct detection of a "scheme" file which contains only + DDT definitions""" + # Setup + scheme_files = [os.path.join(self._host_files_dir, "ddt2.meta")] + # Exercise + scheme_headers, table_dict = parse_scheme_files(scheme_files, + self._run_env_ccpp) +# with self.assertRaises(CCPPError) as context: +# _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) +# # Verify correct error messages returned -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index d9791879..544d3013 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -8,7 +8,7 @@ Command line arguments: none - Usage: python test_metadata_table.py # run the unit tests + Usage: python3 test_metadata_table.py # run the unit tests ----------------------------------------------------------------------- """ import sys From 1683be3406c5608e9a3e00b72bb8b2e2f1ac6807 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 28 Aug 2023 03:43:11 -0600 Subject: [PATCH 02/12] add latest changes --- .github/workflows/capgen_unit_tests.yaml | 4 +- .github/workflows/python.yaml | 45 +++++-- pytest.ini | 4 +- scripts/code_block.py | 27 ++-- scripts/fortran_tools/parse_fortran.py | 16 +-- scripts/metadata_table.py | 18 +-- scripts/metavar.py | 12 +- scripts/parse_tools/parse_object.py | 11 +- scripts/parse_tools/parse_source.py | 12 +- scripts/parse_tools/xml_tools.py | 18 +-- scripts/var_props.py | 135 ++++++++++++------- test/run_doctest.sh | 35 ----- test/{run_tests.sh => run_fortran_tests.sh} | 20 +-- test/unit_tests/test_fortran_write.py | 3 +- test/unit_tests/test_metadata_host_file.py | 1 + test/unit_tests/test_metadata_scheme_file.py | 1 + test/unit_tests/test_metadata_table.py | 3 +- test/unit_tests/test_var_transforms.py | 3 +- 18 files changed, 162 insertions(+), 206 deletions(-) delete mode 100755 test/run_doctest.sh rename test/{run_tests.sh => run_fortran_tests.sh} (73%) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index eb574f2d..4406ae77 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -6,7 +6,7 @@ on: types: [opened, synchronize, reopened] push: branches: - #Trigger workflow on push to any branch or branch heirarchy: + #Trigger workflow on push to any branch or branch hierarchy: - '**' jobs: @@ -18,5 +18,5 @@ jobs: - name: update repos and install dependencies run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git - name: Run unit tests - run: cd test && ./run_tests.sh + run: cd test && ./run_fortran_tests.sh diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index c630c438..3ab5347c 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -1,28 +1,55 @@ name: Python package -on: [push] +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + #Trigger workflow on push to any branch or branch hierarchy: + - '**' jobs: build: - + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install pytest - name: Test with pytest - if: github.repository == 'NCAR/ccpp-framework' # Only run on main repo run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools - pytest + pytest -v + + doctest: + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + - name: Doctest + run: | + export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools + pytest -v scripts/ --doctest-modules diff --git a/pytest.ini b/pytest.ini index 83323d6a..d08180f1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,2 @@ [pytest] -addopts = -ra -q --ignore=tests/test_capgen.py -testpaths = - tests \ No newline at end of file +addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py \ No newline at end of file diff --git a/scripts/code_block.py b/scripts/code_block.py index 96dc30e9..ccd3f209 100644 --- a/scripts/code_block.py +++ b/scripts/code_block.py @@ -13,7 +13,7 @@ class CodeBlock(object): """Class to store a block of code and a method to write it to a file >>> CodeBlock([]) #doctest: +ELLIPSIS - <__main__.CodeBlock object at 0x...> + >>> CodeBlock(['hi mom']) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseInternalError: Each element of must contain exactly two items, a code string and a relative indent @@ -24,7 +24,10 @@ class CodeBlock(object): Traceback (most recent call last): ParseInternalError: Each element of must contain exactly two items, a code string and a relative indent >>> CodeBlock([('hi mom', 1)]) #doctest: +ELLIPSIS - <__main__.CodeBlock object at 0x...> + + >>> from fortran_tools import FortranWriter + >>> outfile_name = "__code_block_temp.F90" + >>> outfile = FortranWriter(outfile_name, 'w', 'test file', 'test_mod') >>> CodeBlock([('hi mom', 1)]).write(outfile, 1, {}) >>> CodeBlock([('hi {greet} mom', 1)]).write(outfile, 1, {}) #doctest: +IGNORE_EXCEPTION_DETAIL @@ -32,6 +35,10 @@ class CodeBlock(object): ParseInternalError: 'greet' missing from >>> CodeBlock([('hi {{greet}} mom', 1)]).write(outfile, 1, {}) >>> CodeBlock([('{greet} there mom', 1)]).write(outfile, 1, {'greet':'hi'}) + >>> outfile.__exit__() + False + >>> import os + >>> os.remove(outfile_name) """ __var_re = re.compile(r"[{][ ]*([A-Za-z][A-Za-z0-9_]*)[ ]*[}]") @@ -110,19 +117,3 @@ def write(self, outfile, indent_level, var_dict): # end for ############################################################################### -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import os - import sys - from fortran_tools import FortranWriter - # pylint: enable=ungrouped-imports - outfile_name = "__code_block_temp.F90" - with FortranWriter(outfile_name, 'w', 'test file', 'test_mod') as outfile: - fail, _ = doctest.testmod() - # end with - if os.path.exists(outfile_name): - os.remove(outfile_name) - # end if - sys.exit(fail) -# end if diff --git a/scripts/fortran_tools/parse_fortran.py b/scripts/fortran_tools/parse_fortran.py index 624f02cb..817ae184 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -665,6 +665,10 @@ def parse_fortran_var_decl(line, source, run_env): '(8)' >>> _VAR_ID_RE.match("foo(::,a:b,a:,:b)").group(2) '(::,a:b,a:,:b)' + >>> from framework_env import CCPPFrameworkEnv + >>> _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}) >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') @@ -826,15 +830,3 @@ def parse_fortran_var_decl(line, source, run_env): ######################################################################## ######################################################################## - -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - # pylint: enable=ungrouped-imports - from framework_env import CCPPFrameworkEnv - _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', - 'scheme_files':'', - 'suites':''}) - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 17e8dfdb..c7f84534 100644 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -504,6 +504,10 @@ 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':'', \ + 'suites':''}) >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ parse_object=ParseObject("foobar.txt", \ ["name = footable", "type = scheme", "module = foo", \ @@ -511,7 +515,7 @@ class MetadataSection(ParseSource): "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in"])) #doctest: +ELLIPSIS - <__main__.MetadataSection foo / footable at 0x...> + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ parse_object=ParseObject("foobar.txt", \ ["name = footable", "type = scheme", "module = foobar", \ @@ -1267,15 +1271,3 @@ def is_scalar_reference(test_val): return check_fortran_ref(test_val, None, False) is not None ######################################################################## - -if __name__ == "__main__": -# pylint: enable=ungrouped-imports - import doctest - import sys -# pylint: disable=ungrouped-imports - from framework_env import CCPPFrameworkEnv - _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, {'host_files':'', - 'scheme_files':'', - 'suites':''}) - fail, _ = doctest.testmod() - sys.exit(fail) diff --git a/scripts/metavar.py b/scripts/metavar.py index 71609580..bf165587 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1379,11 +1379,11 @@ class VarDictionary(OrderedDict): >>> VarDictionary('bar', _MVAR_DUMMY_RUN_ENV, variables={}) VarDictionary(bar) >>> VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)) #doctest: +ELLIPSIS - VarDictionary(baz, [('hi_mom', <__main__.Var hi_mom: foo at 0x...>)]) + VarDictionary(baz, [('hi_mom', )]) >>> print("{}".format(VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)))) VarDictionary(baz, ['hi_mom']) >>> VarDictionary('qux', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]) #doctest: +ELLIPSIS - VarDictionary(qux, [('hi_mom', <__main__.Var hi_mom: foo at 0x...>)]) + VarDictionary(qux, [('hi_mom', )]) >>> VarDictionary('boo', _MVAR_DUMMY_RUN_ENV).add_variable(Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV), _MVAR_DUMMY_RUN_ENV) >>> VarDictionary('who', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]).prop_list('local_name') @@ -1982,11 +1982,3 @@ def new_internal_variable_name(self, prefix=None, max_len=63): _MVAR_DUMMY_RUN_ENV)]) ############################################################################### -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import sys - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/parse_tools/parse_object.py b/scripts/parse_tools/parse_object.py index 4518d75c..216eb5c5 100644 --- a/scripts/parse_tools/parse_object.py +++ b/scripts/parse_tools/parse_object.py @@ -10,7 +10,7 @@ class ParseObject(ParseContext): """ParseObject is a simple class that keeps track of an object's place in a file and safely produces lines from an array of lines >>> ParseObject('foobar.F90', []) #doctest: +ELLIPSIS - <__main__.ParseObject object at 0x...> + >>> ParseObject('foobar.F90', []).filename 'foobar.F90' >>> ParseObject('foobar.F90', ["##hi mom",], line_start=1).curr_line() @@ -170,12 +170,3 @@ def __del__(self): # end try ######################################################################## - -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import sys - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 6d28b694..30f05f7f 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -201,7 +201,7 @@ def __getitem__(self, index): class ParseContext(object): """A class for keeping track of a parsing position >>> ParseContext(32, "source.F90") #doctest: +ELLIPSIS - <__main__.ParseContext object at 0x...> + >>> ParseContext("source.F90", 32) Traceback (most recent call last): CCPPError: ParseContext linenum must be an int @@ -382,7 +382,7 @@ class ParseSource(object): """ A simple object for providing source information >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")) #doctest: +ELLIPSIS - <__main__.ParseSource object at 0x...> + >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).type 'mytype' >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).name @@ -413,11 +413,3 @@ def context(self): return self._context ######################################################################## - -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index 414ffc5a..120ab645 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -41,6 +41,8 @@ def call_command(commands, logger, silent=False): ############################################################################### """ Try a command line and return the output on success (None on failure) + >>> _LOGGER = init_log('xml_tools') + >>> set_log_to_null(_LOGGER) >>> call_command(['ls', 'really__improbable_fffilename.foo'], _LOGGER) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Execution of 'ls really__improbable_fffilename.foo' failed: @@ -350,19 +352,3 @@ def write(self, file, encoding="us-ascii", xml_declaration=None, # end with ############################################################################## - -if __name__ == "__main__": - _LOGGER = init_log('xml_tools') - set_log_to_null(_LOGGER) - try: - # First, run doctest - # pylint: disable=ungrouped-imports - import doctest - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) - except CCPPError as cerr: - print("{}".format(cerr)) - sys.exit(fail) - # end try -# end if diff --git a/scripts/var_props.py b/scripts/var_props.py index 69b6b766..15613567 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -198,7 +198,7 @@ def default_kind_val(prop_dict, context=None): >>> default_kind_val({'local_name':'foo'}) #doctest: +ELLIPSIS Traceback (most recent call last): ... - parse_source.CCPPError: No type to find default kind for foo + parse_source.CCPPError: No type to find default kind for foo >>> default_kind_val({}) #doctest: +ELLIPSIS Traceback (most recent call last): ... @@ -206,7 +206,7 @@ def default_kind_val(prop_dict, context=None): >>> default_kind_val({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): ... - parse_source.CCPPError: No type to find default kind for foo, at foo.F90:4 + parse_source.CCPPError: No type to find default kind for foo, at foo.F90:4 >>> default_kind_val({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): ... @@ -515,25 +515,25 @@ def __is_horizontal_loop_dimension(hdim): class VariableProperty: """Class to represent a single property of a metadata header entry >>> VariableProperty('local_name', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('standard_name', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('long_name', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('units', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('dimensions', list) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('type', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('kind', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('state_variable', str, valid_values_in=['True', 'False', '.true.', '.false.' ], optional_in=True, default_in=False) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('intent', str, valid_values_in=['in', 'out', 'inout']) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('optional', str, valid_values_in=['True', 'False', '.true.', '.false.' ], optional_in=True, default_in=False) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('local_name', str).name 'local_name' >>> VariableProperty('standard_name', str).ptype == str @@ -763,31 +763,41 @@ class VarCompatObj: character(len=) # Test that we can create a standard VarCompatObj object + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", [], \ "var1_lname", "var_stdname", "real", "kind_phys", \ "m", [], "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with no horizontal transform works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", \ ['horizontal_dimension'], "var1_lname", "var_stdname", \ "real", "kind_phys", "m", ['horizontal_dimension'], \ "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with a horizontal transform works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", \ ['horizontal_dimension'], "var1_lname", "var_stdname", \ "real", "kind_phys", "m", ['horizontal_loop_extent'], \ "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with unit conversion m->km works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", \ ['horizontal_dimension'], "var1_lname", "var_stdname", \ "real", "kind_phys", "km", ['horizontal_dimension'], \ "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with unit conversion m->km works and that it # produces the correct forward transformation @@ -1012,6 +1022,26 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): If a conversion is required, return a tuple with the two kinds, i.e., (var1_kind, var2_kind). + # Initial setup + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) + >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ + "m", [], "var1_lname", "var_stdname", \ + "real", "kind_phys", "m", [], \ + "var2_lname", _DOCTEST_RUNENV, \ + v1_context=_DOCTEST_CONTEXT1, \ + v2_context=_DOCTEST_CONTEXT2) + # Try some kind conversions >>> _DOCTEST_VCOMPAT._get_kind_convstrs('kind_phys', 'kind_dyn', \ _DOCTEST_RUNENV) @@ -1046,6 +1076,26 @@ def _get_unit_convstrs(self, var1_units, var2_units): for transforming a variable in to / from a variable in . + # Initial setup + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) + >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ + "m", [], "var1_lname", "var_stdname", \ + "real", "kind_phys", "m", [], \ + "var2_lname", _DOCTEST_RUNENV, \ + v1_context=_DOCTEST_CONTEXT1, \ + v2_context=_DOCTEST_CONTEXT2) + # Try some working unit transforms >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m', 'mm') ('1.0E+3{kind}*{var}', '1.0E-3{kind}*{var}') @@ -1098,20 +1148,40 @@ def _get_dim_transforms(self, var1_dims, var2_dims): The reverse dimension transformation is a permutation of the indices of the second variable to the first. + # Initial setup + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) + >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ + "m", [], "var1_lname", "var_stdname", \ + "real", "kind_phys", "m", [], \ + "var2_lname", _DOCTEST_RUNENV, \ + v1_context=_DOCTEST_CONTEXT1, \ + v2_context=_DOCTEST_CONTEXT2) + # Test simple permutations >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ 'vertical_layer_dimension'], \ ['vertical_layer_dimension', \ 'horizontal_dimension']) \ #doctest: +ELLIPSIS - <__main__.DimTransform object at 0x...> + >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ 'vertical_layer_dimension', \ 'xdim'], \ ['vertical_layer_dimension', \ 'horizontal_dimension', \ 'xdim']) #doctest: +ELLIPSIS - <__main__.DimTransform object at 0x...> + >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ 'vertical_layer_dimension', \ 'xdim'], \ @@ -1119,7 +1189,7 @@ def _get_dim_transforms(self, var1_dims, var2_dims): 'horizontal_dimension', \ 'vertical_layer_dimension']) \ #doctest: +ELLIPSIS - <__main__.DimTransform object at 0x...> + # Test some mismatch sets >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ @@ -1323,30 +1393,3 @@ def __bool__(self): return self.equiv ############################################################################### -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import sys - from parse_tools import init_log, set_log_to_null - # pylint: enable=ungrouped-imports - _DOCTEST_LOGGING = init_log('var_props') - set_log_to_null(_DOCTEST_LOGGING) - _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, - ndict={'host_files':'', - 'scheme_files':'', - 'suites':''}, - kind_types=["kind_phys=REAL64", - "kind_dyn=REAL32", - "kind_host=REAL64"]) - _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') - _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') - _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", - "m", [], "var1_lname", "var_stdname", - "real", "kind_phys", "m", [], - "var2_lname", _DOCTEST_RUNENV, - v1_context=_DOCTEST_CONTEXT1, - v2_context=_DOCTEST_CONTEXT2) - OPTIONS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE - fail, _ = doctest.testmod(optionflags=OPTIONS) - sys.exit(fail) -# end if diff --git a/test/run_doctest.sh b/test/run_doctest.sh deleted file mode 100755 index aeecb133..00000000 --- a/test/run_doctest.sh +++ /dev/null @@ -1,35 +0,0 @@ -#! /bin/bash - -root=$( dirname $( cd $( dirname ${0}); pwd -P ) ) -scripts=${root}/scripts - -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 ${scripts} -perr $? "Cannot cd to scripts directory, '${scripts}'" - -errcnt=0 - -export PYTHONPATH="${scripts}:${PYTHONPATH}" -# Find all python scripts that have doctest -for pyfile in $(find . -name \*.py); do - if [ -f "${pyfile}" ]; then - if [ $(grep -c doctest ${pyfile}) -ne 0 ]; then - python3 ${pyfile} - res=$? - errcnt=$((errcnt + res)) - fi - fi -done - -exit ${errcnt} diff --git a/test/run_tests.sh b/test/run_fortran_tests.sh similarity index 73% rename from test/run_tests.sh rename to test/run_fortran_tests.sh index 04ce5e8c..3b3512c2 100755 --- a/test/run_tests.sh +++ b/test/run_fortran_tests.sh @@ -47,27 +47,9 @@ fi # fi echo "Skipping var_action_test/run_test until feature is fully implemented" -# Run doctests -./run_doctest.sh -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "${errcnt} doctest failures" -fi - -for test in `ls unit_tests/test_*.py`; do - echo "Running unit test, ${test}" - python3 ${test} - res=$? - errcnt=$((errcnt + res)) - if [ $res -ne 0 ]; then - echo "Failure, '${res}', running unit test, ${test}" - fi -done - if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else echo "${errcnt} tests FAILed" - return 1 + exit 1 fi diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py index fdc00085..87e64baa 100644 --- a/test/unit_tests/test_fortran_write.py +++ b/test/unit_tests/test_fortran_write.py @@ -122,5 +122,6 @@ def test_good_comments(self): amsg = f"{generate} does not match {compare}" self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() + diff --git a/test/unit_tests/test_metadata_host_file.py b/test/unit_tests/test_metadata_host_file.py index cc5ba7b5..d01fda97 100644 --- a/test/unit_tests/test_metadata_host_file.py +++ b/test/unit_tests/test_metadata_host_file.py @@ -258,3 +258,4 @@ def test_module_with_two_ddts_and_extra_var(self): 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 9598406d..6651c2de 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -346,3 +346,4 @@ def test_scheme_ddt_only(self): if __name__ == "__main__": unittest.main() + diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index 544d3013..3a01f98b 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -394,5 +394,6 @@ def test_invalid_table_properties_type(self): emsg = "Invalid metadata table type, 'banana', at " self.assertTrue(emsg in str(context.exception)) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() + diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index 70f31d1e..12cffcdb 100644 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -432,5 +432,6 @@ def test_valid_dim_transforms(self): expected = f"{v5_lname}({lind_str}) = {v4_lname}({rind_str})" self.assertEqual(rev_stmt, expected) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() + From b0851398428635d9de1d7beaf28df3e5f3bdfee4 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 10:09:55 -0600 Subject: [PATCH 03/12] fix grammar --- test/run_fortran_tests.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/run_fortran_tests.sh b/test/run_fortran_tests.sh index 3b3512c2..8b0f5bcb 100755 --- a/test/run_fortran_tests.sh +++ b/test/run_fortran_tests.sh @@ -50,6 +50,11 @@ echo "Skipping var_action_test/run_test until feature is fully implemented" if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else - echo "${errcnt} tests FAILed" + if [ $errcnt -eq 1 ]; then + echo "${errcnt} test FAILed" + else + echo "${errcnt} tests FAILed" + fi + #Exit with non-zero exit code exit 1 fi From a30888726e1f8638b0d4e01c8185e6a49e69ef8b Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 10:39:14 -0600 Subject: [PATCH 04/12] remove tests that should be in constituents branch --- test/unit_tests/test_fortran_write.py | 127 -------- test/unit_tests/test_metadata_host_file.py | 261 --------------- test/unit_tests/test_metadata_scheme_file.py | 326 ++++++++----------- 3 files changed, 132 insertions(+), 582 deletions(-) delete mode 100644 test/unit_tests/test_fortran_write.py delete mode 100644 test/unit_tests/test_metadata_host_file.py diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py deleted file mode 100644 index 87e64baa..00000000 --- a/test/unit_tests/test_fortran_write.py +++ /dev/null @@ -1,127 +0,0 @@ -#! /usr/bin/env python3 -""" ------------------------------------------------------------------------ - Description: Contains unit tests for FortranWriter - in scripts file fortran/fortran_write.py - - Assumptions: - - Command line arguments: none - - Usage: python3 test_fortran_write.py # run the unit tests ------------------------------------------------------------------------ -""" - -import filecmp -import glob -import os -import sys -import unittest - -_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -_SCRIPTS_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, - os.pardir, "scripts")) -_SAMPLE_FILES_DIR = os.path.join(_TEST_DIR, "sample_files", "fortran_files") -_PRE_TMP_DIR = os.path.join(_TEST_DIR, "tmp") -_TMP_DIR = os.path.join(_PRE_TMP_DIR, "fortran_files") - -if not os.path.exists(_SCRIPTS_DIR): - raise ImportError(f"Cannot find scripts directory, {_SCRIPTS_DIR}") - -sys.path.append(_SCRIPTS_DIR) - -# pylint: disable=wrong-import-position -from fortran_tools import FortranWriter -# pylint: enable=wrong-import-position - -############################################################################### -def remove_files(file_list): -############################################################################### - """Remove files in if they exist""" - if isinstance(file_list, str): - file_list = [file_list] - # end if - for fpath in file_list: - if os.path.exists(fpath): - os.remove(fpath) - # End if - # End for - -class MetadataTableTestCase(unittest.TestCase): - - """Tests for `FortranWriter`.""" - - @classmethod - def setUpClass(cls): - """Clean output directory (tmp) before running tests""" - #Does "tmp" directory exist? If not then create it: - if not os.path.exists(_PRE_TMP_DIR): - os.mkdir(_PRE_TMP_DIR) - # Ensure the "sample_files/fortran_files" directory exists and is empty - if os.path.exists(_TMP_DIR): - # Clear out all files: - remove_files(glob.iglob(os.path.join(_TMP_DIR, '*.*'))) - else: - os.makedirs(_TMP_DIR) - # end if - - #Run inherited setup method: - super().setUpClass() - - def test_line_breaking(self): - """Test that FortranWriter correctly breaks long lines""" - # Setup - testname = "linebreak_test" - compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") - generate = os.path.join(_TMP_DIR, f"{testname}.F90") - # Exercise - header = "Test of line breaking for FortranWriter" - with FortranWriter(generate, 'w', header, f"{testname}") as gen: - # Test long declaration - data_items = ', '.join([f"name{x:03}" for x in range(100)]) - gen.write(f"character(len=7) :: data = (/ {data_items} /)", 1) - gen.end_module_header() - # Test long code lines - line_items = ["call endrun('Cannot read columns_on_task from ", - "file'//', columns_on_task has no horizontal ", - "dimension; columns_on_task is a ", - "protected variable')"] - gen.write(f"{''.join(line_items)}", 2) - # 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) - - def test_good_comments(self): - """Test that comments are written and broken correctly.""" - # Setup - testname = "comments_test" - compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") - generate = os.path.join(_TMP_DIR, f"{testname}.F90") - # Exercise - header = "Test of comment writing for FortranWriter" - with FortranWriter(generate, 'w', header, f"{testname}") as gen: - gen.comment("We can write comments in the module header", 0) - gen.comment("We can write indented comments in the header", 1) - gen.write("integer :: foo ! Comment at end of line works", 1) - # Test long comments at end of line - gen.write(f"integer :: bar ! {'x'*100}", 1) - gen.write(f"integer :: baz ! {'y'*130}", 1) - gen.end_module_header() - # Test comment line in body - gen.comment("We can write comments in the module body", 1) - - # end with - - # Check that file was generated - amsg = f"{generate} does not exist" - self.assertTrue(os.path.exists(generate), msg=amsg) - amsg = f"{generate} does not match {compare}" - self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) - -if __name__ == "__main__": - unittest.main() - diff --git a/test/unit_tests/test_metadata_host_file.py b/test/unit_tests/test_metadata_host_file.py deleted file mode 100644 index d01fda97..00000000 --- a/test/unit_tests/test_metadata_host_file.py +++ /dev/null @@ -1,261 +0,0 @@ -#! /usr/bin/env python3 - -""" ------------------------------------------------------------------------ - Description: capgen needs to compare a metadata header against the - associated CCPP Fortran interface routine. This set of - tests is testing the parse_host_model_files function in - ccpp_capgen.py which performs the operations in the first - bullet below. Each test calls this function. - - * This script contains unit tests that do the following: - 1) Read one or more metadata files (to collect - the metadata headers) - 2) Read the associated CCPP Fortran host file(s) (to - collect Fortran interfaces) - 3) Create a CCPP host model object - 3) Test the properties of the CCPP host model object - - * Tests include: - - Correctly parse and match a simple module file with - data fields (a data block) - - Correctly parse and match a simple module file with - a DDT definition - - Correctly parse and match a simple module file with - two DDT definitions - - Correctly parse and match a simple module file with - two DDT definitions and a data block - - Assumptions: - - Command line arguments: none - - Usage: python3 test_metadata_host_file.py # run the unit tests ------------------------------------------------------------------------ -""" -import sys -import os -import logging -import unittest - -_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -_SCRIPTS_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, - os.pardir, "scripts")) -if not os.path.exists(_SCRIPTS_DIR): - raise ImportError("Cannot find scripts directory") - -sys.path.append(_SCRIPTS_DIR) - -# pylint: disable=wrong-import-position -from ccpp_capgen import parse_host_model_files -from framework_env import CCPPFrameworkEnv -from parse_tools import CCPPError -# pylint: enable=wrong-import-position - -class MetadataHeaderTestCase(unittest.TestCase): - """Unit tests for parse_host_model_files""" - - def setUp(self): - """Setup important directories and logging""" - self._sample_files_dir = os.path.join(_TEST_DIR, "sample_host_files") - logger = logging.getLogger(self.__class__.__name__) - self._run_env = CCPPFrameworkEnv(logger, ndict={'host_files':'', - 'scheme_files':'', - 'suites':''}) - - def test_module_with_data(self): - """Test that a module containing a data block is parsed and matched - correctly.""" - # Setup - module_files = [os.path.join(self._sample_files_dir, "data1_mod.meta")] - # Exercise - hname = 'host_name_data1' - host_model = parse_host_model_files(module_files, hname, self._run_env) - # Verify the name of the host model - self.assertEqual(host_model.name, hname) - module_headers = host_model.metadata_tables() - self.assertEqual(len(module_headers), 1) - # Verify header titles - self.assertTrue('data1_mod' in module_headers) - # Verify host model variable list - vlist = host_model.variable_list() - self.assertEqual(len(vlist), 3) - std_names = [x.get_prop_value('standard_name') for x in vlist] - self.assertTrue('play_station' in std_names) - self.assertTrue('xbox' in std_names) - self.assertTrue('nintendo_switch' in std_names) - - def test_module_with_one_ddt(self): - """Test that a module containing a DDT definition is parsed and matched - correctly.""" - # Setup - ddt_name = 'ddt1_t' - module_files = [os.path.join(self._sample_files_dir, "ddt1.meta")] - # Exercise - hname = 'host_name_ddt1' - host_model = parse_host_model_files(module_files, hname, self._run_env) - # Verify the name of the host model - self.assertEqual(host_model.name, hname) - module_headers = host_model.metadata_tables() - self.assertEqual(len(module_headers), 1) - # Verify header titles - self.assertTrue(ddt_name in module_headers) - # Verify host model variable list - vlist = host_model.variable_list() - self.assertEqual(len(vlist), 0) - # Verify that the DDT was found and parsed - ddt_lib = host_model.ddt_lib - self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") - # Check DDT variables - ddt_mod = ddt_lib[ddt_name] - self.assertEqual(ddt_mod.name, ddt_name) - vlist = ddt_mod.variable_list() - self.assertEqual(len(vlist), 2) - std_names = [x.get_prop_value('standard_name') for x in vlist] - self.assertTrue('ddt_var_array_dimension' in std_names) - self.assertTrue('vars_array' in std_names) - - def test_module_with_two_ddts(self): - """Test that a module containing two DDT definitions is parsed and - matched correctly.""" - # Setup - ddt_names = ['ddt1_t', 'ddt2_t'] - ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] - - module_files = [os.path.join(self._sample_files_dir, "ddt2.meta")] - # Exercise - hname = 'host_name_ddt2' - host_model = parse_host_model_files(module_files, hname, self._run_env) - # Verify the name of the host model - self.assertEqual(host_model.name, hname) - module_headers = host_model.metadata_tables() - self.assertEqual(len(module_headers), len(ddt_names)) - # Verify header titles - for ddt_name in ddt_names: - self.assertTrue(ddt_name in module_headers) - # end for - # Verify host model variable list - vlist = host_model.variable_list() - self.assertEqual(len(vlist), 0) - # Verify that each DDT was found and parsed - ddt_lib = host_model.ddt_lib - self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") - # Check DDT variables - for index, ddt_name in enumerate(ddt_names): - ddt_mod = ddt_lib[ddt_name] - self.assertEqual(ddt_mod.name, ddt_name) - vlist = ddt_mod.variable_list() - self.assertEqual(len(vlist), len(ddt_vars[index])) - std_names = [x.get_prop_value('standard_name') for x in vlist] - for sname in ddt_vars[index]: - self.assertTrue(sname in std_names) - # end for - # end for - - def test_module_with_two_ddts_and_data(self): - """Test that a module containing two DDT definitions and a block of - module data is parsed and matched correctly.""" - # Setup - ddt_names = ['ddt1_t', 'ddt2_t'] - ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] - - module_files = [os.path.join(self._sample_files_dir, - "ddt_data1_mod.meta")] - # Exercise - hname = 'host_name_ddt_data' - host_model = parse_host_model_files(module_files, hname, self._run_env) - # Verify the name of the host model - self.assertEqual(host_model.name, hname) - module_headers = host_model.metadata_tables() - self.assertEqual(len(module_headers), len(ddt_names) + 1) - # Verify header titles - for ddt_name in ddt_names: - self.assertTrue(ddt_name in module_headers) - # end for - # Verify host model variable list - vlist = host_model.variable_list() - self.assertEqual(len(vlist), 3) - # Verify that each DDT was found and parsed - ddt_lib = host_model.ddt_lib - self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") - # Check DDT variables - for index, ddt_name in enumerate(ddt_names): - ddt_mod = ddt_lib[ddt_name] - self.assertEqual(ddt_mod.name, ddt_name) - vlist = ddt_mod.variable_list() - self.assertEqual(len(vlist), len(ddt_vars[index])) - std_names = [x.get_prop_value('standard_name') for x in vlist] - for sname in ddt_vars[index]: - self.assertTrue(sname in std_names) - # end for - # end for - # Verify header titles - self.assertTrue('ddt_data1_mod' in module_headers) - # Verify host model variable list - vlist = host_model.variable_list() - self.assertEqual(len(vlist), 3) - std_names = [x.get_prop_value('standard_name') for x in vlist] - self.assertTrue('play_station' in std_names) - self.assertTrue('xbox' in std_names) - self.assertTrue('nintendo_switch' in std_names) - - def test_module_with_one_ddt_plus_undoc(self): - """Test that a module containing a one documented DDT definition - (i.e., with metadata) and one DDT without (i.e., no metadata) - is parsed and matched correctly.""" - # Setup - ddt_name = 'ddt2_t' - module_files = [os.path.join(self._sample_files_dir, "ddt1_plus.meta")] - # Exercise - hname = 'host_name_ddt1_plus' - host_model = parse_host_model_files(module_files, hname, self._run_env) - # Verify the name of the host model - self.assertEqual(host_model.name, hname) - module_headers = host_model.metadata_tables() - self.assertEqual(len(module_headers), 1) - # Verify header titles - self.assertTrue(ddt_name in module_headers) - # Verify host model variable list - vlist = host_model.variable_list() - self.assertEqual(len(vlist), 0) - # Verify that the DDT was found and parsed - ddt_lib = host_model.ddt_lib - self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") - # Check DDT variables - ddt_mod = ddt_lib[ddt_name] - self.assertEqual(ddt_mod.name, ddt_name) - vlist = ddt_mod.variable_list() - self.assertEqual(len(vlist), 2) - std_names = [x.get_prop_value('standard_name') for x in vlist] - self.assertTrue('ddt_var_array_dimension' in std_names) - self.assertTrue('vars_array' in std_names) - - def test_module_with_two_ddts_and_extra_var(self): - """Test that a module containing two DDT definitions is parsed and - a useful error message is produced if the DDT metadata has an - extra variable.""" - # Setup - ddt_names = ['ddt1_t', 'ddt2_t'] - ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] - - module_files = [os.path.join(self._sample_files_dir, - "ddt2_extra_var.meta")] - # Exercise - hname = 'host_name_ddt_extra_var' - with self.assertRaises(CCPPError) as context: - host_model = parse_host_model_files(module_files, hname, - self._run_env) - # end with - # 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 - -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 6651c2de..28b2ee50 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -59,7 +59,6 @@ # pylint: disable=wrong-import-position from ccpp_capgen import parse_scheme_files from framework_env import CCPPFrameworkEnv -from parse_tools import CCPPError # pylint: enable=wrong-import-position class MetadataHeaderTestCase(unittest.TestCase): @@ -68,7 +67,6 @@ class MetadataHeaderTestCase(unittest.TestCase): def setUp(self): """Setup important directories and logging""" self._sample_files_dir = os.path.join(_TEST_DIR, "sample_scheme_files") - self._host_files_dir = os.path.join(_TEST_DIR, "sample_host_files") logger = logging.getLogger(self.__class__.__name__) self._run_env = CCPPFrameworkEnv(logger, ndict={'host_files':'', 'scheme_files':'', @@ -87,263 +85,203 @@ def setUp(self): 'CCPP=2'}) def test_good_scheme_file(self): - """Test that good metadata file matches the Fortran, - with routines in the same order """ - # Setup + """Test that good metadata file matches the Fortran, with routines in the same order """ + #Setup scheme_files = [os.path.join(self._sample_files_dir, "temp_adjust.meta")] - # Exercise + #Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env) - # Verify size of returned list equals number of scheme headers - # in the test file and that header (subroutine) names are - # 'temp_adjust_[init,run,finalize]' + #Verify size of returned list equals number of scheme headers in the test file + # and that header (subroutine) names are 'temp_adjust_[init,run,finalize]' self.assertEqual(len(scheme_headers), 3) - # Verify header titles + #Verify header titles titles = [elem.title for elem in scheme_headers] self.assertTrue('temp_adjust_init' in titles) self.assertTrue('temp_adjust_run' in titles) self.assertTrue('temp_adjust_finalize' in titles) - # Verify size and name of table_dict matches scheme name + #Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) self.assertTrue('temp_adjust' in table_dict) def test_reordered_scheme_file(self): - """Test that metadata file matches the Fortran when the - routines are not in the same order """ - # Setup + """Test that metadata file matches the Fortran when the routines are not in the same order """ + #Setup scheme_files = [os.path.join(self._sample_files_dir, "reorder.meta")] - # Exercise + #Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env) - # Verify size of returned list equals number of scheme headers - # in the test file and that header (subroutine) names are - # 'reorder_[init,run,finalize]' + #Verify size of returned list equals number of scheme headers in the test file + # and that header (subroutine) names are 'reorder_[init,run,finalize]' self.assertEqual(len(scheme_headers), 3) - # Verify header titles + #Verify header titles titles = [elem.title for elem in scheme_headers] self.assertTrue('reorder_init' in titles) self.assertTrue('reorder_run' in titles) self.assertTrue('reorder_finalize' in titles) - # Verify size and name of table_dict matches scheme name + #Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) self.assertTrue('reorder' in table_dict) def test_missing_metadata_header(self): - """Test that a missing metadata header (aka arg table) is - corretly detected """ - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "missing_arg_table.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: + """Test that a missing metadata header (aka arg table) is corretly detected """ + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "missing_arg_table.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify correct error message returned + #Verify correct error message returned emsg = "No matching metadata header found for missing_arg_table_run in" self.assertTrue(emsg in str(context.exception)) def test_missing_fortran_header(self): """Test that a missing fortran header is corretly detected """ - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "missing_fort_header.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "missing_fort_header.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify correct error message returned + #Verify correct error message returned emsg = "No matching Fortran routine found for missing_fort_header_run in" self.assertTrue(emsg in str(context.exception)) def test_mismatch_intent(self): - """Test that differing intent, kind, rank, and type between - metadata and fortran is corretly detected """ - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "mismatch_intent.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: + """Test that differing intent, kind, rank, and type between metadata and fortran is corretly detected """ + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "mismatch_intent.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify 4 correct error messages returned - emsg = "intent mismatch (in != inout) in mismatch_intent_run, at" - self.assertTrue(emsg in str(context.exception)) - emsg = "kind mismatch (kind_fizz != kind_phys) in mismatch_intent_run, at" - self.assertTrue(emsg in str(context.exception)) - emsg = "rank mismatch in mismatch_intent_run/potential_temperature (0 != 1), at" - self.assertTrue(emsg in str(context.exception)) - emsg = "type mismatch (integer != real) in mismatch_intent_run, at" - self.assertTrue(emsg in str(context.exception)) - self.assertTrue("4 errors found comparing" in str(context.exception)) + #Verify 4 correct error messages returned + self.assertTrue('intent mismatch (in != inout) in mismatch_intent_run, at' in str(context.exception)) + self.assertTrue('kind mismatch (kind_fizz != kind_phys) in mismatch_intent_run, at' in str(context.exception)) + self.assertTrue('rank mismatch in mismatch_intent_run/potential_temperature (0 != 1), at' in str(context.exception)) + self.assertTrue('type mismatch (integer != real) in mismatch_intent_run, at' in str(context.exception)) + self.assertTrue('4 errors found comparing' in str(context.exception)) def test_invalid_subr_stmnt(self): - """Test that invalid Fortran subroutine statements are correctly - detected """ - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "invalid_subr_stmnt.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: + """Test that invalid Fortran subroutine statements are correctly detected """ + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "invalid_subr_stmnt.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify correct error message returned - self.assertTrue("Invalid dummy argument, 'errmsg', at" - in str(context.exception)) + #Verify correct error message returned + self.assertTrue("Invalid dummy argument, 'errmsg', at" in str(context.exception)) def test_invalid_dummy_arg(self): - """Test that invalid dummy argument statements are correctly detected""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "invalid_dummy_arg.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: + """Test that invalid dummy argument statements are correctly detected """ + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "invalid_dummy_arg.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify correct error message returned - emsg = "Invalid dummy argument, 'woohoo', at" - self.assertTrue(emsg in str(context.exception)) + #Verify correct error message returned + self.assertTrue("Invalid dummy argument, 'woohoo', at" in str(context.exception)) - def test_ccpp_notset_var_missing_in_meta(self): - """Test for correct detection of a variable that REMAINS in the - subroutine argument list - (due to an undefined pre-processor directive: #ifndef CCPP), - BUT IS NOT PRESENT in meta file""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "CCPPnotset_var_missing_in_meta.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: +# pylint: disable=invalid-name + def test_CCPPnotset_var_missing_in_meta(self): + """Test for correct detection of a variable that REMAINS in the subroutine argument list + (due to an undefined pre-processor directive: #ifndef CCPP), BUT IS NOT PRESENT in meta file""" + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "CCPPnotset_var_missing_in_meta.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env) - # Verify 3 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" - self.assertTrue(emsg in str(context.exception)) - self.assertTrue("3 errors found comparing" in str(context.exception)) + #Verify 3 correct error messages returned + self.assertTrue('Variable mismatch in CCPPnotset_var_missing_in_meta_run, variables missing from metadata header.' + in str(context.exception)) + self.assertTrue('Out of order argument, errmsg in CCPPnotset_var_missing_in_meta_run' in str(context.exception)) + self.assertTrue('Out of order argument, errflg in CCPPnotset_var_missing_in_meta_run' in str(context.exception)) + self.assertTrue('3 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 - subroutine argument list - (due to a pre-processor directive: #ifndef CCPP), - but IS PRESENT in meta file""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "CCPPeq1_var_missing_in_fort.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: + def test_CCPPeq1_var_missing_in_fort(self): + """Test for correct detection of a variable that IS REMOVED the subroutine argument list + (due to a pre-processor directive: #ifndef CCPP), but IS PRESENT in meta file""" + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_missing_in_fort.meta")] + #Exercise + with self.assertRaises(Exception) as context: parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify 3 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)) + #Verify 3 correct error messages returned + self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_fort_run, variables missing from Fortran scheme.' + in str(context.exception)) + self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_fort_run, no Fortran variable bar.' + in str(context.exception)) + self.assertTrue('Out of order argument, errmsg in CCPPeq1_var_missing_in_fort_run' in str(context.exception)) + self.assertTrue('3 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 - subroutine argument list - (due to a pre-processor directive: #ifdef CCPP), - and IS PRESENT in meta file""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "CCPPeq1_var_in_fort_meta.meta")] - # Exercise + def test_CCPPeq1_var_in_fort_meta(self): + """Test positive case of a variable that IS PRESENT the subroutine argument list + (due to a pre-processor directive: #ifdef CCPP), and IS PRESENT in meta file""" + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_in_fort_meta.meta")] + #Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify size of returned list equals number of scheme headers in - # the test file (1) and that header (subroutine) name is - # 'CCPPeq1_var_in_fort_meta_run' + #Verify size of returned list equals number of scheme headers in the test file (1) + # and that header (subroutine) name is 'CCPPeq1_var_in_fort_meta_run' self.assertEqual(len(scheme_headers), 1) - # Verify header titles + #Verify header titles titles = [elem.title for elem in scheme_headers] - self.assertTrue("CCPPeq1_var_in_fort_meta_run" in titles) + self.assertTrue('CCPPeq1_var_in_fort_meta_run' in titles) - # Verify size and name of table_dict matches scheme name + #Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) - self.assertTrue("CCPPeq1_var_in_fort_meta" in table_dict) + self.assertTrue('CCPPeq1_var_in_fort_meta' in table_dict) - def test_ccpp_gt1_var_in_fort_meta(self): - """Test positive case of a variable that IS PRESENT the - subroutine argument list - (due to a pre-processor directive: #if CCPP > 1), - and IS PRESENT in meta file""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "CCPPgt1_var_in_fort_meta.meta")] - # Exercise + def test_CCPPgt1_var_in_fort_meta(self): + """Test positive case of a variable that IS PRESENT the subroutine argument list + (due to a pre-processor directive: #if CCPP > 1), and IS PRESENT in meta file""" + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "CCPPgt1_var_in_fort_meta.meta")] + #Exercise # Set CCPP directive to > 1 scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp2) - # Verify size of returned list equals number of scheme headers - # in the test file (1) and that header (subroutine) name is - # 'CCPPgt1_var_in_fort_meta_init' + #Verify size of returned list equals number of scheme headers in the test file (1) + # and that header (subroutine) name is 'CCPPgt1_var_in_fort_meta_init' self.assertEqual(len(scheme_headers), 1) - # Verify header titles + #Verify header titles titles = [elem.title for elem in scheme_headers] - self.assertTrue("CCPPgt1_var_in_fort_meta_init" in titles) + self.assertTrue('CCPPgt1_var_in_fort_meta_init' in titles) - # Verify size and name of table_dict matches scheme name + #Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) - self.assertTrue("CCPPgt1_var_in_fort_meta" in table_dict) + self.assertTrue('CCPPgt1_var_in_fort_meta' in table_dict) - def test_ccpp_gt1_var_in_fort_meta2(self): - """Test correct detection of a variable that - IS NOT PRESENT the subroutine argument list - (due to a pre-processor directive: #if CCPP > 1), - but IS PRESENT in meta file""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "CCPPgt1_var_in_fort_meta.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: - _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify 3 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)) + def test_CCPPgt1_var_in_fort_meta2(self): + """Test correct detection of a variable that IS NOT PRESENT the subroutine argument list + (due to a pre-processor directive: #if CCPP > 1), but IS PRESENT in meta file""" + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "CCPPgt1_var_in_fort_meta.meta")] + #Exercise + with self.assertRaises(Exception) as context: + parse_scheme_files(scheme_files, self._run_env_ccpp) + #Verify 3 correct error messages returned + self.assertTrue('Variable mismatch in CCPPgt1_var_in_fort_meta_init, variables missing from Fortran scheme.' + in str(context.exception)) + self.assertTrue('Variable mismatch in CCPPgt1_var_in_fort_meta_init, no Fortran variable bar.' + in str(context.exception)) + self.assertTrue('Out of order argument, errmsg in CCPPgt1_var_in_fort_meta_init' in str(context.exception)) + self.assertTrue('3 errors found comparing' in str(context.exception)) - def test_ccpp_eq1_var_missing_in_meta(self): - """Test correct detection of a variable that - IS PRESENT the subroutine argument list - (due to a pre-processor directive: #ifdef CCPP), - and IS NOT PRESENT in meta file""" - # Setup - scheme_files = [os.path.join(self._sample_files_dir, - "CCPPeq1_var_missing_in_meta.meta")] - # Exercise - with self.assertRaises(CCPPError) as context: - _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - # Verify 3 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" - 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)) + def test_CCPPeq1_var_missing_in_meta(self): + """Test correct detection of a variable that IS PRESENT the subroutine argument list + (due to a pre-processor directive: #ifdef CCPP), and IS NOT PRESENT in meta file""" + #Setup + scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_missing_in_meta.meta")] + #Exercise + with self.assertRaises(Exception) as context: + parse_scheme_files(scheme_files, self._run_env_ccpp) + #Verify 3 correct error messages returned + self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_meta_finalize, variables missing from metadata header.' + in str(context.exception)) + self.assertTrue('Out of order argument, errmsg in CCPPeq1_var_missing_in_meta_finalize' in str(context.exception)) + self.assertTrue('Out of order argument, errflg in CCPPeq1_var_missing_in_meta_finalize' in str(context.exception)) + self.assertTrue('3 errors found comparing' in str(context.exception)) - def test_scheme_ddt_only(self): - """Test correct detection of a "scheme" file which contains only - DDT definitions""" - # Setup - scheme_files = [os.path.join(self._host_files_dir, "ddt2.meta")] - # Exercise - scheme_headers, table_dict = parse_scheme_files(scheme_files, - self._run_env_ccpp) -# with self.assertRaises(CCPPError) as context: -# _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) -# # Verify correct error messages returned +# pylint: enable=invalid-name if __name__ == "__main__": unittest.main() - From a1aaf9ec64cb789194dd83f10b4651e528d11337 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 10:43:07 -0600 Subject: [PATCH 05/12] remove testing files --- .../fortran_files/comments_test.F90 | 33 ---------------- .../fortran_files/linebreak_test.F90 | 39 ------------------- .../sample_host_files/data1_mod.F90 | 11 ------ .../sample_host_files/data1_mod.meta | 25 ------------ 4 files changed, 108 deletions(-) delete mode 100644 test/unit_tests/sample_files/fortran_files/comments_test.F90 delete mode 100644 test/unit_tests/sample_files/fortran_files/linebreak_test.F90 delete mode 100644 test/unit_tests/sample_host_files/data1_mod.F90 delete mode 100644 test/unit_tests/sample_host_files/data1_mod.meta diff --git a/test/unit_tests/sample_files/fortran_files/comments_test.F90 b/test/unit_tests/sample_files/fortran_files/comments_test.F90 deleted file mode 100644 index d4820a36..00000000 --- a/test/unit_tests/sample_files/fortran_files/comments_test.F90 +++ /dev/null @@ -1,33 +0,0 @@ -! -! 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 comment writing for FortranWriter -!! -! -module comments_test - -! We can write comments in the module header - ! We can write indented comments in the header - integer :: foo ! Comment at end of line works - integer :: bar ! - ! xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - ! - integer :: baz ! - ! yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy - ! yyyyy - -CONTAINS - ! We can write comments in the module body - -end module comments_test diff --git a/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 b/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 deleted file mode 100644 index 4f89441f..00000000 --- a/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 +++ /dev/null @@ -1,39 +0,0 @@ -! -! 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 line breaking for FortranWriter -!! -! -module linebreak_test - - character(len=7) :: data = (/ name000, name001, name002, name003, name004, name005, name006, & - name007, name008, name009, name010, name011, name012, name013, name014, name015, & - name016, name017, name018, name019, name020, name021, name022, name023, name024, & - name025, name026, name027, name028, name029, name030, name031, name032, name033, & - name034, name035, name036, name037, name038, name039, name040, name041, name042, & - name043, name044, name045, name046, name047, name048, name049, name050, name051, & - name052, name053, name054, name055, name056, name057, name058, name059, name060, & - name061, name062, name063, name064, name065, name066, name067, name068, name069, & - name070, name071, name072, name073, name074, name075, name076, name077, name078, & - name079, name080, name081, name082, name083, name084, name085, name086, name087, & - name088, name089, name090, name091, name092, name093, name094, name095, name096, & - name097, name098, name099 /) - -CONTAINS - call & - endrun('Cannot read columns_on_task from file'// & - ', columns_on_task has no horizontal dimension; columns_on_task is a protected variable') - - -end module linebreak_test diff --git a/test/unit_tests/sample_host_files/data1_mod.F90 b/test/unit_tests/sample_host_files/data1_mod.F90 deleted file mode 100644 index b85db315..00000000 --- a/test/unit_tests/sample_host_files/data1_mod.F90 +++ /dev/null @@ -1,11 +0,0 @@ -module data1_mod - - use ccpp_kinds, only: kind_phys - - !> \section arg_table_data1_mod Argument Table - !! \htmlinclude arg_table_data1_mod.html - real(kind_phys) :: ps1 - real(kind_phys), allocatable :: xbox(:,:) - real(kind_phys), allocatable :: switch(:,:) - -end module data1_mod diff --git a/test/unit_tests/sample_host_files/data1_mod.meta b/test/unit_tests/sample_host_files/data1_mod.meta deleted file mode 100644 index 37e2de96..00000000 --- a/test/unit_tests/sample_host_files/data1_mod.meta +++ /dev/null @@ -1,25 +0,0 @@ -[ccpp-table-properties] - name = data1_mod - type = module -[ccpp-arg-table] - name = data1_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_dimension, 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_dimension, vertical_layer_dimension) From 424701962fd0bc42a6ade3b1489c1ad7d3a3f22d Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 11:09:02 -0600 Subject: [PATCH 06/12] update regexes in state_machine --- scripts/ccpp_state_machine.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ccpp_state_machine.py b/scripts/ccpp_state_machine.py index 30540fc9..0de2c7bd 100644 --- a/scripts/ccpp_state_machine.py +++ b/scripts/ccpp_state_machine.py @@ -3,11 +3,11 @@ # CCPP framework imports from state_machine import StateMachine -_INIT_ST = r"(?:(?i)init(?:ial(?:ize)?)?)" -_FINAL_ST = r"(?:(?i)final(?:ize)?)" -_RUN_ST = r"(?:(?i)run)" -_TS_INIT_ST = r"(?:(?i)timestep_init(?:ial(?:ize)?)?)" -_TS_FINAL_ST = r"(?:(?i)timestep_final(?:ize)?)" +_INIT_ST = r"(?:init(?:ial(?:ize)?)?)" +_FINAL_ST = r"(?:final(?:ize)?)" +_RUN_ST = r"(?:run)" +_TS_INIT_ST = r"(?:timestep_init(?:ial(?:ize)?)?)" +_TS_FINAL_ST = r"(?:timestep_final(?:ize)?)" # Allowed CCPP transitions # pylint: disable=bad-whitespace From 104e18f93495924ed04bb78546fb563452ada3c6 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 11:13:43 -0600 Subject: [PATCH 07/12] fix doctest --- scripts/parse_tools/parse_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 30f05f7f..44a2014f 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -204,7 +204,7 @@ class ParseContext(object): >>> ParseContext("source.F90", 32) Traceback (most recent call last): - CCPPError: ParseContext linenum must be an int + parse_tools.parse_source.CCPPError: ParseContext linenum must be an int >>> ParseContext(32, 90) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: ParseContext filenum must be a string From bbdf0d05ba419a7618700e5236fdf5a57fe1ce58 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 11:56:39 -0600 Subject: [PATCH 08/12] add state_machine updates --- scripts/state_machine.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/state_machine.py b/scripts/state_machine.py index 966ad04f..692da7a9 100644 --- a/scripts/state_machine.py +++ b/scripts/state_machine.py @@ -30,7 +30,7 @@ class StateMachine: >>> StateMachine([('ab','a','b','a')]).final_state('ab') 'b' >>> StateMachine([('ab','a','b','a')]).transition_regex('ab') - re.compile('a$') + re.compile('a$', re.IGNORECASE) >>> StateMachine([('ab','a','b','a')]).function_match('foo_a', transition='ab') ('foo', 'a', 'ab') >>> StateMachine([('ab','a','b',r'ax?')]).function_match('foo_a', transition='ab') @@ -162,8 +162,9 @@ def __setitem__(self, key, value): if len(value) != 3: raise ValueError("Invalid transition ({}), should be of the form (inital_state, final_state, regex).".format(value)) # end if - regex = re.compile(value[2] + r"$") - function = re.compile(FORTRAN_ID + r"_(" + value[2] + r")$") + regex = re.compile(value[2] + r"$", re.IGNORECASE) + function = re.compile(FORTRAN_ID + r"_(" + value[2] + r")$", + re.IGNORECASE) self.__stt__[key] = (value[0], value[1], regex, function) def __delitem__(self, key): From e75e14a842d69cc5df0f6fd57fe1f25dd2c03468 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 25 Sep 2023 13:56:33 -0600 Subject: [PATCH 09/12] simplify workflow --- .github/workflows/capgen_unit_tests.yaml | 7 +------ .github/workflows/python.yaml | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 4406ae77..c2d52ee8 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -3,15 +3,10 @@ name: Capgen Unit Tests on: workflow_dispatch: pull_request: - types: [opened, synchronize, reopened] - push: - branches: - #Trigger workflow on push to any branch or branch hierarchy: - - '**' + branches: [feature/capgen, main] jobs: unit_tests: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 3ab5347c..2f04533e 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -3,15 +3,10 @@ name: Python package on: workflow_dispatch: pull_request: - types: [opened, synchronize, reopened] - push: - branches: - #Trigger workflow on push to any branch or branch hierarchy: - - '**' + branches: [feature/capgen, main] jobs: build: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest strategy: matrix: From d58facedc1be48914b8ab77317ffd6c33a9a7ae0 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 2 Oct 2023 15:51:43 -0600 Subject: [PATCH 10/12] remove unnecessary logic from workflow --- .github/workflows/python.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 2f04533e..3775a985 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -28,7 +28,6 @@ jobs: pytest -v doctest: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest strategy: matrix: From 07828e68ef76ae086a1e0921420f773163cccafe Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 12 Oct 2023 16:06:59 -0600 Subject: [PATCH 11/12] update metadata parser test with cleanup --- tests/test_metadata_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_metadata_parser.py b/tests/test_metadata_parser.py index 0f7d18d6..febc3d93 100644 --- a/tests/test_metadata_parser.py +++ b/tests/test_metadata_parser.py @@ -51,7 +51,7 @@ def test_MetadataTable_parse_table(tmpdir): assert len(metadata_header.sections()) == 1 metadata_section = metadata_header.sections()[0] assert metadata_section.name == "" - assert metadata_section.type == "scheme" + assert metadata_section.ptype == "scheme" (im_data,) = metadata_section.variable_list() assert isinstance(im_data, Var) assert im_data.get_dimensions() == [] From 1bacf44bad1748e0ab3aa8ea1adfe80e68136dd1 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 12 Oct 2023 16:11:25 -0600 Subject: [PATCH 12/12] fix merge --- scripts/parse_tools/parse_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 6a521a9b..38a72953 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -375,7 +375,7 @@ class ParseSource(): """ A simple object for providing source information >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")) #doctest: +ELLIPSIS - <__main__.ParseSource object at 0x...> + >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).ptype 'mytype' >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).name