Skip to content

Commit

Permalink
Added missing tests for calling psyclone, and converting old style to…
Browse files Browse the repository at this point in the history
… new stle arguments and vice versa.
  • Loading branch information
hiker committed Sep 30, 2024
1 parent 16d3ff5 commit 71fd1ae
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 10 deletions.
8 changes: 6 additions & 2 deletions source/fab/tools/psyclone.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def process(self,
kernel_roots: Optional[List[Union[str, Path]]] = None,
api: Optional[str] = None,
):
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, too-many-branches
'''Run PSyclone with the specified parameters. If PSyclone is used to
transform existing Fortran files, `api` must be None, and the output
file name is `transformed_file`. If PSyclone is using its DSL
Expand All @@ -122,6 +122,10 @@ def process(self,
if not self.is_available:
raise RuntimeError("PSyclone is not available.")

# Convert the old style API nemo to be empty
if api and api.lower() == "nemo":
api = ""

if api:
# API specified, we need both psy- and alg-file, but not
# transformed file.
Expand All @@ -143,7 +147,7 @@ def process(self,
"alg_file is specified.")
if not transformed_file:
raise RuntimeError("PSyclone called without api, but "
"transformed_file it not specified.")
"transformed_file is not specified.")

parameters: List[Union[str, Path]] = []
# If an api is defined in this call (or in the constructor) add it
Expand Down
198 changes: 190 additions & 8 deletions tests/unit_tests/tools/test_psyclone.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def test_psyclone_constructor():
assert psyclone.category == Category.PSYCLONE
assert psyclone.name == "psyclone"
assert psyclone.exec_name == "psyclone"
# pylint: disable=use-implicit-booleaness-not-comparison
assert psyclone.flags == []


Expand Down Expand Up @@ -120,36 +121,103 @@ def test_psyclone_check_available_errors():
match="Unexpected version information for PSyclone: "
"'PSyclone version: NOT_A_NUMBER.4.0'"):
assert not psyclone.check_available()
# Also check that we can't call process if PSyclone is not available.
psyclone._is_available = False
config = mock.Mock()
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file")
assert "PSyclone is not available" in str(err.value)


def test_psyclone_processing_errors_without_api():
'''Test all processing errors in PSyclone if no API is specified.'''

psyclone = Psyclone()
psyclone._is_available = True
config = mock.Mock()

# No API --> we need transformed file, but not psy or alg:
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file", api=None, psy_file="psy_file")
assert ("PSyclone called without api, but psy_file is specified"
in str(err.value))
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file", api=None, alg_file="alg_file")
assert ("PSyclone called without api, but alg_file is specified"
in str(err.value))
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file", api=None)
assert ("PSyclone called without api, but transformed_file is not "
"specified" in str(err.value))


@pytest.mark.parametrize("api", ["dynamo0.3", "lfric"])
def test_psyclone_process_api_2_4_0(api):
'''Test running PSyclone.'''
def test_psyclone_processing_errors_with_api(api):
'''Test all processing errors in PSyclone if an API is specified.'''

psyclone = Psyclone()
mock_result = get_mock_result("2.4.0")
psyclone._is_available = True
config = mock.Mock()

# No API --> we need transformed file, but not psy or alg:
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file", api=api, psy_file="psy_file")
assert (f"PSyclone called with api '{api}', but no alg_file is specified"
in str(err.value))
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file", api=api, alg_file="alg_file")
assert (f"PSyclone called with api '{api}', but no psy_file is specified"
in str(err.value))
with pytest.raises(RuntimeError) as err:
psyclone.process(config, "x90file", api=api,
psy_file="psy_file", alg_file="alg_file",
transformed_file="transformed_file")
assert (f"PSyclone called with api '{api}' and transformed_file"
in str(err.value))


@pytest.mark.parametrize("version", ["2.4.0", "2.5.0"])
@pytest.mark.parametrize("api", [("dynamo0.3", "dynamo0.3"),
("lfric", "dynamo0.3"),
("gocean1.0", "gocean1.0"),
("gocean", "gocean1.0")
])
def test_psyclone_process_api_old_psyclone(api, version):
'''Test running 'old style' PSyclone (2.5.0 and earlier) with the old API
names (dynamo0.3 and gocean1.0). Also check that the new API names will
be accepted, but are mapped to the old style names. The 'api' parameter
contains the input api, and expected output API.
'''
api_in, api_out = api
psyclone = Psyclone()
mock_result = get_mock_result(version)
transformation_function = mock.Mock(return_value="script_called")
config = mock.Mock()
with mock.patch('fab.tools.tool.subprocess.run',
return_value=mock_result) as tool_run:
psyclone.process(config=config,
api=api,
api=api_in,
x90_file="x90_file",
psy_file="psy_file",
alg_file="alg_file",
transformation_script=transformation_function,
kernel_roots=["root1", "root2"],
additional_parameters=["-c", "psyclone.cfg"])
tool_run.assert_called_with(
['psyclone', '-api', 'dynamo0.3', '-opsy', 'psy_file',
['psyclone', '-api', api_out, '-opsy', 'psy_file',
'-oalg', 'alg_file', '-l', 'all', '-s', 'script_called', '-c',
'psyclone.cfg', '-d', 'root1', '-d', 'root2', 'x90_file'],
capture_output=True, env=None, cwd=None, check=False)


def test_psyclone_process_no_api_2_4_0():
'''Test running PSyclone.'''
@pytest.mark.parametrize("version", ["2.4.0", "2.5.0"])
def test_psyclone_process_no_api_old_psyclone(version):
'''Test running old-style PSyclone (2.5.0 and earlier) when requesting
to transform existing files by not specifying an API. We need to add
the flags `-api nemo` in this case for older PSyclone versions.
'''
psyclone = Psyclone()
mock_result = get_mock_result("2.4.0")
mock_result = get_mock_result(version)
transformation_function = mock.Mock(return_value="script_called")
config = mock.Mock()

Expand All @@ -169,6 +237,119 @@ def test_psyclone_process_no_api_2_4_0():
capture_output=True, env=None, cwd=None, check=False)


@pytest.mark.parametrize("version", ["2.4.0", "2.5.0"])
def test_psyclone_process_nemo_api_old_psyclone(version):
'''Test running old-style PSyclone (2.5.0 and earlier) when requesting
to transform existing files by specifying the nemo api.
'''

psyclone = Psyclone()
mock_result = get_mock_result(version)
transformation_function = mock.Mock(return_value="script_called")
config = mock.Mock()

with mock.patch('fab.tools.tool.subprocess.run',
return_value=mock_result) as tool_run:
psyclone.process(config=config,
api="nemo",
x90_file="x90_file",
transformed_file="psy_file",
transformation_script=transformation_function,
kernel_roots=["root1", "root2"],
additional_parameters=["-c", "psyclone.cfg"])
tool_run.assert_called_with(
['psyclone', '-api', 'nemo', '-opsy', 'psy_file', '-l', 'all',
'-s', 'script_called', '-c',
'psyclone.cfg', '-d', 'root1', '-d', 'root2', 'x90_file'],
capture_output=True, env=None, cwd=None, check=False)


@pytest.mark.parametrize("api", [("dynamo0.3", "lfric"),
("lfric", "lfric"),
("gocean1.0", "gocean"),
("gocean", "gocean")
])
def test_psyclone_process_api_new__psyclone(api):
'''Test running the new PSyclone version. Since this version is not
yet released, we use the Fab internal version number 2.5.0.1 for
now. It uses new API names, and we need to check that the old style
names are converted to the new names.
'''
api_in, api_out = api
psyclone = Psyclone()
mock_result = get_mock_result("2.5.0.1")
transformation_function = mock.Mock(return_value="script_called")
config = mock.Mock()
with mock.patch('fab.tools.tool.subprocess.run',
return_value=mock_result) as tool_run:
psyclone.process(config=config,
api=api_in,
x90_file="x90_file",
psy_file="psy_file",
alg_file="alg_file",
transformation_script=transformation_function,
kernel_roots=["root1", "root2"],
additional_parameters=["-c", "psyclone.cfg"])
tool_run.assert_called_with(
['psyclone', '--psykal-dsl', api_out, '-opsy', 'psy_file',
'-oalg', 'alg_file', '-l', 'all', '-s', 'script_called', '-c',
'psyclone.cfg', '-d', 'root1', '-d', 'root2', 'x90_file'],
capture_output=True, env=None, cwd=None, check=False)


def test_psyclone_process_no_api_new_psyclone():
'''Test running the new PSyclone version without an API. Since this
version is not yet released, we use the Fab internal version number
2.5.0.1 for now.
'''
psyclone = Psyclone()
mock_result = get_mock_result("2.5.0.1")
transformation_function = mock.Mock(return_value="script_called")
config = mock.Mock()

with mock.patch('fab.tools.tool.subprocess.run',
return_value=mock_result) as tool_run:
psyclone.process(config=config,
api="",
x90_file="x90_file",
transformed_file="psy_file",
transformation_script=transformation_function,
kernel_roots=["root1", "root2"],
additional_parameters=["-c", "psyclone.cfg"])
tool_run.assert_called_with(
['psyclone', '-o', 'psy_file', '-l', 'all',
'-s', 'script_called', '-c',
'psyclone.cfg', '-d', 'root1', '-d', 'root2', 'x90_file'],
capture_output=True, env=None, cwd=None, check=False)


def test_psyclone_process_nemo_api_new_psyclone():
'''Test running PSyclone. Since this version is not yet released, we use
the Fab internal version number 2.5.0.1 for now. This tests that
backwards compatibility of using the nemo api works, i.e. '-api nemo' is
just removed.
'''
psyclone = Psyclone()
mock_result = get_mock_result("2.5.0.1")
transformation_function = mock.Mock(return_value="script_called")
config = mock.Mock()

with mock.patch('fab.tools.tool.subprocess.run',
return_value=mock_result) as tool_run:
psyclone.process(config=config,
api="nemo",
x90_file="x90_file",
transformed_file="psy_file",
transformation_script=transformation_function,
kernel_roots=["root1", "root2"],
additional_parameters=["-c", "psyclone.cfg"])
tool_run.assert_called_with(
['psyclone', '-o', 'psy_file', '-l', 'all',
'-s', 'script_called', '-c',
'psyclone.cfg', '-d', 'root1', '-d', 'root2', 'x90_file'],
capture_output=True, env=None, cwd=None, check=False)


def test_type_checking_import():
'''PSyclone contains an import of TYPE_CHECKING to break a circular
dependency. In order to reach 100% coverage of PSyclone, we set
Expand All @@ -178,5 +359,6 @@ def test_type_checking_import():
with mock.patch('typing.TYPE_CHECKING', True):
# This import will not actually re-import, since the module
# is already imported. But we need this in order to call reload:
# pylint: disable=import-outside-toplevel
import fab.tools.psyclone
reload(fab.tools.psyclone)

0 comments on commit 71fd1ae

Please sign in to comment.