def test_get_int_array_section_subscript_err(monkeypatch): ''' Check that we raise the appropriate error if the parse tree for the LHS of the array declaration is broken. ''' from fparser.two import Fortran2003 # First create a valid KernelType object ast = parse(DIFF_BASIS, ignore_comments=False) ktype = KernelType(ast) # Create a valid fparser2 result assign = Fortran2003.Assignment_Stmt("gh_evaluator_targets(2) = [1, 2]") # Break the array constructor expression by replacing the # Section_Subscript_List with a str assign.children[0].items = (assign.children[0].items[0], "hello") # Use monkeypatch to ensure that that's the result that is returned # when we attempt to use fparser2 from within the routine under test def my_init(self, _): ''' dummy constructor ''' self.items = assign.items monkeypatch.setattr(Fortran2003.Assignment_Stmt, "__init__", my_init) with pytest.raises(InternalError) as err: _ = ktype.get_integer_array("gh_evaluator_targets") assert ("expected array declaration to have a Section_Subscript_List but " "found" in str(err.value))
def test_get_int_array_err1(monkeypatch): ''' Tests that we raise the correct error if there is something wrong with the assignment statement obtained from fparser2. ''' from fparser.two import Fortran2003 # This is difficult as we have to break the result returned by fparser2. # We therefore create a valid KernelType object ast = fpapi.parse(MDATA, ignore_comments=False) ktype = KernelType(ast) # Next we create a valid fparser2 result my_assign = Fortran2003.Assignment_Stmt("my_array(2) = [1, 2]") # Break its `items` property by replacing the Name object with a string # (tuples are immutable so make a new one) broken_items = tuple(["invalid"] + list(my_assign.items[1:])) # Use monkeypatch to ensure that that the Assignment_Stmt that # is returned when we attempt to use fparser2 from within the # routine under test now has the broken tuple of items. def my_init(self, _): ''' dummy class ''' self.items = broken_items monkeypatch.setattr(Fortran2003.Assignment_Stmt, "__init__", my_init) with pytest.raises(InternalError) as err: _ = ktype.get_integer_array("gh_evaluator_targets") assert "Unsupported assignment statement: 'invalid = [1, 2]'" in str(err)
def test_get_int_array_constructor_err(monkeypatch): ''' Check that we raise the appropriate error if we fail to parse the array constructor expression. ''' from fparser.two import Fortran2003 # First create a valid KernelType object ast = parse(DIFF_BASIS, ignore_comments=False) ktype = KernelType(ast) # Create a valid fparser2 result assign = Fortran2003.Assignment_Stmt("gh_evaluator_targets(2) = [1, 2]") # Break the array constructor expression (tuples are immutable so make a # new one) assign.items[2].items[1].items = tuple(["hello", "goodbye"]) # Use monkeypatch to ensure that that's the result that is returned # when we attempt to use fparser2 from within the routine under test def my_init(self, _): ''' dummy class ''' self.items = assign.items monkeypatch.setattr(Fortran2003.Assignment_Stmt, "__init__", my_init) with pytest.raises(InternalError) as err: _ = ktype.get_integer_array("gh_evaluator_targets") assert ("Failed to parse array constructor: '[hello, goodbye]'" in str(err.value))
def test_get_int_array_not_array(): ''' Test that get_integer_array returns the expected error if the requested variable is not an array. ''' ast = fpapi.parse(MDATA, ignore_comments=False) ktype = KernelType(ast) # Erroneously call get_integer_array with the name of a scalar meta-data # entry with pytest.raises(ParseError) as err: _ = ktype.get_integer_array("iterates_over") assert ("RHS of assignment is not an array constructor: 'iterates_over = " "cells'" in str(err))
def test_kerneltype_operates_on(operates): ''' Test the parsing of the 'operates_on' metadata element. ''' code = CODE.replace("cell_column", operates) parse_tree = parse(code) ktype = KernelType(parse_tree) assert ktype.iterates_over == operates # Check that the parsing is not case sensitive code = CODE.replace("cell_column", operates.upper()) parse_tree = parse(code) ktype = KernelType(parse_tree) assert ktype.iterates_over == operates
def test_kerneltype_iterates_over(iterates): ''' Test the parsing of the 'iterates_over' metadata element. TODO #870 remove this test. ''' code = ITERATES_OVER_CODE.replace("cells", iterates) parse_tree = parse(code) ktype = KernelType(parse_tree) assert ktype.iterates_over == iterates # Check that the parsing is not case sensitive code = ITERATES_OVER_CODE.replace("cells", iterates.upper()) parse_tree = parse(code) ktype = KernelType(parse_tree) assert ktype.iterates_over == iterates
def test_kerneltype_repr(): '''Test that the __repr__ method in KernelType() behaves as expected.''' # With operates_on set parse_tree = parse(CODE) tmp = KernelType(parse_tree) assert repr(tmp) == "KernelType(test_type, cell_column)" # With iterates_over set parse_tree = parse(ITERATES_OVER_CODE) tmp = KernelType(parse_tree) assert repr(tmp) == "KernelType(test_type, cells)"
def test_kerneltype_repr(): '''Test that the __repr__ method in KernelType() behaves as expected.''' parse_tree = parse(CODE) tmp = KernelType(parse_tree) assert repr(tmp) == "KernelType(test_type, cells)"
def __init__(self, ast, name=None): KernelType.__init__(self, ast, name=name) self._arg_descriptors = [] for init in self._inits: if init.name != 'arg_type': raise ParseError( "dynamo0p1.py:DynKernelType:__init__: Each meta_arg " "value must be of type 'arg_type' for the " "dynamo0.1 api, but found '{0}'.".format(init.name)) access = init.args[0].name funcspace = init.args[1].name stencil = init.args[2].name x1 = init.args[3].name x2 = init.args[4].name x3 = init.args[5].name self._arg_descriptors.append( DynDescriptor(access, funcspace, stencil, x1, x2, x3))
def test_get_int_err(): ''' Tests that we raise the expected error if the meta-data contains an integer literal instead of a name. ''' mdata = MDATA.replace("= cells", "= 1") ast = fpapi.parse(mdata, ignore_comments=False) with pytest.raises(ParseError) as err: _ = KernelType(ast) assert ("RHS of assignment is not a variable name: 'iterates_over = 1'" in str(err))
def test_kernel_binding_not_code(): ''' Check that we raise the expected error when Kernel meta-data uses a specific binding but does not have 'code' as the generic name. ''' mdata = MDATA.replace("code => test", "my_code => test") ast = fpapi.parse(mdata) with pytest.raises(ParseError) as err: _ = KernelType(ast) assert ("binds to a specific procedure but does not use 'code' as the " "generic name" in str(err))
def test_get_integer_variable_err(): ''' Tests that we raise the expected error if the meta-data contains an integer literal instead of a name. ''' mdata = DIFF_BASIS.replace("= cell_column", "= 1") ast = parse(mdata, ignore_comments=False) with pytest.raises(ParseError) as err: _ = KernelType(ast) assert ("RHS of assignment is not a variable name: 'operates_on = 1'" in str(err.value))
def test_kernel_binding_missing(): ''' Check that we raise the correct error when the Kernel meta-data is missing the type-bound procedure giving the name of the subroutine. ''' mdata = MDATA.replace( "contains\n procedure, nopass :: code => testkern_eval_code\n", "") ast = fpapi.parse(mdata) with pytest.raises(ParseError) as err: _ = KernelType(ast) assert ("Kernel type testkern_eval_type does not bind a specific " "procedure" in str(err))
def __init__(self, ast, name=None): KernelType.__init__(self, ast, name=name) self._arg_descriptors = [] for init in self._inits: if init.name != 'arg': raise ParseError( "gocean0p1.py:GOKernelType:__init__: Each meta_arg value " "must be of type 'arg' for the gocean0.1 api, but found " "'{0}'.".format(init.name)) access = init.args[0].name funcspace = init.args[1].name stencil = init.args[2].name if len(init.args) != 3: raise ParseError( "gocean0p1.py:GOKernelType:__init__: 'arg' type expects " "3 arguments but found {0} in '{1}'".format( str(len(init.args)), init.args)) self._arg_descriptors.append( GODescriptor(access, funcspace, stencil))
def test_kerneltype_both_operates_on_iterates_over(): ''' Check that KernelType raises the expected error if the kernel metadata specifies *both* operates_on and iterates_over. ''' code = ITERATES_OVER_CODE.replace( " contains\n", " integer :: operates_on = cell_column\n" " contains\n") parse_tree = parse(code) with pytest.raises(ParseError) as err: KernelType(parse_tree) assert ("kernel 'test_type' contains both 'operates_on' and " "'iterates_over'" in str(err.value))
def test_kerneltype_typename(): '''Test that an exception is raised if the metadata type name is not what was expected. ''' my_code = CODE.replace("meta_args", "invalid_name") parse_tree = parse(my_code) with pytest.raises(ParseError) as excinfo: _ = KernelType(parse_tree) assert "No kernel metadata with type name 'meta_args' found." \ in str(excinfo.value)
def test_kerneltype_dimensions(): '''Test that an exception is raised if the metadata variable is a multi-dimensional array. ''' my_code = CODE.replace("dimension(1)", "dimension(1,1)") parse_tree = parse(my_code) with pytest.raises(ParseError) as excinfo: _ = KernelType(parse_tree) assert ("In kernel metadata 'test_type': 'meta_args' variable must be a " "1 dimensional array") in str(excinfo.value)
def test_kerneltype_nargs(): '''Test that an exception is raised if the number of arguments does not match the specified number of arguments in the kernel metadata. ''' my_code = CODE.replace("dimension(1)", "dimension(2)") parse_tree = parse(my_code) with pytest.raises(ParseError) as excinfo: _ = KernelType(parse_tree) assert ("In the 'meta_args' metadata, the number of args '2' and extent " "of the dimension '1' do not match.") in str(excinfo.value)
def test_kerneltype_brackets(): '''Test that an exception is raised if the metadata is supplied within square brackets as this is not supported within the parser. ''' my_code = CODE.replace("(/", "[") my_code = my_code.replace("/)", "]") parse_tree = parse(my_code) with pytest.raises(ParseError) as excinfo: _ = KernelType(parse_tree) assert ("Parser does not currently support [...] initialisation for " "'meta_args', please use (/.../) instead.") in str(excinfo.value)
def test_empty_kernel_name(monkeypatch): ''' Check that we raise the correct error when we get a blank string for the name of the Kernel subroutine. ''' import fparser mdata = MDATA.replace("procedure, nopass :: code => testkern_eval_code", "procedure, nopass :: testkern_eval_code") ast = fpapi.parse(mdata) # Break the AST for statement, _ in fpapi.walk(ast, -1): if isinstance(statement, fparser.one.statements.SpecificBinding): monkeypatch.setattr(statement, "name", "") break with pytest.raises(InternalError) as err: _ = KernelType(ast) assert ("Empty Kernel name returned for Kernel type testkern_eval_type" in str(err))
def test_get_int_array(): ''' Tests for the KernelType.get_integer_array() method. ''' ast = fpapi.parse(MDATA, ignore_comments=False) ktype = KernelType(ast) targets = ktype.get_integer_array("gh_evaluator_targets") assert targets == ["w0", "w1"] mdata = MDATA.replace("[W0, W1]", "(/W0, W1/)") ast = fpapi.parse(mdata, ignore_comments=False) ktype = KernelType(ast) targets = ktype.get_integer_array("gh_evaluator_targets") assert targets == ["w0", "w1"]
def test_kerneltype_both_operates_on_iterates_over(): ''' Check that KernelType raises the expected error if the kernel metadata specifies *both* operates_on and iterates_over (the GOcean API uses iterates_over while LFRic uses operates_on). TODO #1204 this test can be removed once the check for this metadata has been moved into the API-specific subclasses. ''' code = CODE.replace( " contains\n", " integer :: iterates_over = cell_column\n" " contains\n") parse_tree = parse(code) with pytest.raises(ParseError) as err: KernelType(parse_tree) assert ("kernel 'test_type' contains both 'operates_on' and " "'iterates_over'" in str(err.value))
def test_get_integer_variable(): ''' Test that the KernelType get_integer_variable method works as expected. ''' parse_tree = parse(DIFF_BASIS) tmp = KernelType(parse_tree) # Check that we return None if the matched name is an array assert tmp.get_integer_variable("GH_SHAPE") is None assert tmp.get_integer_variable("gh_shape") is None new_code = DIFF_BASIS.replace( "integer :: gh_shape(2) = (/gh_quadrature_XYoZ, gh_quadrature_edge/)", "integer :: gh_shape = gh_quadrature_face") parse_tree = parse(new_code) tmp = KernelType(parse_tree) assert tmp.get_integer_variable("GH_SHAPE") == "gh_quadrature_face" assert tmp.get_integer_variable("Gh_Shape") == "gh_quadrature_face"
def test_get_int(): ''' Tests for the KernelType.get_integer(). method ''' ast = fpapi.parse(MDATA, ignore_comments=False) ktype = KernelType(ast) iter_val = ktype.get_integer_variable("iterates_over") assert iter_val == "cells"
def test_get_integer_array(): ''' Test that the KernelType get_integer_array method works as expected. ''' parse_tree = parse(DIFF_BASIS) tmp = KernelType(parse_tree) assert tmp.get_integer_array("gh_shape") == [ 'gh_quadrature_xyoz', 'gh_quadrature_edge' ] assert tmp.get_integer_array("GH_SHAPE") == [ 'gh_quadrature_xyoz', 'gh_quadrature_edge' ] new_code = DIFF_BASIS.replace("(/gh_quadrature_XYoZ, gh_quadrature_edge/)", "[gh_quadrature_XYoZ, gh_quadrature_edge]") parse_tree = parse(new_code) tmp = KernelType(parse_tree) assert tmp.get_integer_array("GH_SHAPE") == [ 'gh_quadrature_xyoz', 'gh_quadrature_edge' ] new_code = DIFF_BASIS.replace("gh_shape(2)", "gh_shape(3)") parse_tree = parse(new_code) tmp = KernelType(parse_tree) with pytest.raises(ParseError) as err: tmp.get_integer_array("gh_shape") assert ("declared length of array 'gh_shape' is 3 but constructor only " "contains 2 names: 'gh_shape" in str(err.value)) # Use variable name instead of integer to dimension array new_code = DIFF_BASIS.replace("gh_shape(2)", "gh_shape(npts)") parse_tree = parse(new_code) tmp = KernelType(parse_tree) with pytest.raises(ParseError) as err: tmp.get_integer_array("gh_shape") assert ("array extent must be specified using an integer literal but " "found 'npts' for array 'gh_shape'" in str(err.value)) # Only 1D arrays are supported new_code = DIFF_BASIS.replace("gh_shape(2)", "gh_shape(2,2)") parse_tree = parse(new_code) tmp = KernelType(parse_tree) with pytest.raises(ParseError) as err: tmp.get_integer_array("gh_shape") assert ("array must be 1D but found an array with 2 dimensions for name " "'gh_shape'" in str(err.value)) # Break RHS so that it is not an array constructor new_code = DIFF_BASIS.replace("(/gh_quadrature_XYoZ, gh_quadrature_edge/)", "gh_quadrature_XYoZ") parse_tree = parse(new_code) tmp = KernelType(parse_tree) with pytest.raises(ParseError) as err: tmp.get_integer_array("gh_shape") assert "RHS of assignment is not an array constructor" in str(err.value) # Check that we return an empty list if the matched name is a scalar new_code = DIFF_BASIS.replace( "integer :: gh_shape(2) = (/gh_quadrature_XYoZ, gh_quadrature_edge/)", "integer :: gh_shape = gh_quadrature_face") parse_tree = parse(new_code) tmp = KernelType(parse_tree) assert tmp.get_integer_array("gh_shape") == []