From a4c6ef8d37a9996dec52633cef1bb4d4e3c7df38 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 22 Jan 2026 16:12:17 +1100 Subject: [PATCH 01/13] #3292 Added command line option to overwrite settings in the config file, --- src/psyclone/configuration.py | 32 ++++++++++++++-- src/psyclone/generator.py | 8 +++- src/psyclone/tests/configuration_test.py | 48 ++++++++++++++++++++++-- src/psyclone/tests/generator_test.py | 25 +++++++++++- 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/src/psyclone/configuration.py b/src/psyclone/configuration.py index f895e62b16..48a39e5652 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,10 +243,18 @@ def __init__(self): self._backend_intrinsic_named_kwargs = False # ------------------------------------------------------------------------- - def load(self, config_file=None): - '''Loads a configuration file. + def load(self, + config_file: 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 config_file: Override default configuration file to read. + :param overwrite: Optional string of key-value pairs that will + overwrite settings in the config file. - :param str config_file: Override default configuration file to read. :raises ConfigurationError: if there are errors or inconsistencies in \ the specified config file. ''' @@ -286,6 +294,22 @@ 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"Unknown config overwrite: '{pair}' - ignored." + 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 diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index ab7ffd9aae..381dc1d5ea 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -488,6 +488,12 @@ 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=true"' + ) parser.add_argument( '-l', '--limit', dest='limit', default='off', choices=['off', 'all', 'output'], @@ -645,7 +651,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 1cab25f17b..8a9f54caf9 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 @@ -155,13 +157,14 @@ def int_entry_fixture(request): return request.param -def get_config(config_file, content): +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. :returns: a test Config instance. :rtype: :py:class:`psyclone.configuration.Config` @@ -173,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 @@ -794,3 +797,40 @@ def test_intrinsic_settings(): assert ("backend_intrinsic_named_kwargs must be a bool but found " "'int'." in str(err.value)) + + +def test_config_overwrite(tmpdir: Path, monkeypatch) -> None: + """ + Test that configuration settings can be overwritten. + """ + + config_file = tmpdir.join("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 "Unknown config overwrite: 'DOES_NOT_EXIST=1" in str(err.value) diff --git a/src/psyclone/tests/generator_test.py b/src/psyclone/tests/generator_test.py index 416d50f22c..1a937de42c 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 @@ -2089,3 +2089,26 @@ 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 "Unknown config overwrite: 'DOES_NOT_EXIST=27'" in str(err.value) + + # Delete this config instance, so we don't affect any other tests + Config._instance = None From 872d19a190dca6a6c36bf1ac81c7fad9d22e4897 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 22 Jan 2026 16:17:36 +1100 Subject: [PATCH 02/13] #3292 Fix flake8 error. --- src/psyclone/generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index 381dc1d5ea..937f73ca73 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -492,7 +492,8 @@ def main(arguments): '--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=true"' + '--config-opts "reproducible_reductions=true ' + 'run_time_checks=true".' ) parser.add_argument( '-l', '--limit', dest='limit', default='off', From fcaef457963761d88df365d1c5fe8a7c98cb12e4 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 22 Jan 2026 16:32:36 +1100 Subject: [PATCH 03/13] #3292 Added documentation for new command line option. --- doc/user_guide/configuration.rst | 19 +++++++++++++++++++ doc/user_guide/psyclone_command.rst | 8 ++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/user_guide/configuration.rst b/doc/user_guide/configuration.rst index e82d8f45cb..aadb632306 100644 --- a/doc/user_guide/configuration.rst +++ b/doc/user_guide/configuration.rst @@ -237,3 +237,22 @@ 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=True 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/psyclone_command.rst b/doc/user_guide/psyclone_command.rst index 726a7a637d..9dde337962 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=true". -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' From aefc5dc38adca07177cb88708a7fb1ffcb104720 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 23 Jan 2026 11:45:30 +1100 Subject: [PATCH 04/13] #3292 Enable lfric_runtime_checks to create warning or error messages (the latter will abort execution, a warning does not). --- config/psyclone.cfg | 2 +- src/psyclone/configuration.py | 30 ++++++---- .../domain/lfric/lfric_run_time_checks.py | 55 ++++++++++++------- src/psyclone/tests/configuration_test.py | 7 +-- .../tests/domain/lfric/lfric_config_test.py | 45 +++++++++++++-- src/psyclone/tests/lfric_test.py | 19 ++++--- .../tests/test_files/dummy_config.cfg | 2 +- 7 files changed, 110 insertions(+), 50 deletions(-) diff --git a/config/psyclone.cfg b/config/psyclone.cfg index b5b21e2a37..e02aeeea9a 100644 --- a/config/psyclone.cfg +++ b/config/psyclone.cfg @@ -94,7 +94,7 @@ precision_map = i_def: 4, r_um: 8 # Specify whether we generate code to perform runtime correctness checks -RUN_TIME_CHECKS = false +RUN_TIME_CHECKS = none # Number of ANY_SPACE and ANY_DISCONTINUOUS_SPACE function spaces NUM_ANY_SPACE = 10 diff --git a/src/psyclone/configuration.py b/src/psyclone/configuration.py index 48a39e5652..588b47ab88 100644 --- a/src/psyclone/configuration.py +++ b/src/psyclone/configuration.py @@ -940,8 +940,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 = {} @@ -978,15 +978,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 53ab209f75..82894efe4a 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") + print("XX", api_conf.run_time_checks) + if api_conf.run_time_checks == "none": + return + + # 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) + ) + if api_conf.run_time_checks == "error": + log_level = "LOG_LEVEL_ERROR" + else: + log_level = "LOG_LEVEL_WARN" + self.symtab.find_or_create( + log_level, symbol_type=DataSymbol, + datatype=UnresolvedType(), + interface=ImportInterface(csym) + ) def _check_field_fs(self, cursor: int) -> int: ''' @@ -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_WARN" + 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_WARN" 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/tests/configuration_test.py b/src/psyclone/tests/configuration_test.py index 8a9f54caf9..1baa85601c 100644 --- a/src/psyclone/tests/configuration_test.py +++ b/src/psyclone/tests/configuration_test.py @@ -96,7 +96,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 ''' @@ -126,7 +126,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): @@ -799,12 +798,12 @@ def test_intrinsic_settings(): "'int'." in str(err.value)) -def test_config_overwrite(tmpdir: Path, monkeypatch) -> None: +def test_config_overwrite(tmp_path: Path, monkeypatch) -> None: """ Test that configuration settings can be overwritten. """ - config_file = tmpdir.join("config") + config_file = tmp_path / "config" # Reset the ignore modules of the module manager mod_manager = ModuleManager.get() 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/lfric_test.py b/src/psyclone/tests/lfric_test.py index 7bfc88dc97..b6a0b75563 100644 --- a/src/psyclone/tests/lfric_test.py +++ b/src/psyclone/tests/lfric_test.py @@ -3881,22 +3881,25 @@ class returns None. This is because LFRic currently does not # Class LFRicKernelArguments end -def test_lfricinvoke_runtime(tmpdir, monkeypatch): +@pytest.mark.parametrize("level", [("warn", "LOG_LEVEL_WARN"), + ("error", "LOG_LEVEL_ERROR")]) +def test_lfricinvoke_runtime(level, tmpdir, monkeypatch): '''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", True) + monkeypatch.setattr(lfric_config, "_run_time_checks", level_string) _, 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 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 = ( @@ -3910,32 +3913,32 @@ def test_lfricinvoke_runtime(tmpdir, monkeypatch): " 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" + 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" - "ed in the kernel metadata 'w2'.\", LOG_LEVEL_ERROR)\n" + 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" - "ed in the kernel metadata 'w2'.\", LOG_LEVEL_ERROR)\n" + 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" - "ed in the kernel metadata 'w3'.\", LOG_LEVEL_ERROR)\n" + 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" - "fied by kernel 'testkern_code'.\", LOG_LEVEL_ERROR)\n" + f"fied by kernel 'testkern_code'.\", {log_level})\n" " end if\n" "\n" " ! Initialise number of layers\n") 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 From 1a916ea0b1e71cb8bf0d38b5dbdf9a6abba19f98 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 23 Jan 2026 12:24:26 +1100 Subject: [PATCH 05/13] #3292 Fixed failing tests. --- src/psyclone/tests/lfric_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/psyclone/tests/lfric_test.py b/src/psyclone/tests/lfric_test.py index e08913f7d1..ddd0715ec7 100644 --- a/src/psyclone/tests/lfric_test.py +++ b/src/psyclone/tests/lfric_test.py @@ -3953,7 +3953,7 @@ def test_lfricruntimechecks_anyspace(tmpdir, monkeypatch): # 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) + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") _, 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) @@ -3995,7 +3995,7 @@ def test_lfricruntimechecks_vector(tmpdir, monkeypatch): # 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) + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") _, 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) @@ -4058,7 +4058,7 @@ def test_lfricruntimechecks_multikern(tmpdir, monkeypatch): # 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) + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") _, 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) @@ -4134,7 +4134,7 @@ def test_lfricruntimechecks_builtins(tmpdir, monkeypatch): # 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) + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") _, invoke_info = parse(os.path.join(BASE_PATH, "15.1.1_X_plus_Y_builtin.f90"), api=TEST_API) @@ -4169,7 +4169,7 @@ def test_lfricruntimechecks_anydiscontinuous(tmpdir, monkeypatch): # 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) + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") _, invoke_info = parse(os.path.join(BASE_PATH, "11.4_any_discontinuous_space.f90"), api=TEST_API) @@ -4231,7 +4231,7 @@ def test_lfricruntimechecks_anyw2(tmpdir, monkeypatch): # 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) + monkeypatch.setattr(lfric_config, "_run_time_checks", "error") _, invoke_info = parse(os.path.join(BASE_PATH, "21.1_single_invoke_multi_anyw2.f90"), api=TEST_API) From b103823117d77c7df4d91187eb6e42752bddd74d Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 23 Jan 2026 14:41:40 +1100 Subject: [PATCH 06/13] #3292 Small fixes. --- src/psyclone/domain/lfric/lfric_run_time_checks.py | 1 - src/psyclone/generator.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/psyclone/domain/lfric/lfric_run_time_checks.py b/src/psyclone/domain/lfric/lfric_run_time_checks.py index 2a12f6a868..27a14fb556 100644 --- a/src/psyclone/domain/lfric/lfric_run_time_checks.py +++ b/src/psyclone/domain/lfric/lfric_run_time_checks.py @@ -67,7 +67,6 @@ def invoke_declarations(self): ''' super().invoke_declarations() api_conf = Config.get().api_conf("lfric") - print("XX", api_conf.run_time_checks) if api_conf.run_time_checks == "none": return diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index 937f73ca73..2865f5d77d 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -493,7 +493,7 @@ def main(arguments): 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=true".' + 'run_time_checks=warn".' ) parser.add_argument( '-l', '--limit', dest='limit', default='off', From 056795b334b398bb2af3928d0259f138db29e097 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 23 Jan 2026 15:26:21 +1100 Subject: [PATCH 07/13] #3292 Fixed spelling mistake WARN vs WARNING. --- src/psyclone/domain/lfric/lfric_run_time_checks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/psyclone/domain/lfric/lfric_run_time_checks.py b/src/psyclone/domain/lfric/lfric_run_time_checks.py index 27a14fb556..7dd59237b5 100644 --- a/src/psyclone/domain/lfric/lfric_run_time_checks.py +++ b/src/psyclone/domain/lfric/lfric_run_time_checks.py @@ -83,7 +83,7 @@ def invoke_declarations(self): if api_conf.run_time_checks == "error": log_level = "LOG_LEVEL_ERROR" else: - log_level = "LOG_LEVEL_WARN" + log_level = "LOG_LEVEL_WARNING" self.symtab.find_or_create( log_level, symbol_type=DataSymbol, datatype=UnresolvedType(), @@ -173,7 +173,7 @@ def _check_field_fs(self, cursor: int) -> int: if Config.get().api_conf("lfric").run_time_checks == "error": log_level = "LOG_LEVEL_ERROR" else: - log_level = "LOG_LEVEL_WARN" + log_level = "LOG_LEVEL_WARNING" if_body = Call.create( symtab.lookup("log_event"), @@ -240,7 +240,7 @@ def _check_field_ro(self, cursor: int) -> int: if Config.get().api_conf("lfric").run_time_checks == "error": log_level = "LOG_LEVEL_ERROR" else: - log_level = "LOG_LEVEL_WARN" + log_level = "LOG_LEVEL_WARNING" if_body = Call.create( symtab.lookup("log_event"), [Literal(f"In alg '{self._invoke.invokes.psy.orig_name}' " From b9ccc3df13cfe96aa215d30376802a0c9222d108 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 23 Jan 2026 15:27:04 +1100 Subject: [PATCH 08/13] #3292 Moved run_time_checks tests into a separate file; added tests to reach 100% coverage. --- .../lfric/lfric_run_time_checks_test.py | 484 ++++++++++++++++++ src/psyclone/tests/lfric_test.py | 405 --------------- 2 files changed, 484 insertions(+), 405 deletions(-) create mode 100644 src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py 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..a1efe31190 --- /dev/null +++ b/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py @@ -0,0 +1,484 @@ +# ----------------------------------------------------------------------------- +# 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 LFRic API using pytest. ''' + +from pathlib import Path +import pytest + +from psyclone.configuration import Config +from psyclone.parse.algorithm import parse +from psyclone.psyGen import PSyFactory +from psyclone.tests.lfric_build import LFRicBuild + + +# constants +BASE_PATH = Path(__file__).parents[2] / "test_files" / "lfric" + +TEST_API = "lfric" + + +@pytest.fixture(scope="function", autouse=True) +def setup(): + '''Make sure that all tests here use lfric as API.''' + Config.get().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) + _, invoke_info = parse(str(BASE_PATH / "1_single_invoke.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + assert LFRicBuild(tmp_path).code_compiles(psy) + 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 + + +def test_lfricinvoke_runtime_disabled() -> 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. + ''' + + _, invoke_info = parse(str(BASE_PATH / "1_single_invoke.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + generated_code = str(psy.gen) + + assert "LOG_LEVEL" not in generated_code + + +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") + _, invoke_info = parse(str(BASE_PATH / "11_any_space.f90"), api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + assert LFRicBuild(tmp_path).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(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") + _, invoke_info = parse(str(BASE_PATH / "8_vector_field_2.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + + assert LFRicBuild(tmp_path).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(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") + _, invoke_info = parse(str(BASE_PATH / "1.2_multi_invoke.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + assert LFRicBuild(tmp_path).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(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") + _, invoke_info = parse(str(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(tmp_path).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( + 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") + _, invoke_info = parse(str(BASE_PATH / "11.4_any_discontinuous_space.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + assert LFRicBuild(tmp_path).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(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") + _, invoke_info = parse(str(BASE_PATH / + "21.1_single_invoke_multi_anyw2.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + assert LFRicBuild(tmp_path).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 diff --git a/src/psyclone/tests/lfric_test.py b/src/psyclone/tests/lfric_test.py index ddd0715ec7..bc7cf558a7 100644 --- a/src/psyclone/tests/lfric_test.py +++ b/src/psyclone/tests/lfric_test.py @@ -3881,411 +3881,6 @@ class returns None. This is because LFRic currently does not # Class LFRicKernelArguments end -@pytest.mark.parametrize("level", [("warn", "LOG_LEVEL_WARN"), - ("error", "LOG_LEVEL_ERROR")]) -def test_lfricinvoke_runtime(level, tmpdir, monkeypatch): - '''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) - _, 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 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 - - -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", "error") - _, 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", "error") - _, 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", "error") - _, 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", "error") - _, 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", "error") - _, 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", "error") - _, 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.''' From 22c77b129777ea3f92c5e9f9c65e89578fb5822e Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Fri, 23 Jan 2026 15:28:08 +1100 Subject: [PATCH 09/13] #3292 Updated documentation for new option. --- doc/user_guide/configuration.rst | 7 ++++--- doc/user_guide/lfric.rst | 11 ++++++++++- doc/user_guide/psyclone_command.rst | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/doc/user_guide/configuration.rst b/doc/user_guide/configuration.rst index aadb632306..ad26b933e2 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`. @@ -249,7 +250,7 @@ The option takes a space-separated list of ``key=value`` pairs, for example: :: - psyclone --config-opts="run_time_checks=True reprod_pad_size=27" ... + 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 diff --git a/doc/user_guide/lfric.rst b/doc/user_guide/lfric.rst index 729adf3596..fca9c1ef6c 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 on 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 00961d1c0a..7b867b94f9 100644 --- a/doc/user_guide/psyclone_command.rst +++ b/doc/user_guide/psyclone_command.rst @@ -83,7 +83,7 @@ by the command: --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=true". + --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' From 0d9dfe46bfbc345c2cc67fd40689783dcc106d44 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Sat, 24 Jan 2026 11:37:39 +1100 Subject: [PATCH 10/13] #3292 Simplified setting of base path. --- src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index a1efe31190..453e896c87 100644 --- a/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py @@ -45,12 +45,12 @@ from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory from psyclone.tests.lfric_build import LFRicBuild +from psyclone.tests.utilities import get_base_path # constants -BASE_PATH = Path(__file__).parents[2] / "test_files" / "lfric" - TEST_API = "lfric" +BASE_PATH = Path(get_base_path(TEST_API)) @pytest.fixture(scope="function", autouse=True) From e212c029c703761c51250e03dcd77180af419147 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Wed, 4 Feb 2026 10:59:33 +1100 Subject: [PATCH 11/13] #3292 Addressed issues raised in review. --- config/psyclone.cfg | 6 +- doc/user_guide/configuration.rst | 5 +- doc/user_guide/lfric.rst | 2 +- src/psyclone/configuration.py | 12 ++-- .../domain/lfric/lfric_run_time_checks.py | 5 +- src/psyclone/tests/configuration_test.py | 9 ++- .../lfric/lfric_run_time_checks_test.py | 72 ++++++++----------- src/psyclone/tests/generator_test.py | 18 +++-- 8 files changed, 62 insertions(+), 67 deletions(-) diff --git a/config/psyclone.cfg b/config/psyclone.cfg index e02aeeea9a..c368420a8a 100644 --- a/config/psyclone.cfg +++ b/config/psyclone.cfg @@ -93,7 +93,11 @@ precision_map = i_def: 4, r_bl: 8, r_um: 8 -# Specify whether we generate code to perform runtime correctness checks +# 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 diff --git a/doc/user_guide/configuration.rst b/doc/user_guide/configuration.rst index ad26b933e2..2291c7484e 100644 --- a/doc/user_guide/configuration.rst +++ b/doc/user_guide/configuration.rst @@ -242,15 +242,16 @@ grid-properties This key contains definitions to access various grid 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" ... + + 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 diff --git a/doc/user_guide/lfric.rst b/doc/user_guide/lfric.rst index fca9c1ef6c..998f6debbf 100644 --- a/doc/user_guide/lfric.rst +++ b/doc/user_guide/lfric.rst @@ -3951,7 +3951,7 @@ performance costs associated with run-time checks they may be switched 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 on of: +must be one of: - `none` No runtime checks will be added (default) - `warn` Runtime checks will be added, and violations will cause a warning diff --git a/src/psyclone/configuration.py b/src/psyclone/configuration.py index c66dddbf64..7baf0d95fc 100644 --- a/src/psyclone/configuration.py +++ b/src/psyclone/configuration.py @@ -244,7 +244,7 @@ def __init__(self): # ------------------------------------------------------------------------- def load(self, - config_file: str = None, + 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 @@ -255,8 +255,11 @@ def load(self, :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 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: @@ -306,7 +309,8 @@ def load(self, break else: logger = logging.getLogger(__name__) - msg = f"Unknown config overwrite: '{pair}' - ignored." + msg = (f"Attempt to overwrite unknown configuration " + f"option: '{pair}'.") logger.error(msg) raise ConfigurationError(msg) diff --git a/src/psyclone/domain/lfric/lfric_run_time_checks.py b/src/psyclone/domain/lfric/lfric_run_time_checks.py index 7dd59237b5..c818592f0e 100644 --- a/src/psyclone/domain/lfric/lfric_run_time_checks.py +++ b/src/psyclone/domain/lfric/lfric_run_time_checks.py @@ -67,10 +67,11 @@ def invoke_declarations(self): ''' super().invoke_declarations() api_conf = Config.get().api_conf("lfric") + + # Only add if run-time checks are requested if api_conf.run_time_checks == "none": return - # Only add if run-time checks are requested const = LFRicConstants() csym = self.symtab.find_or_create( const.UTILITIES_MOD_MAP["logging"]["module"], @@ -147,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"])) diff --git a/src/psyclone/tests/configuration_test.py b/src/psyclone/tests/configuration_test.py index 1baa85601c..90be79b971 100644 --- a/src/psyclone/tests/configuration_test.py +++ b/src/psyclone/tests/configuration_test.py @@ -159,14 +159,16 @@ def int_entry_fixture(request): def get_config(config_file: Path, content: str, overwrite: Optional[str] = None) -> Config: - ''' A utility function that creates and populates a temporary + ''' + 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. :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 @@ -832,4 +834,5 @@ def test_config_overwrite(tmp_path: Path, monkeypatch) -> None: with pytest.raises(ConfigurationError) as err: config = get_config(config_file, _CONFIG_CONTENT, overwrite="DOES_NOT_EXIST=1") - assert "Unknown config overwrite: 'DOES_NOT_EXIST=1" in str(err.value) + assert ("Attempt to overwrite unknown configuration option: " + "'DOES_NOT_EXIST=1" in str(err.value)) 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 index 453e896c87..0b26f36a70 100644 --- a/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_run_time_checks_test.py @@ -36,27 +36,22 @@ # C. M. Maynard, Met Office/University of Reading, # J. Henrichs, Bureau of Meteorology. -''' This module tests the LFRic API using pytest. ''' +""" +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.parse.algorithm import parse -from psyclone.psyGen import PSyFactory from psyclone.tests.lfric_build import LFRicBuild -from psyclone.tests.utilities import get_base_path +from psyclone.tests.utilities import get_invoke # constants TEST_API = "lfric" -BASE_PATH = Path(get_base_path(TEST_API)) - - -@pytest.fixture(scope="function", autouse=True) -def setup(): - '''Make sure that all tests here use lfric as API.''' - Config.get().api = "lfric" @pytest.mark.parametrize("level", [("warn", "LOG_LEVEL_WARNING"), @@ -73,10 +68,7 @@ def test_lfricinvoke_runtime(level: tuple[str, str], config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", level_string) - _, invoke_info = parse(str(BASE_PATH / "1_single_invoke.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmp_path).code_compiles(psy) + 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 @@ -123,20 +115,20 @@ def test_lfricinvoke_runtime(level: tuple[str, str], "\n" " ! Initialise number of layers\n") assert expected in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) -def test_lfricinvoke_runtime_disabled() -> None: +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. ''' - _, invoke_info = parse(str(BASE_PATH / "1_single_invoke.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) + 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, @@ -149,9 +141,7 @@ def test_lfricruntimechecks_anyspace(tmp_path: Path, config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", "error") - _, invoke_info = parse(str(BASE_PATH / "11_any_space.f90"), api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmp_path).code_compiles(psy) + 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 @@ -182,6 +172,7 @@ def test_lfricruntimechecks_anyspace(tmp_path: Path, "\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, @@ -191,11 +182,8 @@ def test_lfricruntimechecks_vector(tmp_path: Path, config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", "error") - _, invoke_info = parse(str(BASE_PATH / "8_vector_field_2.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - - assert LFRicBuild(tmp_path).code_compiles(psy) + 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" @@ -241,6 +229,7 @@ def test_lfricruntimechecks_vector(tmp_path: Path, "\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, @@ -255,10 +244,8 @@ def test_lfricruntimechecks_multikern(tmp_path: Path, config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", "error") - _, invoke_info = parse(str(BASE_PATH / "1.2_multi_invoke.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmp_path).code_compiles(psy) + 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 @@ -323,6 +310,7 @@ def test_lfricruntimechecks_multikern(tmp_path: Path, "\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, @@ -332,10 +320,8 @@ def test_lfricruntimechecks_builtins(tmp_path: Path, config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", "error") - _, invoke_info = parse(str(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(tmp_path).code_compiles(psy) + 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 @@ -354,6 +340,7 @@ def test_lfricruntimechecks_builtins(tmp_path: Path, "\n" " ! Create a mesh object\n") assert expected_code2 in generated_code + assert LFRicBuild(tmp_path).code_compiles(psy) def test_lfricruntimechecks_anydiscontinuous( @@ -368,10 +355,8 @@ def test_lfricruntimechecks_anydiscontinuous( config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", "error") - _, invoke_info = parse(str(BASE_PATH / "11.4_any_discontinuous_space.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmp_path).code_compiles(psy) + 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 @@ -417,6 +402,7 @@ def test_lfricruntimechecks_anydiscontinuous( "\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, @@ -430,11 +416,8 @@ def test_lfricruntimechecks_anyw2(tmp_path: Path, config = Config.get() lfric_config = config.api_conf("lfric") monkeypatch.setattr(lfric_config, "_run_time_checks", "error") - _, invoke_info = parse(str(BASE_PATH / - "21.1_single_invoke_multi_anyw2.f90"), - api=TEST_API) - psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) - assert LFRicBuild(tmp_path).code_compiles(psy) + 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) @@ -482,3 +465,4 @@ def test_lfricruntimechecks_anyw2(tmp_path: Path, "\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 1a937de42c..75b08a6836 100644 --- a/src/psyclone/tests/generator_test.py +++ b/src/psyclone/tests/generator_test.py @@ -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. @@ -2108,7 +2108,5 @@ def test_config_overwrite() -> None: # Check error handling with pytest.raises(ConfigurationError) as err: main([filename, "--config-opts", "DOES_NOT_EXIST=27"]) - assert "Unknown config overwrite: 'DOES_NOT_EXIST=27'" in str(err.value) - - # Delete this config instance, so we don't affect any other tests - Config._instance = None + assert ("Attempt to overwrite unknown configuration option: " + "'DOES_NOT_EXIST=27'" in str(err.value)) From 52157d5d0c87f3956d2725677c65b2ebe145e772 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 4 Feb 2026 09:08:13 +0000 Subject: [PATCH 12/13] #3295 update changelog --- changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog b/changelog index 4660340bb5..2bcc3e326d 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,6 @@ + 37) PR #3295 for #3292. Add command-line flag to permit config-file + settings to be overridden. + 36) PR #3308. Update Spack environment to use updated compiler versions. 35) PR #3202. Update nvidia compiler module version. From f873e780bae3b57e4eb7939dd295f1dd7bc17710 Mon Sep 17 00:00:00 2001 From: Andy Porter Date: Wed, 4 Feb 2026 09:10:13 +0000 Subject: [PATCH 13/13] #3292 Update changelog with new command-line flag details Added information about command-line flag and runtime warnings option. --- changelog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog b/changelog index 2bcc3e326d..33d7c06f0f 100644 --- a/changelog +++ b/changelog @@ -1,5 +1,6 @@ 37) PR #3295 for #3292. Add command-line flag to permit config-file - settings to be overridden. + settings to be overridden and extend possible settings for the + runtime warnings option. 36) PR #3308. Update Spack environment to use updated compiler versions.