Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5dd98c6

Browse files
authored
Added pfunit tool and tests. (#562)
1 parent 41ebc4c commit 5dd98c6

4 files changed

Lines changed: 212 additions & 2 deletions

File tree

source/fab/tools/category.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ def is_compiler(self) -> bool:
127127
CATEGORY_FOR_UNIT_TESTS: Category
128128

129129

130-
# Now create the default categories that Fab needs
130+
# Now create the default categories for all categories that
131+
# have more than one implementation. All tools that have only
132+
# one class here, the corresponding class will create the
133+
# required category.
131134
Category.add("C_COMPILER")
132135
Category.add("C_PREPROCESSOR")
133136
Category.add("FORTRAN_COMPILER")

source/fab/tools/pfunit.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
##############################################################################
2+
# (c) Crown copyright Met Office. All rights reserved.
3+
# For further details please refer to the file COPYRIGHT
4+
# which you should have received as part of this distribution
5+
##############################################################################
6+
7+
"""
8+
This file contains the pFUnit tool for Fab.
9+
"""
10+
11+
import logging
12+
import os
13+
from pathlib import Path
14+
15+
from fab.tools.tool import Tool
16+
from fab.tools.category import Category
17+
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
class PfUnit(Tool):
23+
"""
24+
This is a class to encapsulate pFUnit. It relies on the environment
25+
variable $PFUNIT to indicate the location of the source code.
26+
This is required since besides .mod files and executable, it also
27+
contains the source code for a Fortran driver program .
28+
It assumes that pFUnit's preprocessor `funitproc` is in $PFUNIT/bin.
29+
"""
30+
Category.add("PFUNIT")
31+
32+
def __init__(self):
33+
pfunit_home = os.environ.get("PFUNIT", "")
34+
if not pfunit_home:
35+
logger.error("$PFUNIT not defined in environment, pFUnit will "
36+
"likely not work.")
37+
self._pfunit_home = Path(pfunit_home)
38+
39+
exec_name = self._pfunit_home / "bin" / "funitproc"
40+
super().__init__("funitproc", exec_name=exec_name,
41+
category=Category.PFUNIT,
42+
availability_option="-v")
43+
44+
def get_root_path(self) -> Path:
45+
"""
46+
:returns: the root path of pFUnit.
47+
"""
48+
return self._pfunit_home
49+
50+
def get_include_path(self) -> Path:
51+
"""
52+
:returns: the include directory for PFUnit.
53+
"""
54+
return self._pfunit_home / "include"
55+
56+
def get_driver_f90(self) -> str:
57+
"""
58+
:returns: the content of pFUnit's driver.F90 file.
59+
"""
60+
driver_path = self._pfunit_home / "include" / "driver.F90"
61+
with driver_path.open("r", encoding='utf-8') as f:
62+
driver_f90 = f.read()
63+
return driver_f90
64+
65+
def process(self, pf_path: Path,
66+
f90_out_path: Path):
67+
"""
68+
Processes the .pf file to create an output f90 file.
69+
70+
:param pf_path: the input path.
71+
:param f90_out_path: destination path.
72+
"""
73+
return self.run(additional_parameters=[pf_path, f90_out_path])

source/fab/tools/tool_repository.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from fab.tools.preprocessor import Cpp, CppFortran
2727
from fab.tools.compiler import (Craycc, Crayftn, Gcc, Gfortran, Icc, Icx,
2828
Ifort, Ifx, Nvc, Nvfortran)
29+
from fab.tools.pfunit import PfUnit
2930
from fab.tools.psyclone import Psyclone
3031
from fab.tools.rsync import Rsync
3132
from fab.tools.shell import Shell
@@ -74,7 +75,7 @@ def __init__(self):
7475
Icc, Icx, Ifort, Ifx,
7576
Nvc, Nvfortran,
7677
Cpp, CppFortran,
77-
Ar, Fcm, Git, Psyclone, Rsync, Subversion]:
78+
Ar, Fcm, Git, PfUnit, Psyclone, Rsync, Subversion]:
7879
self.add_tool(cls())
7980

