diff --git a/changelog b/changelog index 4660340bb5..33d7c06f0f 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,7 @@ + 37) PR #3295 for #3292. Add command-line flag to permit config-file + settings to be overridden and extend possible settings for the + runtime warnings option. + 36) PR #3308. Update Spack environment to use updated compiler versions. 35) PR #3202. Update nvidia compiler module version. diff --git a/config/psyclone.cfg b/config/psyclone.cfg index b5b21e2a37..c368420a8a 100644 --- a/config/psyclone.cfg +++ b/config/psyclone.cfg @@ -93,8 +93,12 @@ precision_map = i_def: 4, r_bl: 8, r_um: 8 -# Specify whether we generate code to perform runtime correctness checks -RUN_TIME_CHECKS = false +# Specify whether we generate code to perform runtime correctness checks. +# Allowed values: +# none: no run time checks are added +# warn: run time checks are added, violations will create a warning +# error: run time checks are added, violations will create an error and abort +RUN_TIME_CHECKS = none # Number of ANY_SPACE and ANY_DISCONTINUOUS_SPACE function spaces NUM_ANY_SPACE = 10 diff --git a/doc/user_guide/configuration.rst b/doc/user_guide/configuration.rst index e82d8f45cb..2291c7484e 100644 --- a/doc/user_guide/configuration.rst +++ b/doc/user_guide/configuration.rst @@ -117,7 +117,7 @@ and an optional API specific section, for example for the r_tran: 8, r_bl: 8, r_um: 8 - RUN_TIME_CHECKS = false + RUN_TIME_CHECKS = none NUM_ANY_SPACE = 10 NUM_ANY_DISCONTINUOUS_SPACE = 10 @@ -207,7 +207,8 @@ precision_map Captures the value of the actual precisions in bytes, see :ref:`lfric-precision-map` RUN_TIME_CHECKS Specifies whether to generate run-time validation - checks, see :ref:`lfric-run-time-checks`. + checks, see :ref:`lfric-run-time-checks`. Must be + one of `none`, `warn` or `error`. NUM_ANY_SPACE Sets the number of ``ANY_SPACE`` function spaces in LFRic, see :ref:`lfric-num-any-spaces`. @@ -237,3 +238,23 @@ grid-properties This key contains definitions to access various grid in the :ref:`gocean-configuration-grid-properties` section of the GOcean1.0 chapter. ======================= ======================================================= + + +Overwriting Config Settings on the Command Line +----------------------------------------------- + +PSyclone provides the command line option ``--config-opts`` to overwrite +settings in the configuration file. This can be convenient to test different +scenarios without having to maintain a config files for each of them. + +The option takes a space-separated list of ``key=value`` pairs, for +example: +:: + + psyclone --config-opts="run_time_checks=warn reprod_pad_size=27" ... + +This will overwrite the settings for ``run_time_checks`` and ``reprod_pad_size`` +in the configuration file. You can overwrite any setting in any section (without +having to specify the section). Capitalisation of the keys is ignored. +If an invalid key is specified, an exception will be raised and +execution will be aborted. diff --git a/doc/user_guide/lfric.rst b/doc/user_guide/lfric.rst index 729adf3596..998f6debbf 100644 --- a/doc/user_guide/lfric.rst +++ b/doc/user_guide/lfric.rst @@ -3948,7 +3948,16 @@ Run-time Checks PSyclone performs static consistency checks where possible. When this is not possible PSyclone can generate run-time checks. As there may be performance costs associated with run-time checks they may be switched -on or off by the `RUN_TIME_CHECKS` option in the configuration file. +on or off by the `RUN_TIME_CHECKS` option in the configuration file +(or by using the ``--config-opts`` command line option to overwrite +the setting in the configuration file). The value of `RUN_TIME_CHECKS` +must be one of: + +- `none` No runtime checks will be added (default) +- `warn` Runtime checks will be added, and violations will cause a warning + message to be logged. +- `error` Runtime checks will be added, and violations will cause an error + message to be logged. The application will then abort. Currently run-time checks can be generated to: diff --git a/doc/user_guide/psyclone_command.rst b/doc/user_guide/psyclone_command.rst index 11dec4e9a1..7b867b94f9 100644 --- a/doc/user_guide/psyclone_command.rst +++ b/doc/user_guide/psyclone_command.rst @@ -52,8 +52,8 @@ by the command: > psyclone -h - usage: psyclone [-h] [-v] [-c CONFIG] [-s SCRIPT] [--enable-cache] [-l {off,all,output}] - [-p {invokes,routines,kernels}] + usage: psyclone [-h] [-v] [-c CONFIG] [-s SCRIPT] [--enable-cache] [--config-opts CONFIG_OPTS] + [-l {off,all,output}] [-p {invokes,routines,kernels}] [-o OUTPUT_FILE] [-api DSL] [-oalg OUTPUT_ALGORITHM_FILE] [-opsy OUTPUT_PSY_FILE] [-okern OUTPUT_KERNEL_PATH] [-dm] [-nodm] [--kernel-renaming {multiple,single}] @@ -80,6 +80,10 @@ by the command: --enable-cache whether to enable caching of imported module dependencies (if enabled, it will generate a .psycache file of each imported module in the same location as the imported source file). + --config-opts CONFIG_OPTS + Settings that will overwrite values in the config file as a + space-separated list of key=value pairs. Example: + --config-opts "reproducible_reductions=true run_time_checks=warn". -l {off,all,output}, --limit {off,all,output} limit the Fortran line length to 132 characters (default 'off'). Use 'all' to apply limit to both input and output Fortran. Use 'output' diff --git a/src/psyclone/configuration.py b/src/psyclone/configuration.py index a16ebedeb5..7baf0d95fc 100644 --- a/src/psyclone/configuration.py +++ b/src/psyclone/configuration.py @@ -51,7 +51,7 @@ import os import re import sys -from typing import Union +from typing import Optional, Union import psyclone from psyclone.errors import PSycloneError, InternalError @@ -243,12 +243,23 @@ def __init__(self): self._backend_intrinsic_named_kwargs = False # ------------------------------------------------------------------------- - def load(self, config_file=None): - '''Loads a configuration file. + def load(self, + config_file: Optional[str] = None, + overwrite: Optional[str] = None) -> None: + '''Loads a configuration file. The optional 'overwrite' parameter + is a space-separated string of key=value pairs which will overwrite + values in the config files (e.g. + "reproducible_reductions=true run_time_checks=true") - :param str config_file: Override default configuration file to read. - :raises ConfigurationError: if there are errors or inconsistencies in \ - the specified config file. + :param config_file: Override default configuration file to read. + :param overwrite: Optional string of key-value pairs that will + overwrite settings in the config file. + + :raises ConfigurationError: if there are errors or inconsistencies in + the specified config file. + + :raises ConfigurationError: if a user-provided overwrite string + contains an invalid key. ''' # pylint: disable=too-many-branches, too-many-statements if config_file: @@ -286,6 +297,23 @@ def load(self, config_file=None): raise ConfigurationError( "Configuration file has no [DEFAULT] section", config=self) + if overwrite: + # If overwrite information is specified, parse this information + # and use it to overwrite the settings just read: + pairs = overwrite.split() + for pair in pairs: + key, value = pair.split("=") + for section in self._config: + if key in self._config[section]: + self._config[section][key] = value + break + else: + logger = logging.getLogger(__name__) + msg = (f"Attempt to overwrite unknown configuration " + f"option: '{pair}'.") + logger.error(msg) + raise ConfigurationError(msg) + # The call to the 'read' method above populates a dictionary. # All of the entries in that dict are unicode strings so here # we pull out the values we want and deal with any type @@ -916,8 +944,8 @@ def __init__(self, config, section): self._config = config # Initialise redundant computation setting self._compute_annexed_dofs = None - # Initialise run_time_checks setting - self._run_time_checks = None + # Initialise run_time_checks setting - one of "none", "warn", "error" + self._run_time_checks = "none" # Initialise LFRic datatypes' default kinds (precisions) settings self._supported_fortran_datatypes = [] self._default_kind = {} @@ -954,15 +982,23 @@ def __init__(self, config, section): config=self._config) from err # Parse setting for run_time_checks flag - try: - self._run_time_checks = section.getboolean( - "run_time_checks") - except ValueError as err: - raise ConfigurationError( - f"Error while parsing RUN_TIME_CHECKS in the " - f"'[{section.name}]' section of the configuration file " - f"'{config.filename}': {str(err)}.", - config=self._config) from err + self._run_time_checks = section["run_time_checks"].lower() + if self._run_time_checks not in ["none", "warn", "error"]: + # Test for old-style boolean value: + try: + self._run_time_checks = section.getboolean("run_time_checks") + except ValueError as err: + raise ConfigurationError( + f"Error while parsing RUN_TIME_CHECKS in the " + f"'[{section.name}]' section of the configuration file " + f"'{config.filename}': Found '{self._run_time_checks}', " + f" must be one of 'none', 'warn', 'error'.", + config=self._config) from err + if self._run_time_checks: + # True - old behaviour is to create an error (and abort) + self._run_time_checks = "error" + else: + self._run_time_checks = "none" # Parse setting for the supported Fortran datatypes. No # need to check whether the keyword is found as it is diff --git a/src/psyclone/domain/lfric/lfric_run_time_checks.py b/src/psyclone/domain/lfric/lfric_run_time_checks.py index bae426a5f5..c818592f0e 100644 --- a/src/psyclone/domain/lfric/lfric_run_time_checks.py +++ b/src/psyclone/domain/lfric/lfric_run_time_checks.py @@ -66,22 +66,30 @@ def invoke_declarations(self): ''' super().invoke_declarations() - if Config.get().api_conf("lfric").run_time_checks: - # Only add if run-time checks are requested - const = LFRicConstants() - csym = self.symtab.find_or_create( - const.UTILITIES_MOD_MAP["logging"]["module"], - symbol_type=ContainerSymbol - ) - self.symtab.find_or_create( - "log_event", symbol_type=RoutineSymbol, - interface=ImportInterface(csym) - ) - self.symtab.find_or_create( - "LOG_LEVEL_ERROR", symbol_type=DataSymbol, - datatype=UnresolvedType(), - interface=ImportInterface(csym) - ) + api_conf = Config.get().api_conf("lfric") + + # Only add if run-time checks are requested + if api_conf.run_time_checks == "none": + return + + const = LFRicConstants() + csym = self.symtab.find_or_create( + const.UTILITIES_MOD_MAP["logging"]["module"], + symbol_type=ContainerSymbol + ) + self.symtab.find_or_create( + "log_event", symbol_type=RoutineSymbol, + interface=ImportInterface(csym) + ) + if api_conf.run_time_checks == "error": + log_level = "LOG_LEVEL_ERROR" + else: + log_level = "LOG_LEVEL_WARNING" + self.symtab.find_or_create( + log_level, symbol_type=DataSymbol, + datatype=UnresolvedType(), + interface=ImportInterface(csym) + ) def _check_field_fs(self, cursor: int) -> int: ''' @@ -140,7 +148,7 @@ def _check_field_fs(self, cursor: int) -> int: if_condition = None for name in function_space_names: - if arg._vector_size > 1: + if arg.vector_size > 1: call = Call.create(ArrayOfStructuresReference.create( field_symbol, [Literal('1', INTEGER_TYPE)], ["which_function_space"])) @@ -163,6 +171,11 @@ def _check_field_fs(self, cursor: int) -> int: BinaryOperation.Operator.AND, if_condition, cmp ) + if Config.get().api_conf("lfric").run_time_checks == "error": + log_level = "LOG_LEVEL_ERROR" + else: + log_level = "LOG_LEVEL_WARNING" + if_body = Call.create( symtab.lookup("log_event"), [Literal(f"In alg '{self._invoke.invokes.psy.orig_name}' " @@ -172,7 +185,7 @@ def _check_field_fs(self, cursor: int) -> int: f"not compatible with the function space " f"specified in the kernel metadata '{fs_name}'.", CHARACTER_TYPE), - Reference(symtab.lookup("LOG_LEVEL_ERROR"))]) + Reference(symtab.lookup(log_level))]) ifblock = IfBlock.create(if_condition, [if_body]) self._invoke.schedule.addchild(ifblock, cursor) @@ -225,13 +238,17 @@ def _check_field_ro(self, cursor: int) -> int: first = True for field, call in modified_fields: if_condition = field.generate_method_call("is_readonly") + if Config.get().api_conf("lfric").run_time_checks == "error": + log_level = "LOG_LEVEL_ERROR" + else: + log_level = "LOG_LEVEL_WARNING" if_body = Call.create( symtab.lookup("log_event"), [Literal(f"In alg '{self._invoke.invokes.psy.orig_name}' " f"invoke '{self._invoke.name}', field '{field.name}' " f"is on a read-only function space but is modified " f"by kernel '{call.name}'.", CHARACTER_TYPE), - Reference(symtab.lookup("LOG_LEVEL_ERROR"))]) + Reference(symtab.lookup(log_level))]) ifblock = IfBlock.create(if_condition, [if_body]) self._invoke.schedule.addchild(ifblock, cursor) @@ -254,7 +271,7 @@ def initialise(self, cursor: int) -> int: :returns: Updated cursor value. ''' - if not Config.get().api_conf("lfric").run_time_checks: + if Config.get().api_conf("lfric").run_time_checks == "none": # Run-time checks are not requested. return cursor diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index ab7ffd9aae..2865f5d77d 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -488,6 +488,13 @@ def main(arguments): 'enabled, it will generate a .psycache file of each imported ' 'module in the same location as the imported source file).' ) + parser.add_argument( + '--config-opts', + help='Settings that will overwrite values in the config file as a ' + 'space-separated list of key=value pairs. Example:' + '--config-opts "reproducible_reductions=true ' + 'run_time_checks=warn".' + ) parser.add_argument( '-l', '--limit', dest='limit', default='off', choices=['off', 'all', 'output'], @@ -645,7 +652,7 @@ def main(arguments): # If no config file name is specified, args.config is none # and config will load the default config file. - Config.get().load(args.config) + Config.get().load(args.config, args.config_opts) # Check whether a PSyKAl API has been specified. if args.psykal_dsl is None: diff --git a/src/psyclone/tests/configuration_test.py b/src/psyclone/tests/configuration_test.py index 47b0839146..171b006c75 100644 --- a/src/psyclone/tests/configuration_test.py +++ b/src/psyclone/tests/configuration_test.py @@ -44,8 +44,10 @@ import logging import os +from pathlib import Path import re import sys +from typing import Optional import pytest @@ -93,7 +95,7 @@ r_tran: 8, r_bl: 8, r_um: 8 -RUN_TIME_CHECKS = false +RUN_TIME_CHECKS = none NUM_ANY_SPACE = 10 NUM_ANY_DISCONTINUOUS_SPACE = 10 ''' @@ -123,7 +125,6 @@ def clear_config_instance(): params=["DISTRIBUTED_MEMORY", "REPRODUCIBLE_REDUCTIONS", "COMPUTE_ANNEXED_DOFS", - "RUN_TIME_CHECKS", "BACKEND_CHECKS_ENABLED", "BACKEND_INDENTATION_DISABLED"]) def bool_entry_fixture(request): @@ -154,16 +155,19 @@ def int_entry_fixture(request): return request.param -def get_config(config_file, content): - ''' A utility function that creates and populates a temporary +def get_config(config_file: Path, + content: str, + overwrite: Optional[str] = None) -> Config: + ''' + A utility function that creates and populates a temporary PSyclone configuration file for testing purposes. :param config_file: local path to the temporary configuration file. - :type config: :py:class:`py._path.local.LocalPath` - :param str content: the entry for the temporary configuration file. + :param content: the entry for the temporary configuration file. + :param overwrite: Optional string of key-value pairs that will + overwrite settings in the config file. :returns: a test Config instance. - :rtype: :py:class:`psyclone.configuration.Config` ''' # Create and populate a temporary config file @@ -172,7 +176,7 @@ def get_config(config_file, content): new_cfg.close() # Create and populate a test Config object config_obj = Config() - config_obj.load(config_file=str(config_file)) + config_obj.load(config_file=str(config_file), overwrite=overwrite) return config_obj @@ -815,10 +819,10 @@ def test_cmd_line_flag_override(tmp_path, monkeypatch): # We want to monkeypatch Config so that we catch any attempt to load # a new one. - # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel, redefined-outer-name, reimported from psyclone.configuration import Config - def fake_load(_, config_file=None): + def fake_load(_, config_file=None, overwrite=None): raise InternalError(f"config being loaded: '{config_file}'") monkeypatch.setattr(Config, "load", fake_load) @@ -845,3 +849,41 @@ def fake_load(_, config_file=None): main(["--config", str(cfg_file), str(f90_file)]) assert re.search(r"loaded: '[a-z\/\\\-_q0-9]*psyclone_test.cfg'", str(err.value), re.I) + + +def test_config_overwrite(tmp_path: Path, monkeypatch) -> None: + """ + Test that configuration settings can be overwritten. + """ + + config_file = tmp_path / "config" + + # Reset the ignore modules of the module manager + mod_manager = ModuleManager.get() + monkeypatch.setattr(mod_manager, "_ignore_modules", set()) + + # First verify unmodified values to be as expected: + config = get_config(config_file, _CONFIG_CONTENT) + assert config._config["DEFAULT"]["backend_checks_enabled"] == "false" + assert config._config["DEFAULT"]["ignore_modules"] == "netcdf, mpi" + assert config.backend_checks_enabled is False + assert mod_manager.ignores() == set(["netcdf", "mpi"]) + + # Now overwrite some values. Reset the module manager (since it stores + # the values from the config file / overwrite) + monkeypatch.setattr(mod_manager, "_ignore_modules", set()) + + config = get_config(config_file, _CONFIG_CONTENT, + overwrite="backend_checks_enabled=True " + "ignore_modules=a,b") + assert config._config["DEFAULT"]["backend_checks_enabled"] == "True" + assert config.backend_checks_enabled is True + assert config._config["DEFAULT"]["ignore_modules"] == "a,b" + assert mod_manager.ignores() == set(["a", "b"]) + + # Now test invalid values: + with pytest.raises(ConfigurationError) as err: + config = get_config(config_file, _CONFIG_CONTENT, + overwrite="DOES_NOT_EXIST=1") + assert ("Attempt to overwrite unknown configuration option: " + "'DOES_NOT_EXIST=1" in str(err.value)) diff --git a/src/psyclone/tests/domain/lfric/lfric_config_test.py b/src/psyclone/tests/domain/lfric/lfric_config_test.py index 50c88bda1a..fb7ffed3c7 100644 --- a/src/psyclone/tests/domain/lfric/lfric_config_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_config_test.py @@ -39,6 +39,7 @@ Module containing tests for LFRic API configuration handling. ''' +from pathlib import Path import re import pytest @@ -70,7 +71,7 @@ r_tran: 8, r_bl: 8, r_um: 8 -RUN_TIME_CHECKS = false +RUN_TIME_CHECKS = none NUM_ANY_SPACE = 10 NUM_ANY_DISCONTINUOUS_SPACE = 10 ''' @@ -141,7 +142,7 @@ def test_no_mandatory_option(tmpdir, option): in str(err.value)) -@pytest.mark.parametrize("option", ["COMPUTE_ANNEXED_DOFS", "RUN_TIME_CHECKS"]) +@pytest.mark.parametrize("option", ["COMPUTE_ANNEXED_DOFS"]) def test_entry_not_bool(tmpdir, option): ''' Check that we raise an error if the value of any options expecting a boolean value are not Boolean ''' @@ -339,13 +340,45 @@ def test_precision_map(): assert api_config.precision_map["r_um"] == 8 -def test_run_time_checks(): +@pytest.mark.parametrize("run_time_check", [("none", "none"), + ("warn", "warn"), + ("error", "error"), + ("false", "none"), + ("true", "error"), + ]) +def test_run_time_checks(tmp_path: Path, + run_time_check: tuple[str, str]) -> None: '''Check that we load the expected default RUN_TIME_CHECKS value - (False) + and also test that we are backwards compatible (true/false), which + must be mapped to "error"/"none". + ''' + config_value, expected = run_time_check + content = re.sub(r"RUN_TIME_CHECKS = none", + f"RUN_TIME_CHECKS = {config_value}", + _CONFIG_CONTENT, + flags=re.MULTILINE) + conf = config(tmp_path / "config_lfric", content) + api_config = conf.api_conf(TEST_API) + assert api_config.run_time_checks == expected + + +def test_run_time_checks_error(tmp_path: Path) -> None: + '''Check that we raise an error if we get an invalid value for + run_time_check. ''' - api_config = Config().get().api_conf(TEST_API) - assert not api_config.run_time_checks + content = re.sub(r"RUN_TIME_CHECKS = none", + "RUN_TIME_CHECKS = INVALID", + _CONFIG_CONTENT, + flags=re.MULTILINE) + + with pytest.raises(ConfigurationError) as err: + config(tmp_path / "config_lfric", content) + + assert ("Error while parsing RUN_TIME_CHECKS in the '[lfric]' section of " + "the configuration file" in str(err.value)) + assert ("Found 'invalid', must be one of 'none', 'warn', 'error'" + in str(err.value)) def test_num_any_space(): diff --git a/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py b/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py new file mode 100644 index 0000000000..0b26f36a70 --- /dev/null +++ b/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py @@ -0,0 +1,468 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2026, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office, +# C. M. Maynard, Met Office/University of Reading, +# J. Henrichs, Bureau of Meteorology. + +""" +This module tests the run-time checks functionality with the LFRic API +using pytest. +""" + + +from pathlib import Path +import pytest + +from psyclone.configuration import Config +from psyclone.tests.lfric_build import LFRicBuild +from psyclone.tests.utilities import get_invoke + + +# constants +TEST_API = "lfric" + + +@pytest.mark.parametrize("level", [("warn", "LOG_LEVEL_WARNING"), + ("error", "LOG_LEVEL_ERROR")]) +def test_lfricinvoke_runtime(level: tuple[str, str], + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + '''Test that run-time checks are added to the PSy-layer via LFRicInvoke + in the expected way (correct location and correct code). + + ''' + level_string, log_level = level + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", level_string) + psy, _ = get_invoke("1_single_invoke.f90", TEST_API, idx=0, dist_mem=True) + generated_code = str(psy.gen) + assert "use testkern_mod, only : testkern_code" in generated_code + assert f"use log_mod, only : {log_level}, log_event" in generated_code + assert "use fs_continuity_mod" in generated_code + assert "use mesh_mod, only : mesh_type" in generated_code + expected = ( + " m2_proxy = m2%get_proxy()\n" + " m2_data => m2_proxy%data\n" + "\n" + " ! Perform run-time checks\n" + " ! Check field function space and kernel metadata function spac" + "es are compatible\n" + " if (f1%which_function_space() /= W1) then\n" + " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" + "tkern_type', the field 'f1' is passed to kernel 'testkern_code' but " + "its function space is not compatible with the function space specifi" + f"ed in the kernel metadata 'w1'.\", {log_level})\n" + " end if\n" + " if (f2%which_function_space() /= W2) then\n" + " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" + "tkern_type', the field 'f2' is passed to kernel 'testkern_code' but " + "its function space is not compatible with the function space specifi" + f"ed in the kernel metadata 'w2'.\", {log_level})\n" + " end if\n" + " if (m1%which_function_space() /= W2) then\n" + " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" + "tkern_type', the field 'm1' is passed to kernel 'testkern_code' but " + "its function space is not compatible with the function space specifi" + f"ed in the kernel metadata 'w2'.\", {log_level})\n" + " end if\n" + " if (m2%which_function_space() /= W3) then\n" + " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" + "tkern_type', the field 'm2' is passed to kernel 'testkern_code' but " + "its function space is not compatible with the function space specifi" + f"ed in the kernel metadata 'w3'.\", {log_level})\n" + " end if\n" + "\n" + " ! Check that read-only fields are not modified\n" + " if (f1_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" + "tkern_type', field 'f1' is on a read-only function space but is modi" + f"fied by kernel 'testkern_code'.\", {log_level})\n" + " end if\n" + "\n" + " ! Initialise number of layers\n") + assert expected in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricinvoke_runtime_disabled(tmp_path) -> None: + '''Test that no tests are added if they are disabled. This is the same + example as the previous one, so just check that the generated code does + not contain any "LOG_LEVEL" strings. + ''' + + psy, _ = get_invoke("1_single_invoke.f90", TEST_API, idx=0, dist_mem=True) + generated_code = str(psy.gen) + + assert "LOG_LEVEL" not in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricruntimechecks_anyspace(tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + '''Test that run-time checks are not added for fields where the kernel + metadata specifies anyspace. + + ''' + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") + psy, _ = get_invoke("11_any_space.f90", TEST_API, idx=0, dist_mem=True) + generated_code = str(psy.gen) + assert "use function_space_mod, only : BASIS, DIFF_BASIS" in generated_code + assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code + assert "use fs_continuity_mod, only : W0\n" in generated_code + assert "use mesh_mod, only : mesh_type" in generated_code + expected2 = ( + " c_proxy(3) = c(3)%get_proxy()\n" + " c_3_data => c_proxy(3)%data\n" + "\n" + " ! Perform run-time checks\n" + " ! Check field function space and kernel metadata function spac" + "es are compatible\n" + " if (c(1)%which_function_space() /= W0) then\n" + " call log_event(\"In alg 'any_space_example' invoke 'invoke_0" + "_testkern_any_space_1_type', the field 'c' is passed to kernel 'test" + "kern_any_space_1_code' but its function space is not compatible with" + " the function space specified in the kernel metadata 'w0'.\", LOG_LE" + "VEL_ERROR)\n" + " end if\n" + "\n" + " ! Check that read-only fields are not modified\n" + " if (a_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'any_space_example' invoke 'invoke_0" + "_testkern_any_space_1_type', field 'a' is on a read-only function sp" + "ace but is modified by kernel 'testkern_any_space_1_code'.\", LOG_LE" + "VEL_ERROR)\n" + " end if\n" + "\n" + " ! Initialise number of layers\n") + assert expected2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricruntimechecks_vector(tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + ''' Test that run-time checks work for vector fields. ''' + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") + psy, _ = get_invoke("8_vector_field_2.f90", TEST_API, + dist_mem=True, idx=0) + + generated_code = str(psy.gen) + assert ("use testkern_coord_w0_2_mod, only : testkern_coord_w0_2_code" + in generated_code) + assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code + assert "use fs_continuity_mod, only : W0\n" in generated_code + assert "use mesh_mod, only : mesh_type" in generated_code + expected2 = ( + " f1_proxy = f1%get_proxy()\n" + " f1_data => f1_proxy%data\n" + "\n" + " ! Perform run-time checks\n" + " ! Check field function space and kernel metadata function spac" + "es are compatible\n" + " if (chi(1)%which_function_space() /= W0) then\n" + " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" + "kern_coord_w0_2_type', the field 'chi' is passed to kernel 'testkern" + "_coord_w0_2_code' but its function space is not compatible with the " + "function space specified in the kernel metadata 'w0'.\", " + "LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f1%which_function_space() /= W0) then\n" + " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" + "kern_coord_w0_2_type', the field 'f1' is passed to kernel 'testkern_" + "coord_w0_2_code' but its function space is not compatible with the " + "function space specified in the kernel metadata 'w0'.\", " + "LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Check that read-only fields are not modified\n" + " if (chi_proxy(1)%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" + "kern_coord_w0_2_type', field 'chi' is on a read-only function space " + "but is modified by kernel 'testkern_coord_w0_2_code'.\", " + "LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f1_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" + "kern_coord_w0_2_type', field 'f1' is on a read-only function space " + "but is modified by kernel 'testkern_coord_w0_2_code'.\", " + "LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Initialise number of layers\n") + assert expected2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricruntimechecks_multikern(tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + ''' Test that run-time checks work when there are multiple kernels and + at least one field is specified as being on a given function space + more than once. In this case we want to avoid checking the same + thing twice. + + ''' + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") + psy, _ = get_invoke("1.2_multi_invoke.f90", TEST_API, idx=0, + dist_mem=True) + generated_code = str(psy.gen) + assert "use testkern_mod, only : testkern_code" in generated_code + assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code + assert "use mesh_mod, only : mesh_type" in generated_code + assert "use fs_continuity_mod, only" in generated_code + expected2 = ( + " f3_proxy = f3%get_proxy()\n" + " f3_data => f3_proxy%data\n" + "\n" + " ! Perform run-time checks\n" + " ! Check field function space and kernel metadata function spac" + "es are compatible\n" + " if (f1%which_function_space() /= W1) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'f1' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w1'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f2%which_function_space() /= W2) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'f2' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (m1%which_function_space() /= W2) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'm1' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (m2%which_function_space() /= W3) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'm2' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w3'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f3%which_function_space() /= W2) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'f3' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (m2%which_function_space() /= W2) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'm2' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (m1%which_function_space() /= W3) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" + "e field 'm1' is passed to kernel 'testkern_code' but its function sp" + "ace is not compatible with the function space specified in the kerne" + "l metadata 'w3'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Check that read-only fields are not modified\n" + " if (f1_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', fi" + "eld 'f1' is on a read-only function space but is modified by kernel " + "'testkern_code'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Initialise number of layers\n") + assert expected2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricruntimechecks_builtins(tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + '''Test that run-time checks work when there are builtins.''' + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") + psy, _ = get_invoke("15.1.1_X_plus_Y_builtin.f90", TEST_API, idx=0, + dist_mem=True) + generated_code = str(psy.gen) + assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code + assert "use mesh_mod, only : mesh_type" in generated_code + assert "type(field_type), intent(in) :: f3" in generated_code + expected_code2 = ( + " f2_proxy = f2%get_proxy()\n" + " f2_data => f2_proxy%data\n" + "\n" + " ! Perform run-time checks\n" + " ! Check that read-only fields are not modified\n" + " if (f3_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'single_invoke' invoke 'invoke_0', f" + "ield 'f3' is on a read-only function space but is modified by kernel" + " 'x_plus_y'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Create a mesh object\n") + assert expected_code2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricruntimechecks_anydiscontinuous( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + '''Test that run-time checks work when we have checks for a field + function space being consistent with an any_discontinuous_* + function space. + + ''' + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") + psy, _ = get_invoke("11.4_any_discontinuous_space.f90", TEST_API, idx=0, + dist_mem=True) + generated_code = str(psy.gen) + assert ("use testkern_any_discontinuous_space_op_1_mod, only : testkern_" + "any_discontinuous_space_op_1_code") in generated_code + assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code + assert "use mesh_mod, only : mesh_type" in generated_code + expected2 = ( + " op4_proxy = op4%get_proxy()\n" + " op4_local_stencil => op4_proxy%local_stencil\n" + "\n" + " ! Perform run-time checks\n" + " ! Check field function space and kernel metadata function spac" + "es are compatible\n" + " if (f1(1)%which_function_space() /= W3 .AND. f1(1)%which_funct" + "ion_space() /= WTHETA .AND. f1(1)%which_function_space() /= W2V .AND" + ". f1(1)%which_function_space() /= W2VTRACE .AND. f1(1)%which_funct" + "ion_space() /= W2BROKEN) then\n" + " call log_event(\"In alg 'any_discontinuous_space_op_example_" + "1' invoke 'invoke_0_testkern_any_discontinuous_space_op_1_type', the" + " field 'f1' is passed to kernel 'testkern_any_discontinuous_space_op" + "_1_code' but its function space is not compatible with the function " + "space specified in the kernel metadata 'any_discontinuous_space_1'." + "\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f2%which_function_space() /= W3 .AND. f2%which_function_sp" + "ace() /= WTHETA .AND. f2%which_function_space() /= W2V .AND. f2%whic" + "h_function_space() /= W2VTRACE .AND. f2%which_function_space() /= " + "W2BROKEN) then\n" + " call log_event(\"In alg 'any_discontinuous_space_op_example_" + "1' invoke 'invoke_0_testkern_any_discontinuous_space_op_1_type', the" + " field 'f2' is passed to kernel 'testkern_any_discontinuous_space_op" + "_1_code' but its function space is not compatible with the function " + "space specified in the kernel metadata 'any_discontinuous_space_2'." + "\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Check that read-only fields are not modified\n" + " if (f2_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'any_discontinuous_space_op_example_" + "1' invoke 'invoke_0_testkern_any_discontinuous_space_op_1_type', fie" + "ld 'f2' is on a read-only function space but is modified by kernel '" + "testkern_any_discontinuous_space_op_1_code'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Initialise number of layers\n") + assert expected2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) + + +def test_lfricruntimechecks_anyw2(tmp_path: Path, + monkeypatch: pytest.MonkeyPatch) -> None: + '''Test that run-time checks work when we have checks for a field + function space being consistent with an anyw2 function + space. + + ''' + # run-time checks are off by default so switch them on + config = Config.get() + lfric_config = config.api_conf("lfric") + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") + psy, _ = get_invoke("21.1_single_invoke_multi_anyw2.f90", TEST_API, + idx=0, dist_mem=True) + generated_code = str(psy.gen) + assert ("use testkern_multi_anyw2_mod, only : testkern_multi_anyw2_code\n" + in generated_code) + assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code + expected2 = ( + "\n" + " ! Perform run-time checks\n" + " ! Check field function space and kernel metadata function spac" + "es are compatible\n" + " if (f1%which_function_space() /= W2 .AND. f1%which_function_sp" + "ace() /= W2H .AND. f1%which_function_space() /= W2V .AND. f1%which_f" + "unction_space() /= W2BROKEN) then\n" + " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" + "invoke_0_testkern_multi_anyw2_type', the field 'f1' is passed to ker" + "nel 'testkern_multi_anyw2_code' but its function space is not compat" + "ible with the function space specified in the kernel metadata 'any_w" + "2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f2%which_function_space() /= W2 .AND. f2%which_function_sp" + "ace() /= W2H .AND. f2%which_function_space() /= W2V .AND. f2%which_f" + "unction_space() /= W2BROKEN) then\n" + " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" + "invoke_0_testkern_multi_anyw2_type', the field 'f2' is passed to ker" + "nel 'testkern_multi_anyw2_code' but its function space is not compat" + "ible with the function space specified in the kernel metadata 'any_w" + "2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + " if (f3%which_function_space() /= W2 .AND. f3%which_function_sp" + "ace() /= W2H .AND. f3%which_function_space() /= W2V .AND. f3%which_f" + "unction_space() /= W2BROKEN) then\n" + " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" + "invoke_0_testkern_multi_anyw2_type', the field 'f3' is passed to ker" + "nel 'testkern_multi_anyw2_code' but its function space is not compat" + "ible with the function space specified in the kernel metadata 'any_w" + "2'.\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Check that read-only fields are not modified\n" + " if (f1_proxy%vspace%is_readonly()) then\n" + " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" + "invoke_0_testkern_multi_anyw2_type', field 'f1' is on a read-only fu" + "nction space but is modified by kernel 'testkern_multi_anyw2_code'." + "\", LOG_LEVEL_ERROR)\n" + " end if\n" + "\n" + " ! Initialise number of layers\n") + assert expected2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) diff --git a/src/psyclone/tests/generator_test.py b/src/psyclone/tests/generator_test.py index 416d50f22c..75b08a6836 100644 --- a/src/psyclone/tests/generator_test.py +++ b/src/psyclone/tests/generator_test.py @@ -59,7 +59,7 @@ from psyclone import generator from psyclone.alg_gen import NoInvokesError -from psyclone.configuration import Config +from psyclone.configuration import Config, ConfigurationError from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.transformations import LFRicLoopFuseTrans from psyclone.errors import GenerationError @@ -1266,9 +1266,9 @@ def test_code_transformation_fixed_form(tmpdir, capsys, caplog): with open(inputfile, "w", encoding='utf-8') as my_file: my_file.write(code) assert error.value.code == 1 - out, err = capsys.readouterr() - assert ("Failed to create PSyIR from file " in err) - assert ("File was treated as free form" in err) + _, err = capsys.readouterr() + assert "Failed to create PSyIR from file " in err + assert "File was treated as free form" in err # Check that if we use a fixed form file extension we get the expected # behaviour. @@ -1303,8 +1303,8 @@ def test_code_transformation_fixed_form(tmpdir, capsys, caplog): with pytest.raises(SystemExit) as error: main([inputfile]) assert error.value.code == 1 - out, err = capsys.readouterr() - assert ("Failed to create PSyIR from file " in err) + _, err = capsys.readouterr() + assert "Failed to create PSyIR from file " in err assert ("' doesn't end with a recognised file extension. Assuming " "free form." in caplog.text) @@ -2078,7 +2078,7 @@ def test_ignore_pattern(): assert mod_man._ignore_files == set(["abc1", "abc2"]) -def test_intrinsic_control_settings(tmpdir, caplog): +def test_intrinsic_control_settings(tmpdir): '''Checks that the intrinsic output control settings update the config correctly''' # Create dummy piece of code. @@ -2089,3 +2089,24 @@ def test_intrinsic_control_settings(tmpdir, caplog): my_file.write(code) main([filename, "--backend-add-all-intrinsic-arg-names"]) assert Config.get().backend_intrinsic_named_kwargs is True + + +def test_config_overwrite() -> None: + ''' Test that configuration settings can be overwritten. + ''' + + # First make sure that the default values are as expected: + assert Config.get().reprod_pad_size == 8 + filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "test_files", "lfric", + "1_single_invoke.f90") + + # Overwrite the config file's reprod_pad_size setting: + main([filename, "--config-opts", "reprod_pad_size=27"]) + assert Config.get().reprod_pad_size == 27 + + # Check error handling + with pytest.raises(ConfigurationError) as err: + main([filename, "--config-opts", "DOES_NOT_EXIST=27"]) + assert ("Attempt to overwrite unknown configuration option: " + "'DOES_NOT_EXIST=27'" in str(err.value)) diff --git a/src/psyclone/tests/lfric_test.py b/src/psyclone/tests/lfric_test.py index 7e20349774..a26a0c6b3d 100644 --- a/src/psyclone/tests/lfric_test.py +++ b/src/psyclone/tests/lfric_test.py @@ -3906,408 +3906,6 @@ class returns None. This is because LFRic currently does not # Class LFRicKernelArguments end -def test_lfricinvoke_runtime(tmpdir, monkeypatch): - '''Test that run-time checks are added to the PSy-layer via LFRicInvoke - in the expected way (correct location and correct code). - - ''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmpdir).code_compiles(psy) - generated_code = str(psy.gen) - assert "use testkern_mod, only : testkern_code" in generated_code - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - assert "use fs_continuity_mod" in generated_code - assert "use mesh_mod, only : mesh_type" in generated_code - expected = ( - " m2_proxy = m2%get_proxy()\n" - " m2_data => m2_proxy%data\n" - "\n" - " ! Perform run-time checks\n" - " ! Check field function space and kernel metadata function spac" - "es are compatible\n" - " if (f1%which_function_space() /= W1) then\n" - " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" - "tkern_type', the field 'f1' is passed to kernel 'testkern_code' but " - "its function space is not compatible with the function space specifi" - "ed in the kernel metadata 'w1'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f2%which_function_space() /= W2) then\n" - " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" - "tkern_type', the field 'f2' is passed to kernel 'testkern_code' but " - "its function space is not compatible with the function space specifi" - "ed in the kernel metadata 'w2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (m1%which_function_space() /= W2) then\n" - " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" - "tkern_type', the field 'm1' is passed to kernel 'testkern_code' but " - "its function space is not compatible with the function space specifi" - "ed in the kernel metadata 'w2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (m2%which_function_space() /= W3) then\n" - " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" - "tkern_type', the field 'm2' is passed to kernel 'testkern_code' but " - "its function space is not compatible with the function space specifi" - "ed in the kernel metadata 'w3'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Check that read-only fields are not modified\n" - " if (f1_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'single_invoke' invoke 'invoke_0_tes" - "tkern_type', field 'f1' is on a read-only function space but is modi" - "fied by kernel 'testkern_code'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Initialise number of layers\n") - assert expected in generated_code - - -def test_lfricruntimechecks_anyspace(tmpdir, monkeypatch): - '''Test that run-time checks are not added for fields where the kernel - metadata specifies anyspace. - - ''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, "11_any_space.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmpdir).code_compiles(psy) - generated_code = str(psy.gen) - assert "use function_space_mod, only : BASIS, DIFF_BASIS" in generated_code - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - assert "use fs_continuity_mod, only : W0\n" in generated_code - assert "use mesh_mod, only : mesh_type" in generated_code - expected2 = ( - " c_proxy(3) = c(3)%get_proxy()\n" - " c_3_data => c_proxy(3)%data\n" - "\n" - " ! Perform run-time checks\n" - " ! Check field function space and kernel metadata function spac" - "es are compatible\n" - " if (c(1)%which_function_space() /= W0) then\n" - " call log_event(\"In alg 'any_space_example' invoke 'invoke_0" - "_testkern_any_space_1_type', the field 'c' is passed to kernel 'test" - "kern_any_space_1_code' but its function space is not compatible with" - " the function space specified in the kernel metadata 'w0'.\", LOG_LE" - "VEL_ERROR)\n" - " end if\n" - "\n" - " ! Check that read-only fields are not modified\n" - " if (a_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'any_space_example' invoke 'invoke_0" - "_testkern_any_space_1_type', field 'a' is on a read-only function sp" - "ace but is modified by kernel 'testkern_any_space_1_code'.\", LOG_LE" - "VEL_ERROR)\n" - " end if\n" - "\n" - " ! Initialise number of layers\n") - assert expected2 in generated_code - - -def test_lfricruntimechecks_vector(tmpdir, monkeypatch): - ''' Test that run-time checks work for vector fields. ''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, "8_vector_field_2.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - - assert LFRicBuild(tmpdir).code_compiles(psy) - - generated_code = str(psy.gen) - assert ("use testkern_coord_w0_2_mod, only : testkern_coord_w0_2_code" - in generated_code) - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - assert "use fs_continuity_mod, only : W0\n" in generated_code - assert "use mesh_mod, only : mesh_type" in generated_code - expected2 = ( - " f1_proxy = f1%get_proxy()\n" - " f1_data => f1_proxy%data\n" - "\n" - " ! Perform run-time checks\n" - " ! Check field function space and kernel metadata function spac" - "es are compatible\n" - " if (chi(1)%which_function_space() /= W0) then\n" - " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" - "kern_coord_w0_2_type', the field 'chi' is passed to kernel 'testkern" - "_coord_w0_2_code' but its function space is not compatible with the " - "function space specified in the kernel metadata 'w0'.\", " - "LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f1%which_function_space() /= W0) then\n" - " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" - "kern_coord_w0_2_type', the field 'f1' is passed to kernel 'testkern_" - "coord_w0_2_code' but its function space is not compatible with the " - "function space specified in the kernel metadata 'w0'.\", " - "LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Check that read-only fields are not modified\n" - " if (chi_proxy(1)%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" - "kern_coord_w0_2_type', field 'chi' is on a read-only function space " - "but is modified by kernel 'testkern_coord_w0_2_code'.\", " - "LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f1_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'vector_field' invoke 'invoke_0_test" - "kern_coord_w0_2_type', field 'f1' is on a read-only function space " - "but is modified by kernel 'testkern_coord_w0_2_code'.\", " - "LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Initialise number of layers\n") - assert expected2 in generated_code - - -def test_lfricruntimechecks_multikern(tmpdir, monkeypatch): - ''' Test that run-time checks work when there are multiple kernels and - at least one field is specified as being on a given function space - more than once. In this case we want to avoid checking the same - thing twice. - - ''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, "1.2_multi_invoke.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmpdir).code_compiles(psy) - generated_code = str(psy.gen) - assert "use testkern_mod, only : testkern_code" in generated_code - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - assert "use mesh_mod, only : mesh_type" in generated_code - assert "use fs_continuity_mod, only" in generated_code - expected2 = ( - " f3_proxy = f3%get_proxy()\n" - " f3_data => f3_proxy%data\n" - "\n" - " ! Perform run-time checks\n" - " ! Check field function space and kernel metadata function spac" - "es are compatible\n" - " if (f1%which_function_space() /= W1) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'f1' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w1'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f2%which_function_space() /= W2) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'f2' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (m1%which_function_space() /= W2) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'm1' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (m2%which_function_space() /= W3) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'm2' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w3'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f3%which_function_space() /= W2) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'f3' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (m2%which_function_space() /= W2) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'm2' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (m1%which_function_space() /= W3) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', th" - "e field 'm1' is passed to kernel 'testkern_code' but its function sp" - "ace is not compatible with the function space specified in the kerne" - "l metadata 'w3'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Check that read-only fields are not modified\n" - " if (f1_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'multi_invoke' invoke 'invoke_0', fi" - "eld 'f1' is on a read-only function space but is modified by kernel " - "'testkern_code'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Initialise number of layers\n") - assert expected2 in generated_code - - -def test_lfricruntimechecks_builtins(tmpdir, monkeypatch): - '''Test that run-time checks work when there are builtins.''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, - "15.1.1_X_plus_Y_builtin.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmpdir).code_compiles(psy) - generated_code = str(psy.gen) - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - assert "use mesh_mod, only : mesh_type" in generated_code - assert "type(field_type), intent(in) :: f3" in generated_code - expected_code2 = ( - " f2_proxy = f2%get_proxy()\n" - " f2_data => f2_proxy%data\n" - "\n" - " ! Perform run-time checks\n" - " ! Check that read-only fields are not modified\n" - " if (f3_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'single_invoke' invoke 'invoke_0', f" - "ield 'f3' is on a read-only function space but is modified by kernel" - " 'x_plus_y'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Create a mesh object\n") - assert expected_code2 in generated_code - - -def test_lfricruntimechecks_anydiscontinuous(tmpdir, monkeypatch): - '''Test that run-time checks work when we have checks for a field - function space being consistent with an any_discontinuous_* - function space. - - ''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, - "11.4_any_discontinuous_space.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmpdir).code_compiles(psy) - generated_code = str(psy.gen) - assert ("use testkern_any_discontinuous_space_op_1_mod, only : testkern_" - "any_discontinuous_space_op_1_code") in generated_code - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - assert "use mesh_mod, only : mesh_type" in generated_code - expected2 = ( - " op4_proxy = op4%get_proxy()\n" - " op4_local_stencil => op4_proxy%local_stencil\n" - "\n" - " ! Perform run-time checks\n" - " ! Check field function space and kernel metadata function spac" - "es are compatible\n" - " if (f1(1)%which_function_space() /= W3 .AND. f1(1)%which_funct" - "ion_space() /= WTHETA .AND. f1(1)%which_function_space() /= W2V .AND" - ". f1(1)%which_function_space() /= W2VTRACE .AND. f1(1)%which_funct" - "ion_space() /= W2BROKEN) then\n" - " call log_event(\"In alg 'any_discontinuous_space_op_example_" - "1' invoke 'invoke_0_testkern_any_discontinuous_space_op_1_type', the" - " field 'f1' is passed to kernel 'testkern_any_discontinuous_space_op" - "_1_code' but its function space is not compatible with the function " - "space specified in the kernel metadata 'any_discontinuous_space_1'." - "\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f2%which_function_space() /= W3 .AND. f2%which_function_sp" - "ace() /= WTHETA .AND. f2%which_function_space() /= W2V .AND. f2%whic" - "h_function_space() /= W2VTRACE .AND. f2%which_function_space() /= " - "W2BROKEN) then\n" - " call log_event(\"In alg 'any_discontinuous_space_op_example_" - "1' invoke 'invoke_0_testkern_any_discontinuous_space_op_1_type', the" - " field 'f2' is passed to kernel 'testkern_any_discontinuous_space_op" - "_1_code' but its function space is not compatible with the function " - "space specified in the kernel metadata 'any_discontinuous_space_2'." - "\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Check that read-only fields are not modified\n" - " if (f2_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'any_discontinuous_space_op_example_" - "1' invoke 'invoke_0_testkern_any_discontinuous_space_op_1_type', fie" - "ld 'f2' is on a read-only function space but is modified by kernel '" - "testkern_any_discontinuous_space_op_1_code'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Initialise number of layers\n") - assert expected2 in generated_code - - -def test_lfricruntimechecks_anyw2(tmpdir, monkeypatch): - '''Test that run-time checks work when we have checks for a field - function space being consistent with an anyw2 function - space. - - ''' - # run-time checks are off by default so switch them on - config = Config.get() - lfric_config = config.api_conf("lfric") - monkeypatch.setattr(lfric_config, "_run_time_checks", True) - _, invoke_info = parse(os.path.join(BASE_PATH, - "21.1_single_invoke_multi_anyw2.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmpdir).code_compiles(psy) - generated_code = str(psy.gen) - assert ("use testkern_multi_anyw2_mod, only : testkern_multi_anyw2_code\n" - in generated_code) - assert "use log_mod, only : LOG_LEVEL_ERROR, log_event" in generated_code - expected2 = ( - "\n" - " ! Perform run-time checks\n" - " ! Check field function space and kernel metadata function spac" - "es are compatible\n" - " if (f1%which_function_space() /= W2 .AND. f1%which_function_sp" - "ace() /= W2H .AND. f1%which_function_space() /= W2V .AND. f1%which_f" - "unction_space() /= W2BROKEN) then\n" - " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" - "invoke_0_testkern_multi_anyw2_type', the field 'f1' is passed to ker" - "nel 'testkern_multi_anyw2_code' but its function space is not compat" - "ible with the function space specified in the kernel metadata 'any_w" - "2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f2%which_function_space() /= W2 .AND. f2%which_function_sp" - "ace() /= W2H .AND. f2%which_function_space() /= W2V .AND. f2%which_f" - "unction_space() /= W2BROKEN) then\n" - " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" - "invoke_0_testkern_multi_anyw2_type', the field 'f2' is passed to ker" - "nel 'testkern_multi_anyw2_code' but its function space is not compat" - "ible with the function space specified in the kernel metadata 'any_w" - "2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - " if (f3%which_function_space() /= W2 .AND. f3%which_function_sp" - "ace() /= W2H .AND. f3%which_function_space() /= W2V .AND. f3%which_f" - "unction_space() /= W2BROKEN) then\n" - " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" - "invoke_0_testkern_multi_anyw2_type', the field 'f3' is passed to ker" - "nel 'testkern_multi_anyw2_code' but its function space is not compat" - "ible with the function space specified in the kernel metadata 'any_w" - "2'.\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Check that read-only fields are not modified\n" - " if (f1_proxy%vspace%is_readonly()) then\n" - " call log_event(\"In alg 'single_invoke_multi_anyw2' invoke '" - "invoke_0_testkern_multi_anyw2_type', field 'f1' is on a read-only fu" - "nction space but is modified by kernel 'testkern_multi_anyw2_code'." - "\", LOG_LEVEL_ERROR)\n" - " end if\n" - "\n" - " ! Initialise number of layers\n") - assert expected2 in generated_code - - def test_read_only_fields_hex(tmpdir): '''Test that halo exchange code is produced for read-only fields.''' diff --git a/src/psyclone/tests/test_files/dummy_config.cfg b/src/psyclone/tests/test_files/dummy_config.cfg index 7def772019..972612ab41 100644 --- a/src/psyclone/tests/test_files/dummy_config.cfg +++ b/src/psyclone/tests/test_files/dummy_config.cfg @@ -61,6 +61,6 @@ precision_map = i_def: 4, r_bl: 8, r_phys: 8, r_um: 8 -RUN_TIME_CHECKS = false +RUN_TIME_CHECKS = none NUM_ANY_SPACE = 10 NUM_ANY_DISCONTINUOUS_SPACE = 10