예제 #1
0
def test_main_write_psy_file(capsys):
    '''Tests that the main() function outputs successfully writes the
    generated psy output to a specified file'''

    alg_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3",
                                 "1_single_invoke.f90"))

    filetemp_psy = tempfile.NamedTemporaryFile()
    psy_filename = filetemp_psy.name
    filetemp_psy.close()
    # no need to delete the file as it has not been created

    main([alg_filename, '-opsy', psy_filename])

    # check psy file is created
    assert os.path.isfile(psy_filename)

    # extract psy file content
    psy_file = open(psy_filename)
    psy_str = psy_file.read()

    # check content of generated psy file by comparing it with stdout
    main([alg_filename])
    stdout, _ = capsys.readouterr()

    assert psy_str in stdout
예제 #2
0
def test_main_unexpected_fatal_error(capsys, monkeypatch):
    ''' Tests that we get the expected output and the code exits with an
    error when an unexpected fatal error is returned from the generate
    function. '''

    # Make sure the attribute VALID_ARG_TYPE_NAMES exist
    # before we modify it.
    _ = LFRicConstants()
    # sabotage the code so one of our constant lists is now an int
    monkeypatch.setattr(LFRicConstants, "VALID_ARG_TYPE_NAMES", value=1)
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3", "1_single_invoke.f90"))
    with pytest.raises(SystemExit) as excinfo:
        main([filename])
    # the error code should be 1
    assert str(excinfo.value) == "1"
    _, output = capsys.readouterr()
    expected_output = (
        "Error, unexpected exception, please report to the authors:\n"
        "Description ...\n"
        "argument of type 'int' is not iterable\n"
        "Type ...\n"
        "%s\n"
        "Stacktrace ...\n" % type(TypeError()))
    assert expected_output in output
예제 #3
0
def test_main_no_invoke_alg_file(capsys, tmpdir):
    '''Tests that the main() function outputs the original algorithm input
    file to file when the algorithm file does not contain an invoke and that
    it does not produce any psy output.'''

    # pass in a kernel file as that has no invokes in it
    kern_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                  "test_files", "dynamo0p3",
                                  "testkern_mod.F90"))

    alg_filename = str(tmpdir.join("alg.f90"))
    psy_filename = str(tmpdir.join("psy.f90"))
    # no need to delete the files as they have not been created

    main([kern_filename, '-oalg', alg_filename, '-opsy', psy_filename])
    stdout, _ = capsys.readouterr()

    # check stdout contains warning
    kern_file = open(kern_filename)
    kern_str = kern_file.read()
    expected_stdout = ("Warning: 'Algorithm Error: Algorithm file contains "
                       "no invoke() calls: refusing to generate empty PSy "
                       "code'\n")
    assert expected_stdout == stdout

    # check alg file has same output as input file
    expected_file = open(alg_filename)
    expected_alg_str = expected_file.read()
    assert expected_alg_str == kern_str
    os.remove(alg_filename)

    # check psy file is not created
    assert not os.path.isfile(psy_filename)
예제 #4
0
def test_main_include_path(capsys):
    ''' Test that the main function supplies any INCLUDE paths to
    fparser. '''
    # This algorithm file INCLUDE's a file that defines a variable called
    # "some_fake_mpi_handle"
    alg_file = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "nemo", "test_files", "include_stmt.f90"))
    # First try without specifying where to find the include file. Currently
    # fparser2 just removes any include statement that it cannot resolve
    # (https://github.com/stfc/fparser/issues/138).
    main([alg_file, '-api', 'nemo'])
    stdout, _ = capsys.readouterr()
    assert "some_fake_mpi_handle" not in stdout
    # Now specify two locations to search with only the second containing
    # the necessary header file
    inc_path1 = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files")
    inc_path2 = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "nemo", "test_files", "include_files")
    main(
        [alg_file, '-api', 'nemo', '-I',
         str(inc_path1), '-I',
         str(inc_path2)])
    stdout, _ = capsys.readouterr()
    assert "some_fake_mpi_handle" in stdout
    # Check that the Config object contains the provided include paths
    assert str(inc_path1) in Config.get().include_paths
    assert str(inc_path2) in Config.get().include_paths