8081
# Add a standard shell. Additional shells (bash, ksh, dash)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
##############################################################################
2+
# (c) Crown copyright Met Office. All rights reserved.
3+
# For further details please refer to the file COPYRIGHT
4+
# which you should have received as part of this distribution
5+
##############################################################################
6+
"""
7+
Tests 'pfunit' tool.
8+
"""
9+
10+
import logging
11+
from pathlib import Path
12+
13+
from pytest_subprocess.fake_process import FakeProcess
14+
15+
16+
from fab.tools.category import Category
17+
from fab.tools.pfunit import PfUnit
18+
19+
from tests.conftest import ExtendedRecorder, call_list
20+
21+
22+
def test_pfunit_constructor_no_env(monkeypatch, caplog) -> None:
23+
"""
24+
Tests constructor when $PFUNIT is not defined
25+
"""
26+
# Make sure the environment variable PFUNIT is not defined:
27+
monkeypatch.delenv("PFUNIT", raising=False)
28+
29+
with caplog.at_level(logging.ERROR):
30+
pfunit = PfUnit()
31+
assert ("$PFUNIT not defined in environment, pFUnit will likely "
32+
"not work." in caplog.text)
33+
assert len(caplog.records) == 1
34+
assert caplog.records[0].levelname == "ERROR"
35+
36+
assert pfunit.category == Category.PFUNIT
37+
assert pfunit.name == "funitproc"
38+
assert pfunit.exec_name == "funitproc"
39+
40+
41+
def test_pfunit_constructor_with_env(monkeypatch, caplog) -> None:
42+
"""
43+
Tests constructor when $PFUNIT is defined
44+
"""
45+
46+
# Make sure the environment variable PFUNIT is defined:
47+
monkeypatch.setenv("PFUNIT", "/tmp")
48+
49+
with caplog.at_level(logging.ERROR):
50+
pfunit = PfUnit()
51+
assert len(caplog.records) == 0
52+
assert pfunit.category == Category.PFUNIT
53+
assert pfunit.name == "funitproc"
54+
assert pfunit.exec_name == "funitproc"
55+
assert pfunit.get_root_path() == Path("/tmp")
56+
57+
58+
def test_pfunit_paths(monkeypatch) -> None:
59+
"""
60+
Tests root and include paths.
61+
"""
62+
63+
# Make sure the environment variable PFUNIT is defined:
64+
monkeypatch.setenv("PFUNIT", "/tmp")
65+
66+
pfunit = PfUnit()
67+
assert pfunit.get_root_path() == Path("/tmp")
68+
assert pfunit.get_include_path() == Path("/tmp/include")
69+
70+
71+
def test_pfunit_driver(monkeypatch, tmp_path: Path) -> None:
72+
"""
73+
Tests that pfunit reads the driver.F90 file:
74+
"""
75+
76+
# Make sure the environment variable PFUNIT is defined:
77+
monkeypatch.setenv("PFUNIT", str(tmp_path))
78+
include_path = tmp_path / "include"
79+
include_path.mkdir()
80+
(include_path / "driver.F90").write_text("DRIVER\n")
81+
82+
pfunit = PfUnit()
83+
assert pfunit.get_driver_f90() == "DRIVER\n"
84+
85+
86+
def test_pfunit_check_available(monkeypatch,
87+
subproc_record: ExtendedRecorder) -> None:
88+
"""
89+
Tests availability functionality.
90+
"""
91+
monkeypatch.setenv("PFUNIT", "/tmp")
92+
pfunit = PfUnit()
93+
assert pfunit.check_available()
94+
assert subproc_record.invocations() == [["/tmp/bin/funitproc", "-v"]]
95+
assert subproc_record.extras() == [{'cwd': None,
96+
'env': None,
97+
'stdout': None,
98+
'stderr': None}]
99+
100+
101+
def test_pfunit_check_unavailable(monkeypatch,
102+
fake_process: FakeProcess) -> None:
103+
"""
104+
Tests availability failure.
105+
"""
106+
monkeypatch.setenv("PFUNIT", "/tmp")
107+
fake_process.register(['/tmp/bin/funitproc', '-v'],
108+
returncode=1,
109+
stderr="Something went wrong.")
110+
pfunit = PfUnit()
111+
assert not pfunit.check_available()
112+
assert call_list(fake_process) == [["/tmp/bin/funitproc", "-v"]]
113+
114+
115+
def test_pfunit_process(monkeypatch,
116+
tmp_path: Path,
117+
subproc_record: ExtendedRecorder) -> None:
118+
"""
119+
Tests processing a file
120+
"""
121+
monkeypatch.setenv("PFUNIT", str(tmp_path))
122+
pfunit = PfUnit()
123+
pfunit.process(pf_path=tmp_path / "file.pf",
124+
f90_out_path=tmp_path / "file.f90")
125+
126+
assert subproc_record.invocations() \
127+
== [[str(tmp_path / "bin" / "funitproc"),
128+
str(tmp_path / "file.pf"),
129+
str(tmp_path / "file.f90")]]
130+
assert subproc_record.extras() == [{'cwd': None,
131+
'env': None,
132+
'stderr': None,
133+
'stdout': None}]

0 commit comments

Comments
 (0)