Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion source/fab/tools/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def is_compiler(self) -> bool:
FORTRAN_PREPROCESSOR: Category
GIT: Category
LINKER: Category
PFUNIT: Category
PSYCLONE: Category
RSYNC: Category
SHELL: Category
Expand All @@ -125,7 +126,10 @@ def is_compiler(self) -> bool:
CATEGORY_FOR_UNIT_TESTS: Category


# Now create the default categories that Fab needs
# Now create the default categories for all categories that
# have more than one implementation. All tools that have only
# one class here, the corresponding class will create the
# required category.
Category.add("C_COMPILER")
Category.add("C_PREPROCESSOR")
Category.add("FORTRAN_COMPILER")
Expand Down
72 changes: 72 additions & 0 deletions source/fab/tools/pfunit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
##############################################################################
# (c) Crown copyright Met Office. All rights reserved.
# For further details please refer to the file COPYRIGHT
# which you should have received as part of this distribution
##############################################################################

"""This file contains the Rsync class for synchronising file trees.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In response to this documentation I say "no it doesn't." Looks like a copy-and-paste mistake.

"""

import logging
import os
from pathlib import Path

from fab.tools.tool import Tool
from fab.tools.category import Category


logger = logging.getLogger(__name__)


class PfUnit(Tool):
"""
This is a class to encapsulate pFUnit. It relies on the environment
variable $PFUNIT to indicate the location of the source code.
This is required since besides .mod files and executable, it also
contains the source code for a Fortran driver program .
It assumes that pFUnit's preprocessor `funitproc` is in $PFUNIT/bin.
"""
Category.add("PFUNIT")

def __init__(self):
pfunit_home = os.environ.get("PFUNIT", "")
Comment thread
MatthewHambley marked this conversation as resolved.
if not pfunit_home:
logger.error("$PFUNIT not defined in environment, pFUnit will "
"likely not work.")
self._pfunit_home = Path(pfunit_home)

exec_name = self._pfunit_home / "bin" / "funitproc"
super().__init__("funitproc", exec_name=exec_name,
category=Category.PFUNIT,
availability_option="-v")

def get_root_path(self) -> Path:
"""
:returns: the root path of pFUnit.
"""
return self._pfunit_home

def get_include_path(self) -> Path:
"""
:returns: the include directory for PFUnit.
"""
return self._pfunit_home / "include"

def get_driver_f90(self) -> str:
"""
:returns: the content of pFUnit's driver.F90 file.
"""
driver_path = self._pfunit_home / "include" / "driver.F90"
with driver_path.open("r", encoding='utf-8') as f:
driver_f90 = f.read()
return driver_f90

def process(self, pf_path: Path,
f90_out_path: Path):
"""
Processes the .pf file to create an output f90 file.

:param pf_path: the input path.
:param f90_out_path: destination path.
"""
return self.run(additional_parameters=[pf_path, f90_out_path])
3 changes: 2 additions & 1 deletion source/fab/tools/tool_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from fab.tools.preprocessor import Cpp, CppFortran
from fab.tools.compiler import (Craycc, Crayftn, Gcc, Gfortran, Icc, Icx,
Ifort, Ifx, Nvc, Nvfortran)
from fab.tools.pfunit import PfUnit
from fab.tools.psyclone import Psyclone
from fab.tools.rsync import Rsync
from fab.tools.shell import Shell
Expand Down Expand Up @@ -74,7 +75,7 @@ def __init__(self):
Icc, Icx, Ifort, Ifx,
Nvc, Nvfortran,
Cpp, CppFortran,
Ar, Fcm, Git, Psyclone, Rsync, Subversion]:
Ar, Fcm, Git, PfUnit, Psyclone, Rsync, Subversion]:
self.add_tool(cls())

# Add a standard shell. Additional shells (bash, ksh, dash)
Expand Down
135 changes: 135 additions & 0 deletions tests/unit_tests/tools/test_pfunit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
##############################################################################
# (c) Crown copyright Met Office. All rights reserved.
# For further details please refer to the file COPYRIGHT
# which you should have received as part of this distribution
##############################################################################
"""
Tests 'pfunit' tool.
"""

