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
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
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)
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
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)
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)
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)
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'))
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
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
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'))
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)
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
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
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
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
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
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)
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
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
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)
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
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
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"
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)
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)