예제 #5
0
def test_command_line(capsys):
    '''Tests that the config command line flag works as expected.
    '''
    f90_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "test_files", "gocean1p0", "test27_loop_swap.f90")

    # Get the full path to the gocean1.0 config file that adds
    # new iteration spaces for tests.
    config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                               "test_files", "gocean1p0",
                               "new_iteration_space.psyclone")

    options = ["-api", "gocean1.0"]

    # Make sure we always trigger the GOLoop.setup_bounds()
    # in the constructor so that part is always tested!
    GOLoop._bounds_lookup = {}
    # Check that --config with a parameter is accepted
    main(options + ["--config", config_file, f90_file])

    # Check that a missing parameter raises an error:
    with pytest.raises(SystemExit):
        main(options + ["--config", f90_file])
    _, outerr = capsys.readouterr()

    # Python 2 has the first error message, python 3 the second :()
    assert "too few arguments" in str(outerr) or \
           "the following arguments are required:" in str(outerr)
예제 #6
0
def test_main_kern_output_dir(tmpdir):
    ''' Test that we can specify a valid kernel output directory. '''
    alg_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3",
                                 "1_single_invoke.f90"))
    main([alg_filename, '-okern', str(tmpdir)])
    # The specified kernel output directory should have been stored in
    # the configuration object
    assert Config.get().kernel_output_dir == str(tmpdir)
예제 #7
0
def test_utf_char(tmpdir):
    ''' Check that we generate the PSy layer OK when the original Fortran
    code contains UTF characters with no representation in the ASCII
    character set. '''
    from psyclone.generator import main
    test_file = os.path.join(BASE_PATH, "utf_char.f90")
    tmp_file = os.path.join(str(tmpdir), "test_psy.f90")
    main(["-api", "nemo", "-opsy", tmp_file, test_file])
    assert os.path.isfile(tmp_file)
예제 #8
0
def test_main_fort_line_length_off(capsys, limit):
    '''Tests that the Fortran line-length limiting is off by default and is
    also disabled by `-l off`. One of the generated psy-layer lines should be
    longer than 132 characters. '''
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3",
                             "10.3_operator_different_spaces.f90"))
    main([filename, '-api', 'dynamo0.3'] + limit)
    output, _ = capsys.readouterr()
    assert not all(len(line) <= 132 for line in output.split('\n'))
예제 #9
0
def test_main_include_invalid(capsys, tmpdir):
    ''' Check that the main function complains if a non-existant location
    is specified as a search path for INCLUDE files. '''
    alg_file = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "nemo", "test_files", "include_stmt.f90"))
    fake_path = tmpdir.join('does_not_exist')
    with pytest.raises(SystemExit) as err:
        main([alg_file, '-api', 'nemo', '-I', fake_path.strpath])
    assert str(err.value) == "1"
    capout = capsys.readouterr()
    assert "does_not_exist' does not exist" in capout.err
예제 #10
0
def test_main_fort_line_length(capsys):
    '''Tests that the fortran line length object works correctly. Without
    the -l option one of the generated psy-layer lines would be longer
    than 132 characters'''
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3",
                             "10.3_operator_different_spaces.f90"))
    main([filename, '-api', 'dynamo0.3', '-l'])
    output, _ = capsys.readouterr()
    for line in output.split('\n'):
        assert len(line) <= 132
예제 #11
0
def test_main_fort_line_length(capsys, limit):
    '''Tests that the Fortran line length object works correctly. Without
    the -l option one of the generated psy-layer lines would be longer
    than 132 characters. Since it is in the output code, both the 'all' and
   'output' options should cause the limit to be applied. '''
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3",
                             "10.3_operator_different_spaces.f90"))
    main([filename, '-api', 'dynamo0.3', '-l', limit])
    output, _ = capsys.readouterr()
    assert all(len(line) <= 132 for line in output.split('\n'))
예제 #12
0
def test_main_kern_output_no_dir(capsys):
    ''' Test for when the specified output directory (for transformed
    kernels) does not exist. '''
    alg_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3",
                                 "1_single_invoke.f90"))
    with pytest.raises(SystemExit) as err:
        main([alg_filename, '-okern', "/does/not/exist"])
    assert str(err.value) == "1"
    _, output = capsys.readouterr()
    assert ("Specified kernel output directory (/does/not/exist) does not "
            "exist" in output)
예제 #13
0
def test_main_invalid_api(capsys):
    '''Tests that we get the expected output and the code exits
    with an error if the supplied API is not known'''
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3", "1_single_invoke.f90"))
    with pytest.raises(SystemExit) as excinfo:
        main([filename, "-api", "madeup"])
    # The error code should be 1
    assert str(excinfo.value) == "1"
    _, output = capsys.readouterr()
    expected_output = ("Unsupported API 'madeup' specified. Supported API's "
                       "are ['dynamo0.1', 'dynamo0.3', "
                       "'gocean0.1', 'gocean1.0', 'nemo'].\n")
    assert output == expected_output