import logging
from pathlib import Path

from pytest_subprocess.fake_process import FakeProcess


from fab.tools.category import Category
from fab.tools.pfunit import PfUnit

from tests.conftest import ExtendedRecorder, call_list


def test_pfunit_constructor_no_env(monkeypatch, caplog) -> None:
"""
Tests constructor when $PFUNIT is not defined
"""
# Make sure the environment variable PFUNIT is not defined:
monkeypatch.delenv("PFUNIT", raising=False)

with caplog.at_level(logging.ERROR):
pfunit = PfUnit()
assert ("$PFUNIT not defined in environment, pFUnit will likely "
"not work." in caplog.text)
assert len(caplog.records) == 1
assert caplog.records[0].levelname == "ERROR"

assert pfunit.category == Category.PFUNIT
assert pfunit.name == "funitproc"
assert pfunit.exec_name == "funitproc"
assert pfunit.get_flags() == []


def test_pfunit_constructor_with_env(monkeypatch, caplog) -> None:
"""
Tests constructor when $PFUNIT is defined
"""

# Make sure the environment variable PFUNIT is defined:
monkeypatch.setenv("PFUNIT", "/tmp")

with caplog.at_level(logging.ERROR):
pfunit = PfUnit()
assert len(caplog.records) == 0
assert pfunit.category == Category.PFUNIT
assert pfunit.name == "funitproc"
assert pfunit.exec_name == "funitproc"
assert pfunit.get_flags() == []
assert pfunit.get_root_path() == Path("/tmp")


def test_pfunit_paths(monkeypatch) -> None:
"""
Tests root and include paths.
"""

# Make sure the environment variable PFUNIT is defined:
monkeypatch.setenv("PFUNIT", "/tmp")

pfunit = PfUnit()
assert pfunit.get_root_path() == Path("/tmp")
assert pfunit.get_include_path() == Path("/tmp/include")


def test_pfunit_driver(monkeypatch, tmp_path: Path) -> None:
"""
Tests that pfunit reads the driver.F90 file:
"""

# Make sure the environment variable PFUNIT is defined:
monkeypatch.setenv("PFUNIT", str(tmp_path))
include_path = tmp_path / "include"
include_path.mkdir()
(include_path / "driver.F90").write_text("DRIVER\n")

pfunit = PfUnit()
assert pfunit.get_driver_f90() == "DRIVER\n"


def test_pfunit_check_available(monkeypatch,
subproc_record: ExtendedRecorder) -> None:
"""
Tests availability functionality.
"""
monkeypatch.setenv("PFUNIT", "/tmp")
pfunit = PfUnit()
assert pfunit.check_available()
assert subproc_record.invocations() == [["/tmp/bin/funitproc", "-v"]]
assert subproc_record.extras() == [{'cwd': None,
'env': None,
'stdout': None,
'stderr': None}]


def test_pfunit_check_unavailable(monkeypatch,
fake_process: FakeProcess) -> None:
"""
Tests availability failure.
"""
monkeypatch.setenv("PFUNIT", "/tmp")
fake_process.register(['/tmp/bin/funitproc', '-v'],
returncode=1,
stderr="Something went wrong.")
pfunit = PfUnit()
assert not pfunit.check_available()
assert call_list(fake_process) == [["/tmp/bin/funitproc", "-v"]]


def test_pfunit_process(monkeypatch,
tmp_path: Path,
subproc_record: ExtendedRecorder) -> None:
"""
Tests processing a file
"""
monkeypatch.setenv("PFUNIT", str(tmp_path))
pfunit = PfUnit()
pfunit.process(pf_path=tmp_path / "file.pf",
f90_out_path=tmp_path / "file.f90")

assert subproc_record.invocations() \
== [[str(tmp_path / "bin" / "funitproc"),
str(tmp_path / "file.pf"),
str(tmp_path / "file.f90")]]
assert subproc_record.extras() == [{'cwd': None,
'env': None,
'stderr': None,
'stdout': None}]
Loading