diff --git a/source/fab/tools/psyclone.py b/source/fab/tools/psyclone.py index 1a2b3b40..cbf12a9f 100644 --- a/source/fab/tools/psyclone.py +++ b/source/fab/tools/psyclone.py @@ -8,7 +8,9 @@ """ from pathlib import Path +import re from typing import Callable, List, Optional, TYPE_CHECKING, Union +import warnings from fab.tools.category import Category from fab.tools.tool import Tool @@ -24,45 +26,163 @@ class Psyclone(Tool): '''This is the base class for `PSyclone`. ''' - def __init__(self, api: Optional[str] = None): + def __init__(self): super().__init__("psyclone", "psyclone", Category.PSYCLONE) - self._api = api + self._version = None + + def check_available(self) -> bool: + '''This function determines if PSyclone is available. Additionally, + it established the version, since command line option changes + significantly from python 2.5.0 to the next release. + ''' + + # First get the version (and confirm that PSyclone is installed): + try: + # Older versions of PSyclone (2.3.1 and earlier) expect a filename + # even when --version is used, and won't produce version info + # without this. So provide a dummy file (which does not need to + # exist), and check the error for details to see if PSyclone does + # not exist, or if the error is because of the non-existing file + version_output = self.run(["--version", "does_not_exist"], + capture_output=True) + except RuntimeError as err: + # If the command is not found, the error contains the following: + if "could not be executed" in str(err): + return False + # Otherwise, psyclone likely complained about the not existing + # file. Continue and try to find version information in the output: + version_output = str(err) + + # Search for the version info: + exp = r"PSyclone version: (\d[\d.]+\d)" + matches = re.search(exp, version_output) + if not matches: + warnings.warn(f"Unexpected version information for PSyclone: " + f"'{version_output}'.") + # If we don't recognise the version number, something is wrong + return False + + # Now convert the version info to integer. The regular expression + # match guarantees that we have integer numbers now: + version = tuple(int(x) for x in matches.groups()[0].split('.')) + + if version == (2, 5, 0): + # The behaviour of PSyclone changes from 2.5.0 to the next + # release. But since head-of-trunk still reports 2.5.0, we + # need to run additional tests to see if we have the official + # 2.5.0 release, or current trunk (which already has the new + # command line options). PSyclone needs an existing file + # in order to work, so use __file__ to present this file. + # PSyclone will obviously abort since this is not a Fortran + # file, but we only need to check the error message to + # see if the domain name is incorrect (--> current trunk) + # or not (2.5.0 release) + try: + self.run(["-api", "nemo", __file__], capture_output=True) + except RuntimeError as err: + if "Unsupported PSyKAL DSL / API 'nemo' specified" in str(err): + # It is current development. Just give it a version number + # greater than 2.5.0 for now, till the official release + # is done. + version = (2, 5, 0, 1) + + self._version = version + return True def process(self, config: "BuildConfig", x90_file: Path, - psy_file: Path, - alg_file: Union[Path, str], + psy_file: Optional[Path] = None, + alg_file: Optional[Union[Path, str]] = None, + transformed_file: Optional[Path] = None, transformation_script: Optional[Callable[[Path, "BuildConfig"], Path]] = None, additional_parameters: Optional[List[str]] = None, kernel_roots: Optional[List[Union[str, Path]]] = None, api: Optional[str] = None, ): - # pylint: disable=too-many-arguments - '''Run PSyclone with the specified parameters. + # 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 + features, api must be a valid PSyclone API, and the two output + filenames are `psy_file` and `alg_file`. :param api: the PSyclone API. :param x90_file: the input file for PSyclone :param psy_file: the output PSy-layer file. :param alg_file: the output modified algorithm file. + :param transformed_file: the output filename if PSyclone is called + as transformation tool. :param transformation_script: an optional transformation script :param additional_parameters: optional additional parameters for PSyclone :param kernel_roots: optional directories with kernels. ''' + 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. + if not psy_file: + raise RuntimeError(f"PSyclone called with api '{api}', but " + f"no psy_file is specified.") + if not alg_file: + raise RuntimeError(f"PSyclone called with api '{api}', but " + f"no alg_file is specified.") + if transformed_file: + raise RuntimeError(f"PSyclone called with api '{api}' and " + f"transformed_file.") + else: + if psy_file: + raise RuntimeError("PSyclone called without api, but " + "psy_file is specified.") + if alg_file: + raise RuntimeError("PSyclone called without api, but " + "alg_file is specified.") + if not transformed_file: + raise RuntimeError("PSyclone called without api, but " + "transformed_file is not specified.") + parameters: List[Union[str, Path]] = [] # If an api is defined in this call (or in the constructor) add it # as parameter. No API is required if PSyclone works as # transformation tool only, so calling PSyclone without api is # actually valid. if api: - parameters.extend(["-api", api]) - elif self._api: - parameters.extend(["-api", self._api]) - - parameters.extend(["-l", "all", "-opsy", psy_file, "-oalg", alg_file]) + if self._version > (2, 5, 0): + api_param = "--psykal-dsl" + # Mapping from old names to new names: + mapping = {"dynamo0.3": "lfric", + "gocean1.0": "gocean"} + else: + api_param = "-api" + # Mapping from new names to old names: + mapping = {"lfric": "dynamo0.3", + "gocean": "gocean1.0"} + # Make mypy happy - we tested above that these variables + # are defined + assert psy_file + assert alg_file + parameters.extend([api_param, mapping.get(api, api), + "-opsy", psy_file, "-oalg", alg_file]) + else: # no api + # Make mypy happy - we tested above that transformed_file is + # specified when no api is specified. + assert transformed_file + if self._version > (2, 5, 0): + # New version: no API, parameter, but -o for output name: + parameters.extend(["-o", transformed_file]) + else: + # 2.5.0 or earlier: needs api nemo, output name is -oalg + parameters.extend(["-api", "nemo", "-opsy", transformed_file]) + parameters.extend(["-l", "all"]) if transformation_script: transformation_script_return_path = \ diff --git a/source/fab/tools/versioning.py b/source/fab/tools/versioning.py index 17534c1a..410cc250 100644 --- a/source/fab/tools/versioning.py +++ b/source/fab/tools/versioning.py @@ -3,11 +3,10 @@ # For further details please refer to the file COPYRIGHT # which you should have received as part of this distribution ############################################################################## - -"""This file contains the base class for versioning tools like git and -subversion. It also contains derived classes Git, Subversion, and Fcm. """ - +Versioning tools such as Subversion and Git. +""" +from abc import ABC from pathlib import Path from typing import Dict, List, Optional, Union @@ -15,25 +14,29 @@ from fab.tools.tool import Tool -class Versioning(Tool): - '''This is the base class for versioning tools like git and svn. - - :param name: the name of the tool. - :param exec_name: the name of the executable of this tool. - :param category: the category to which this tool belongs). - ''' - +class Versioning(Tool, ABC): + """ + Base class for versioning tools like Git and Subversion. + """ def __init__(self, name: str, exec_name: Union[str, Path], category: Category): + """ + Constructor. + + :param name: Display name of this tool. + :param exec_name: Executable for this tool. + :param category: Tool belongs to this category. + """ super().__init__(name, exec_name, category, availability_option="help") # ============================================================================= class Git(Versioning): - '''This is the base class for git. - ''' + """ + Interface to Git version control system. + """ def __init__(self): super().__init__("git", "git", @@ -111,20 +114,23 @@ def merge(self, dst: Union[str, Path], # ============================================================================= class Subversion(Versioning): - '''This is the base class for subversion. Note that this is also the - base class for FCM, so it allows overwriting name, exec_name and - category, but will default to use svn. - - :param name: name of the tool, defaults to subversion. - :param exec_name: name of the executable, defaults to "svn". - :param category: the category, FCM or SUBVERSION (the latter is - the default) - ''' - + """ + Interface to the Subversion version control system. + """ def __init__(self, name: Optional[str] = None, exec_name: Optional[Union[str, Path]] = None, category: Category = Category.SUBVERSION): - name = name or "subversion" + """ + Constructor. + + This is class is extended by the FCM interface which is why name and + executable are mutable. + + :param name: Tool name, defaults to "subversion." + :param exec_name: Tool executable, defaults to "svn." + :param category: Tool category, defaults to SUBVERSION. + """ + name = name or "Subversion" exec_name = exec_name or "svn" super().__init__(name, exec_name, category=category) @@ -166,7 +172,9 @@ def export(self, src: Union[str, Path], :param dst: destination path. :param revision: revision to export. ''' - self.execute(['export', '--force'], revision, [str(src), str(dst)]) + self.execute(['export', '--force'], + revision, + [str(src), str(dst)]) def checkout(self, src: Union[str, Path], dst: Union[str, Path], @@ -214,4 +222,4 @@ class Fcm(Subversion): ''' def __init__(self): - super().__init__("fcm", "fcm", Category.FCM) + super().__init__("FCM", "fcm", Category.FCM) diff --git a/tests-old/TestCases/CompiletimeDependency/Makefile b/tests-old/TestCases/CompiletimeDependency/Makefile deleted file mode 100644 index 41b2f272..00000000 --- a/tests-old/TestCases/CompiletimeDependency/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################### -# The best way to show how a test case should be built is to build it. -# While we don't have a build system we will use someone elses. -# -# This test builds the same set of source with and without the BEEF macro set. -# -# This will cause the resulting executables to behave differently. -############################################################################### -# The Fortran compiler has to be gfortran as we use the -J argument to redirect -# module file output. -# -export FC = gfortran -export CC ?= gcc - -# By default gmake sets FC to "f77" we need to detect that and force it to our -# default. If it is not set then we still have a default but we allow the user -# to override it. -# -ifeq "x$(shell command -v $(FC))" "x" -$(error Could not find gfortran on PATH) -endif - -all: with-beef without-beef - -with-beef: - $(MAKE) -f with-beef.mk - -without-beef: - $(MAKE) -f without-beef.mk - -clean: - $(MAKE) -f with-beef.mk clean - $(MAKE) -f without-beef.mk clean diff --git a/tests-old/TestCases/CompiletimeDependency/bisto.h b/tests-old/TestCases/CompiletimeDependency/bisto.h deleted file mode 100644 index bcddc2a7..00000000 --- a/tests-old/TestCases/CompiletimeDependency/bisto.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef BISTO_H -#define BISTO_H - -#define LIMIT 5 - -#endif diff --git a/tests-old/TestCases/CompiletimeDependency/hillfort.F90 b/tests-old/TestCases/CompiletimeDependency/hillfort.F90 deleted file mode 100644 index 27907780..00000000 --- a/tests-old/TestCases/CompiletimeDependency/hillfort.F90 +++ /dev/null @@ -1,20 +0,0 @@ -program hillfort - - use iso_fortran_env, only : input_unit, output_unit -#ifdef BEEF - use support_mod, only : characters_in_number -#endif - - implicit none - - integer :: input = 50 - - write(output_unit, '("Input is ", I0)') input - -#ifdef BEEF - write(output_unit, & - '("Number is ", I0, " characters long")') characters_in_number(input) -#endif - write(output_unit, '("Halving the number gives ", I0)') input / 2 - -end program hillfort diff --git a/tests-old/TestCases/CompiletimeDependency/looper.c b/tests-old/TestCases/CompiletimeDependency/looper.c deleted file mode 100644 index 857bac43..00000000 --- a/tests-old/TestCases/CompiletimeDependency/looper.c +++ /dev/null @@ -1,17 +0,0 @@ -#include - -#ifdef BEEF -#include "oxo.h" -#else -#include "bisto.h" -#endif - -int main(int argc, char **argv) { - int counter; - - for (counter=0; counter $@ - -$(DIR)hillfort.expected: | $(DIR) - printf "Input is 50\nNumber is 2 characters long\nHalving the number gives 25\n" > $@ - -test-looper: $(DIR)looper.out $(DIR)looper.expected - diff $^ - -$(DIR)looper.out: $(DIR)looper - ./$< > $@ - -$(DIR)looper.expected: | $(DIR) - printf "Test string\nTest string\nTest string\n" > $@ - -$(DIR)hillfort: $(DIR)support_mod.o $(DIR)hillfort.o - @echo Linking $@ - $(FC) -o $@ $(FFLAGS) $^ - -$(DIR)looper: $(DIR)looper.o - @echo Linking $@ - $(CC) -o $@ $(CFLAGS) $^ - -$(DIR)%.o: %.c | $(DIR) - @echo Compiling $@ - $(CC) -o $@ $(CFLAGS) -c $< - -$(DIR)%.o $(DIR)%.mod: %.f90 | $(DIR) - @echo Compiling $@ - $(FC) -o $(DIR)$*.o $(FFLAGS) -J$(DIR) -c $< - -$(DIR)%.o $(DIR)%.mod: %.F90 | $(DIR) - @echo Compiling $@ - $(FC) -o $(DIR)$*.o $(FFLAGS) -J$(DIR) -c $< - -$(DIR)hillfort.o: hillfort.F90 $(DIR)support_mod.mod -$(DIR)looper.o: looper.c oxo.h -$(DIR)support_mod.o $(DIR)support_mod.mod: support_mod.f90 - -$(DIR): - mkdir -p $@ - -clean: - -rm -r $(DIR) diff --git a/tests-old/TestCases/CompiletimeDependency/without-beef.mk b/tests-old/TestCases/CompiletimeDependency/without-beef.mk deleted file mode 100644 index 09eb3f0b..00000000 --- a/tests-old/TestCases/CompiletimeDependency/without-beef.mk +++ /dev/null @@ -1,55 +0,0 @@ -$(info Building without beef) - -export CFLAGS = -export FFLAGS = - -DIR = nobeef/ - -all: test-hillfort test-looper - -test-hillfort: $(DIR)hillfort.out $(DIR)hillfort.expected - diff $^ - -$(DIR)hillfort.out: $(DIR)hillfort - ./$< > $@ - -$(DIR)hillfort.expected: | $(DIR) - printf "Input is 50\nHalving the number gives 25\n" > $@ - -test-looper: $(DIR)looper.out $(DIR)looper.expected - diff $^ - -$(DIR)looper.out: $(DIR)looper - ./$< > $@ - -$(DIR)looper.expected: | $(DIR) - printf "Test string\nTest string\nTest string\nTest string\nTest string\n" > $@ - -$(DIR)hillfort: $(DIR)hillfort.o - @echo Linking $@ - $(FC) -o $@ $(FFLAGS) $^ - -$(DIR)looper: $(DIR)looper.o - @echo Linking $@ - $(CC) -o $@ $(CFLAGS) $^ - -$(DIR)%.o: %.c | $(DIR) - @echo Compiling $@ - $(CC) -o $@ $(CFLAGS) -c $< - -$(DIR)%.o $(DIR)%.mod: %.f90 | $(DIR) - @echo Compiling $@ - $(FC) -o $(DIR)$*.o $(FFLAGS) -c $< - -$(DIR)%.o $(DIR)%.mod: %.F90 | $(DIR) - @echo Compiling $@ - $(FC) -o $(DIR)$*.o $(FFLAGS) -c $< - -$(DIR)hillfort.o: hillfort.F90 -$(DIR)looper.o: looper.c bisto.h - -$(DIR): - mkdir -p $(DIR) - -clean: - -rm -r $(DIR) diff --git a/tests-old/TestCases/FortranSourceTree/ReadMe b/tests-old/TestCases/FortranSourceTree/ReadMe deleted file mode 100644 index dda01562..00000000 --- a/tests-old/TestCases/FortranSourceTree/ReadMe +++ /dev/null @@ -1,8 +0,0 @@ -Although this directory holds source it is not intended to be compiled. - -Instead it demonstrates the various aspects of a Fortran source tree. It is -intended to exercise corner cases for tools like the database explorer. To -that end there are multiple files containing identically named (but -different) modules. Obviously these cannot be linked as there are ambiguous -symbol names. But the source analysis stage has to be able to deal with -things like this. diff --git a/tests-old/TestCases/FortranSourceTree/fpp_one.f90 b/tests-old/TestCases/FortranSourceTree/fpp_one.f90 deleted file mode 100644 index 574ba1bf..00000000 --- a/tests-old/TestCases/FortranSourceTree/fpp_one.f90 +++ /dev/null @@ -1,23 +0,0 @@ -#if CHOOSE == ONE -module fpp_mod - - use nosuch_mod, only : nonexistant - - implicit none - - public fpp_choice - -contains - - function fpp_choice() - - implicit none - - character(3) :: fpp_choice - - fpp_choice = "ONE" - - end function fpp_choice - -end module fpp_mod -#endif diff --git a/tests-old/TestCases/FortranSourceTree/fpp_two.f90 b/tests-old/TestCases/FortranSourceTree/fpp_two.f90 deleted file mode 100644 index 0b792779..00000000 --- a/tests-old/TestCases/FortranSourceTree/fpp_two.f90 +++ /dev/null @@ -1,23 +0,0 @@ -#if CHOOSE == TWO -module fpp_mod - - implicit none - - public fpp_choice - -contains - - function fpp_choice() - - use unfound_mod, only : not_there - - implicit none - - character(3) :: fpp_choice - - fpp_choice = "TWO" - - end function fpp_choice - -end module fpp_mod -#endif diff --git a/tests-old/TestCases/FortranSourceTree/link1.f90 b/tests-old/TestCases/FortranSourceTree/link1.f90 deleted file mode 100644 index 00c8887e..00000000 --- a/tests-old/TestCases/FortranSourceTree/link1.f90 +++ /dev/null @@ -1,19 +0,0 @@ -module link_mod - - implicit none - - public link_choice - -contains - - function link_choice() - - implicit none - - integer :: link_choice - - link_choice = 1 - - end function link_choice - -end module link_mod diff --git a/tests-old/TestCases/FortranSourceTree/link2.f90 b/tests-old/TestCases/FortranSourceTree/link2.f90 deleted file mode 100644 index b7e86d5f..00000000 --- a/tests-old/TestCases/FortranSourceTree/link2.f90 +++ /dev/null @@ -1,19 +0,0 @@ -module link_mod - - implicit none - - public link_choice - -contains - - function link_choice() - - implicit none - - integer :: link_choice - - link_choice = 2 - - end function link_choice - -end module link_mod diff --git a/tests-old/TestCases/FortranSourceTree/program.F90 b/tests-old/TestCases/FortranSourceTree/program.F90 deleted file mode 100644 index 2e5caafe..00000000 --- a/tests-old/TestCases/FortranSourceTree/program.F90 +++ /dev/null @@ -1,13 +0,0 @@ -program thingumy - - use iso_fortran_env, only : output_unit - use link_mod, only : link_choice - use fpp_mod, only : fpp_choice - - implicit none - - write(output_unit, '("Someone made a decission")') - write(output_unit, '("By linking choice ", I0)') link_choice() - write(output_unit, '("By setting preprocessor variable CHOOSE to ", A)') fpp_choice() - -end program thingumy diff --git a/tests-old/TestCases/FortranSubmodule/Makefile b/tests-old/TestCases/FortranSubmodule/Makefile deleted file mode 100644 index def8a21e..00000000 --- a/tests-old/TestCases/FortranSubmodule/Makefile +++ /dev/null @@ -1,47 +0,0 @@ -# The best way to show how a test case should be built is to build it. -# While we don't have a build system we will use someone elses. -# -.SUFFIXES: -.SUFFIXES: .f90 .o .mod - -# By default gmake sets FC to "f77" we need to detect that and force it to our -# default. If it is not set then we still have a default but we allow the user -# to override it. -# -ifeq "$(origin FC)" "default" -export FC = ifort -else -export FC ?= ifort -endif - -OBJECTS = simple_impl.o simple_mod.o class_mod.o class_impl.o test.o - -verify: test.out test.expected - diff $^ - -test.out: test - ./$< >$@ - -test.expected: - printf "Doubler in submodule 14\n\nInitial value 12\nAfter submodule method 29\n" >$@ - -test: $(OBJECTS) - @echo Linking $@ - $(FC) -o $@ $(OBJECTS) - -.f90.o: - @echo Building $@ - $(FC) -o $@ -c $< - -.f90.mod: - @echo Building $@ - $(FC) -o $*.o -c $< - -simple_mod.o simple_mod.mod: simple_mod.f90 -simple_impl.o: simple_impl.f90 simple_mod.mod -class_mod.o class_mod.mod: class_mod.f90 -class_impl.o: class_impl.f90 class_mod.mod -test.o: test.f90 simple_mod.mod class_mod.mod - -clean: - rm *.o *.mod *.smod test test.out test.expected diff --git a/tests-old/TestCases/FortranSubmodule/class_impl.f90 b/tests-old/TestCases/FortranSubmodule/class_impl.f90 deleted file mode 100644 index cd5c2fc6..00000000 --- a/tests-old/TestCases/FortranSubmodule/class_impl.f90 +++ /dev/null @@ -1,28 +0,0 @@ -submodule(class_mod) class_impl - - implicit none - -contains - - module function bar_initialiser( starter ) result(instance) - implicit none - integer, intent(in) :: starter - type(bar_type) :: instance - instance%stuff = starter - end function bar_initialiser - - - module subroutine bar_mangle(this, factor) - implicit none - class(bar_type), intent(inout) :: this - integer, intent(in) :: factor - this%stuff = ieor(this%stuff, factor) - end subroutine bar_mangle - - - module procedure bar_howmuch ! Alternative syntax - implicit none - bar_howmuch = this%stuff - end procedure bar_howmuch - -end submodule class_impl diff --git a/tests-old/TestCases/FortranSubmodule/class_mod.f90 b/tests-old/TestCases/FortranSubmodule/class_mod.f90 deleted file mode 100644 index 25d6616c..00000000 --- a/tests-old/TestCases/FortranSubmodule/class_mod.f90 +++ /dev/null @@ -1,59 +0,0 @@ -module class_mod - - implicit none - - type, abstract :: foo_type - private - integer :: stuff - contains - private - procedure(mangle_if), public, deferred :: mangle - procedure(how_much_if), public, deferred :: how_much - end type foo_type - - interface - subroutine mangle_if(this, factor) - import foo_type - implicit none - class(foo_type), intent(inout) :: this - integer, intent(in) :: factor - end subroutine mangle_if - function how_much_if(this) - import foo_type - implicit none - class(foo_type), intent(inout) :: this - integer :: how_much_if - end function how_much_if - end interface - - type, extends(foo_type) :: bar_type - private - contains - private - procedure, public :: mangle => bar_mangle - procedure, public :: how_much => bar_howmuch - end type bar_type - - interface bar_type - procedure bar_initialiser - end interface bar_type - - interface - module function bar_initialiser(starter) result(instance) - implicit none - integer,intent(in) :: starter - type(bar_type) :: instance - end function bar_initialiser - module subroutine bar_mangle(this, factor) - implicit none - class(bar_type), intent(inout) :: this - integer, intent(in) :: factor - end subroutine bar_mangle - module function bar_howmuch(this) - implicit none - class(bar_type), intent(inout) :: this - integer :: bar_howmuch - end function bar_howmuch - end interface - -end module class_mod diff --git a/tests-old/TestCases/FortranSubmodule/simple_impl.f90 b/tests-old/TestCases/FortranSubmodule/simple_impl.f90 deleted file mode 100644 index 74b63601..00000000 --- a/tests-old/TestCases/FortranSubmodule/simple_impl.f90 +++ /dev/null @@ -1,18 +0,0 @@ -submodule(simple_mod) simple_impl - - implicit none - -contains - - module function returnerer(thing) - - implicit none - - integer, intent(in) :: thing - integer :: returnerer - - returnerer = 2 * thing - - end function returnerer - -end submodule simple_impl diff --git a/tests-old/TestCases/FortranSubmodule/simple_mod.f90 b/tests-old/TestCases/FortranSubmodule/simple_mod.f90 deleted file mode 100644 index 8bd6b4d9..00000000 --- a/tests-old/TestCases/FortranSubmodule/simple_mod.f90 +++ /dev/null @@ -1,13 +0,0 @@ -module simple_mod - - implicit none - - interface - module function returnerer(thing) - implicit none - integer, intent(in) :: thing - integer :: returnerer - end function returnerer - end interface - -end module simple_mod diff --git a/tests-old/TestCases/FortranSubmodule/test.f90 b/tests-old/TestCases/FortranSubmodule/test.f90 deleted file mode 100644 index 3ac9f4c2..00000000 --- a/tests-old/TestCases/FortranSubmodule/test.f90 +++ /dev/null @@ -1,22 +0,0 @@ -program test - - use iso_fortran_env, only : output_unit - - use class_mod, only : bar_type - use simple_mod, only : returnerer - - implicit none - - type(bar_type) :: thing - - thing = bar_type(12) - - write(output_unit, '("Doubler in submodule ", I0)') returnerer(7) - write(output_unit, '()') - - write(output_unit, '("Initial value ", I0)') thing%how_much() - call thing%mangle(17) - write(output_unit, & - '("After submodule method ", I0)') thing%how_much() - -end program test diff --git a/tests-old/TestCases/Makefile b/tests-old/TestCases/Makefile deleted file mode 100644 index 506623c4..00000000 --- a/tests-old/TestCases/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -THIS_DIR := $(dir $(lastword $(MAKEFILE_LIST))) - -TEST_DIRS := $(shell find $(THIS_DIR) -type d -mindepth 1 -maxdepth 1 -print) - -run: $(addprefix run/, $(TEST_DIRS)) - -run/%: - $(MAKE) -C $* - -clean: $(addprefix clean/, $(TEST_DIRS)) - -clean/%: - $(MAKE) -C $* clean diff --git a/tests-old/TestCases/MinimalC/Makefile b/tests-old/TestCases/MinimalC/Makefile deleted file mode 100644 index 95242a70..00000000 --- a/tests-old/TestCases/MinimalC/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# The best way to show how a test case should be built is to build it. -# While we don't have a build system we will use someone elses. -# -.SUFFIXES: -.SUFFIXES: .c .o - -# By default gmake sets FC to "cc" we need to detect that and force it to our -# default. If it is not set then we still have a default but we allow the user -# to override it. -# -ifeq "$(origin CC)" "default" -export CC = gcc -else -export CC ?= gcc -endif - -OBJECTS = program.o - -verify: test.out test.expected - diff $^ - -test.out: test - ./$< >$@ - -test.expected: - printf "Hello world!\n" >$@ - -test: $(OBJECTS) - @echo Linking $@ - $(CC) -o $@ $(OBJECTS) - -.c.o: - @echo Compiling $@ - $(CC) -o $@ -c $< - -program.o: program.c - -clean: - rm *.o test test.out test.expected diff --git a/tests-old/TestCases/MinimalC/program.c b/tests-old/TestCases/MinimalC/program.c deleted file mode 100644 index ccec439e..00000000 --- a/tests-old/TestCases/MinimalC/program.c +++ /dev/null @@ -1,11 +0,0 @@ -/***************************************************************************** - * It's that simple example program again. - *****************************************************************************/ - -#include - -int main(int argc, char **argv) -{ - printf("Hello world!\n"); - return 0; -} diff --git a/tests-old/TestCases/PSyclone/Makefile b/tests-old/TestCases/PSyclone/Makefile deleted file mode 100644 index ca4e7069..00000000 --- a/tests-old/TestCases/PSyclone/Makefile +++ /dev/null @@ -1,56 +0,0 @@ -# The best way to show how a test case should be built is to build it. -# While we don't have a build system we will use someone elses. -# -# This test assumes that the PSyclone code generation tool is available on the -# executino path. i.e. That the directory containing it appears in the PATH -# environment variable. -# -.SUFFIXES: -.SUFFIXES: .x90 .f90 .F90 .o .mod - -# The compiler has to be ifort as we use the "-module" argument to redirect -# module file storage. -# -export FC = ifort - -PSYCLONE = psyclone - -ifeq "x$(shell command -v $(PSYCLONE))" "x" -$(error Could not find the PSyclone script on PATH) -endif - -DIRECTORIES = kernels model - -objects: kernels/my_kernel_mod.o algorithm_mod.o algorithm_mod_psy.o - -%.o %.mod: %.f90 - @echo Compiling $< - $(FC) -o $*.o -module $(dir $@) $(addprefix -I,$(DIRECTORIES)) -c $< - -%_psy.f90 %.f90: %.x90 optimisation.py - @echo Psyclone $< - $(PSYCLONE) -oalg $*.f90 -opsy $*_psy.f90 -d kernels \ - -s $(realpath optimisation.py) \ - -api dynamo0.3 -l -nodm $< - -algorithm_mod.f90 \ -algorithm_mod_psy.f90: algorithm_mod.x90 \ - kernels/my_kernel_mod.f90 optimisation.py -algorithm_mod.o \ -algorithm_mod.mod: algorithm_mod.f90 algorithm_mod_psy.mod \ - model/field_mod.mod kernels/my_kernel_mod.mod -algorithm_mod_psy.o \ -algorithm_mod_psy.mod: algorithm_mod_psy.f90 \ - model/field_mod.mod model/operator_mod.mod \ - kernels/my_kernel_mod.mod -kernels/my_kernel_mod.o \ -kernels/my_kernel_mod.mod: kernels/my_kernel_mod.f90 \ - model/argument_mod.mod model/constants_mod.mod \ - model/functionspace_mod.mod model/kernel_mod.mod \ - -model/field_mod.o \ -model/field_mod.mod: model/field_mod.f90 model/functionspace_mod.mod - -clean: - -rm -r *.o *.mod kernels/*.o kernels/*.mod model/*.o model/*.mod - -rm *.pyc algorithm_mod.f90 algorithm_mod_psy.f90 diff --git a/tests-old/TestCases/PSyclone/algorithm_mod.x90 b/tests-old/TestCases/PSyclone/algorithm_mod.x90 deleted file mode 100644 index cfac16bb..00000000 --- a/tests-old/TestCases/PSyclone/algorithm_mod.x90 +++ /dev/null @@ -1,21 +0,0 @@ -module algorithm_mod - - use field_mod, only : field_type - use my_kernel_mod, only : my_kernel_type - - implicit none - -contains - - subroutine algorithm() - - implicit none - - type(field_type) :: field - - field = field_type() - call invoke( name='a_test', my_kernel_type( field ) ) - - end subroutine algorithm - -end module algorithm_mod diff --git a/tests-old/TestCases/PSyclone/kernels/my_kernel_mod.f90 b/tests-old/TestCases/PSyclone/kernels/my_kernel_mod.f90 deleted file mode 100644 index 035de698..00000000 --- a/tests-old/TestCases/PSyclone/kernels/my_kernel_mod.f90 +++ /dev/null @@ -1,49 +0,0 @@ -module my_kernel_mod - - use argument_mod, only : arg_type, cells, gh_field, gh_write - use functionspace_mod, only : w3 - use kernel_mod, only : kernel_type - - implicit none - - private - - type, public, extends(kernel_type) :: my_kernel_type - private - type(arg_type) :: meta_args(1) = (/ & - arg_type( gh_field, gh_write, w3 ) & - /) - integer :: iterates_over = cells - contains - procedure, nopass :: my_kernel_code - end type - - public :: my_kernel_code - -contains - - subroutine my_kernel_code( nlayers, field_1_w3, ndf_w3, undf_w3, map_w3 ) - - use constants_mod, only : r_def - - implicit none - - integer, intent(in) :: nlayers - integer, intent(in) :: ndf_w3 - integer, intent(in) :: undf_w3 - real(kind=r_def), intent(out) :: field_1_w3(undf_w3) - integer, intent(in) :: map_w3(ndf_w3) - - integer :: d, k - real :: v(ndf_w3) - - call random_number(v) - do k=0, nlayers - do d=0, ndf_w3 - field_1_w3(map_w3(d)) = v(d) + k - end do - end do - - end subroutine my_kernel_code - -end module my_kernel_mod diff --git a/tests-old/TestCases/PSyclone/model/argument_mod.f90 b/tests-old/TestCases/PSyclone/model/argument_mod.f90 deleted file mode 100644 index 2cf7638c..00000000 --- a/tests-old/TestCases/PSyclone/model/argument_mod.f90 +++ /dev/null @@ -1,21 +0,0 @@ -module argument_mod - - implicit none - - private - - integer, public, parameter :: gh_field = 507 - integer, public, parameter :: gh_write = 65 - integer, public, parameter :: cells = 396 - - type, public :: arg_type - integer :: arg_type - integer :: arg_intent - integer :: wspace = -1 - integer :: from_wspace = -1 - integer :: stencil_map = -1 - integer :: mesh_arg = -1 - end type arg_type - -end module argument_mod - diff --git a/tests-old/TestCases/PSyclone/model/constants_mod.f90 b/tests-old/TestCases/PSyclone/model/constants_mod.f90 deleted file mode 100644 index bc5170e2..00000000 --- a/tests-old/TestCases/PSyclone/model/constants_mod.f90 +++ /dev/null @@ -1,11 +0,0 @@ -module constants_mod - - use iso_fortran_env, only : real64 - - implicit none - - private - - integer, public, parameter :: r_def = real64 - -end module constants_mod diff --git a/tests-old/TestCases/PSyclone/model/field_mod.f90 b/tests-old/TestCases/PSyclone/model/field_mod.f90 deleted file mode 100644 index 5fb9b872..00000000 --- a/tests-old/TestCases/PSyclone/model/field_mod.f90 +++ /dev/null @@ -1,32 +0,0 @@ -module field_mod - - use constants_mod, only : r_def - use functionspace_mod, only : functionspace_type - - implicit none - - private - - type, public :: field_type - private - contains - private - procedure, public :: get_proxy - end type field_type - - type, public :: field_proxy_type - private - real(r_def), public :: data(10) - type(functionspace_type), public :: vspace - end type field_proxy_type - -contains - - function get_proxy(this) - implicit none - class(field_type), intent(inout) :: this - type(field_proxy_type) :: get_proxy - get_proxy%vspace = functionspace_type() - end function get_proxy - -end module field_mod diff --git a/tests-old/TestCases/PSyclone/model/functionspace_mod.f90 b/tests-old/TestCases/PSyclone/model/functionspace_mod.f90 deleted file mode 100644 index 56c9b016..00000000 --- a/tests-old/TestCases/PSyclone/model/functionspace_mod.f90 +++ /dev/null @@ -1,78 +0,0 @@ -module functionspace_mod - - implicit none - - private - - integer, public, parameter :: W0 = 173 - integer, public, parameter :: W1 = 194 - integer, public, parameter :: W2 = 889 - integer, public, parameter :: W2V = 857 - integer, public, parameter :: W2H = 884 - integer, public, parameter :: W2broken = 211 - integer, public, parameter :: W2trace = 213 - integer, public, parameter :: W3 = 424 - integer, public, parameter :: Wtheta = 274 - integer, public, parameter :: Wchi = 869 - - type, public :: functionspace_type - private - integer, pointer :: dofmap(:, :) - contains - private - procedure, public :: get_ncell - procedure, public :: get_ndf - procedure, public :: get_nlayers - procedure, public :: get_undf - procedure, public :: get_whole_dofmap - ! There should be a finaliser but for testing it's too much work. - end type functionspace_type - - interface functionspace_type - procedure functionspace_initialise - end interface - -contains - - function functionspace_initialise() result(instance) - implicit none - type(functionspace_type) :: instance - allocate( instance%dofmap(2, 1) ) - end function functionspace_initialise - - function get_ncell(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_ncell - get_ncell = 1 - end function get_ncell - - function get_ndf(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_ndf - get_ndf = 1 - end function get_ndf - - function get_undf(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_undf - get_undf = 1 - end function get_undf - - function get_nlayers(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_nlayers - get_nlayers = 1 - end function get_nlayers - - function get_whole_dofmap(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer, pointer :: get_whole_dofmap(:, :) - get_whole_dofmap => this%dofmap - end function get_whole_dofmap - -end module functionspace_mod diff --git a/tests-old/TestCases/PSyclone/model/kernel_mod.f90 b/tests-old/TestCases/PSyclone/model/kernel_mod.f90 deleted file mode 100644 index 821c1ec3..00000000 --- a/tests-old/TestCases/PSyclone/model/kernel_mod.f90 +++ /dev/null @@ -1,11 +0,0 @@ -module kernel_mod - - implicit none - - private - - type, public, abstract :: kernel_type - private - end type - -end module kernel_mod diff --git a/tests-old/TestCases/PSyclone/model/operator_mod.f90 b/tests-old/TestCases/PSyclone/model/operator_mod.f90 deleted file mode 100644 index a06a63ca..00000000 --- a/tests-old/TestCases/PSyclone/model/operator_mod.f90 +++ /dev/null @@ -1,23 +0,0 @@ -module operator_mod - - implicit none - - private - - type, public :: operator_type - private - end type operator_type - - type, public :: operator_proxy_type - private - end type operator_proxy_type - - type, public :: columnwise_operator_type - private - end type columnwise_operator_type - - type, public :: columnwise_operator_proxy_type - private - end type columnwise_operator_proxy_type - -end module operator_mod diff --git a/tests-old/TestCases/PSyclone/optimisation.py b/tests-old/TestCases/PSyclone/optimisation.py deleted file mode 100644 index d995e883..00000000 --- a/tests-old/TestCases/PSyclone/optimisation.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -''' -PSyclone transformation script for the Dynamo0p3 API to apply -colouring and OpenMP. -''' -from __future__ import absolute_import, print_function - -from psyclone.transformations import Dynamo0p3ColourTrans, \ - Dynamo0p3OMPLoopTrans, \ - OMPParallelTrans -from psyclone.dynamo0p3 import DISCONTINUOUS_FUNCTION_SPACES - - -def trans(psy): - ''' - Applies PSyclone colouring and OpenMP transformations. - ''' - ctrans = Dynamo0p3ColourTrans() - otrans = Dynamo0p3OMPLoopTrans() - oregtrans = OMPParallelTrans() - - # Loop over all of the Invokes in the PSy object - for invoke in psy.invokes.invoke_list: - - print("Transforming invoke '{0}' ...".format(invoke.name)) - schedule = invoke.schedule - - # Colour loops over cells unless they are on discontinuous - # spaces (W3, WTHETA and W2V) or over dofs - for loop in schedule.loops(): - if loop.iteration_space == "cells" \ - and loop.field_space.orig_name \ - not in DISCONTINUOUS_FUNCTION_SPACES: - schedule, _ = ctrans.apply(loop) - - # Add OpenMP to loops over colours. - for loop in schedule.loops(): - if loop.loop_type != "colours": - schedule, _ = oregtrans.apply(loop) - schedule, _ = otrans.apply(loop, reprod=True) - - schedule.view() - - return psy diff --git a/tests-old/TestCases/SimpleLFRic/Makefile b/tests-old/TestCases/SimpleLFRic/Makefile deleted file mode 100644 index 78fa0630..00000000 --- a/tests-old/TestCases/SimpleLFRic/Makefile +++ /dev/null @@ -1,143 +0,0 @@ -# The best way to show how a test case should be built is to build it. -# While we don't have a build system we will use someone elses. -# -# This test expects the MPICH compiler wrapper scripts to be available on -# PATH. Furthermore it expects them to be wrapping Intel Fortran. -# -# It also expects the PSyclone source generator and pFUnit processor to be -# available on PATH. -# -# As recommended by the pFUnit developers it exptects a macro PFUNIT to -# contain the full path to the pFUnit install directory. -# -.SUFFIXES: -.SUFFIXES: .pf .f90 .F90 .o .mod - -APP_OBJECTS = util.o util_mod.o \ - algorithm_mod.o algorithm_mod_psy.o \ - model/field_mod.o model/functionspace_mod.o model/mesh_mod.o \ - model/kernel_mod.o kernels/my_kernel_mod.o \ - thing.o -TEST_OBJECTS = kernels/my_kernel_mod.o my_kernel_test_mod.o driver.o - -FC = mpif90 -FFLAGS = -Ikernels -Imodel -I$(PFUNIT)/mod -debug full -traceback -CC = gcc -CFLAGS = -std=c99 -g -LFLAGS = -qopenmp -debug full -traceback -PSYCLONE = psyclone -PFPROC = pFUnitParser.py - -# We use the "-module" argument to redirect module file creation so the -# compiler wrapped by MPI must be ifort. -# -ifneq "$(firstword $(shell $(FC) -show))" "ifort" -$(error Please build with MPICH for Intel Fortran) -endif - -ifeq "x$(shell command -v $(PSYCLONE))" "x" -$(error Could not find the PSyclone script on PATH) -endif - -ifeq "x$(shell command -v $(PFPROC))" "x" -$(error Could not find the pFUnit processor on PATH) -endif - -verify: test-thing test-test - -test-thing: thing.out thing.expected - diff $^ - -thing.out: thing - ./$< >$@ - -thing.expected: - printf "Some hash: 7\n" >$@ - for iter in 1 2 3 4 5 6 7 8 9 10; do printf "Field data: 1.0000\n"; done >>$@ - -test-test: test.out test.expected - diff $^ - -test.out: test - ./$< | tail -n 2 >$@ - -test.expected: - printf " OK\n (1 test)\n" >$@ - -thing: $(APP_OBJECTS) - @echo Linking $@ - $(FC) -o $@ -g $(APP_OBJECTS) -lstdc++ - -test: $(TEST_OBJECTS) - @echo Linking $@ - $(FC) $(LFLAGS) -o $@ -L$(PFUNIT)/lib $(TEST_OBJECTS) -l pfunit - -%.o: %.c - @echo Compiling $@ - $(CC) -o $@ $(CFLAGS) -c $< - -%.o %.mod: %.f90 - @echo Compiling $@ - $(FC) -o $*.o $(FFLAGS) -module $(dir $@) -c $< - -%.o %.mod: %.F90 - @echo Compiling $@ - $(FC) -o $*.o $(FFLAGS) -module $(dir $@) -c $< - -%.f90 %_psy.F90: %.x90 optimisation.py - @echo Generating $@ - $(PSYCLONE) -oalg $*.f90 -opsy $*_psy.F90 -d kernels \ - -s $(realpath optimisation.py) -api dynamo0.3 -l -dm $< - -util_mod.f90: util_mod.template # Really should be util.c - @echo Generating $@ - # This cat is in lue of a generator - cat util_mod.template > $@ - -.pf.F90: - @echo Generating $@ - $(PFPROC) $< $@ - -driver.o: $(PFUNIT)/include/driver.F90 testSuites.inc - @echo Compiling $@ - $(FC) -o $@ -I$(PFUNIT)/mod -c $< - -testSuites.inc: - @echo Creating $@ - echo ADD_TEST_SUITE\(my_kernel_test_mod_suite\) > $@ - -my_kernel_test_mod.o \ -my_kernel_test_mod.mod: my_kernel_test_mod.F90 \ - kernels/my_kernel_mod.mod model/constants_mod.mod -kernel_test_mod.F90: kernel_test_mod.pf - -algorithm_mod.o \ -algorithm_mod.mod: algorithm_mod.f90 algorithm_mod_psy.mod \ - model/field_mod.mod kernels/my_kernel_mod.mod -algorithm_mod.f90: algorithm_mod.x90 kernels/my_kernel_mod.f90 optimisation.py -algorithm_mod_psy.o \ -algorithm_mod_psy.mod: algorithm_mod_psy.F90 \ - model/field_mod.mod model/operator_mod.mod \ - kernels/my_kernel_mod.mod -algorithm_mod_psy.F90: algorithm_mod.x90 \ - kernels/my_kernel_mod.f90 optimisation.py -kernels/my_kernel_mod.o \ -kernels/my_kernel_mod.mod: kernels/my_kernel_mod.f90 model/mesh_mod.mod\ - model/argument_mod.mod model/constants_mod.mod \ - model/functionspace_mod.mod model/kernel_mod.mod - -model/field_mod.o: model/field_mod.f90 \ - model/constants_mod.mod model/functionspace_mod.mod \ - model/mesh_mod.mod - -util_mod.o: util_mod.f90 model/constants_mod.mod - -thing.o: thing.f90 algorithm_mod.mod algorithm_mod_psy.mod util_mod.mod \ - model/field_mod.mod - -clean: - -rm *.o *.mod *.pyc kernels/*.o kernels/*.mod model/*.o model/*.mod - -rm testSuites.inc - -rm algorithm_mod.f90 algorithm_mod_psy.F90 my_kernel_test_mod.F90 - -rm util_mod.f90 - -rm test test.out test.expected thing thing.out thing.expected diff --git a/tests-old/TestCases/SimpleLFRic/algorithm_mod.x90 b/tests-old/TestCases/SimpleLFRic/algorithm_mod.x90 deleted file mode 100644 index d37dbf98..00000000 --- a/tests-old/TestCases/SimpleLFRic/algorithm_mod.x90 +++ /dev/null @@ -1,24 +0,0 @@ -module algorithm_mod - - use field_mod, only : field_type - use my_kernel_mod, only : my_kernel_type - use util_mod, only : hash - - implicit none - - private - public :: algorithm - -contains - - subroutine algorithm(field) - - implicit none - - class(field_type), intent(inout) :: field - - call invoke( my_kernel_type(field) ) - - end subroutine algorithm - -end module algorithm_mod diff --git a/tests-old/TestCases/SimpleLFRic/kernels/my_kernel_mod.f90 b/tests-old/TestCases/SimpleLFRic/kernels/my_kernel_mod.f90 deleted file mode 100644 index d6e3dfc2..00000000 --- a/tests-old/TestCases/SimpleLFRic/kernels/my_kernel_mod.f90 +++ /dev/null @@ -1,47 +0,0 @@ -module my_kernel_mod - - use argument_mod, only : arg_type, cells, gh_field, gh_write - use functionspace_mod, only : w3 - use kernel_mod, only : kernel_type - - implicit none - - private - - type, public, extends(kernel_type) :: my_kernel_type - private - type(arg_type) :: meta_args(1) = (/ & - arg_type( gh_field, gh_write, w3 ) & - /) - integer :: iterates_over = cells - contains - procedure, nopass :: my_kernel_code - end type - - public :: my_kernel_code - -contains - - subroutine my_kernel_code( nlayers, field_1_w3, ndf_w3, undf_w3, map_w3 ) - - use constants_mod, only : r_def - - implicit none - - integer, intent(in) :: nlayers - integer, intent(in) :: ndf_w3 - integer, intent(in) :: undf_w3 - real(kind=r_def), intent(out) :: field_1_w3(undf_w3) - integer, intent(in) :: map_w3(ndf_w3) - - integer :: d, k - - do k=0, nlayers - do d=0, ndf_w3 - field_1_w3(map_w3(d)) = d + k - end do - end do - - end subroutine my_kernel_code - -end module my_kernel_mod diff --git a/tests-old/TestCases/SimpleLFRic/model/argument_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/argument_mod.f90 deleted file mode 100644 index 2cf7638c..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/argument_mod.f90 +++ /dev/null @@ -1,21 +0,0 @@ -module argument_mod - - implicit none - - private - - integer, public, parameter :: gh_field = 507 - integer, public, parameter :: gh_write = 65 - integer, public, parameter :: cells = 396 - - type, public :: arg_type - integer :: arg_type - integer :: arg_intent - integer :: wspace = -1 - integer :: from_wspace = -1 - integer :: stencil_map = -1 - integer :: mesh_arg = -1 - end type arg_type - -end module argument_mod - diff --git a/tests-old/TestCases/SimpleLFRic/model/constants_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/constants_mod.f90 deleted file mode 100644 index bc5170e2..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/constants_mod.f90 +++ /dev/null @@ -1,11 +0,0 @@ -module constants_mod - - use iso_fortran_env, only : real64 - - implicit none - - private - - integer, public, parameter :: r_def = real64 - -end module constants_mod diff --git a/tests-old/TestCases/SimpleLFRic/model/field_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/field_mod.f90 deleted file mode 100644 index 9675d36c..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/field_mod.f90 +++ /dev/null @@ -1,59 +0,0 @@ -module field_mod - - use constants_mod, only : r_def - use functionspace_mod, only : functionspace_type - use mesh_mod, only : mesh_type - - implicit none - - private - - type, public :: field_type - private - type(mesh_type), pointer :: mesh - contains - private - procedure, public :: get_mesh - procedure, public :: get_proxy - ! There should be a finalising but I can't be bothered - end type field_type - - interface field_type - procedure :: field_initialiser - end interface field_type - - type, public :: field_proxy_type - private - real(r_def), public :: data(10) - type(functionspace_type), public :: vspace - contains - procedure set_dirty - end type field_proxy_type - -contains - - function field_initialiser() result(instance) - implicit none - type(field_type) :: instance - allocate( instance%mesh ) - end function field_initialiser - - function get_mesh(this) - implicit none - class(field_type), intent(inout) :: this - type(mesh_type), pointer :: get_mesh - get_mesh => this%mesh - end function get_mesh - - function get_proxy(this) - implicit none - class(field_type), intent(inout) :: this - type(field_proxy_type) :: get_proxy - get_proxy%vspace = functionspace_type() - end function get_proxy - - subroutine set_dirty(this) - implicit none - class(field_Proxy_type), intent(inout) :: this - end subroutine set_dirty -end module field_mod diff --git a/tests-old/TestCases/SimpleLFRic/model/functionspace_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/functionspace_mod.f90 deleted file mode 100644 index 56c9b016..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/functionspace_mod.f90 +++ /dev/null @@ -1,78 +0,0 @@ -module functionspace_mod - - implicit none - - private - - integer, public, parameter :: W0 = 173 - integer, public, parameter :: W1 = 194 - integer, public, parameter :: W2 = 889 - integer, public, parameter :: W2V = 857 - integer, public, parameter :: W2H = 884 - integer, public, parameter :: W2broken = 211 - integer, public, parameter :: W2trace = 213 - integer, public, parameter :: W3 = 424 - integer, public, parameter :: Wtheta = 274 - integer, public, parameter :: Wchi = 869 - - type, public :: functionspace_type - private - integer, pointer :: dofmap(:, :) - contains - private - procedure, public :: get_ncell - procedure, public :: get_ndf - procedure, public :: get_nlayers - procedure, public :: get_undf - procedure, public :: get_whole_dofmap - ! There should be a finaliser but for testing it's too much work. - end type functionspace_type - - interface functionspace_type - procedure functionspace_initialise - end interface - -contains - - function functionspace_initialise() result(instance) - implicit none - type(functionspace_type) :: instance - allocate( instance%dofmap(2, 1) ) - end function functionspace_initialise - - function get_ncell(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_ncell - get_ncell = 1 - end function get_ncell - - function get_ndf(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_ndf - get_ndf = 1 - end function get_ndf - - function get_undf(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_undf - get_undf = 1 - end function get_undf - - function get_nlayers(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer :: get_nlayers - get_nlayers = 1 - end function get_nlayers - - function get_whole_dofmap(this) - implicit none - class(functionspace_type), intent(inout) :: this - integer, pointer :: get_whole_dofmap(:, :) - get_whole_dofmap => this%dofmap - end function get_whole_dofmap - -end module functionspace_mod diff --git a/tests-old/TestCases/SimpleLFRic/model/kernel_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/kernel_mod.f90 deleted file mode 100644 index 821c1ec3..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/kernel_mod.f90 +++ /dev/null @@ -1,11 +0,0 @@ -module kernel_mod - - implicit none - - private - - type, public, abstract :: kernel_type - private - end type - -end module kernel_mod diff --git a/tests-old/TestCases/SimpleLFRic/model/mesh_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/mesh_mod.f90 deleted file mode 100644 index 3e8ccd91..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/mesh_mod.f90 +++ /dev/null @@ -1,22 +0,0 @@ -module mesh_mod - - implicit none - - private - - type, public :: mesh_type - private - contains - procedure get_last_edge_cell - end type mesh_type - -contains - - function get_last_edge_cell(this) - implicit none - class(mesh_type), intent(inout) :: this - integer :: get_last_edge_cell - get_last_edge_cell = 1 - end function get_last_edge_cell - -end module mesh_mod diff --git a/tests-old/TestCases/SimpleLFRic/model/operator_mod.f90 b/tests-old/TestCases/SimpleLFRic/model/operator_mod.f90 deleted file mode 100644 index a06a63ca..00000000 --- a/tests-old/TestCases/SimpleLFRic/model/operator_mod.f90 +++ /dev/null @@ -1,23 +0,0 @@ -module operator_mod - - implicit none - - private - - type, public :: operator_type - private - end type operator_type - - type, public :: operator_proxy_type - private - end type operator_proxy_type - - type, public :: columnwise_operator_type - private - end type columnwise_operator_type - - type, public :: columnwise_operator_proxy_type - private - end type columnwise_operator_proxy_type - -end module operator_mod diff --git a/tests-old/TestCases/SimpleLFRic/my_kernel_test_mod.pf b/tests-old/TestCases/SimpleLFRic/my_kernel_test_mod.pf deleted file mode 100644 index 832926b1..00000000 --- a/tests-old/TestCases/SimpleLFRic/my_kernel_test_mod.pf +++ /dev/null @@ -1,27 +0,0 @@ -module my_kernel_test_mod - - use pFUnit_mod - use constants_mod, only : r_def - use my_kernel_mod, only : my_kernel_code - - implicit none - -contains - - @test - subroutine test_my_kernel - - implicit none - - real(r_def) :: dblock(27) - real(r_def) :: expected(27) = (/4,5,6,7,8,9,10,11,12, & - 0,0,0,0,0,0,0,0,0, & - 0,0,0,0,0,0,0,0,0/) - integer :: dofs(9) = (/1,2,3,4,5,6,7,8,9/) - - call my_kernel_code( 3, dblock, 9, 27, dofs) - @assertEqual(expected, dblock) - - end subroutine test_my_kernel - -end module my_kernel_test_mod diff --git a/tests-old/TestCases/SimpleLFRic/optimisation.py b/tests-old/TestCases/SimpleLFRic/optimisation.py deleted file mode 100644 index d995e883..00000000 --- a/tests-old/TestCases/SimpleLFRic/optimisation.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -''' -PSyclone transformation script for the Dynamo0p3 API to apply -colouring and OpenMP. -''' -from __future__ import absolute_import, print_function - -from psyclone.transformations import Dynamo0p3ColourTrans, \ - Dynamo0p3OMPLoopTrans, \ - OMPParallelTrans -from psyclone.dynamo0p3 import DISCONTINUOUS_FUNCTION_SPACES - - -def trans(psy): - ''' - Applies PSyclone colouring and OpenMP transformations. - ''' - ctrans = Dynamo0p3ColourTrans() - otrans = Dynamo0p3OMPLoopTrans() - oregtrans = OMPParallelTrans() - - # Loop over all of the Invokes in the PSy object - for invoke in psy.invokes.invoke_list: - - print("Transforming invoke '{0}' ...".format(invoke.name)) - schedule = invoke.schedule - - # Colour loops over cells unless they are on discontinuous - # spaces (W3, WTHETA and W2V) or over dofs - for loop in schedule.loops(): - if loop.iteration_space == "cells" \ - and loop.field_space.orig_name \ - not in DISCONTINUOUS_FUNCTION_SPACES: - schedule, _ = ctrans.apply(loop) - - # Add OpenMP to loops over colours. - for loop in schedule.loops(): - if loop.loop_type != "colours": - schedule, _ = oregtrans.apply(loop) - schedule, _ = otrans.apply(loop, reprod=True) - - schedule.view() - - return psy diff --git a/tests-old/TestCases/SimpleLFRic/thing.f90 b/tests-old/TestCases/SimpleLFRic/thing.f90 deleted file mode 100644 index b5dd42a0..00000000 --- a/tests-old/TestCases/SimpleLFRic/thing.f90 +++ /dev/null @@ -1,27 +0,0 @@ -program thing - - use iso_fortran_env, only : output_unit - - use algorithm_mod, only : algorithm - use field_mod, only : field_type, field_proxy_type - use util_mod, only : hash - - implicit none - - type(field_type) :: field - - real, target :: something(4) - real, pointer :: some_pointer(:) => null() - - type(field_proxy_type) :: accessor - - call random_number(something) - some_pointer => something - write(output_unit, '("Some hash: ", I0)') hash(some_pointer) - - accessor = field%get_proxy() - accessor%data = 1.0 - call algorithm(field) - write(output_unit, '("Field data: ", F17.4)') accessor%data - -end program thing diff --git a/tests-old/TestCases/SimpleLFRic/util.c b/tests-old/TestCases/SimpleLFRic/util.c deleted file mode 100644 index 9deefc1b..00000000 --- a/tests-old/TestCases/SimpleLFRic/util.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "util.h" - -int8_t eor_hash(void *block, int length) { - int8_t hash = 0xff; - for (unsigned int index = 0; index < length; ++index) { - hash = hash ^ ((int8_t *)block)[index]; - } - return hash; -} diff --git a/tests-old/TestCases/SimpleLFRic/util.h b/tests-old/TestCases/SimpleLFRic/util.h deleted file mode 100644 index c254a41d..00000000 --- a/tests-old/TestCases/SimpleLFRic/util.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -#include - -extern int8_t eor_hash(void *block, int length); - -#endif diff --git a/tests-old/TestCases/SimpleLFRic/util_mod.template b/tests-old/TestCases/SimpleLFRic/util_mod.template deleted file mode 100644 index b5100ca0..00000000 --- a/tests-old/TestCases/SimpleLFRic/util_mod.template +++ /dev/null @@ -1,29 +0,0 @@ -module util_mod - - use iso_c_binding, only : c_int, c_int8_t, c_loc, c_ptr - - implicit none - - private - public :: hash - - interface - function eor_hash(block, length) bind(c, name='eor_hash') - import c_int, c_int8_t, c_ptr - implicit none - integer(c_int8_t) eor_hash - type(c_ptr), intent(in) :: block - integer(c_int), intent(in), value :: length - end function eor_hash - end interface - -contains - - function hash(block) - implicit none - real, pointer :: block(:) - integer :: hash - hash = eor_hash(c_loc(block), size(block, 1)) - end function hash - -end module util_mod diff --git a/tests-old/TestCases/pFUnit/Build.txt b/tests-old/TestCases/pFUnit/Build.txt deleted file mode 100644 index 52d84952..00000000 --- a/tests-old/TestCases/pFUnit/Build.txt +++ /dev/null @@ -1,5 +0,0 @@ -stuff_test.o (Compile) - - stuff_test.F90 (pFUnit) - - stuff_test.pf -module.o (Compile) - - module.f90 diff --git a/tests-old/TestCases/pFUnit/Makefile b/tests-old/TestCases/pFUnit/Makefile deleted file mode 100644 index c1d304a6..00000000 --- a/tests-old/TestCases/pFUnit/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -# The best way to show how a test case should be built is to build it. -# While we don't have a build system we will use someone elses. -# -# This test simulates using the pFUnit framework for unit testing. -# As distributed it expects version 3.x.x of the framework. The -# processing script has changed name with version 4.x.x so you would -# need to change the PFRPOC macro to be "pfproc". -# -# The test assumes that the processing script is visible on the execution -# path. i.e. That it is included in the PATH environment variable. -# -.SUFFIXES: -.SUFFIXES: .pf .f90 .F90 .o .mod - -# By default gmake sets FC to "f77" we need to detect that and force it to our -# default. If it is not set then we still have a default but we allow the user -# to override it. -# -ifeq "$(origin FC)" "default" -export FC = ifort -else -export FC ?= ifort -endif - -PFPROC ?= pFUnitParser.py - -ifeq "x$(shell command -v $(PFPROC))" "x" -$(error Could not find the pFUnit processor on PATH) -endif - -objects: stuff_mod.o stuff_test.o - -.f90.o: - @echo Compiling $@ - $(FC) -o $@ -c $< - -.f90.mod: - @echo Compiling $@ - $(FC) -o $*.o -c $< - -.F90.o: - @echo Compiling $@ - $(FC) -o $@ -I$(PFUNIT)/mod -c $< - -.F90.mod: - @echo Compiling $@ - $(FC) -o $*.o -I$(PFUNIT)/mod -c $< - -.pf.F90: - @echo Processing $@ - $(PFPROC) $< $@ - -stuff_mod.o stuff_mod.mod: stuff_mod.f90 -stuff_test.o stuff_test_mod.mod: stuff_test.F90 stuff_mod.mod -stuff_test.F90: stuff_test.pf - -clean: - -rm *.o *.mod stuff_test.F90 diff --git a/tests-old/TestCases/pFUnit/stuff_mod.f90 b/tests-old/TestCases/pFUnit/stuff_mod.f90 deleted file mode 100644 index cbee2cca..00000000 --- a/tests-old/TestCases/pFUnit/stuff_mod.f90 +++ /dev/null @@ -1,17 +0,0 @@ -module stuff_mod - - implicit none - -contains - - function number() - - implicit none - - integer :: number - - number = 42 - - end function number - -end module stuff_mod diff --git a/tests-old/TestCases/pFUnit/stuff_test.pf b/tests-old/TestCases/pFUnit/stuff_test.pf deleted file mode 100644 index e9300aa7..00000000 --- a/tests-old/TestCases/pFUnit/stuff_test.pf +++ /dev/null @@ -1,22 +0,0 @@ -module stuff_test_mod - - use pFUnit_mod - use stuff_mod, only : number - - implicit none - -contains - - @test - subroutine test_number_okay() - - implicit none - - integer :: result - - result = number() - @assertEqual( 42, result ) - - end subroutine test_number_okay - -end module stuff_test_mod diff --git a/tests-old/system-tests/GitRepository/expected/aleph b/tests-old/system-tests/GitRepository/expected/aleph deleted file mode 100644 index 3c3670a7..00000000 --- a/tests-old/system-tests/GitRepository/expected/aleph +++ /dev/null @@ -1 +0,0 @@ -File the first. diff --git a/tests-old/system-tests/GitRepository/expected/beis/veis b/tests-old/system-tests/GitRepository/expected/beis/veis deleted file mode 100644 index 44a7476d..00000000 --- a/tests-old/system-tests/GitRepository/expected/beis/veis +++ /dev/null @@ -1,2 +0,0 @@ -File the second. - diff --git a/tests-old/system-tests/GitRepository/repo.tar b/tests-old/system-tests/GitRepository/repo.tar deleted file mode 100644 index 32b26270..00000000 Binary files a/tests-old/system-tests/GitRepository/repo.tar and /dev/null differ diff --git a/tests-old/system-tests/GitRepository_test.py b/tests-old/system-tests/GitRepository_test.py deleted file mode 100644 index f2267bfc..00000000 --- a/tests-old/system-tests/GitRepository_test.py +++ /dev/null @@ -1,17 +0,0 @@ -############################################################################## -# (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 -############################################################################## -from pathlib import Path -from common import CompareFileTrees, RunGrab - -TEST_PATH = Path('system-tests') / Path(__file__).name.split('_test.py')[0] - - -def test_grab(): - # TODO: I can't test with the Git protocol as for some reason the - # Git daemon isn't installed. - command = RunGrab(TEST_PATH, 'git', 'file') - comparison = CompareFileTrees(command) - comparison.run() diff --git a/tests-old/system-tests/SubversionRepository/expected/trunk/alpha b/tests-old/system-tests/SubversionRepository/expected/trunk/alpha deleted file mode 100644 index e69de29b..00000000 diff --git a/tests-old/system-tests/SubversionRepository/expected/trunk/beta/gamma b/tests-old/system-tests/SubversionRepository/expected/trunk/beta/gamma deleted file mode 100644 index e69de29b..00000000 diff --git a/tests-old/system-tests/SubversionRepository/repo.tar b/tests-old/system-tests/SubversionRepository/repo.tar deleted file mode 100644 index 9aec198a..00000000 Binary files a/tests-old/system-tests/SubversionRepository/repo.tar and /dev/null differ diff --git a/tests-old/system-tests/SubversionRepository_test.py b/tests-old/system-tests/SubversionRepository_test.py deleted file mode 100644 index 396b51e7..00000000 --- a/tests-old/system-tests/SubversionRepository_test.py +++ /dev/null @@ -1,21 +0,0 @@ -############################################################################## -# (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 -############################################################################## -from pathlib import Path -from common import CompareFileTrees, RunGrab - -TEST_PATH = Path('system-tests') / Path(__file__).name.split('_test.py')[0] - - -def test_grab_file(): - command = RunGrab(TEST_PATH, 'svn', 'file') - comparison = CompareFileTrees(command) - comparison.run() - - -def test_grab_svn(): - command = RunGrab(TEST_PATH, 'svn', 'svn') - comparison = CompareFileTrees(command) - comparison.run() diff --git a/tests-old/system-tests/common.py b/tests-old/system-tests/common.py deleted file mode 100644 index d9528ad9..00000000 --- a/tests-old/system-tests/common.py +++ /dev/null @@ -1,384 +0,0 @@ -############################################################################## -# (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 -############################################################################## -""" -System testing for Fab. - -Currently runs the tool as a subprocess but should also use it as a library. -""" -from abc import ABC, ABCMeta, abstractmethod -import filecmp -import os.path -from pathlib import Path -import shutil -import subprocess -import sys -from tarfile import TarFile -import os -from typing import Dict, List, Optional, Sequence - - -class TestParameters(object): - """ - Holds information about the environment a test is happening in. - """ - def __init__(self, test_directory: Path, tag: str): - self._test_dir = test_directory - self._tag = tag - - @property - def test_directory(self): - return self._test_dir - - @property - def work_directory(self): - return self._test_dir / 'working' - - @property - def tag(self): - return self._tag - - -class RunCommand(ABC): - """ - Base class for tests containing useful utility functions. - """ - def __init__(self, - parameters: TestParameters, - command: List[str], - environment: Dict): - self._parameters = parameters - self._command = command - self._environment = environment - self._debug_output: Optional[List[str]] = None - - self.return_code: Optional[bool] = None - self.standard_out: Optional[str] = None - self.standard_error: Optional[str] = None - - @property - def test_parameters(self) -> TestParameters: - return self._parameters - - @abstractmethod - def description(self) -> str: - raise NotImplementedError("Abstract methods must be implemented.") - - @property - def debug_output(self) -> Optional[List[str]]: - return self._debug_output - - @debug_output.setter - def debug_output(self, additional_line: str): - if self._debug_output is None: - self._debug_output = [] - self._debug_output.append(additional_line) - - def set_up(self): - """ - Called prior to the run. - """ - pass - - def execute(self): - """ - Runs the command and changes state to reflect results. - """ - thread: subprocess.Popen = subprocess.Popen(self._command, - env=self._environment, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout: bytes - stderr: bytes - stdout, stderr = thread.communicate() - self.return_code = thread.returncode - self.standard_out = stdout.decode('utf-8') - self.standard_error = stderr.decode('utf-8') - - if self.return_code != 0: - self._debug_output = ['Running command failed:'] - command = ' '.join(self._command) - self._debug_output.append(f' command: {command}') - self._debug_output.append(' stdout: ' + self.standard_out) - self._debug_output.append(' stderr: ' + self.standard_error) - - def tear_down(self): - """ - Called following the run. - """ - pass - - -class EnterPython(RunCommand, metaclass=ABCMeta): - """ - Run a Python entry point. - """ - def __init__(self, - tag: str, - test_directory: Path, - module: str, - args: Sequence[str] = (), - working_dir=True): - parameters = TestParameters(test_directory, tag) - - script = f'import sys; import fab.{module}; ' \ - f'sys.exit(fab.{module}.entry())' - command = ['python3', '-c', script] - if working_dir: - command.extend(['-w', str(parameters.work_directory)]) - command.extend(args) - - system_path = os.environ.get('PATH') or '' - user_path: List[str] = system_path.split(':') - try: - while True: - user_path.remove('') - except ValueError: - pass # No empty entries to be removed. - user_path.append(os.path.dirname(sys.executable)) - - environment = {'PATH': ':'.join(user_path), - 'PYTHONPATH': 'source'} - - super().__init__(parameters, command, environment) - self._working_dir = working_dir - - -class RunExec(RunCommand): - """ - Run an executable produced by fab. - """ - def __init__(self, test_directory: Path): - parameters = TestParameters(test_directory, 'exec') - args: List[str] = [] - executable = test_directory / 'working' / 'fab_test' - command = [str(executable)] + list(args) - super().__init__(parameters, command, {}) - - def description(self) -> str: - return f"{self.test_parameters.test_directory.stem} - Executing" - - -class RunFab(EnterPython): - """ - Run Fab build tool against a source tree. - """ - def __init__(self, - test_directory: Path, - target: str, - fpp_flags: str = None, - fc_flags: str = None, - ld_flags: str = None): - args: List[str] = [] - - if fpp_flags: - # different config file name for fpp flag test - self.conf_file = test_directory/('stay_config.ini') - else: - self.conf_file = test_directory/'config.ini' - - args.append(str(test_directory)) - args.append(str(self.conf_file)) - - with open(self.conf_file, 'wt') as configfile: - configfile.write('[settings] \n' - 'target = {}\n' - 'exec-name = fab_test \n' - '[flags] \n'.format(target)) - if fpp_flags: - configfile.write('fpp-flags = {}\n'.format(fpp_flags)) - else: - configfile.write('fpp-flags = ' + '\n') - if fc_flags: - configfile.write('fc-flags = {}\n'.format(fc_flags)) - else: - configfile.write('fc-flags = ' + '\n') - if ld_flags: - configfile.write('ld-flags = {}\n'.format(ld_flags)) - else: - configfile.write('ld-flags = ' + '\n') - super().__init__('fab', test_directory, 'builder', args) - - def description(self) -> str: - return f"{self.test_parameters.test_directory.stem} - Building" - - def set_up(self): - """ - Ensure there's no working directory left over from previous runs. - """ - if self.test_parameters.work_directory.is_dir(): - shutil.rmtree(self.test_parameters.work_directory) - - def tear_down(self): - """ - Clean up config files following the run. - """ - self.conf_file.unlink() - - -class RunDump(EnterPython): - """ - Run Fab dump tool against working directory. - """ - def __init__(self, test_directory: Path): - super().__init__('dump', test_directory, 'dumper') - - def description(self) -> str: - return f"{self.test_parameters.test_directory.stem} - Dumping" - - def teardown(self): - if self.test_parameters.work_directory.is_dir(): - shutil.rmtree(str(self.test_parameters.work_directory)) - - def tear_down(self): - """ - Tidy up now we're finished with the working directroy. - """ - shutil.rmtree(self.test_parameters.work_directory) - - -class RunGrab(EnterPython): - """ - Run Fab grab tool against a repository. - """ - def __init__(self, test_directory: Path, repo: str, protocol: str): - self._scheme = f"{repo}+{protocol}" - self._repo_path = test_directory.absolute() / "repo" - self._server: Optional[subprocess.Popen] = None - - if protocol == 'http': - # TODO: This scheme is included for completeness. Currently there - # is no obvious way to test this without an Apache server - # which is way too much to consider at the moment. - # repo_url = f'http://localhost/repo' - message = "Unable to test Fetch over HTTP protocol." - raise NotImplementedError(message) - - repo_url = f'{self._scheme}://' - if protocol == 'file': - repo_url += f'//{self._repo_path}' - # HTTP would be included here as well if we were able to test it. - elif protocol in ['git', 'svn']: - repo_url += 'localhost/' - else: - message = f"Unrecognised URL scheme '{self._scheme}'" - raise Exception(message) - - super().__init__('grab', - test_directory, - 'grabber', - [str(test_directory / 'working'), repo_url], - working_dir=False) - - def description(self) -> str: - name = self.test_parameters.test_directory.stem - return f"{name} - Grabbing with {self._scheme}" - - def set_up(self): - if self._repo_path.is_dir(): - shutil.rmtree(self._repo_path) - archiver = TarFile(self._repo_path.with_suffix('.tar')) - archiver.extractall(self._repo_path.parent) - - if self.test_parameters.work_directory.is_dir(): - shutil.rmtree(self.test_parameters.work_directory) - - if self._scheme.endswith('+git'): - # TODO: We would start the daemon here - raise NotImplementedError("Git protocol not supported") - elif self._scheme.endswith('+svn'): - command: List[str] = ['svnserve', '--root', str(self._repo_path), - '-X', '--foreground'] - self._server = subprocess.Popen(command) - - def tear_down(self): - shutil.rmtree(self.test_parameters.work_directory) - - if self._scheme.endswith('+git'): - # TODO: We would kill the daemon here - raise NotImplementedError("Git protocol not supported") - elif self._scheme.endswith('+svn'): - self._server.wait(timeout=1) - if self._server.returncode != 0: - message = f"Trouble with svnserve: {self._server.stderr}" - self.debug_output = message - - if self._repo_path.is_dir(): - shutil.rmtree(self._repo_path) - - -class CheckTask(ABC): - """ - Abstract parent of all checking test cases. - """ - def __init__(self, task: RunCommand, name: str): - self._name = name - self._task = task - - @property - def task(self): - return self._task - - def run(self): - self._task.set_up() - self._task.execute() - # - # We print this out for debug purposes. If a test fails this output - # should be visible. - # - if self._task.debug_output is not None: - print('\n'.join(self._task.debug_output)) - self.check() - self.task.tear_down() - - @abstractmethod - def check(self): - raise NotImplementedError("Abstract methods must be implemented.") - - -class CompareConsoleWithFile(CheckTask): - """ - Checks console output against expected result. - - The expected result is held in a file "expected.[.].txt. - Where "tag" comes from the task and "suffix" is specified. - """ - def __init__(self, task: RunCommand, expectation_suffix=None): - super().__init__(task, name=task.description()) - leaf_name = f'expected.{task.test_parameters.tag}' - if expectation_suffix is not None: - leaf_name = leaf_name + '.' + expectation_suffix - leaf_name = leaf_name + '.txt' - path = task.test_parameters.test_directory / leaf_name - self._expected = path.read_text() - - def check(self): - assert self.task.return_code == 0 - lines = self.task.standard_out - assert lines == self._expected - - -class CompareFileTrees(CheckTask): - """ - Checks filetree against expected result. - - The test tree is the tasks working directory and the expected result - is in "expected". - """ - def __init__(self, task: RunCommand): - super().__init__(task, name=task.description()) - self._expected = task.test_parameters.test_directory / 'expected' - - def check(self): - first = self.task.test_parameters.work_directory - second = self._expected - tree_comparison = filecmp.dircmp(first, second) - assert len(tree_comparison.left_only) == 0 - assert len(tree_comparison.right_only) == 0 - _, mismatch, errors = filecmp.cmpfiles(first, second, - tree_comparison.common_files, - shallow=False) - assert len(mismatch) == 0 - assert len(errors) == 0 diff --git a/tests-old/unit-tests/repository_test.py b/tests-old/unit-tests/repository_test.py deleted file mode 100644 index d9f878a3..00000000 --- a/tests-old/unit-tests/repository_test.py +++ /dev/null @@ -1,262 +0,0 @@ -############################################################################## -# (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 -############################################################################## -""" -Exercise the 'repository' module. -""" -import filecmp -from pathlib import Path -from subprocess import run, Popen -import shutil -import signal -import time -from typing import List, Tuple - -from pytest import fixture, mark, raises # type: ignore -from _pytest.tmpdir import TempPathFactory # type: ignore - -from fab import FabException -from fab.repository import repository_from_url, GitRepo, SubversionRepo - - -def _tree_compare(first: Path, second: Path) -> None: - """ - Compare two file trees to ensure they are identical. - """ - tree_comparison = filecmp.dircmp(str(first), str(second)) - assert len(tree_comparison.left_only) == 0 \ - and len(tree_comparison.right_only) == 0 - _, mismatch, errors = filecmp.cmpfiles(str(first), str(second), - tree_comparison.common_files, - shallow=False) - assert len(mismatch) == 0 and len(errors) == 0 - - -class TestSubversion: - """ - Tests of the Subversion repository interface. - """ - @fixture(scope='class') - def repo(self, tmp_path_factory: TempPathFactory) -> Tuple[Path, Path]: - """ - Set up a repository and return its path along with the path of the - original file tree. - """ - repo_path = tmp_path_factory.mktemp('repo', numbered=True) - command = ['svnadmin', 'create', str(repo_path)] - assert run(command).returncode == 0 - tree_path = tmp_path_factory.mktemp('tree', numbered=True) - (tree_path / 'alpha').write_text("First file") - (tree_path / 'beta').mkdir() - (tree_path / 'beta' / 'gamma').write_text("Second file") - command = ['svn', 'import', '-m', "Initial import", - str(tree_path), f'file://{repo_path}/trunk'] - assert run(command).returncode == 0 - return repo_path, tree_path - - def test_extract_from_file(self, repo: Tuple[Path, Path], tmp_path: Path): - """ - Checks that a source tree can be extracted from a Subversion - repository stored on disc. - """ - test_unit = SubversionRepo(f'file://{repo[0]}/trunk') - test_unit.extract(tmp_path) - _tree_compare(repo[1], tmp_path) - assert not (tmp_path / '.svn').exists() - - def test_extract_from_svn(self, repo: Tuple[Path, Path], tmp_path: Path): - """ - Checks that a source tree can be extracted from a Subversion - repository accessed through its own protocol. - """ - command: List[str] = ['svnserve', '-r', str(repo[0]), '-X'] - process = Popen(command) - - test_unit = SubversionRepo('svn://localhost/trunk') - # - # It seems there can be a delay between the server starting and the - # listen socket opening. Thus we have a number of retries. - # - # TODO: Is there a better solution such that we don't try to connect - # until the socket is open? - # - for retry in range(3, 0, -1): - try: - test_unit.extract(tmp_path) - except FabException as ex: - if range == 0: - raise ex - time.sleep(1.0) - else: - break - _tree_compare(repo[1], tmp_path) - assert not (tmp_path / '.svn').exists() - - process.wait(timeout=1) - assert process.returncode == 0 - - @mark.skip(reason="Too hard to test at the moment.") - def test_extract_from_http(self, repo: Tuple[Path, Path], tmp_path: Path): - """ - Checks that a source tree can be extracted from a Subversion - repository accessed through HTTP. - - TODO: This is hard to test without a full Apache installation. For the - moment we forgo the test on the basis that it's too hard. - """ - pass - - -class TestGit: - """ - Tests of the Git repository interface. - """ - @fixture(scope='class') - def repo(self, tmp_path_factory: TempPathFactory) -> Tuple[Path, Path]: - """ - Set up a repository and return its path along with the path of the - original file tree. - """ - tree_path = tmp_path_factory.mktemp('tree', numbered=True) - (tree_path / 'alpha').write_text("First file") - (tree_path / 'beta').mkdir() - (tree_path / 'beta' / 'gamma').write_text("Second file") - - repo_path = tmp_path_factory.mktemp('repo', numbered=True) - command = ['git', 'init', str(repo_path)] - assert run(command).returncode == 0 - # - # We have to configure this information or the forthcoming commands - # will fail. - # - command = ['git', 'config', 'user.name', 'Testing Tester Tests'] - assert run(command, cwd=str(repo_path)).returncode == 0 - command = ['git', 'config', 'user.email', 'tester@example.com'] - assert run(command, cwd=str(repo_path)).returncode == 0 - - for file_object in tree_path.glob('*'): - if file_object.is_dir(): - shutil.copytree(str(file_object), - str(repo_path / file_object.name)) - else: - shutil.copy(str(file_object), - str(repo_path / file_object.name)) - command = ['git', 'add', '-A'] - assert run(command, cwd=str(repo_path)).returncode == 0 - command = ['git', 'commit', '-m', "Initial import"] - assert run(command, cwd=str(repo_path)).returncode == 0 - return repo_path.absolute(), tree_path.absolute() - - def test_extract_from_file(self, repo: Tuple[Path, Path], tmp_path: Path): - """ - Tests that a source tree can be extracted from a local repository. - """ - test_unit = GitRepo(f'file://{repo[0]}') - test_unit.extract(tmp_path) - _tree_compare(repo[1], tmp_path) - assert not (tmp_path / '.git').exists() - - def test_missing_repo(self, tmp_path: Path): - """ - Tests that an error is returned if the repository is not there. - """ - fake_repo = tmp_path / "nonsuch.repo" - fake_repo.mkdir() - test_unit = GitRepo(f'file://{fake_repo}') - with raises(FabException) as ex: - test_unit.extract(tmp_path / 'working') - expected = "Fault exporting tree from Git repository:" - assert str(ex.value).startswith(expected) - - @mark.skip(reason="The daemon doesn't seem to be installed.") - def test_extract_from_git(self, repo: Tuple[Path, Path], tmp_path: Path): - """ - Checks that a source tree can be extracted from a Git repository - accessed through its own protocol. - """ - command: List[str] = ['git', 'daemon', '--reuseaddr', - '--base-path='+str(repo[0].parent), - str(repo[0])] - process = Popen(command) - - test_unit = GitRepo('git://localhost/'+repo[0].name) - test_unit.extract(tmp_path) - _tree_compare(repo[1], tmp_path) - assert not (tmp_path / '.git').exists() - - process.send_signal(signal.SIGTERM) - process.wait(timeout=2) - assert process.returncode == -15 - - @mark.skip(reason="Too hard to test at the moment.") - def test_extract_from_http(self, repo: Tuple[Path, Path], tmp_path: Path): - """ - Checks that a source tree can be extracted from a Git repository - accessed through HTTP. - - TODO: This is hard to test without a full Apache installation. For the - moment we forgo the test on the basis that it's too hard. - """ - pass - - -class TestRepoFromURL: - """ - Tests that a URL can be converted into the correct Repository object. - """ - @fixture(scope='class', - params=[ - {'access_url': 'git://example.com/git', - 'repo_class': GitRepo, - 'repo_url': 'git://example.com/git'}, - {'access_url': 'git+file:///tmp/git', - 'repo_class': GitRepo, - 'repo_url': 'file:///tmp/git'}, - {'access_url': 'git+git://example.com/git', - 'repo_class': GitRepo, - 'repo_url': 'git://example.com/git'}, - {'access_url': 'git+http://example.com/git', - 'repo_class': GitRepo, - 'repo_url': 'http://example.com/git'}, - {'access_url': 'svn://example.com/svn', - 'repo_class': SubversionRepo, - 'repo_url': 'svn://example.com/svn'}, - {'access_url': 'svn+file:///tmp/svn', - 'repo_class': SubversionRepo, - 'repo_url': 'file:///tmp/svn'}, - {'access_url': 'svn+http://example.com/svn', - 'repo_class': SubversionRepo, - 'repo_url': 'http://example.com/svn'}, - {'access_url': 'svn+svn://example.com/svn', - 'repo_class': SubversionRepo, - 'repo_url': 'svn://example.com/svn'}, - {'access_url': 'file:///tmp/repo', - 'repo_class': FabException, - 'exception': "Unrecognised repository scheme: file+file"}, - {'access_url': 'http://example.com/repo', - 'repo_class': FabException, - 'exception': "Unrecognised repository scheme: http+http"}, - {'access_url': 'foo+file:///tmp/foo', - 'repo_class': FabException, - 'exception': "Unrecognised repository scheme: foo+file"} - ]) - def cases(self, request): - """ - Generates a set of test cases. - """ - yield request.param - - def test_action(self, cases): - """ - Checks that each URL creates an appropriate Repository object. - """ - if issubclass(cases['repo_class'], Exception): - with raises(cases['repo_class']) as ex: - _ = repository_from_url(cases['access_url']) - assert ex.value.args[0] == cases['exception'] - else: - repo = repository_from_url(cases['access_url']) - assert isinstance(repo, cases['repo_class']) - assert repo.url == cases['repo_url'] diff --git a/tests-old/unit-tests/tasks/__init__.py b/tests-old/unit-tests/tasks/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests-old/unit-tests/tasks/c_test.py b/tests-old/unit-tests/tasks/c_test.py deleted file mode 100644 index 3d188af6..00000000 --- a/tests-old/unit-tests/tasks/c_test.py +++ /dev/null @@ -1,64 +0,0 @@ -############################################################################## -# (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 -############################################################################## -from pathlib import Path -from textwrap import dedent - - -class TestCPragmaInjector: - def test_run(self, tmp_path): - workspace = tmp_path / 'working' - workspace.mkdir() - - test_file: Path = tmp_path / 'test.c' - test_file.write_text( - dedent(''' - #include "user_include.h" - Unrelated text - #include 'another_user_include.h' - #include - More unrelated text - #include - ''')) - test_artifact = Artifact(test_file, - CSource, - HeadersAnalysed) - test_artifact.add_dependency('foo') - - # Run the Injector - injector = CPragmaInjector(workspace) - artifacts_out = injector.run([test_artifact]) - - assert len(artifacts_out) == 1 - assert artifacts_out[0].location == workspace / 'test.c' - assert artifacts_out[0].filetype is CSource - assert artifacts_out[0].state is Modified - assert artifacts_out[0].depends_on == ['foo'] - assert artifacts_out[0].defines == [] - - new_file = workspace / 'test.c' - assert new_file.exists() - with new_file.open('r') as fh: - new_text = fh.read() - - expected_text = ( - dedent(''' - #pragma FAB UsrIncludeStart - #include "user_include.h" - #pragma FAB UsrIncludeEnd - Unrelated text - #pragma FAB UsrIncludeStart - #include 'another_user_include.h' - #pragma FAB UsrIncludeEnd - #pragma FAB SysIncludeStart - #include - #pragma FAB SysIncludeEnd - More unrelated text - #pragma FAB SysIncludeStart - #include - #pragma FAB SysIncludeEnd - ''')) - - assert new_text == expected_text diff --git a/tests-old/unit-tests/tasks/common_test.py b/tests-old/unit-tests/tasks/common_test.py deleted file mode 100644 index 1ab27b9e..00000000 --- a/tests-old/unit-tests/tasks/common_test.py +++ /dev/null @@ -1,58 +0,0 @@ -############################################################################## -# (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 -############################################################################## -from pathlib import Path -from textwrap import dedent - -from fab.tasks.common import Linker, HeaderAnalyser -from fab.artifact import \ - Artifact, \ - New, \ - Unknown, \ - Executable, \ - Linked, \ - HeadersAnalysed - - -class TestLinker: - def test_run(self, mocker, tmp_path: Path): - # Instantiate Linker - workspace = Path(tmp_path) - linker = Linker('foo', - ['--bar', '--baz'], - workspace, - 'qux') - - # Create artifacts (object files for linking) - file1 = '/path/to/file.1' - file2 = '/path/to/file.2' - artifacts = [Artifact(Path(file1), - Unknown, - New), - Artifact(Path(file2), - Unknown, - New)] - - # Monkeypatch the subprocess call out and run linker - patched_run = mocker.patch('subprocess.run') - artifacts_out = linker.run(artifacts) - - # Check that the subprocess call contained the command - # that we would expect based on the above - expected_command = ['foo', - '-o', - str(workspace / 'qux'), - file1, - file2, - '--bar', - '--baz'] - patched_run.assert_called_once_with(expected_command, - check=True) - assert len(artifacts_out) == 1 - assert artifacts_out[0].location == workspace / 'qux' - assert artifacts_out[0].filetype is Executable - assert artifacts_out[0].state is Linked - assert artifacts_out[0].depends_on == [] - assert artifacts_out[0].defines == [] diff --git a/tests/system_tests/psyclone/test_psyclone_system_test.py b/tests/system_tests/psyclone/test_psyclone_system_test.py index cf3c80d0..3c16fd4a 100644 --- a/tests/system_tests/psyclone/test_psyclone_system_test.py +++ b/tests/system_tests/psyclone/test_psyclone_system_test.py @@ -199,6 +199,9 @@ class TestTransformationScript: """ def test_transformation_script(self, psyclone_lfric_api): psyclone_tool = Psyclone() + psyclone_tool._version = (2, 4, 0) + psyclone_tool._is_available = True + mock_transformation_script = mock.Mock(return_value=__file__) with mock.patch('fab.tools.psyclone.Psyclone.run') as mock_run_command: mock_transformation_script.return_value = Path(__file__) @@ -216,8 +219,9 @@ def test_transformation_script(self, psyclone_lfric_api): mock_transformation_script.assert_called_once_with(Path(__file__), None) # check transformation_script is passed to psyclone command with '-s' mock_run_command.assert_called_with( - additional_parameters=['-api', psyclone_lfric_api, '-l', 'all', + additional_parameters=['-api', psyclone_lfric_api, '-opsy', Path(__file__), '-oalg', Path(__file__), + '-l', 'all', '-s', Path(__file__), __file__]) diff --git a/tests/unit_tests/parse/c/test_c_analyser.py b/tests/unit_tests/parse/c/test_c_analyser.py index b4f84c94..c288baf9 100644 --- a/tests/unit_tests/parse/c/test_c_analyser.py +++ b/tests/unit_tests/parse/c/test_c_analyser.py @@ -7,12 +7,14 @@ from unittest import mock from unittest.mock import Mock -import clang # type: ignore +from pytest import importorskip from fab.build_config import BuildConfig from fab.parse.c import CAnalyser, AnalysedC from fab.tools import ToolBox +clang = importorskip('clang') + def test_simple_result(tmp_path): c_analyser = CAnalyser() diff --git a/tests/unit_tests/steps/test_c_pragma_injector.py b/tests/unit_tests/steps/test_c_pragma_injector.py index e75e9f19..666bae1b 100644 --- a/tests/unit_tests/steps/test_c_pragma_injector.py +++ b/tests/unit_tests/steps/test_c_pragma_injector.py @@ -1,4 +1,5 @@ -import sys +from sys import version_info as python_version +from textwrap import dedent from unittest import mock from unittest.mock import mock_open @@ -9,32 +10,52 @@ class Test_inject_pragmas(object): - @pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher for mock_open iteration") + @pytest.mark.skipif(python_version < (3, 8), + reason="Requires python version 3.8 or higher for " + "mock_open iteration") def test_vanilla(self): - input = [ - '', - '// hi there, ignore me', - '', - '#include ', - '', - '#include "bar.h"', - '', - ] - data = "\n".join(input) + source = dedent( + """ + // C++ style comment, ignore this. + #include "user_include.h" + #include "second_user_include.h" + Unrelated text + /* Including C style comment */ + #include 'another_user_include.h' + #include + More unrelated text + #include + #include "final_user_include.h" + """ + ) - with mock.patch('fab.steps.c_pragma_injector.open', mock_open(read_data=data)): + with mock.patch('fab.steps.c_pragma_injector.open', + mock_open(read_data=source)): result = inject_pragmas(fpath="foo") output = list(result) assert output == [ '\n', - '// hi there, ignore me\n', - '\n', + '// C++ style comment, ignore this.\n', + '#pragma FAB UsrIncludeStart\n', + '#include "user_include.h"\n', + '#pragma FAB UsrIncludeEnd\n', + '#pragma FAB UsrIncludeStart\n', + '#include "second_user_include.h"\n', + '#pragma FAB UsrIncludeEnd\n', + 'Unrelated text\n', + '/* Including C style comment */\n', + '#pragma FAB UsrIncludeStart\n', + "#include 'another_user_include.h'\n", + '#pragma FAB UsrIncludeEnd\n', '#pragma FAB SysIncludeStart\n', - '#include \n', + '#include \n', + '#pragma FAB SysIncludeEnd\n', + "More unrelated text\n", + '#pragma FAB SysIncludeStart\n', + '#include \n', '#pragma FAB SysIncludeEnd\n', - '\n', '#pragma FAB UsrIncludeStart\n', - '#include "bar.h"\n', - '#pragma FAB UsrIncludeEnd\n', + '#include "final_user_include.h"\n', + '#pragma FAB UsrIncludeEnd\n' ] diff --git a/tests/unit_tests/tools/test_psyclone.py b/tests/unit_tests/tools/test_psyclone.py index 7efc60ec..5586c485 100644 --- a/tests/unit_tests/tools/test_psyclone.py +++ b/tests/unit_tests/tools/test_psyclone.py @@ -10,55 +10,193 @@ from importlib import reload from unittest import mock +import pytest + from fab.tools import (Category, Psyclone) +def get_mock_result(version_info: str) -> mock.Mock: + '''Returns a mock PSyclone object that will return + the specified str as version info. + + :param version_info: the simulated output of psyclone --version + The leading "PSyclone version: " will be added automatically. + ''' + # The return of subprocess run has an attribute 'stdout', + # that returns the stdout when its `decode` method is called. + # So we mock stdout, then put this mock_stdout into the mock result: + mock_stdout = mock.Mock(decode=lambda: f"PSyclone version: {version_info}") + mock_result = mock.Mock(stdout=mock_stdout, returncode=0) + return mock_result + + def test_psyclone_constructor(): '''Test the PSyclone constructor.''' psyclone = Psyclone() assert psyclone.category == Category.PSYCLONE assert psyclone.name == "psyclone" assert psyclone.exec_name == "psyclone" + # pylint: disable=use-implicit-booleaness-not-comparison assert psyclone.flags == [] - assert psyclone._api is None - - psyclone = Psyclone(api="gocean1.0") - assert psyclone.category == Category.PSYCLONE - assert psyclone.name == "psyclone" - assert psyclone.exec_name == "psyclone" - assert psyclone.flags == [] - assert psyclone._api == "gocean1.0" -def test_psyclone_check_available(): - '''Tests the is_available functionality.''' +def test_psyclone_check_available_2_4_0(): + '''Tests the is_available functionality with version 2.4.0. + We get only one call. + ''' psyclone = Psyclone() - mock_result = mock.Mock(returncode=0) + + mock_result = get_mock_result("2.4.0") with mock.patch('fab.tools.tool.subprocess.run', return_value=mock_result) as tool_run: assert psyclone.check_available() tool_run.assert_called_once_with( - ["psyclone", "--version"], capture_output=True, env=None, - cwd=None, check=False) + ["psyclone", "--version", mock.ANY], capture_output=True, + env=None, cwd=None, check=False) + + +def test_psyclone_check_available_2_5_0(): + '''Tests the is_available functionality with PSyclone 2.5.0. + We get two calls. First version, then check if nemo API exists + ''' + psyclone = Psyclone() + + mock_result = get_mock_result("2.5.0") + with mock.patch('fab.tools.tool.subprocess.run', + return_value=mock_result) as tool_run: + assert psyclone.check_available() + tool_run.assert_any_call( + ["psyclone", "--version", mock.ANY], capture_output=True, + env=None, cwd=None, check=False) + tool_run.assert_any_call( + ["psyclone", "-api", "nemo", mock.ANY], capture_output=True, + env=None, cwd=None, check=False) # Test behaviour if a runtime error happens: with mock.patch("fab.tools.tool.Tool.run", side_effect=RuntimeError("")) as tool_run: + with pytest.warns(UserWarning, + match="Unexpected version information " + "for PSyclone: ''."): + assert not psyclone.check_available() + + +def test_psyclone_check_available_after_2_5_0(): + '''Tests the is_available functionality with releases after 2.5.0. + We get two calls. First version, then check if nemo API exists + ''' + psyclone = Psyclone() + + # We detect the dummy version '2.5.0.1' if psyclone reports 2.5.0 + # but the command line option "-api nemo" is not accepted. + # So we need to return two results from our mock objects: first + # success for version 2.5.0, then a failure with an appropriate + # error message: + mock_result1 = get_mock_result("2.5.0") + mock_result2 = get_mock_result("Unsupported PSyKAL DSL / " + "API 'nemo' specified") + mock_result2.returncode = 1 + + # "Unsupported PSyKAL DSL / API 'nemo' specified" + with mock.patch('fab.tools.tool.subprocess.run', + return_value=mock_result1) as tool_run: + tool_run.side_effect = [mock_result1, mock_result2] + assert psyclone.check_available() + assert psyclone._version == (2, 5, 0, 1) + + +def test_psyclone_check_available_errors(): + '''Test various errors that can happen in check_available. + ''' + psyclone = Psyclone() + with mock.patch('fab.tools.tool.subprocess.run', + side_effect=FileNotFoundError("ERR")): assert not psyclone.check_available() + psyclone = Psyclone() + mock_result = get_mock_result("NOT_A_NUMBER.4.0") + with mock.patch('fab.tools.tool.subprocess.run', + return_value=mock_result): + with pytest.warns(UserWarning, + 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.''' -def test_psyclone_process(psyclone_lfric_api): - '''Test running PSyclone.''' psyclone = Psyclone() - mock_result = mock.Mock(returncode=0) - # Create a mock function that returns a 'transformation script' - # called `script_called`: + 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_processing_errors_with_api(api): + '''Test all processing errors in PSyclone if an 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=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=psyclone_lfric_api, + api=api_in, x90_file="x90_file", psy_file="psy_file", alg_file="alg_file", @@ -66,60 +204,148 @@ def test_psyclone_process(psyclone_lfric_api): kernel_roots=["root1", "root2"], additional_parameters=["-c", "psyclone.cfg"]) tool_run.assert_called_with( - ['psyclone', '-api', psyclone_lfric_api, '-l', 'all', '-opsy', - 'psy_file', '-oalg', 'alg_file', '-s', 'script_called', '-c', + ['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) - # Don't specify an API: + +@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(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="", x90_file="x90_file", - psy_file="psy_file", - alg_file="alg_file", + transformed_file="psy_file", transformation_script=transformation_function, kernel_roots=["root1", "root2"], additional_parameters=["-c", "psyclone.cfg"]) tool_run.assert_called_with( - ['psyclone', '-l', 'all', '-opsy', 'psy_file', '-oalg', 'alg_file', + ['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) - # Don't specify an API, but define an API on the PSyclone tool: - psyclone = Psyclone(api="gocean1.0") + +@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", - psy_file="psy_file", - alg_file="alg_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', 'gocean1.0', '-l', 'all', '-opsy', 'psy_file', - '-oalg', 'alg_file', '-s', 'script_called', '-c', + ['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) - # Have both a default and a command line option - the latter - # must take precedence: - psyclone = Psyclone(api="gocean1.0") + +@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", - api=psyclone_lfric_api, transformation_script=transformation_function, kernel_roots=["root1", "root2"], additional_parameters=["-c", "psyclone.cfg"]) tool_run.assert_called_with( - ['psyclone', '-api', psyclone_lfric_api, '-l', 'all', '-opsy', - 'psy_file', '-oalg', 'alg_file', '-s', 'script_called', '-c', + ['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) @@ -133,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) diff --git a/tests/unit_tests/tools/test_versioning.py b/tests/unit_tests/tools/test_versioning.py index a3b21896..fb825000 100644 --- a/tests/unit_tests/tools/test_versioning.py +++ b/tests/unit_tests/tools/test_versioning.py @@ -3,28 +3,26 @@ # For further details please refer to the file COPYRIGHT # which you should have received as part of this distribution ############################################################################## - -'''Tests the compiler implementation. -''' - +""" +Tests version control interfaces. +""" +from filecmp import cmpfiles, dircmp +from pathlib import Path +from shutil import which from unittest import mock +from subprocess import Popen, run +from time import sleep +from typing import List, Tuple -import pytest +from pytest import TempPathFactory, fixture, mark, raises -from fab.tools import Category, Fcm, Git, Subversion, Versioning +from fab.tools import Category, Fcm, Git, Subversion class TestGit: - '''Contains all git related tests.''' - - def test_versioning_constructor(self): - '''Test the versioning constructor.''' - versioning = Versioning("versioning", "versioning.exe", Category.GIT) - assert versioning.category == Category.GIT - assert versioning.name == "versioning" - assert versioning.flags == [] - assert versioning.exec_name == "versioning.exe" - + """ + Tests of the Git repository interface. + """ def test_git_constructor(self): '''Test the git constructor.''' git = Git() @@ -116,7 +114,7 @@ def test_git_fetch(self): with mock.patch.object(git, "run", side_effect=RuntimeError("ERR")) as run: - with pytest.raises(RuntimeError) as err: + with raises(RuntimeError) as err: git.fetch("/src", "/dst", revision="revision") assert "ERR" in str(err.value) run.assert_called_once_with(['fetch', "/src", "revision"], cwd="/dst", @@ -143,7 +141,7 @@ def test_git_checkout(self): with mock.patch.object(git, "run", side_effect=RuntimeError("ERR")) as run: - with pytest.raises(RuntimeError) as err: + with raises(RuntimeError) as err: git.checkout("/src", "/dst", revision="revision") assert "ERR" in str(err.value) run.assert_called_with(['fetch', "/src", "revision"], cwd="/dst", @@ -173,7 +171,7 @@ def raise_1st_time(): with mock.patch.object(git, "run", side_effect=raise_1st_time()) as run: - with pytest.raises(RuntimeError) as err: + with raises(RuntimeError) as err: git.merge("/dst", revision="revision") assert "Error merging revision. Merge aborted." in str(err.value) run.assert_any_call(['merge', "FETCH_HEAD"], cwd="/dst", @@ -184,7 +182,7 @@ def raise_1st_time(): # Test behaviour if both merge and merge --abort fail with mock.patch.object(git, "run", side_effect=RuntimeError("ERR")) as run: - with pytest.raises(RuntimeError) as err: + with raises(RuntimeError) as err: git.merge("/dst", revision="revision") assert "ERR" in str(err.value) run.assert_called_with(['merge', "--abort"], cwd="/dst", @@ -192,22 +190,27 @@ def raise_1st_time(): # ============================================================================ -class TestSvn: - '''Contains all svn related tests.''' - +class TestSubversion: + """ + Tests the Subversion interface. + """ def test_svn_constructor(self): - '''Test the git constructor.''' + """ + Test the git constructor. + """ svn = Subversion() assert svn.category == Category.SUBVERSION assert svn.flags == [] - assert svn.name == "subversion" + assert svn.name == "Subversion" assert svn.exec_name == "svn" def test_svn_export(self): - '''Check export svn functionality. The tests here will actually - mock the git results, so they will work even if subversion is not - installed. The system_tests will test an actual check out etc. ''' + """ + Ensures an export from repository works. + Subversion is mocked here to allow testing without the executable. + Testing with happens below in TestSubversionReal. + """ svn = Subversion() mock_result = mock.Mock(returncode=0) with mock.patch('fab.tools.tool.subprocess.run', @@ -282,14 +285,107 @@ def test_svn_merge(self): env=None, cwd="/dst", capture_output=True, check=False) +def _tree_compare(first: Path, second: Path) -> None: + """ + Compare two file trees to ensure they are identical. + """ + tree_comparison = dircmp(str(first), str(second)) + assert len(tree_comparison.left_only) == 0 \ + and len(tree_comparison.right_only) == 0 + _, mismatch, errors = cmpfiles(str(first), str(second), + tree_comparison.common_files, + shallow=False) + assert len(mismatch) == 0 and len(errors) == 0 + + +@mark.skipif(which('svn') is None, + reason="No Subversion executable found on path.") +class TestSubversionReal: + """ + Tests the Subversion interface against a real executable. + """ + @fixture(scope='class') + def repo(self, tmp_path_factory: TempPathFactory) -> Tuple[Path, Path]: + """ + Set up a repository and return its path along with the path of the + original file tree. + """ + repo_path = tmp_path_factory.mktemp('repo', numbered=True) + command = ['svnadmin', 'create', str(repo_path)] + assert run(command).returncode == 0 + tree_path = tmp_path_factory.mktemp('tree', numbered=True) + (tree_path / 'alpha').write_text("First file") + (tree_path / 'beta').mkdir() + (tree_path / 'beta' / 'gamma').write_text("Second file") + command = ['svn', 'import', '-m', "Initial import", + str(tree_path), f'file://{repo_path}/trunk'] + assert run(command).returncode == 0 + return repo_path, tree_path + + def test_extract_from_file(self, repo: Tuple[Path, Path], tmp_path: Path): + """ + Checks that a source tree can be extracted from a Subversion + repository stored on disc. + """ + test_unit = Subversion() + test_unit.export(f'file://{repo[0]}/trunk', tmp_path) + _tree_compare(repo[1], tmp_path) + assert not (tmp_path / '.svn').exists() + + def test_extract_from_svn(self, repo: Tuple[Path, Path], tmp_path: Path): + """ + Checks that a source tree can be extracted from a Subversion + repository accessed through its own protocol. + """ + command: List[str] = ['svnserve', '-r', str(repo[0]), '-X'] + process = Popen(command) + + test_unit = Subversion() + # + # It seems there can be a delay between the server starting and the + # listen socket opening. Thus we have a number of retries. + # + # TODO: Is there a better solution such that we don't try to connect + # until the socket is open? + # + for retry in range(3, 0, -1): + try: + test_unit.export('svn://localhost/trunk', tmp_path) + except Exception as ex: + if range == 0: + raise ex + sleep(1.0) + else: + break + _tree_compare(repo[1], tmp_path) + assert not (tmp_path / '.svn').exists() + + process.wait(timeout=1) + assert process.returncode == 0 + + @mark.skip(reason="Too hard to test at the moment.") + def test_extract_from_http(self, repo: Tuple[Path, Path], tmp_path: Path): + """ + Checks that a source tree can be extracted from a Subversion + repository accessed through HTTP. + + TODO: This is hard to test without a full Apache installation. For the + moment we forgo the test on the basis that it's too hard. + """ + pass + + # ============================================================================ class TestFcm: - '''Contains all FCM related tests.''' - + """ + Tests the FCM interface task. + """ def test_fcm_constructor(self): - '''Test the fcb constructor.''' + """ + Tests this constructor. + """ fcm = Fcm() assert fcm.category == Category.FCM assert fcm.flags == [] - assert fcm.name == "fcm" + assert fcm.name == "FCM" assert fcm.exec_name == "fcm"