예제 #14
0
def test_utf_char(tmpdir):
    ''' Test that the generate method works OK when both the Algorithm and
    Kernel code contain utf-encoded chars. '''
    algfile = os.path.join(str(tmpdir), "alg.f90")
    main([os.path.join(BASE_PATH, "gocean1p0", "test29_utf_chars.f90"),
          "-api", "gocean1.0", "-oalg", algfile])
    # We only check the algorithm layer since we generate the PSy
    # layer from scratch in this API (and thus it contains no
    # non-ASCII characters).
    encoding = {'encoding': 'utf-8'}
    with io.open(algfile, "r", **encoding) as afile:
        alg = afile.read().lower()
        assert "max reachable coeff" in alg
        assert "call invoke_0_kernel_utf" in alg
예제 #15
0
def test_main_fort_line_length_output_only(capsys):
    ''' Check that the '-l output' option disables the line-length check on
    input files but still limits the line lengths in the output. '''
    alg_filename = os.path.join(NEMO_BASE_PATH, "explicit_do_long_line.f90")
    # If line-length checking is enabled then we should abort
    with pytest.raises(SystemExit):
        main([alg_filename, '-api', 'nemo', '-l', 'all'])
    _, error = capsys.readouterr()
    assert "does not conform to the specified 132 line length limit" in error
    # If we only mandate that the output be limited then we should be fine
    main([alg_filename, '-api', 'nemo', '-l', 'output'])
    output, _ = capsys.readouterr()
    for line in output.split('\n'):
        assert len(line) <= 132
예제 #16
0
def test_main_include_nemo_only(capsys):
    ''' Check that the main function rejects attempts to specify INCLUDE
    paths for all except the nemo API. (Since that is the only one which
    uses fparser2 at the moment.) '''
    alg_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3",
                                 "1_single_invoke.f90"))
    for api in Config.get().supported_apis:
        if api != "nemo":
            with pytest.raises(SystemExit) as err:
                main([alg_filename, '-api', api, '-I', './'])
            assert str(err.value) == "1"
            captured = capsys.readouterr()
            assert captured.err.count("is only supported for the 'nemo' API")\
                == 1
예제 #17
0
def test_main_version(capsys):
    '''Tests that the version info is printed correctly.'''
    # First test if -h includes the right version info:
    with pytest.raises(SystemExit):
        main(["-h"])
    output, _ = capsys.readouterr()
    assert "Display version information ({0})".format(__VERSION__) in output

    # Now test -v, but it needs a filename for argparse to work. Just use
    # some invalid parameters - "-v" prints its output before that.
    with pytest.raises(SystemExit) as _:
        main(["-v", "does-not-exist"])
    output, _ = capsys.readouterr()

    assert "PSyclone version: {0}".format(__VERSION__) in output
예제 #18
0
def test_main_kern_output_no_write(tmpdir, capsys):
    ''' Test for when the specified output directory (for transformed
    kernels) cannot be written to. '''
    alg_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3",
                                 "1_single_invoke.f90"))
    # Create a new directory and make it readonly
    new_dir = os.path.join(str(tmpdir), "no_write_access")
    os.mkdir(new_dir)
    os.chmod(new_dir, stat.S_IREAD)
    with pytest.raises(SystemExit) as err:
        main([alg_filename, '-okern', str(new_dir)])
    assert str(err.value) == "1"
    _, output = capsys.readouterr()
    assert ("Cannot write to specified kernel output directory ({0})".format(
        str(new_dir)) in output)
예제 #19
0
def test_main_no_invoke_alg_stdout(capsys):
    '''Tests that the main() function outputs the original algorithm input
    file to stdout when the algorithm file does not contain an invoke and that
    it does not produce any psy output.'''

    # pass in a kernel file as that has no invokes in it
    kern_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                  "test_files", "dynamo0p3", "testkern.F90"))
    main([kern_filename])
    out, _ = capsys.readouterr()

    kern_file = open(kern_filename)
    kern_str = kern_file.read()
    expected_output = ("Warning: 'Algorithm Error: Algorithm file contains no "
                       "invoke() calls: refusing to generate empty PSy code'\n"
                       "Transformed algorithm code:\n") + kern_str + "\n"
    assert expected_output == out
예제 #20
0
def test_main_expected_fatal_error(capsys):
    '''Tests that we get the expected output and the code exits with an
    error when an expected fatal error is returned from the generate
    function.'''
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3",
                             "2_incorrect_number_of_args.f90"))
    with pytest.raises(SystemExit) as excinfo:
        main([filename])
    # the error code should be 1
    assert str(excinfo.value) == "1"
    _, output = capsys.readouterr()
    expected_output = ("\"Parse Error: Kernel 'testkern_type' called from the "
                       "algorithm layer with an insufficient number of "
                       "arguments as specified by the metadata. Expected at "
                       "least '5' but found '4'.\"\n")
    assert output == expected_output
예제 #21
0
def test_main_profile(capsys):
    '''Tests that the profiling command line flags are working as expected.
    '''
    filename = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "test_files", "gocean1p0",
                            "test27_loop_swap.f90")

    options = ["-api", "gocean1.0"]

    # Check for invokes only parameter:
    main(options+["--profile", "invokes", filename])
    assert not Profiler.profile_kernels()
    assert Profiler.profile_invokes()

    # Check for kernels only parameter:
    main(options+["--profile", "kernels", filename])
    assert Profiler.profile_kernels()
    assert not Profiler.profile_invokes()

    # Check for invokes + kernels
    main(options+["--profile", "kernels",
                  '--profile', 'invokes', filename])
    assert Profiler.profile_kernels()
    assert Profiler.profile_invokes()

    # Check for missing parameter (argparse then
    # takes the filename as parameter for profiler):
    with pytest.raises(SystemExit):
        main(options+["--profile", filename])
    _, outerr = capsys.readouterr()

    correct_re = "invalid choice.*choose from 'invokes', 'kernels'"
    assert re.search(correct_re, outerr) is not None

    # Check for invalid parameter
    with pytest.raises(SystemExit):
        main(options+["--profile", "invalid", filename])
    _, outerr = capsys.readouterr()

    assert re.search(correct_re, outerr) is not None

    # Reset profile flags to avoid further failures in other tests
    Profiler.set_options(None)
예제 #22
0
def test_main_unexpected_fatal_error(capsys, monkeypatch):
    '''Tests that we get the expected output and the code exits with an
    error when an unexpected fatal error is returned from the generate
    function.'''
    # sabotage the code so one of our constant lists is now an int
    from psyclone import dynamo0p3
    monkeypatch.setattr(dynamo0p3, "CONTINUOUS_FUNCTION_SPACES", value=1)
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p3", "1_single_invoke.f90"))
    with pytest.raises(SystemExit) as excinfo:
        main([filename])
    # the error code should be 1
    assert str(excinfo.value) == "1"
    output, _ = capsys.readouterr()
    expected_output = (
        "Error, unexpected exception, please report to the authors:\n"
        "Description ...\n"
        "argument of type 'int' is not iterable\n"
        "Type ...\n"
        "<type 'exceptions.TypeError'>\n"
        "Stacktrace ...\n")
    assert expected_output in output
예제 #23
0
def test_main_write_psy_file(capsys, tmpdir):
    '''Tests that the main() function outputs successfully writes the
    generated psy output to a specified file'''

    alg_filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                 "test_files", "dynamo0p3",
                                 "1_single_invoke.f90"))

    psy_filename = str(tmpdir.join("psy.f90"))

    main([alg_filename, '-opsy', psy_filename])

    # check psy file is created
    assert os.path.isfile(psy_filename)

    # extract psy file content
    psy_file = open(psy_filename)
    psy_str = psy_file.read()

    # check content of generated psy file by comparing it with stdout
    main([alg_filename])
    stdout, _ = capsys.readouterr()

    assert psy_str in stdout
예제 #24
0
def test_main_api():
    '''Tests the three ways of specifying an API: command line, config file,
    or relying on the default.'''

    # 1) Make sure if no paramenters are given,
    #   config will give us the default API

    # Make sure we get a default config instance
    Config._instance = None
    Config.get()

    assert Config.get().api == Config.get().default_api

    # 2) Check that a command line option will overwrite the default
    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "gocean1p0",
                             "single_invoke.f90"))

    main([filename, "-api", "gocean1.0"])
    assert Config.get().api == "gocean1.0"

    # 3) Check that a config option will overwrite the default
    Config._instance = None
    Config.get()
    # This config file specifies the gocean1.0 api
    config_name = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                "test_files", "gocean1p0",
                                "new_iteration_space.psyclone"))
    main([filename, "--config", config_name])
    assert Config.get().api == "gocean1.0"

    # 4) Check that a command line option overwrites what is specified in
    #    in the config file (and the default)
    Config._instance = None
    Config.get()

    filename = (os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             "test_files", "dynamo0p1",
                             "1_kg_inline.f90"))

    # This config file specifies the gocean1.0 api, but
    # command line should take precedence
    main([filename, "--config", config_name, "-api", "dynamo0.1"])
    assert Config.get().api == "dynamo0.1"
예제 #25
0
def test_main_force_profile(capsys):
    '''Tests that the profiling command line flags are working as expected.
    '''
    filename = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "test_files", "gocean1p0", "test27_loop_swap.f90")

    from psyclone.profiler import Profiler
    options = ["-api", "gocean1.0"]

    # Check for invokes only parameter:
    main(options + ["--force-profile", "invokes", filename])
    assert not Profiler.profile_kernels()
    assert Profiler.profile_invokes()

    # Check for kernels only parameter:
    main(options + ["--force-profile", "kernels", filename])
    assert Profiler.profile_kernels()
    assert not Profiler.profile_invokes()

    # Check for invokes + kernels
    main(
        options +
        ["--force-profile", "kernels", '--force-profile', 'invokes', filename])
    assert Profiler.profile_kernels()
    assert Profiler.profile_invokes()

    # Check for missing parameter (argparse then
    # takes the filename as parameter for profiler):
    with pytest.raises(SystemExit):
        main(options + ["--force-profile", filename])
    _, outerr = capsys.readouterr()

    correct_re = "invalid choice.*choose from 'invokes', 'kernels'"
    assert re.search(correct_re, outerr) is not None

    # Check for invalid parameter
    with pytest.raises(SystemExit):
        main(options + ["--force-profile", "invalid", filename])
    _, outerr = capsys.readouterr()

    assert re.search(correct_re, outerr) is not None

    # Check that there is indeed no warning when using --force-profile
    # with a script. Note that this will raise an error because the
    # script does not exist, but the point of this test is to make sure
    # the error about mixing --profile and -s does not happen
    with pytest.raises(SystemExit):
        main(options +
             ["--force-profile", "kernels", "-s", "invalid", filename])
    _, outerr = capsys.readouterr()
    error = "expected the script file 'invalid' to have the '.py' extension"
    assert error in outerr

    # Test that --profile and --force-profile can not be used together
    with pytest.raises(SystemExit):
        main(options +
             ["--force-profile", "kernels", "--profile", "invokes", filename])
    _, outerr = capsys.readouterr()
    error = "Specify only one of --profile and --force-profile."
    assert error in outerr

    # Reset profile flags to avoid further failures in other tests
    Profiler.set_options(None)
예제 #26
0
def test_main_profile(capsys):
    '''Tests that the profiling command line flags are working as expected.
    '''
    filename = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "test_files", "gocean1p0", "test27_loop_swap.f90")

    from psyclone.profiler import Profiler
    options = ["-api", "gocean1.0"]

    # Check for invokes only parameter:
    main(options + ["--profile", "invokes", filename])
    assert not Profiler.profile_kernels()
    assert Profiler.profile_invokes()

    # Check for kernels only parameter:
    main(options + ["--profile", "kernels", filename])
    assert Profiler.profile_kernels()
    assert not Profiler.profile_invokes()

    # Check for invokes + kernels
    main(options + ["--profile", "kernels", '--profile', 'invokes', filename])
    assert Profiler.profile_kernels()
    assert Profiler.profile_invokes()

    # Check for missing parameter (argparse then
    # takes the filename as parameter for profiler):
    with pytest.raises(SystemExit):
        main(options + ["--profile", filename])
    _, outerr = capsys.readouterr()

    correct_re = "invalid choice.*choose from 'invokes', 'kernels'"
    assert re.search(correct_re, outerr) is not None

    # Check for invalid parameter
    with pytest.raises(SystemExit):
        main(options + ["--profile", "invalid", filename])
    _, outerr = capsys.readouterr()

    assert re.search(correct_re, outerr) is not None

    # Check for warning in case of script with profiling"
    with pytest.raises(SystemExit):
        main(options + ["--profile", "kernels", "-s", "somescript", filename])
    _, out = capsys.readouterr()
    out = out.replace("\n", " ")

    warning = ("Error: use of automatic profiling in combination with an "
               "optimisation script is not recommended since it may not work "
               "as expected.")

    assert warning in out

    # Reset profile flags to avoid further failures in other tests
    Profiler.set_options(None)