diff --git a/loki/batch/tests/test_batch.py b/loki/batch/tests/test_batch.py index 95ba62fe9..299d8d72c 100644 --- a/loki/batch/tests/test_batch.py +++ b/loki/batch/tests/test_batch.py @@ -14,7 +14,7 @@ import pytest from loki import ( - Sourcefile, Subroutine, as_tuple, gettempdir, RawSource, TypeDef, + Sourcefile, Subroutine, as_tuple, RawSource, TypeDef, Scalar, ProcedureSymbol ) from loki.batch import ( @@ -632,7 +632,7 @@ def test_procedure_item_with_config2(testdir, disable): @pytest.mark.parametrize('enable_imports', [False, True]) -def test_procedure_item_external_item(enable_imports, default_config): +def test_procedure_item_external_item(tmp_path, enable_imports, default_config): """ Test that dependencies to external module procedures are marked as external item """ @@ -647,9 +647,7 @@ def test_procedure_item_external_item(enable_imports, default_config): my_type%my_val = external_var end subroutine procedure_item_external_item """ - workdir = gettempdir()/'test_procedure_item_external_item' - workdir.mkdir(exist_ok=True) - filepath = workdir/'procedure_item_external_item.F90' + filepath = tmp_path/'procedure_item_external_item.F90' filepath.write_text(fcode) default_config['default']['enable_imports'] = enable_imports @@ -671,9 +669,6 @@ def test_procedure_item_external_item(enable_imports, default_config): assert all(isinstance(it, ExternalItem) for it in items) assert [it.origin_cls for it in items] == [ModuleItem, ProcedureItem] - filepath.unlink(missing_ok=True) - workdir.rmdir() - def test_typedef_item(testdir): proj = testdir/'sources/projBatch' @@ -1000,7 +995,7 @@ def test_item_graph(testdir, comp1_expected_dependencies): # Not fully-qualified procedure name for a module procedure ('mod_proc', 'mod_proc_expected_dependencies'), ]) -def test_sgraph_from_seed(testdir, default_config, seed, dependencies_fixture, request): +def test_sgraph_from_seed(tmp_path, testdir, default_config, seed, dependencies_fixture, request): expected_dependencies = request.getfixturevalue(dependencies_fixture) proj = testdir/'sources/projBatch' suffixes = ['.f90', '.F90'] @@ -1036,7 +1031,7 @@ def test_sgraph_from_seed(testdir, default_config, seed, dependencies_fixture, r } # Check the graph visualization - graph_file = gettempdir()/'sgraph_from_seed.dot' + graph_file = tmp_path/'sgraph_from_seed.dot' sgraph.export_to_file(graph_file) assert graph_file.exists() assert graph_file.with_suffix('.dot.pdf').exists() @@ -1048,8 +1043,6 @@ def test_sgraph_from_seed(testdir, default_config, seed, dependencies_fixture, r for node, dependencies in expected_dependencies.items() for dependency in dependencies } - graph_file.unlink() - graph_file.with_suffix('.dot.pdf').unlink() @pytest.mark.parametrize('seed,disable,active_nodes', [ diff --git a/loki/batch/tests/test_scheduler.py b/loki/batch/tests/test_scheduler.py index f6e6318a4..e491e5924 100644 --- a/loki/batch/tests/test_scheduler.py +++ b/loki/batch/tests/test_scheduler.py @@ -53,13 +53,12 @@ from functools import partial from pathlib import Path import re -from shutil import rmtree from subprocess import CalledProcessError import pytest from loki import ( Sourcefile, Subroutine, Dimension, fexprgen, BasicType, - gettempdir, ProcedureType, DerivedType, flatten, as_tuple, + ProcedureType, DerivedType, flatten, as_tuple, CaseInsensitiveDict, graphviz_present ) from loki.batch import ( @@ -1553,7 +1552,7 @@ def transform_subroutine(self, routine, **kwargs): @pytest.mark.parametrize('use_file_graph', [False, True]) @pytest.mark.parametrize('reverse', [False, True]) -def test_scheduler_member_routines(config, frontend, use_file_graph, reverse): +def test_scheduler_member_routines(tmp_path, config, frontend, use_file_graph, reverse): """ Make sure that transformation processing works also for contained member routines @@ -1581,11 +1580,9 @@ def test_scheduler_member_routines(config, frontend, use_file_graph, reverse): end module member_mod """.strip() - workdir = gettempdir()/'test_scheduler_member_routines' - workdir.mkdir(exist_ok=True) - (workdir/'member_mod.F90').write_text(fcode_mod) + (tmp_path/'member_mod.F90').write_text(fcode_mod) - scheduler = Scheduler(paths=[workdir], config=config, seed_routines=['member_mod#driver'], frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=config, seed_routines=['member_mod#driver'], frontend=frontend) class LoggingTransformation(Transformation): @@ -1609,7 +1606,7 @@ def transform_subroutine(self, routine, **kwargs): scheduler.process(transformation=transformation) if use_file_graph: - expected = [f'{workdir/"member_mod.F90"!s}'.lower() + '::member_mod.F90'] + expected = [f'{tmp_path/"member_mod.F90"!s}'.lower() + '::member_mod.F90'] else: expected = [ 'member_mod#driver::driver', @@ -1626,11 +1623,9 @@ def transform_subroutine(self, routine, **kwargs): assert transformation.record == flatten(expected) - rmtree(workdir) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_scheduler_nested_type_enrichment(frontend, config): +def test_scheduler_nested_type_enrichment(tmp_path, frontend, config): """ Make sure that enrichment works correctly for nested types across multiple source files @@ -1735,13 +1730,11 @@ def test_scheduler_nested_type_enrichment(frontend, config): end subroutine driver """.strip() - workdir = gettempdir()/'test_scheduler_nested_type_enrichment' - workdir.mkdir(exist_ok=True) - (workdir/'typebound_procedure_calls_mod.F90').write_text(fcode1) - (workdir/'other_typebound_procedure_calls_mod.F90').write_text(fcode2) - (workdir/'driver.F90').write_text(fcode3) + (tmp_path/'typebound_procedure_calls_mod.F90').write_text(fcode1) + (tmp_path/'other_typebound_procedure_calls_mod.F90').write_text(fcode2) + (tmp_path/'driver.F90').write_text(fcode3) - scheduler = Scheduler(paths=[workdir], config=config, seed_routines=['driver'], frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=config, seed_routines=['driver'], frontend=frontend) driver = scheduler['#driver'].source['driver'] calls = FindNodes(ir.CallStatement).visit(driver.body) @@ -1790,11 +1783,9 @@ def test_scheduler_nested_type_enrichment(frontend, config): assert call.function.parent.parent.type.dtype.name == 'third_type' assert isinstance(call.function.parent.parent.type.dtype.typedef, ir.TypeDef) - rmtree(workdir) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_scheduler_interface_inline_call(here, testdir, config, frontend): +def test_scheduler_interface_inline_call(tmp_path, testdir, config, frontend): """ Test that inline function calls declared via an explicit interface are added as dependencies. """ @@ -1838,9 +1829,7 @@ def test_scheduler_interface_inline_call(here, testdir, config, frontend): assert isinstance(scheduler['some_module#add_three_args'], ProcedureItem) # Testing of callgraph visualisation with imports - workdir = gettempdir()/'test_scheduler_import_dependencies' - workdir.mkdir(exist_ok=True) - cg_path = workdir/'callgraph' + cg_path = tmp_path/'callgraph' scheduler.callgraph(cg_path) vgraph = VisGraphWrapper(cg_path) @@ -1849,11 +1838,9 @@ def test_scheduler_interface_inline_call(here, testdir, config, frontend): (a.upper(), b.upper()) for a, deps in expected_dependencies.items() for b in deps } - rmtree(workdir) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_scheduler_interface_dependencies(frontend, config): +def test_scheduler_interface_dependencies(tmp_path, frontend, config): """ Ensure that interfaces are treated as intermediate nodes and incur dependencies on the actual procedures @@ -1893,15 +1880,11 @@ def test_scheduler_interface_dependencies(frontend, config): 'role': 'driver' } - workdir = gettempdir()/'test_scheduler_interface_dependencies' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() - (workdir/'test_scheduler_interface_dependencies_mod.F90').write_text(fcode_module) - (workdir/'test_scheduler_interface_dependencies_driver.F90').write_text(fcode_driver) + (tmp_path/'test_scheduler_interface_dependencies_mod.F90').write_text(fcode_module) + (tmp_path/'test_scheduler_interface_dependencies_driver.F90').write_text(fcode_driver) scheduler = Scheduler( - paths=[workdir], config=config, seed_routines=['test_scheduler_interface_dependencies_driver'], + paths=[tmp_path], config=config, seed_routines=['test_scheduler_interface_dependencies_driver'], frontend=frontend ) @@ -1925,8 +1908,6 @@ def test_scheduler_interface_dependencies(frontend, config): assert isinstance(scheduler['test_scheduler_interface_dependencies_mod#proc1'], ProcedureItem) assert isinstance(scheduler['test_scheduler_interface_dependencies_mod#proc2'], ProcedureItem) - rmtree(workdir) - def test_scheduler_item_successors(testdir, config, frontend): """ @@ -1959,7 +1940,7 @@ def test_scheduler_item_successors(testdir, config, frontend): (ProcedureItem, InterfaceItem, ProcedureBindingItem), (ProcedureItem, TypeDefItem), ]) -def test_scheduler_successors(config, trafo_item_filter): +def test_scheduler_successors(tmp_path, config, trafo_item_filter): fcode_mod = """ module some_mod implicit none @@ -2054,12 +2035,10 @@ def transform_subroutine(self, routine, **kwargs): successors = kwargs.get('successors') assert set(successors) == set(self.expected_successors[item.name]) - workdir = gettempdir()/'test_scheduler_successors' - workdir.mkdir(exist_ok=True) - (workdir/'some_mod.F90').write_text(fcode_mod) - (workdir/'caller.F90').write_text(fcode) + (tmp_path/'some_mod.F90').write_text(fcode_mod) + (tmp_path/'caller.F90').write_text(fcode) - scheduler = Scheduler(paths=[workdir], config=config, seed_routines=['caller']) + scheduler = Scheduler(paths=[tmp_path], config=config, seed_routines=['caller']) assert set(scheduler.items) == set(expected_dependencies) assert set(scheduler.dependencies) == { @@ -2105,11 +2084,9 @@ def transform_subroutine(self, routine, **kwargs): 'other': 1, } - rmtree(workdir) - @pytest.mark.parametrize('full_parse', [True, False]) -def test_scheduler_typebound_inline_call(config, full_parse): +def test_scheduler_typebound_inline_call(tmp_path, config, full_parse): fcode_mod = """ module some_mod implicit none @@ -2144,10 +2121,8 @@ def test_scheduler_typebound_inline_call(config, full_parse): end subroutine caller """.strip() - workdir = gettempdir()/'test_scheduler_typebound_inline_call' - workdir.mkdir(exist_ok=True) - (workdir/'some_mod.F90').write_text(fcode_mod) - (workdir/'caller.F90').write_text(fcode_caller) + (tmp_path/'some_mod.F90').write_text(fcode_mod) + (tmp_path/'caller.F90').write_text(fcode_caller) def verify_graph(scheduler, expected_dependencies): assert set(scheduler.items) == set(expected_dependencies) @@ -2158,7 +2133,7 @@ def verify_graph(scheduler, expected_dependencies): assert all(item.source._incomplete is not full_parse for item in scheduler.items) # Testing of callgraph visualisation - cg_path = workdir/'callgraph' + cg_path = tmp_path/'callgraph' scheduler.callgraph(cg_path) vgraph = VisGraphWrapper(cg_path) @@ -2168,7 +2143,7 @@ def verify_graph(scheduler, expected_dependencies): for b in deps } - scheduler = Scheduler(paths=[workdir], config=config, seed_routines=['caller'], full_parse=full_parse) + scheduler = Scheduler(paths=[tmp_path], config=config, seed_routines=['caller'], full_parse=full_parse) expected_dependencies = { '#caller': ( @@ -2190,11 +2165,9 @@ def verify_graph(scheduler, expected_dependencies): # TODO: test adding a nested derived type dependency! - rmtree(workdir) - @pytest.mark.parametrize('full_parse', [False, True]) -def test_scheduler_cycle(config, full_parse): +def test_scheduler_cycle(tmp_path, config, full_parse): fcode_mod = """ module some_mod implicit none @@ -2239,12 +2212,10 @@ def test_scheduler_cycle(config, full_parse): end subroutine caller """.strip() - workdir = gettempdir()/'test_scheduler_cycle' - workdir.mkdir(exist_ok=True) - (workdir/'some_mod.F90').write_text(fcode_mod) - (workdir/'caller.F90').write_text(fcode_caller) + (tmp_path/'some_mod.F90').write_text(fcode_mod) + (tmp_path/'caller.F90').write_text(fcode_caller) - scheduler = Scheduler(paths=[workdir], config=config, seed_routines=['caller'], full_parse=full_parse) + scheduler = Scheduler(paths=[tmp_path], config=config, seed_routines=['caller'], full_parse=full_parse) # Make sure we the outgoing edges from the recursive routine to the procedure binding # and itself are removed but the other edge still exists @@ -2255,8 +2226,6 @@ def test_scheduler_cycle(config, full_parse): assert (scheduler['some_mod#some_proc'], scheduler['some_mod#some_type%other']) in scheduler.dependencies assert (scheduler['some_mod#some_type%other'], scheduler['some_mod#some_other']) in scheduler.dependencies - rmtree(workdir) - def test_scheduler_unqualified_imports(config): """ @@ -2480,7 +2449,7 @@ def test_scheduler_config_match_item_keys(item_name, keys, use_pattern_matching, @pytest.mark.parametrize('frontend', available_frontends()) -def test_scheduler_filter_items_file_graph(frontend, config): +def test_scheduler_filter_items_file_graph(tmp_path, frontend, config): """ Ensure that the ``items`` list given to a transformation in a file graph traversal is filtered to include only used items @@ -2533,15 +2502,11 @@ def test_scheduler_filter_items_file_graph(frontend, config): 'role': 'driver' } - workdir = gettempdir()/'test_scheduler_filter_program_units_file_graph' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() - filepath = workdir/'test_scheduler_filter_program_units_file_graph.F90' + filepath = tmp_path/'test_scheduler_filter_program_units_file_graph.F90' filepath.write_text(fcode) scheduler = Scheduler( - paths=[workdir], config=config, seed_routines=['test_scheduler_filter_program_units_file_graph_driver'], + paths=[tmp_path], config=config, seed_routines=['test_scheduler_filter_program_units_file_graph_driver'], frontend=frontend ) @@ -2583,8 +2548,6 @@ def transform_file(self, sourcefile, **kwargs): scheduler.process(transformation=MyFileTrafo()) - rmtree(workdir) - @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('frontend_args,defines,preprocess,has_cpp_directives,additional_dependencies', [ @@ -2600,7 +2563,7 @@ def transform_file(self, sourcefile, **kwargs): (None, ['SOME_DEFINITION'], True, [], {}), # Global preprocessing with local definition for one file, re-adding a dependency on 3 ( - {'test_scheduler_frontend_args/file3_4.F90': {'defines': ['SOME_DEFINITION','LOCAL_DEFINITION']}}, + {'file3_4.F90': {'defines': ['SOME_DEFINITION','LOCAL_DEFINITION']}}, ['SOME_DEFINITION'], True, [], @@ -2611,7 +2574,7 @@ def transform_file(self, sourcefile, **kwargs): ), # Global preprocessing with preprocessing switched off for 2 ( - {'test_scheduler_frontend_args/file2.F90': {'preprocess': False}}, + {'file2.F90': {'preprocess': False}}, ['SOME_DEFINITION'], True, ['#test_scheduler_frontend_args2'], @@ -2622,7 +2585,7 @@ def transform_file(self, sourcefile, **kwargs): ), # No preprocessing except for 2 ( - {'test_scheduler_frontend_args/file2.F90': {'preprocess': True, 'defines': ['SOME_DEFINITION']}}, + {'file2.F90': {'preprocess': True, 'defines': ['SOME_DEFINITION']}}, None, False, ['#test_scheduler_frontend_args1', '#test_scheduler_frontend_args4'], @@ -2632,7 +2595,7 @@ def transform_file(self, sourcefile, **kwargs): } ), ]) -def test_scheduler_frontend_args(frontend, frontend_args, defines, preprocess, +def test_scheduler_frontend_args(tmp_path, frontend, frontend_args, defines, preprocess, has_cpp_directives, additional_dependencies, config): """ Test overwriting frontend options via Scheduler config @@ -2670,13 +2633,9 @@ def test_scheduler_frontend_args(frontend, frontend_args, defines, preprocess, end subroutine test_scheduler_frontend_args4 """.strip() - workdir = gettempdir()/'test_scheduler_frontend_args' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() - (workdir/'file1.F90').write_text(fcode1) - (workdir/'file2.F90').write_text(fcode2) - (workdir/'file3_4.F90').write_text(fcode3_4) + (tmp_path/'file1.F90').write_text(fcode1) + (tmp_path/'file2.F90').write_text(fcode2) + (tmp_path/'file3_4.F90').write_text(fcode3_4) expected_dependencies = { '#test_scheduler_frontend_args1': ('#test_scheduler_frontend_args2',), @@ -2690,8 +2649,8 @@ def test_scheduler_frontend_args(frontend, frontend_args, defines, preprocess, config['frontend_args'] = frontend_args scheduler = Scheduler( - paths=[workdir], config=config, seed_routines=['test_scheduler_frontend_args1'], - frontend=frontend, defines=defines, preprocess=preprocess, xmods=[workdir] + paths=[tmp_path], config=config, seed_routines=['test_scheduler_frontend_args1'], + frontend=frontend, defines=defines, preprocess=preprocess, xmods=[tmp_path] ) assert set(scheduler.items) == set(expected_dependencies) @@ -2705,11 +2664,9 @@ def test_scheduler_frontend_args(frontend, frontend_args, defines, preprocess, # NB: OMNI always does preprocessing, therefore we won't find the CPP directives # after the full parse - rmtree(workdir) - @pytest.mark.skipif(not (HAVE_OMNI and HAVE_FP), reason="OMNI or FP not available") -def test_scheduler_frontend_overwrite(config): +def test_scheduler_frontend_overwrite(tmp_path, config): """ Test the use of a different frontend via Scheduler config """ @@ -2730,27 +2687,23 @@ def test_scheduler_frontend_overwrite(config): end subroutine test_scheduler_frontend_overwrite_kernel """.strip() - workdir = gettempdir()/'test_scheduler_frontend_overwrite' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() - (workdir/'test_scheduler_frontend_overwrite_header.F90').write_text(fcode_header) - (workdir/'test_scheduler_frontend_overwrite_kernel.F90').write_text(fcode_kernel) + (tmp_path/'test_scheduler_frontend_overwrite_header.F90').write_text(fcode_header) + (tmp_path/'test_scheduler_frontend_overwrite_kernel.F90').write_text(fcode_kernel) # Make sure that OMNI cannot parse the header file with pytest.raises(CalledProcessError): - Sourcefile.from_source(fcode_header, frontend=OMNI, xmods=[workdir]) + Sourcefile.from_source(fcode_header, frontend=OMNI, xmods=[tmp_path]) # ...and that the problem exists also during Scheduler traversal with pytest.raises(CalledProcessError): Scheduler( - paths=[workdir], config=config, seed_routines=['test_scheduler_frontend_overwrite_kernel'], - frontend=OMNI, xmods=[workdir] + paths=[tmp_path], config=config, seed_routines=['test_scheduler_frontend_overwrite_kernel'], + frontend=OMNI, xmods=[tmp_path] ) # Strip the comment from the header file and parse again to generate an xmod fcode_header_lines = fcode_header.split('\n') - Sourcefile.from_source('\n'.join(fcode_header_lines[:3] + fcode_header_lines[4:]), frontend=OMNI, xmods=[workdir]) + Sourcefile.from_source('\n'.join(fcode_header_lines[:3] + fcode_header_lines[4:]), frontend=OMNI, xmods=[tmp_path]) # Setup the config with the frontend overwrite config['frontend_args'] = { @@ -2759,8 +2712,8 @@ def test_scheduler_frontend_overwrite(config): # ...and now it works fine scheduler = Scheduler( - paths=[workdir], config=config, seed_routines=['test_scheduler_frontend_overwrite_kernel'], - frontend=OMNI, xmods=[workdir] + paths=[tmp_path], config=config, seed_routines=['test_scheduler_frontend_overwrite_kernel'], + frontend=OMNI, xmods=[tmp_path] ) assert set(scheduler.items) == { @@ -2776,8 +2729,6 @@ def test_scheduler_frontend_overwrite(config): assert len(comments) == 1 assert comments[0].text == '! We have a comment' - rmtree(workdir) - def test_scheduler_pipeline_simple(testdir, config, frontend): """ diff --git a/loki/batch/tests/test_transformation.py b/loki/batch/tests/test_transformation.py index 7f4cf6a08..e554fbd14 100644 --- a/loki/batch/tests/test_transformation.py +++ b/loki/batch/tests/test_transformation.py @@ -370,7 +370,7 @@ def transform_module(self, module, **kwargs): assert module.variable_map['j'].scope is tmp_scope fcode = """ -module transformation_module_post_apply +module module_post_apply integer :: i = 0 contains subroutine test_post_apply(ret) @@ -378,7 +378,7 @@ def transform_module(self, module, **kwargs): i = i + 1 ret = i end subroutine test_post_apply -end module transformation_module_post_apply +end module module_post_apply """.strip() module = Module.from_source(fcode, frontend=frontend) diff --git a/loki/build/obj.py b/loki/build/obj.py index 67798d338..054e21872 100644 --- a/loki/build/obj.py +++ b/loki/build/obj.py @@ -5,17 +5,9 @@ # granted to it by virtue of its status as an intergovernmental organisation # nor does it submit to any jurisdiction. -import re +from functools import cached_property from pathlib import Path - -try: - from functools import cached_property -except ImportError: - try: - from cached_property import cached_property - except ImportError: - def cached_property(func): - return func +import re from loki.logging import debug from loki.tools import execute, as_tuple, flatten, cached_func @@ -60,6 +52,11 @@ def __new_stage2_(self, name): # pylint: disable=unused-private-member __xnew_cached_ = staticmethod(cached_func(__new_stage2_)) + @classmethod + def clear_cache(cls): + debug('Clearing Obj cache') + cls._Obj__xnew_cached_.cache_clear() + def __init__(self, name=None, source_path=None): # pylint: disable=unused-argument self.path = None # The eventual .o path self.q_task = None # The parallel worker task diff --git a/loki/build/tests/test_build.py b/loki/build/tests/test_build.py index 3bda169ea..862784c64 100644 --- a/loki/build/tests/test_build.py +++ b/loki/build/tests/test_build.py @@ -14,19 +14,20 @@ ) -@pytest.fixture(scope='module', name='path') -def fixture_path(): +@pytest.fixture(scope='module', name='here') +def fixture_here(): return Path(__file__).parent -@pytest.fixture(scope='module', name='builder') -def fixture_builder(path): - return Builder(source_dirs=path, build_dir=path/'build') +@pytest.fixture(scope='function', name='builder') +def fixture_builder(here, tmp_path): + yield Builder(source_dirs=here, build_dir=tmp_path) + Obj.clear_cache() @pytest.fixture(scope='module', name='testdir') -def fixture_testdir(path): - return path.parent.parent/'tests' +def fixture_testdir(here): + return here.parent.parent/'tests' def test_build_clean(builder): @@ -45,21 +46,21 @@ def test_build_clean(builder): assert 'xxx' not in str(f) -def test_build_object(testdir, builder): +def test_build_object(here, testdir, builder): """ Test basic object compilation and wrapping via f90wrap. """ builder.clean() - obj = Obj(source_path='base.f90') + obj = Obj(source_path=here/'base.f90') obj.build(builder=builder) - assert (builder.build_dir/'base.o').exists + assert (builder.build_dir/'base.o').exists() base = obj.wrap(builder=builder, kind_map=testdir/'kind_map') assert base.Base.a_times_b_plus_c(a=2, b=3, c=1) == 7 -def test_build_lib(testdir, builder): +def test_build_lib(here, testdir, builder): """ Test basic library compilation and wrapping via f90wrap from a specific list of source objects. @@ -67,19 +68,19 @@ def test_build_lib(testdir, builder): builder.clean() # Create library with explicit dependencies - base = Obj(source_path='base.f90') - extension = Obj(source_path='extension.f90') + base = Obj(source_path=here/'base.f90') + extension = Obj(source_path=here/'extension.f90') # Note: Need to compile statically to avoid LD_LIBRARY_PATH lookup lib = Lib(name='library', objs=[base, extension], shared=False) lib.build(builder=builder) - assert (builder.build_dir/'liblibrary.a').exists + assert (builder.build_dir/'liblibrary.a').exists() - test = lib.wrap(modname='test', sources=['extension.f90'], builder=builder, + test = lib.wrap(modname='test', sources=[here/'extension.f90'], builder=builder, kind_map=testdir/'kind_map') assert test.extended_fma(2., 3., 1.) == 7. -def test_build_lib_with_c(testdir, builder): +def test_build_lib_with_c(here, testdir, builder): """ Test basic library compilation and wrapping via f90wrap from a specific list of source objects. @@ -88,13 +89,13 @@ def test_build_lib_with_c(testdir, builder): # Create library with explicit dependencies # objects = ['wrapper.f90', 'c_util.c'] - wrapper = Obj(source_path='wrapper.f90') - c_util = Obj(source_path='c_util.c') + wrapper = Obj(source_path=here/'wrapper.f90') + c_util = Obj(source_path=here/'c_util.c') lib = Lib(name='library', objs=[wrapper, c_util], shared=False) lib.build(builder=builder) - assert (builder.build_dir/'liblibrary.so').exists + assert (builder.build_dir/'liblibrary.a').exists() - wrap = lib.wrap(modname='wrap', sources=['wrapper.f90'], builder=builder, + wrap = lib.wrap(modname='wrap', sources=[here/'wrapper.f90'], builder=builder, kind_map=testdir/'kind_map') assert wrap.wrapper.mult_add_external(2., 3., 1.) == 7. diff --git a/loki/expression/tests/test_expression.py b/loki/expression/tests/test_expression.py index 8cb89fb78..1c16f651f 100644 --- a/loki/expression/tests/test_expression.py +++ b/loki/expression/tests/test_expression.py @@ -30,7 +30,7 @@ ) from loki.ir import nodes as ir, FindNodes from loki.tools import ( - gettempdir, filehash, stdchannel_redirected, stdchannel_is_captured + filehash, stdchannel_redirected, stdchannel_is_captured ) # pylint: disable=too-many-lines @@ -489,7 +489,7 @@ def test_index_ranges(frontend): @pytest.mark.parametrize('frontend', available_frontends()) -def test_strings(here, frontend, capsys): +def test_strings(tmp_path, frontend, capsys): """ Test recognition of literal strings. """ @@ -504,17 +504,15 @@ def test_strings(here, frontend, capsys): print *, "42!" end subroutine strings """ - filepath = here/(f'expression_strings_{frontend}.f90') + filepath = tmp_path/(f'expression_strings_{frontend}.f90') routine = Subroutine.from_source(fcode, frontend=frontend) function = jit_compile(routine, filepath=filepath, objname='strings') - output_file = gettempdir()/filehash(str(filepath), prefix='', suffix='.log') + output_file = tmp_path/filehash(str(filepath), prefix='', suffix='.log') with capsys.disabled(): with stdchannel_redirected(sys.stdout, output_file): function() - clean_test(filepath) - with open(output_file, 'r') as f: output_str = f.read() diff --git a/loki/frontend/tests/test_frontends.py b/loki/frontend/tests/test_frontends.py index 072d259f0..de2427b8b 100644 --- a/loki/frontend/tests/test_frontends.py +++ b/loki/frontend/tests/test_frontends.py @@ -15,7 +15,6 @@ # pylint: disable=too-many-lines from pathlib import Path -from shutil import rmtree from time import perf_counter import numpy as np import pytest @@ -23,7 +22,7 @@ from loki import ( Module, Subroutine, FindVariables, BasicType, config, Sourcefile, RawSource, RegexParserClass, ProcedureType, DerivedType, - PreprocessorDirective, config_override, gettempdir + PreprocessorDirective, config_override ) from loki.build import jit_compile, clean_test from loki.expression import symbols as sym @@ -1852,7 +1851,7 @@ def test_inline_comments(frontend): @pytest.mark.parametrize('from_file', (True, False)) @pytest.mark.parametrize('preprocess', (True, False)) -def test_source_sanitize_fp_source(from_file, preprocess): +def test_source_sanitize_fp_source(tmp_path, from_file, preprocess): """ Test that source sanitizing works as expected and postprocessing rules are correctly applied @@ -1871,11 +1870,7 @@ def test_source_sanitize_fp_source(from_file, preprocess): """.strip() if from_file: - workdir = gettempdir()/'test_source_sanitize_fp_source' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() - filepath = workdir/'some_routine.F90' + filepath = tmp_path/'some_routine.F90' filepath.write_text(fcode) obj = Sourcefile.from_file(filepath, frontend=FP, preprocess=preprocess, defines=('MY_VAR=5',)) else: @@ -1892,9 +1887,6 @@ def test_source_sanitize_fp_source(from_file, preprocess): assert 'newunit=fu' in obj.to_fortran() - if from_file: - rmtree(workdir) - @pytest.mark.parametrize('preprocess', (True, False)) def test_source_sanitize_fp_subroutine(preprocess): diff --git a/loki/lint/tests/test_linter.py b/loki/lint/tests/test_linter.py index afe519b3f..a06e9cb51 100644 --- a/loki/lint/tests/test_linter.py +++ b/loki/lint/tests/test_linter.py @@ -7,12 +7,11 @@ import importlib from pathlib import Path -from shutil import rmtree import xml.etree.ElementTree as ET import pytest from fparser.two.utils import FortranSyntaxError -from loki import Sourcefile, Assignment, FindNodes, FindVariables, gettempdir +from loki import Sourcefile, Assignment, FindNodes, FindVariables from loki.lint import ( GenericHandler, Reporter, Linter, GenericRule, LinterTransformation, lint_files, LazyTextfile @@ -398,7 +397,7 @@ def output(self, handler_reports): 'projA/source/another_l2.F90' ]) ]) -def test_linter_lint_files_glob(testdir, rules, counter, exclude, files, max_workers): +def test_linter_lint_files_glob(tmp_path, testdir, rules, counter, exclude, files, max_workers): basedir = testdir/'sources' config = { 'basedir': str(basedir), @@ -409,7 +408,7 @@ def test_linter_lint_files_glob(testdir, rules, counter, exclude, files, max_wor if max_workers is not None: config['max_workers'] = max_workers - target_file_name = gettempdir()/'linter_lint_files_glob.log' + target_file_name = tmp_path/'linter_lint_files_glob.log' if max_workers and max_workers > 1: target = LazyTextfile(target_file_name) else: @@ -430,8 +429,6 @@ def test_linter_lint_files_glob(testdir, rules, counter, exclude, files, max_wor else: assert checked_files == files - target_file_name.unlink(missing_ok=True) - @pytest.mark.parametrize('routines,files', [ ({'driverA': {'role': 'driver'}}, [ @@ -512,7 +509,7 @@ def output(self, handler_reports): {'include': ['linter_lint_files_fix.F90']} ]) @pytest.mark.parametrize('backup_suffix', [None, '.bak']) -def test_linter_lint_files_fix(config, backup_suffix): +def test_linter_lint_files_fix(tmp_path, config, backup_suffix): class TestRule(GenericRule): @@ -549,12 +546,10 @@ def fix_subroutine(cls, subroutine, rule_report, config): assert fcode.count('some_routine') == 3 assert fcode.count('SOME_ROUTINE') == 0 - basedir = gettempdir()/'lint_files_fix' - basedir.mkdir(exist_ok=True) - filename = basedir/'linter_lint_files_fix.F90' + filename = tmp_path/'linter_lint_files_fix.F90' filename.write_text(fcode) - config['basedir'] = basedir + config['basedir'] = tmp_path config['fix'] = True if backup_suffix: config['backup_suffix'] = backup_suffix @@ -570,8 +565,6 @@ def fix_subroutine(cls, subroutine, rule_report, config): backup_file = filename.with_suffix('.bak.F90') assert backup_file.read_text() == fcode - rmtree(basedir) - @pytest.mark.parametrize('config', [ {'scheduler': { @@ -585,7 +578,7 @@ def fix_subroutine(cls, subroutine, rule_report, config): }}, {'include': ['*.F90']} ]) -def test_linter_fortran_syntax_error(config, rules): +def test_linter_fortran_syntax_error(tmp_path, config, rules): fcode = """ subroutine some_routine implicit none @@ -598,14 +591,12 @@ def test_linter_fortran_syntax_error(config, rules): end subroutine OTHER_ROUTINE """.strip() - basedir = gettempdir()/'lint_files_syntax_error' - basedir.mkdir(exist_ok=True) - filename = basedir/'linter_lint_files_syntax_error.F90' + filename = tmp_path/'linter_lint_files_syntax_error.F90' filename.write_text(fcode) - junitxml_file = basedir/'junitxml.xml' + junitxml_file = tmp_path/'junitxml.xml' config.update({ - 'basedir': basedir, + 'basedir': tmp_path, 'junitxml_file': str(junitxml_file) }) @@ -622,5 +613,3 @@ def test_linter_fortran_syntax_error(config, rules): assert xml.attrib['failures'] == '1' report = xml.find('testsuite/testcase/failure') assert 'This is invalid Fortran syntax' in report.attrib['message'] - - rmtree(basedir) diff --git a/loki/lint/tests/test_reporter.py b/loki/lint/tests/test_reporter.py index 8a17ae0db..aaf59bb72 100644 --- a/loki/lint/tests/test_reporter.py +++ b/loki/lint/tests/test_reporter.py @@ -16,7 +16,7 @@ except ImportError: HAVE_YAML = False -from loki import Intrinsic, gettempdir +from loki.ir import Intrinsic from loki.lint.linter import lint_files from loki.lint.reporter import ( ProblemReport, RuleReport, FileReport, @@ -123,10 +123,9 @@ def test_violation_file_handler(dummy_file, dummy_file_report): assert 'GenericRule' in file_report['rules'] -def test_lazy_textfile(): +def test_lazy_textfile(tmp_path): # Choose the output file and make sure it doesn't exist - filename = gettempdir()/'lazytextfile.log' - filename.unlink(missing_ok=True) + filename = tmp_path/'lazytextfile.log' # Instantiating the object should _not_ create the file f = LazyTextfile(filename) @@ -145,12 +144,10 @@ def test_lazy_textfile(): del f assert filename.read_text() == 's0me TEXT AAAAND other Th1ngs!!!' - filename.unlink(missing_ok=True) - @pytest.mark.parametrize('max_workers', [None, 1]) @pytest.mark.parametrize('fail_on,failures', [(None,0), ('kernel',4)]) -def test_linter_junitxml(testdir, max_workers, fail_on, failures): +def test_linter_junitxml(tmp_path, testdir, max_workers, fail_on, failures): class RandomFailingRule(GenericRule): type = RuleType.WARN docs = {'title': 'A dummy rule for the sake of testing the Linter'} @@ -162,8 +159,7 @@ def check_subroutine(cls, subroutine, rule_report, config, **kwargs): rule_report.add(cls.__name__, subroutine) basedir = testdir/'sources' - junitxml_file = gettempdir()/'linter_junitxml_outputfile.xml' - junitxml_file.unlink(missing_ok=True) + junitxml_file = tmp_path/'linter_junitxml_outputfile.xml' config = { 'basedir': str(basedir), 'include': ['projA/**/*.f90', 'projA/**/*.F90'], @@ -182,14 +178,12 @@ def check_subroutine(cls, subroutine, rule_report, config, **kwargs): assert xml.attrib['tests'] == '15' assert xml.attrib['failures'] == str(failures) - junitxml_file.unlink(missing_ok=True) - @pytest.mark.skipif(not HAVE_YAML, reason='Pyyaml not installed') @pytest.mark.parametrize('max_workers', [None, 1]) @pytest.mark.parametrize('fail_on,failures', [(None,0), ('kernel',4)]) @pytest.mark.parametrize('use_line_hashes', [None, False, True]) -def test_linter_violation_file(testdir, rules, max_workers, fail_on, failures, use_line_hashes): +def test_linter_violation_file(tmp_path, testdir, rules, max_workers, fail_on, failures, use_line_hashes): class RandomFailingRule(GenericRule): type = RuleType.WARN docs = {'title': 'A dummy rule for the sake of testing the Linter'} @@ -201,8 +195,7 @@ def check_subroutine(cls, subroutine, rule_report, config, **kwargs): rule_report.add(cls.__name__, subroutine) basedir = testdir/'sources' - violations_file = gettempdir()/'linter_violations_file.yml' - violations_file.unlink(missing_ok=True) + violations_file = tmp_path/'linter_violations_file.yml' config = { 'basedir': str(basedir), 'include': ['projA/**/*.f90', 'projA/**/*.F90'], @@ -244,5 +237,3 @@ def check_subroutine(cls, subroutine, rule_report, config, **kwargs): checked = lint_files([RandomFailingRule, rules.DummyRule], config) assert checked == 15 assert yaml.safe_load(violations_file.read_text()) is None - - violations_file.unlink(missing_ok=True) diff --git a/loki/tests/sources/sourcefile_cpp_stmt_func.F90 b/loki/tests/sources/sourcefile_cpp_stmt_func.F90 index 2e079556a..19f7ae8ea 100644 --- a/loki/tests/sources/sourcefile_cpp_stmt_func.F90 +++ b/loki/tests/sources/sourcefile_cpp_stmt_func.F90 @@ -1,4 +1,4 @@ -module sourcefile_cpp_stmt_func_mod +module cpp_stmt_func_mod IMPLICIT NONE @@ -8,15 +8,15 @@ module sourcefile_cpp_stmt_func_mod contains -subroutine sourcefile_cpp_stmt_func(KIDIA, KFDIA, KLON, KLEV, ZFOEEW) +subroutine cpp_stmt_func(KIDIA, KFDIA, KLON, KLEV, ZFOEEW) INTEGER(KIND=JPIM),INTENT(IN) :: KLON, KLEV - INTEGER(KIND=JPIM),INTENT(IN) :: KIDIA - INTEGER(KIND=JPIM),INTENT(IN) :: KFDIA + INTEGER(KIND=JPIM),INTENT(IN) :: KIDIA + INTEGER(KIND=JPIM),INTENT(IN) :: KFDIA REAL(KIND=JPRB) ,INTENT(OUT) :: ZFOEEW(KLON,KLEV) INTEGER(KIND=JPIM) :: JK, JL - REAL(KIND=JPRB) :: ZTP1(KLON,KLEV) + REAL(KIND=JPRB) :: ZTP1(KLON,KLEV) REAL(KIND=JPRB) :: PAP(KLON,KLEV) REAL(KIND=JPRB) :: ZALFA @@ -45,6 +45,6 @@ subroutine sourcefile_cpp_stmt_func(KIDIA, KFDIA, KLON, KLEV, ZFOEEW) & (1.0_JPRB-ZALFA)*FOEEICE(ZTP1(JL,JK)))/PAP(JL,JK),0.5_JPRB) END DO END DO -end subroutine sourcefile_cpp_stmt_func +end subroutine cpp_stmt_func -end module sourcefile_cpp_stmt_func_mod \ No newline at end of file +end module cpp_stmt_func_mod diff --git a/loki/tests/test_derived_types.py b/loki/tests/test_derived_types.py index 9edfc7b9b..8bd0b438b 100644 --- a/loki/tests/test_derived_types.py +++ b/loki/tests/test_derived_types.py @@ -21,7 +21,7 @@ StringSubscript, Conditional, CallStatement, ProcedureSymbol, FindVariables ) -from loki.build import jit_compile, jit_compile_lib, clean_test +from loki.build import jit_compile, jit_compile_lib, clean_test, Obj from loki.frontend import available_frontends, OMNI, OFP @@ -32,7 +32,8 @@ def fixture_here(): @pytest.fixture(scope='module', name='builder') def fixture_builder(here): - return Builder(source_dirs=here, build_dir=here/'build') + yield Builder(source_dirs=here, build_dir=here/'build') + Obj.clear_cache() @pytest.mark.parametrize('frontend', available_frontends()) diff --git a/loki/tests/test_sourcefile.py b/loki/tests/test_sourcefile.py index 77658c78d..10739b777 100644 --- a/loki/tests/test_sourcefile.py +++ b/loki/tests/test_sourcefile.py @@ -203,12 +203,12 @@ def test_sourcefile_cpp_stmt_func(here, frontend): filepath = sourcepath/'sourcefile_cpp_stmt_func.F90' source = Sourcefile.from_file(filepath, includes=sourcepath, preprocess=True, frontend=frontend) - module = source['sourcefile_cpp_stmt_func_mod'] + module = source['cpp_stmt_func_mod'] module.name += f'_{frontend!s}' # OMNI inlines statement functions, so we can't check the representation if frontend != OMNI: - routine = source['sourcefile_cpp_stmt_func'] + routine = source['cpp_stmt_func'] stmt_func_decls = FindNodes(StatementFunction).visit(routine.spec) assert len(stmt_func_decls) == 4 @@ -227,7 +227,7 @@ def test_sourcefile_cpp_stmt_func(here, frontend): klon, klev = 10, 5 kidia, kfdia = 1, klon zfoeew = np.zeros((klon, klev), order='F') - mod.sourcefile_cpp_stmt_func(kidia, kfdia, klon, klev, zfoeew) + mod.cpp_stmt_func(kidia, kfdia, klon, klev, zfoeew) assert (zfoeew == 0.25).all() clean_test(filepath) diff --git a/loki/transformations/build_system/tests/test_dependency.py b/loki/transformations/build_system/tests/test_dependency.py index 9cb0fec7f..7034e0dd8 100644 --- a/loki/transformations/build_system/tests/test_dependency.py +++ b/loki/transformations/build_system/tests/test_dependency.py @@ -1,8 +1,14 @@ +# (C) Copyright 2018- ECMWF. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + from pathlib import Path -from shutil import rmtree import pytest -from loki import Sourcefile, gettempdir +from loki import Sourcefile from loki.batch import Scheduler, SchedulerConfig from loki.expression import FindInlineCalls from loki.frontend import available_frontends, OMNI, OFP @@ -17,14 +23,6 @@ def fixture_here(): return Path(__file__).parent -@pytest.fixture(scope='function', name='tempdir') -def fixture_tempdir(request): - basedir = gettempdir()/request.function.__name__ - basedir.mkdir(exist_ok=True) - yield basedir - if basedir.exists(): - rmtree(basedir) - @pytest.fixture(scope='function', name='config') def fixture_config(): @@ -44,7 +42,7 @@ def fixture_config(): @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('use_scheduler', [False, True]) -def test_dependency_transformation_globalvar_imports(frontend, use_scheduler, tempdir, config): +def test_dependency_transformation_globalvar_imports(frontend, use_scheduler, tmp_path, config): """ Test that global variable imports are not renamed as a call statement would be. @@ -79,9 +77,9 @@ def test_dependency_transformation_globalvar_imports(frontend, use_scheduler, te transformation = DependencyTransformation(suffix='_test', module_suffix='_mod') if use_scheduler: - (tempdir/'kernel_mod.F90').write_text(kernel_fcode) - (tempdir/'driver.F90').write_text(driver_fcode) - scheduler = Scheduler(paths=[tempdir], config=SchedulerConfig.from_dict(config), frontend=frontend) + (tmp_path/'kernel_mod.F90').write_text(kernel_fcode) + (tmp_path/'driver.F90').write_text(driver_fcode) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) scheduler.process(transformation) # Check that both, old and new module exist now in the scheduler graph @@ -92,10 +90,10 @@ def test_dependency_transformation_globalvar_imports(frontend, use_scheduler, te driver = scheduler['#driver'].source # Check that the not-renamed module is indeed the original one - scheduler.item_factory.item_cache[str(tempdir/'kernel_mod.F90')].source.make_complete(frontend=frontend) + scheduler.item_factory.item_cache[str(tmp_path/'kernel_mod.F90')].source.make_complete(frontend=frontend) assert ( Sourcefile.from_source(kernel_fcode, frontend=frontend).to_fortran() == - scheduler.item_factory.item_cache[str(tempdir/'kernel_mod.F90')].source.to_fortran() + scheduler.item_factory.item_cache[str(tmp_path/'kernel_mod.F90')].source.to_fortran() ) else: @@ -126,7 +124,7 @@ def test_dependency_transformation_globalvar_imports(frontend, use_scheduler, te @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('use_scheduler', [False, True]) -def test_dependency_transformation_globalvar_imports_driver_mod(frontend, use_scheduler, tempdir, config): +def test_dependency_transformation_globalvar_imports_driver_mod(frontend, use_scheduler, tmp_path, config): """ Test that global variable imports are not renamed as a call statement would be. @@ -162,9 +160,9 @@ def test_dependency_transformation_globalvar_imports_driver_mod(frontend, use_sc transformation = DependencyTransformation(suffix='_test', module_suffix='_mod') if use_scheduler: - (tempdir/'kernel_mod.F90').write_text(kernel_fcode) - (tempdir/'driver_mod.F90').write_text(driver_fcode) - scheduler = Scheduler(paths=[tempdir], config=SchedulerConfig.from_dict(config), frontend=frontend) + (tmp_path/'kernel_mod.F90').write_text(kernel_fcode) + (tmp_path/'driver_mod.F90').write_text(driver_fcode) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) scheduler.process(transformation) kernel = scheduler['kernel_test_mod#kernel_test'].source @@ -259,7 +257,7 @@ def test_dependency_transformation_header_includes(here, frontend): @pytest.mark.parametrize('frontend', available_frontends(xfail=[(OMNI, 'C-imports need pre-processing for OMNI')])) @pytest.mark.parametrize('use_scheduler', [False, True]) -def test_dependency_transformation_module_wrap(frontend, use_scheduler, tempdir, config): +def test_dependency_transformation_module_wrap(frontend, use_scheduler, tmp_path, config): """ Test injection of suffixed kernels into unchanged driver routines automatic module wrapping of the kernel. @@ -292,9 +290,9 @@ def test_dependency_transformation_module_wrap(frontend, use_scheduler, tempdir, ) if use_scheduler: - (tempdir/'kernel.F90').write_text(kernel_fcode) - (tempdir/'driver.F90').write_text(driver_fcode) - scheduler = Scheduler(paths=[tempdir], config=SchedulerConfig.from_dict(config), frontend=frontend) + (tmp_path/'kernel.F90').write_text(kernel_fcode) + (tmp_path/'driver.F90').write_text(driver_fcode) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) for transformation in transformations: scheduler.process(transformation) @@ -338,7 +336,7 @@ def test_dependency_transformation_module_wrap(frontend, use_scheduler, tempdir, @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('use_scheduler', [False, True]) @pytest.mark.parametrize('module_wrap', [True, False]) -def test_dependency_transformation_replace_interface(frontend, use_scheduler, module_wrap, tempdir, config): +def test_dependency_transformation_replace_interface(frontend, use_scheduler, module_wrap, tmp_path, config): """ Test injection of suffixed kernels defined in interface block into unchanged driver routines automatic module wrapping of the kernel. @@ -373,12 +371,12 @@ def test_dependency_transformation_replace_interface(frontend, use_scheduler, mo transformations = [] if module_wrap: transformations += [ModuleWrapTransformation(module_suffix='_mod')] - transformations += [DependencyTransformation(suffix='_test', include_path=tempdir, module_suffix='_mod')] + transformations += [DependencyTransformation(suffix='_test', include_path=tmp_path, module_suffix='_mod')] if use_scheduler: - (tempdir/'kernel.F90').write_text(kernel_fcode) - (tempdir/'driver.F90').write_text(driver_fcode) - scheduler = Scheduler(paths=[tempdir], config=SchedulerConfig.from_dict(config), frontend=frontend) + (tmp_path/'kernel.F90').write_text(kernel_fcode) + (tmp_path/'driver.F90').write_text(driver_fcode) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) for transformation in transformations: scheduler.process(transformation) @@ -590,7 +588,7 @@ def test_dependency_transformation_inline_call_result_var(frontend): @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('use_scheduler', [False, True]) -def test_dependency_transformation_contained_member(frontend, use_scheduler, tempdir, config): +def test_dependency_transformation_contained_member(frontend, use_scheduler, tmp_path, config): """ The scheduler currently does not recognize or allow processing contained member routines as part of the scheduler graph traversal. This test ensures that the transformation class @@ -636,9 +634,9 @@ def test_dependency_transformation_contained_member(frontend, use_scheduler, tem transformation = DependencyTransformation(suffix='_test', module_suffix='_mod') if use_scheduler: - (tempdir/'kernel_mod.F90').write_text(kernel_fcode) - (tempdir/'driver.F90').write_text(driver_fcode) - scheduler = Scheduler(paths=[tempdir], config=SchedulerConfig.from_dict(config), frontend=frontend) + (tmp_path/'kernel_mod.F90').write_text(kernel_fcode) + (tmp_path/'driver.F90').write_text(driver_fcode) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) scheduler.process(transformation) kernel = scheduler['kernel_test_mod#kernel_test'].source @@ -679,7 +677,7 @@ def test_dependency_transformation_contained_member(frontend, use_scheduler, tem @pytest.mark.parametrize('frontend', available_frontends( xfail=[(OFP, 'OFP does not correctly handle result variable declaration.')])) -def test_dependency_transformation_item_filter(frontend, tempdir, config): +def test_dependency_transformation_item_filter(frontend, tmp_path, config): """ Test that injection is not applied to modules that have no procedures in the scheduler graph, even if they have other item members. @@ -719,13 +717,13 @@ def test_dependency_transformation_item_filter(frontend, tempdir, config): END MODULE header_mod """.strip() - (tempdir/'kernel_mod.F90').write_text(kernel_fcode) - (tempdir/'header_mod.F90').write_text(header_fcode) - (tempdir/'driver.F90').write_text(driver_fcode) + (tmp_path/'kernel_mod.F90').write_text(kernel_fcode) + (tmp_path/'header_mod.F90').write_text(header_fcode) + (tmp_path/'driver.F90').write_text(driver_fcode) # Create the scheduler such that it chases imports config['default']['enable_imports'] = True - scheduler = Scheduler(paths=[tempdir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) # Make sure the header module item exists assert 'header_mod' in scheduler.items @@ -775,7 +773,7 @@ def test_dependency_transformation_item_filter(frontend, tempdir, config): @pytest.mark.parametrize('frontend', available_frontends()) -def test_dependency_transformation_filter_items_file_graph(frontend, config): +def test_dependency_transformation_filter_items_file_graph(tmp_path, frontend, config): """ Ensure that the ``items`` list given to a transformation in a file graph traversal is filtered to include only used items @@ -828,15 +826,11 @@ def test_dependency_transformation_filter_items_file_graph(frontend, config): 'test_dependency_transformation_filter_items_driver': {'role': 'driver'}, } - workdir = gettempdir()/'test_dependency_transformation_filter_items' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() - filepath = workdir/'test_dependency_transformation_filter_items.F90' + filepath = tmp_path/'test_dependency_transformation_filter_items.F90' filepath.write_text(fcode) scheduler = Scheduler( - paths=[workdir], config=config, + paths=[tmp_path], config=config, seed_routines=['test_dependency_transformation_filter_items_driver'], frontend=frontend ) @@ -921,5 +915,3 @@ def test_dependency_transformation_filter_items_file_graph(frontend, config): assert [r.name.lower() for r in original_mod1.subroutines] == ['proc1', 'unused_proc'] assert [r.name.lower() for r in new_mod1.subroutines] == ['proc1_foo'] - - rmtree(workdir) diff --git a/loki/transformations/single_column/tests/test_single_column_coalesced.py b/loki/transformations/single_column/tests/test_single_column_coalesced.py index 4338b19f8..2bbc7757f 100644 --- a/loki/transformations/single_column/tests/test_single_column_coalesced.py +++ b/loki/transformations/single_column/tests/test_single_column_coalesced.py @@ -5,11 +5,9 @@ # granted to it by virtue of its status as an intergovernmental organisation # nor does it submit to any jurisdiction. -from shutil import rmtree - import pytest -from loki import Subroutine, Sourcefile, Dimension, fgen, gettempdir +from loki import Subroutine, Sourcefile, Dimension, fgen from loki.batch import Scheduler, SchedulerConfig, ProcedureItem, ModuleItem from loki.expression import Scalar, Array, IntLiteral, RangeIndex from loki.frontend import available_frontends, OMNI, OFP @@ -524,7 +522,7 @@ def test_scc_hoist_multiple_kernels(frontend, horizontal, blocking): @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('trim_vector_sections', [True, False]) -def test_scc_hoist_multiple_kernels_loops(frontend, trim_vector_sections, horizontal, blocking): +def test_scc_hoist_multiple_kernels_loops(tmp_path, frontend, trim_vector_sections, horizontal, blocking): """ Test hoisting of column temporaries to "driver" level. """ @@ -613,10 +611,8 @@ def test_scc_hoist_multiple_kernels_loops(frontend, trim_vector_sections, horizo END MODULE kernel_mod """.strip() - basedir = gettempdir() / 'test_scc_hoist_multiple_kernels_loops' - basedir.mkdir(exist_ok=True) - (basedir / 'driver.F90').write_text(fcode_driver) - (basedir / 'kernel.F90').write_text(fcode_kernel) + (tmp_path / 'driver.F90').write_text(fcode_driver) + (tmp_path / 'kernel.F90').write_text(fcode_kernel) config = { 'default': { @@ -629,7 +625,7 @@ def test_scc_hoist_multiple_kernels_loops(frontend, trim_vector_sections, horizo 'driver': {'role': 'driver'} } } - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) driver = scheduler["#driver"].ir kernel = scheduler["kernel_mod#kernel"].ir @@ -725,8 +721,6 @@ def test_scc_hoist_multiple_kernels_loops(frontend, trim_vector_sections, horizo assigns = FindNodes(Assignment).visit(driver_loops[9]) assert not assign in assigns - rmtree(basedir) - @pytest.mark.parametrize('frontend', available_frontends()) def test_scc_annotate_openacc(frontend, horizontal, blocking): diff --git a/loki/transformations/tests/test_array_indexing.py b/loki/transformations/tests/test_array_indexing.py index a6cfc79ab..de19ce76d 100644 --- a/loki/transformations/tests/test_array_indexing.py +++ b/loki/transformations/tests/test_array_indexing.py @@ -6,16 +6,14 @@ # nor does it submit to any jurisdiction. from pathlib import Path -from shutil import rmtree import pytest import numpy as np from loki import Module, Subroutine, fgen -from loki.build import jit_compile, jit_compile_lib, clean_test, Builder +from loki.build import jit_compile, jit_compile_lib, clean_test, Builder, Obj from loki.expression import symbols as sym, FindVariables from loki.frontend import available_frontends from loki.ir import FindNodes, CallStatement -from loki.tools import gettempdir from loki.transformations.array_indexing import ( promote_variables, demote_variables, normalize_range_indexing, @@ -30,18 +28,10 @@ def fixture_here(): return Path(__file__).parent -@pytest.fixture(scope='function', name='tempdir') -def fixture_tempdir(request): - basedir = gettempdir()/request.function.__name__ - basedir.mkdir(exist_ok=True) - yield basedir - if basedir.exists(): - rmtree(basedir) - - @pytest.fixture(scope='function', name='builder') -def fixture_builder(tempdir): - return Builder(source_dirs=tempdir, build_dir=tempdir) +def fixture_builder(tmp_path): + yield Builder(source_dirs=tmp_path, build_dir=tmp_path) + Obj.clear_cache() @pytest.mark.parametrize('frontend', available_frontends()) @@ -344,18 +334,18 @@ def test_transform_demote_dimension_arguments(here, frontend): @pytest.mark.parametrize('start_index', (0, 1, 5)) def test_transform_normalize_array_shape_and_access(here, frontend, start_index): """ - Test normalization of array shape and access, thus changing arrays with start + Test normalization of array shape and access, thus changing arrays with start index different than "1" to have start index "1". - E.g., ``x1(5:len)`` -> ```x1(1:len-4)`` + E.g., ``x1(5:len)`` -> ```x1(1:len-4)`` """ fcode = f""" - module transform_normalize_array_shape_and_access_mod + module norm_arr_shape_access_mod implicit none - + contains - subroutine transform_normalize_array_shape_and_access(x1, x2, x3, x4, assumed_x1, l1, l2, l3, l4) + subroutine norm_arr_shape_access(x1, x2, x3, x4, assumed_x1, l1, l2, l3, l4) ! use nested_routine_mod, only : nested_routine implicit none integer :: i1, i2, i3, i4, c1, c2, c3, c4 @@ -394,7 +384,7 @@ def test_transform_normalize_array_shape_and_access(here, frontend, start_index) end do c1 = c1 + 1 end do - end subroutine transform_normalize_array_shape_and_access + end subroutine norm_arr_shape_access subroutine nested_routine(nested_x1, l1, c1) implicit none @@ -406,7 +396,7 @@ def test_transform_normalize_array_shape_and_access(here, frontend, start_index) end do end subroutine nested_routine - end module transform_normalize_array_shape_and_access_mod + end module norm_arr_shape_access_mod """ def init_arguments(l1, l2, l3, l4): @@ -429,10 +419,10 @@ def validate_routine(routine): module = Module.from_source(fcode, frontend=frontend) for routine in module.routines: normalize_range_indexing(routine) # Fix OMNI nonsense - filepath = here/(f'transform_normalize_array_shape_and_access_{frontend}.f90') + filepath = here/(f'norm_arr_shape_access_{frontend}.f90') # compile and test "original" module/function - mod = jit_compile(module, filepath=filepath, objname='transform_normalize_array_shape_and_access_mod') - function = getattr(mod, 'transform_normalize_array_shape_and_access') + mod = jit_compile(module, filepath=filepath, objname='norm_arr_shape_access_mod') + function = getattr(mod, 'norm_arr_shape_access') orig_x1, orig_x2, orig_x3, orig_x4, orig_assumed_x1 = init_arguments(l1, l2, l3, l4) function(orig_x1, orig_x2, orig_x3, orig_x4, orig_assumed_x1, l1, l2, l3, l4) clean_test(filepath) @@ -441,14 +431,14 @@ def validate_routine(routine): for routine in module.routines: normalize_array_shape_and_access(routine) - filepath = here/(f'transform_normalize_array_shape_and_access_normalized_{frontend}.f90') + filepath = here/(f'norm_arr_shape_access_normalized_{frontend}.f90') # compile and test "normalized" module/function - mod = jit_compile(module, filepath=filepath, objname='transform_normalize_array_shape_and_access_mod') - function = getattr(mod, 'transform_normalize_array_shape_and_access') + mod = jit_compile(module, filepath=filepath, objname='norm_arr_shape_access_mod') + function = getattr(mod, 'norm_arr_shape_access') x1, x2, x3, x4, assumed_x1 = init_arguments(l1, l2, l3, l4) function(x1, x2, x3, x4, assumed_x1, l1, l2, l3, l4) clean_test(filepath) - # validate the routine "transform_normalize_array_shape_and_access" + # validate the routine "norm_arr_shape_access" validate_routine(module.subroutines[0]) # validate the nested routine to see whether the assumed size array got correctly handled assert module.subroutines[1].variable_map['nested_x1'] == 'nested_x1(:)' @@ -470,7 +460,7 @@ def test_transform_flatten_arrays(here, frontend, builder, start_index): index arithmetic. """ fcode = f""" - subroutine transform_flatten_arrays(x1, x2, x3, x4, l1, l2, l3, l4) + subroutine transf_flatten_arr(x1, x2, x3, x4, l1, l2, l3, l4) implicit none integer :: i1, i2, i3, i4, c1, c2, c3, c4 integer, intent(in) :: l1, l2, l3, l4 @@ -503,7 +493,7 @@ def test_transform_flatten_arrays(here, frontend, builder, start_index): c1 = c1 + 1 end do - end subroutine transform_flatten_arrays + end subroutine transf_flatten_arr """ def init_arguments(l1, l2, l3, l4, flattened=False): x1 = np.zeros(shape=(l1,), order='F', dtype=np.int32) @@ -573,7 +563,7 @@ def validate_routine(routine): f2c.apply(source=f2c_routine, path=here) libname = f'fc_{f2c_routine.name}_{start_index}_{frontend}' c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transform_flatten_arrays_fc_mod.transform_flatten_arrays_fc + fc_function = c_kernel.transf_flatten_arr_fc_mod.transf_flatten_arr_fc f2c_x1, f2c_x2, f2c_x3, f2c_x4 = init_arguments(l1, l2, l3, l4, flattened=True) fc_function(f2c_x1, f2c_x2, f2c_x3, f2c_x4, l1, l2, l3, l4) validate_routine(c_routine) @@ -593,8 +583,8 @@ def validate_routine(routine): @pytest.mark.parametrize('ignore', ((), ('i2',), ('i4', 'i1'))) def test_shift_to_zero_indexing(frontend, ignore): """ - Test shifting array dimensions to zero (or rather shift dimension `dim` - to `dim - 1`). This does not produce valid Fortran, but is part of the + Test shifting array dimensions to zero (or rather shift dimension `dim` + to `dim - 1`). This does not produce valid Fortran, but is part of the F2C transpilation logic. """ fcode = """ @@ -648,7 +638,7 @@ def test_shift_to_zero_indexing(frontend, ignore): @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('explicit_dimensions', [True, False]) -def test_transform_flatten_arrays_call(tempdir, frontend, builder, explicit_dimensions): +def test_transform_flatten_arrays_call(tmp_path, frontend, builder, explicit_dimensions): """ Test flattening or arrays, meaning converting multi-dimensional arrays to one-dimensional arrays including corresponding @@ -710,7 +700,7 @@ def validate_routine(routine): # compile and test reference refname = f'ref_{driver.name}_{frontend}' - reference = jit_compile_lib([kernel_module, driver], path=tempdir, name=refname, builder=builder) + reference = jit_compile_lib([kernel_module, driver], path=tmp_path, name=refname, builder=builder) ref_function = reference.driver_routine nlon = 10 @@ -732,7 +722,7 @@ def validate_routine(routine): # compile and test the flattened variant flattenedname = f'flattened_{driver.name}_{frontend}' - flattened = jit_compile_lib([kernel_module, driver], path=tempdir, name=flattenedname, builder=builder) + flattened = jit_compile_lib([kernel_module, driver], path=tmp_path, name=flattenedname, builder=builder) flattened_function = flattened.driver_routine a_flattened, b_flattened = init_arguments(nlon, nlev, flattened=True) diff --git a/loki/transformations/tests/test_data_offload.py b/loki/transformations/tests/test_data_offload.py index 46e1fe20a..2ab5eb139 100644 --- a/loki/transformations/tests/test_data_offload.py +++ b/loki/transformations/tests/test_data_offload.py @@ -6,11 +6,10 @@ # nor does it submit to any jurisdiction. from pathlib import Path -from shutil import rmtree import pytest from loki import ( - Sourcefile, gettempdir, Scheduler, FindInlineCalls + Sourcefile, Scheduler, FindInlineCalls ) from loki.frontend import available_frontends, OMNI from loki.ir import ( @@ -245,7 +244,7 @@ def test_data_offload_region_multiple(frontend): @pytest.fixture(name='global_variable_analysis_code') -def fixture_global_variable_analysis_code(): +def fixture_global_variable_analysis_code(tmp_path): fcode = { #------------------------------ 'global_var_analysis_header_mod': ( @@ -355,16 +354,9 @@ def fixture_global_variable_analysis_code(): ).strip() } - workdir = gettempdir()/'test_global_variable_analysis' - if workdir.exists(): - rmtree(workdir) - workdir.mkdir() for name, code in fcode.items(): - (workdir/f'{name}.F90').write_text(code) - - yield workdir - - rmtree(workdir) + (tmp_path/f'{name}.F90').write_text(code) + return tmp_path @pytest.mark.parametrize('frontend', available_frontends()) diff --git a/loki/transformations/tests/test_hoist_variables.py b/loki/transformations/tests/test_hoist_variables.py index 05e6ea771..9943945d7 100644 --- a/loki/transformations/tests/test_hoist_variables.py +++ b/loki/transformations/tests/test_hoist_variables.py @@ -13,7 +13,7 @@ import numpy as np from loki import ( - Scheduler, SchedulerConfig, is_iterable, gettempdir, + Scheduler, SchedulerConfig, is_iterable, normalize_range_indexing, FindInlineCalls ) from loki.build import jit_compile_lib, clean_test, Builder @@ -534,7 +534,7 @@ def test_hoist_allocatable(here, testdir, frontend, config, as_kwarguments): @pytest.mark.parametrize('frontend', available_frontends()) -def test_hoist_mixed_variable_declarations(frontend, config): +def test_hoist_mixed_variable_declarations(tmp_path, frontend, config): fcode_driver = """ subroutine driver(NLON, NZ, NB, FIELD1, FIELD2) @@ -586,10 +586,8 @@ def test_hoist_mixed_variable_declarations(frontend, config): end module kernel_mod """.strip() - basedir = gettempdir()/'test_hoist_mixed_variable_declarations' - basedir.mkdir(exist_ok=True) - (basedir/'driver.F90').write_text(fcode_driver) - (basedir/'kernel_mod.F90').write_text(fcode_kernel) + (tmp_path/'driver.F90').write_text(fcode_driver) + (tmp_path/'kernel_mod.F90').write_text(fcode_kernel) config = { 'default': { @@ -603,7 +601,7 @@ def test_hoist_mixed_variable_declarations(frontend, config): } } - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) if frontend == OMNI: for item in scheduler.items: diff --git a/loki/transformations/tests/test_inline.py b/loki/transformations/tests/test_inline.py index 4fe640247..e4a1a278b 100644 --- a/loki/transformations/tests/test_inline.py +++ b/loki/transformations/tests/test_inline.py @@ -13,7 +13,7 @@ Module, Subroutine, FindVariables, BasicType, DerivedType, FindInlineCalls ) -from loki.build import jit_compile, jit_compile_lib, Builder +from loki.build import jit_compile, jit_compile_lib, Builder, Obj from loki.expression import symbols as sym from loki.frontend import available_frontends, OMNI, OFP from loki.ir import nodes as ir, FindNodes @@ -34,7 +34,8 @@ def fixture_here(): @pytest.fixture(scope='module', name='builder') def fixture_builder(here): - return Builder(source_dirs=here, build_dir=here/'build') + yield Builder(source_dirs=here, build_dir=here/'build') + Obj.clear_cache() @pytest.mark.parametrize('frontend', available_frontends()) @@ -117,20 +118,20 @@ def test_transform_inline_constant_parameters(here, builder, frontend): """ fcode = """ -module transform_inline_constant_parameters_mod +module inline_const_param_mod ! TODO: use parameters_mod, only: b implicit none integer, parameter :: c = 1+1 contains - subroutine transform_inline_constant_parameters(v1, v2, v3) + subroutine inline_const_param(v1, v2, v3) use parameters_mod, only: a, b integer, intent(in) :: v1 integer, intent(out) :: v2, v3 v2 = v1 + b - a v3 = c - end subroutine transform_inline_constant_parameters -end module transform_inline_constant_parameters_mod + end subroutine inline_const_param +end module inline_const_param_mod """ # Generate reference code, compile run and verify param_module = Module.from_source(fcode_module, frontend=frontend) @@ -138,7 +139,7 @@ def test_transform_inline_constant_parameters(here, builder, frontend): refname = f'ref_{module.name}_{ frontend}' reference = jit_compile_lib([module, param_module], path=here, name=refname, builder=builder) - v2, v3 = reference.transform_inline_constant_parameters_mod.transform_inline_constant_parameters(10) + v2, v3 = reference.inline_const_param_mod.inline_const_param(10) assert v2 == 8 assert v3 == 2 (here/f'{module.name}.f90').unlink() @@ -146,16 +147,16 @@ def test_transform_inline_constant_parameters(here, builder, frontend): # Now transform with supplied elementals but without module module = Module.from_source(fcode, definitions=param_module, frontend=frontend) - assert len(FindNodes(ir.Import).visit(module['transform_inline_constant_parameters'].spec)) == 1 + assert len(FindNodes(ir.Import).visit(module['inline_const_param'].spec)) == 1 for routine in module.subroutines: inline_constant_parameters(routine, external_only=True) - assert not FindNodes(ir.Import).visit(module['transform_inline_constant_parameters'].spec) + assert not FindNodes(ir.Import).visit(module['inline_const_param'].spec) # Hack: rename module to use a different filename in the build module.name = f'{module.name}_' obj = jit_compile_lib([module], path=here, name=f'{module.name}_{frontend}', builder=builder) - v2, v3 = obj.transform_inline_constant_parameters_mod_.transform_inline_constant_parameters(10) + v2, v3 = obj.inline_const_param_mod_.inline_const_param(10) assert v2 == 8 assert v3 == 2 @@ -175,16 +176,16 @@ def test_transform_inline_constant_parameters_kind(here, builder, frontend): """ fcode = """ -module transform_inline_constant_parameters_kind_mod +module inline_const_param_kind_mod implicit none contains - subroutine transform_inline_constant_parameters_kind(v1) + subroutine inline_const_param_kind(v1) use kind_parameters_mod, only: jprb real(kind=jprb), intent(out) :: v1 v1 = real(2, kind=jprb) + 3. - end subroutine transform_inline_constant_parameters_kind -end module transform_inline_constant_parameters_kind_mod + end subroutine inline_const_param_kind +end module inline_const_param_kind_mod """ # Generate reference code, compile run and verify param_module = Module.from_source(fcode_module, frontend=frontend) @@ -192,23 +193,23 @@ def test_transform_inline_constant_parameters_kind(here, builder, frontend): refname = f'ref_{module.name}_{frontend}' reference = jit_compile_lib([module, param_module], path=here, name=refname, builder=builder) - v1 = reference.transform_inline_constant_parameters_kind_mod.transform_inline_constant_parameters_kind() + v1 = reference.inline_const_param_kind_mod.inline_const_param_kind() assert v1 == 5. (here/f'{module.name}.f90').unlink() (here/f'{param_module.name}.f90').unlink() # Now transform with supplied elementals but without module module = Module.from_source(fcode, definitions=param_module, frontend=frontend) - assert len(FindNodes(ir.Import).visit(module['transform_inline_constant_parameters_kind'].spec)) == 1 + assert len(FindNodes(ir.Import).visit(module['inline_const_param_kind'].spec)) == 1 for routine in module.subroutines: inline_constant_parameters(routine, external_only=True) - assert not FindNodes(ir.Import).visit(module['transform_inline_constant_parameters_kind'].spec) + assert not FindNodes(ir.Import).visit(module['inline_const_param_kind'].spec) # Hack: rename module to use a different filename in the build module.name = f'{module.name}_' obj = jit_compile_lib([module], path=here, name=f'{module.name}_{frontend}', builder=builder) - v1 = obj.transform_inline_constant_parameters_kind_mod_.transform_inline_constant_parameters_kind() + v1 = obj.inline_const_param_kind_mod_.inline_const_param_kind() assert v1 == 5. (here/f'{module.name}.f90').unlink() @@ -227,17 +228,17 @@ def test_transform_inline_constant_parameters_replace_kind(here, builder, fronte """ fcode = """ -module transform_inline_constant_parameters_replace_kind_mod +module inline_param_repl_kind_mod implicit none contains - subroutine transform_inline_constant_parameters_replace_kind(v1) + subroutine inline_param_repl_kind(v1) use replace_kind_parameters_mod, only: jprb real(kind=jprb), intent(out) :: v1 real(kind=jprb) :: a = 3._JPRB v1 = 1._jprb + real(2, kind=jprb) + a - end subroutine transform_inline_constant_parameters_replace_kind -end module transform_inline_constant_parameters_replace_kind_mod + end subroutine inline_param_repl_kind +end module inline_param_repl_kind_mod """ # Generate reference code, compile run and verify param_module = Module.from_source(fcode_module, frontend=frontend) diff --git a/loki/transformations/tests/test_parametrise.py b/loki/transformations/tests/test_parametrise.py index 965d212b8..0f1c1e712 100644 --- a/loki/transformations/tests/test_parametrise.py +++ b/loki/transformations/tests/test_parametrise.py @@ -1,3 +1,10 @@ +# (C) Copyright 2018- ECMWF. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + """ A selection of tests for the parametrisation functionality. """ @@ -6,7 +13,7 @@ import numpy as np from loki import Scheduler, fgen -from loki.build import jit_compile, clean_test +from loki.build import jit_compile from loki.expression import symbols as sym from loki.frontend import available_frontends, OMNI from loki.ir import nodes as ir, FindNodes @@ -49,22 +56,33 @@ def fixture_config(): } -def compile_and_test(scheduler, here, a=5, b=1, test_name=""): +def compile_and_test(scheduler, tmp_path, a=5, b=1): """ Compile the source code and call the driver function in order to test the results for correctness. """ - for item in scheduler.items: - if "driver" in item.name: - suffix = '.F90' - if test_name != "": - item.source.path = Path(f"{item.source.path.stem}_{test_name}") - module = jit_compile(item.source, filepath=item.source.path.with_suffix(suffix).name, objname=None) - c = np.zeros((a, b), dtype=np.int32, order='F') - d = np.zeros((b,), dtype=np.int32, order='F') - module.Parametrise.driver(a, b, c, d) + # Pick out the source files to compile + driver_path_map = {item: item.source.path.stem for item in scheduler.items if 'driver' in item.name} + path_source_map = {item.source.path.stem: item.source for item in driver_path_map} + + # Compile each file only once + path_module_map = { + stem: jit_compile(source, filepath=tmp_path/f'{stem}.F90', objname=stem) + for stem, source in path_source_map.items() + } + + # Run and validate each driver + for item, stem in driver_path_map.items(): + c = np.zeros((a, b), dtype=np.int32, order='F') + d = np.zeros((b,), dtype=np.int32, order='F') + if item.local_name == 'driver': + path_module_map[stem].driver(a, b, c, d) assert (c == 11).all() assert (d == 42).all() - clean_test(filepath=here.parent / item.source.path.with_suffix(suffix).name) + elif item.local_name == 'another_driver': + path_module_map[stem].another_driver(a, b, c) + assert (c == 11).all() + else: + assert False, f'Unknown driver name {item.local_name}' def check_arguments_and_parameter(scheduler, subroutine_arguments, call_arguments, parameter_variables): @@ -114,7 +132,7 @@ def check_arguments_and_parameter(scheduler, subroutine_arguments, call_argument @pytest.mark.parametrize('frontend', available_frontends()) -def test_parametrise_source(here, testdir, frontend, config): +def test_parametrise_source(tmp_path, testdir, frontend, config): """ Test the actual source code without any transformations applied. """ @@ -156,11 +174,11 @@ def test_parametrise_source(here, testdir, frontend, config): check_arguments_and_parameter(scheduler=scheduler, subroutine_arguments=subroutine_arguments, call_arguments=call_arguments, parameter_variables=parameter_variables) - compile_and_test(scheduler=scheduler, here=here, a=a, b=b, test_name="original_source") + compile_and_test(scheduler=scheduler, tmp_path=tmp_path, a=a, b=b) @pytest.mark.parametrize('frontend', available_frontends()) -def test_parametrise_simple(here, testdir, frontend, config): +def test_parametrise_simple(tmp_path, testdir, frontend, config): """ Basic testing of parametrisation functionality. """ @@ -204,11 +222,11 @@ def test_parametrise_simple(here, testdir, frontend, config): check_arguments_and_parameter(scheduler=scheduler, subroutine_arguments=subroutine_arguments, call_arguments=call_arguments, parameter_variables=parameter_variables) - compile_and_test(scheduler=scheduler, here=here, a=a, b=b, test_name="parametrised") + compile_and_test(scheduler=scheduler, tmp_path=tmp_path, a=a, b=b) @pytest.mark.parametrize('frontend', available_frontends()) -def test_parametrise_simple_replace_by_value(here, testdir, frontend, config): +def test_parametrise_simple_replace_by_value(tmp_path, testdir, frontend, config): """ Basic testing of parametrisation functionality including replacing of the variables with the actual values. """ @@ -303,11 +321,11 @@ def test_parametrise_simple_replace_by_value(here, testdir, frontend, config): assert f'z({b})' in routine_spec_str assert f'd2_tmp({b})' in routine_spec_str - compile_and_test(scheduler=scheduler, here=here, a=a, b=b, test_name="replaced") + compile_and_test(scheduler=scheduler, tmp_path=tmp_path, a=a, b=b) @pytest.mark.parametrize('frontend', available_frontends()) -def test_parametrise_modified_callback(here, testdir, frontend, config): +def test_parametrise_modified_callback(tmp_path, testdir, frontend, config): """ Testing of the parametrisation functionality with modified callbacks for failed sanity checks. """ @@ -364,11 +382,11 @@ def stop_execution(**kwargs): check_arguments_and_parameter(scheduler=scheduler, subroutine_arguments=subroutine_arguments, call_arguments=call_arguments, parameter_variables=parameter_variables) - compile_and_test(scheduler=scheduler, here=here, a=a, b=b, test_name=f"callback_{i+1}") + compile_and_test(scheduler=scheduler, tmp_path=tmp_path, a=a, b=b) @pytest.mark.parametrize('frontend', available_frontends()) -def test_parametrise_modified_callback_wrong_input(here, testdir, frontend, config): +def test_parametrise_modified_callback_wrong_input(tmp_path, testdir, frontend, config): """ Testing of the parametrisation functionality with modified callback for failed sanity checks including test of a failed sanity check. @@ -417,25 +435,11 @@ def only_warn(**kwargs): check_arguments_and_parameter(scheduler=scheduler, subroutine_arguments=subroutine_arguments, call_arguments=call_arguments, parameter_variables=parameter_variables) - test_name = "parametrised_callback_wrong_input" - a = a + 1 - b = b + 1 - for item in scheduler.items: - if "driver" in item.name: - suffix = '.F90' - if test_name != "": - item.source.path = Path(f"{item.source.path.stem}_{test_name}") - module = jit_compile(item.source, filepath=item.source.path.with_suffix(suffix).name, objname=None) - c = np.zeros((a, b), dtype=np.int32, order='F') - d = np.zeros((b,), dtype=np.int32, order='F') - module.Parametrise.driver(a, b, c, d) - assert not (c == 11).all() - assert not (d == 42).all() - clean_test(filepath=here.parent / item.source.path.with_suffix(suffix).name) + compile_and_test(scheduler=scheduler, tmp_path=tmp_path, a=5, b=1) @pytest.mark.parametrize('frontend', available_frontends()) -def test_parametrise_non_driver_entry_points(here, testdir, frontend, config): +def test_parametrise_non_driver_entry_points(tmp_path, testdir, frontend, config): """ Testing of parametrisation functionality with defined entry points/functions, thus not being the default (driver). """ @@ -479,4 +483,4 @@ def test_parametrise_non_driver_entry_points(here, testdir, frontend, config): check_arguments_and_parameter(scheduler=scheduler, subroutine_arguments=subroutine_arguments, call_arguments=call_arguments, parameter_variables=parameter_variables) - compile_and_test(scheduler=scheduler, here=here, a=a, b=b, test_name="parametrised_entry_points") + compile_and_test(scheduler=scheduler, tmp_path=tmp_path, a=a, b=b) diff --git a/loki/transformations/tests/test_pool_allocator.py b/loki/transformations/tests/test_pool_allocator.py index cdb4ad843..2321c91cc 100644 --- a/loki/transformations/tests/test_pool_allocator.py +++ b/loki/transformations/tests/test_pool_allocator.py @@ -5,11 +5,9 @@ # granted to it by virtue of its status as an intergovernmental organisation # nor does it submit to any jurisdiction. -from shutil import rmtree - import pytest -from loki import gettempdir, Dimension, normalize_range_indexing +from loki import Dimension, normalize_range_indexing from loki.batch import Scheduler, SchedulerConfig, SFilter, ProcedureItem from loki.expression import ( FindVariables, FindInlineCalls, InlineCall, simplify @@ -115,8 +113,8 @@ def check_stack_created_in_driver( @pytest.mark.parametrize('check_bounds', [False, True]) @pytest.mark.parametrize('nclv_param', [False, True]) @pytest.mark.parametrize('cray_ptr_loc_rhs', [False, True]) -def test_pool_allocator_temporaries(frontend, generate_driver_stack, block_dim, check_bounds, nclv_param, - cray_ptr_loc_rhs): +def test_pool_allocator_temporaries(tmp_path, frontend, generate_driver_stack, block_dim, check_bounds, + nclv_param, cray_ptr_loc_rhs): fcode_iso_c_binding = "use, intrinsic :: iso_c_binding, only: c_sizeof" fcode_nclv_param = 'integer, parameter :: nclv = 2' if frontend == OMNI: @@ -230,10 +228,8 @@ def test_pool_allocator_temporaries(frontend, generate_driver_stack, block_dim, end module kernel_mod """.strip() - basedir = gettempdir()/'test_pool_allocator_temporaries' - basedir.mkdir(exist_ok=True) - (basedir/'driver.F90').write_text(fcode_driver) - (basedir/'kernel_mod.F90').write_text(fcode_kernel) + (tmp_path/'driver.F90').write_text(fcode_driver) + (tmp_path/'kernel_mod.F90').write_text(fcode_kernel) config = { 'default': { @@ -256,7 +252,7 @@ def test_pool_allocator_temporaries(frontend, generate_driver_stack, block_dim, Fortran2003.Intrinsic_Name.generic_function_names.update({"LOC": {'min': 1, 'max': 1}}) Fortran2003.Intrinsic_Name.function_names += ["LOC"] - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) if frontend == OMNI: for item in SFilter(scheduler.sgraph, item_filter=ProcedureItem): @@ -459,15 +455,14 @@ def test_pool_allocator_temporaries(frontend, generate_driver_stack, block_dim, else: assert 'if (ylstack_l > ylstack_u)' not in fcode.lower() assert 'stop' not in fcode.lower() - rmtree(basedir) @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('directive', [None, 'openmp', 'openacc']) @pytest.mark.parametrize('stack_insert_pragma', [False, True]) @pytest.mark.parametrize('cray_ptr_loc_rhs', [False, True]) -def test_pool_allocator_temporaries_kernel_sequence(frontend, block_dim, directive, stack_insert_pragma, - cray_ptr_loc_rhs): +def test_pool_allocator_temporaries_kernel_sequence(tmp_path, frontend, block_dim, directive, + stack_insert_pragma, cray_ptr_loc_rhs): if directive == 'openmp': driver_loop_pragma1 = '!$omp parallel default(shared) private(b) firstprivate(a)\n !$omp do' driver_end_loop_pragma1 = '!$omp end do\n !$omp end parallel' @@ -584,11 +579,9 @@ def test_pool_allocator_temporaries_kernel_sequence(frontend, block_dim, directi end module kernel_mod """.strip() - basedir = gettempdir()/'test_pool_allocator_temporaries_kernel_sequence' - basedir.mkdir(exist_ok=True) - (basedir/'driver.F90').write_text(fcode_driver) - (basedir/'kernel_mod.F90').write_text(fcode_kernel) - (basedir/'parkind_mod.F90').write_text(fcode_parkind_mod) + (tmp_path/'driver.F90').write_text(fcode_driver) + (tmp_path/'kernel_mod.F90').write_text(fcode_kernel) + (tmp_path/'parkind_mod.F90').write_text(fcode_parkind_mod) config = { 'default': { @@ -602,7 +595,7 @@ def test_pool_allocator_temporaries_kernel_sequence(frontend, block_dim, directi 'driver': {'role': 'driver'} } } - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) if frontend == OMNI: for item in SFilter(scheduler.sgraph, item_filter=ProcedureItem): normalize_range_indexing(item.ir) @@ -780,13 +773,11 @@ def test_pool_allocator_temporaries_kernel_sequence(frontend, block_dim, directi assert fcode.lower().count('if (ylstack_l > ylstack_u)') == 2 assert fcode.lower().count('stop') == 2 - rmtree(basedir) - @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('directive', [None, 'openmp', 'openacc']) @pytest.mark.parametrize('cray_ptr_loc_rhs', [False, True]) -def test_pool_allocator_temporaries_kernel_nested(frontend, block_dim, directive, cray_ptr_loc_rhs): +def test_pool_allocator_temporaries_kernel_nested(tmp_path, frontend, block_dim, directive, cray_ptr_loc_rhs): if directive == 'openmp': driver_pragma = '!$omp PARALLEL do PRIVATE(b)' driver_end_pragma = '!$omp end parallel do' @@ -884,11 +875,9 @@ def test_pool_allocator_temporaries_kernel_nested(frontend, block_dim, directive end module kernel_mod """.strip() - basedir = gettempdir()/'test_pool_allocator_temporaries_kernel_nested' - basedir.mkdir(exist_ok=True) - (basedir/'driver.F90').write_text(fcode_driver) - (basedir/'kernel_mod.F90').write_text(fcode_kernel) - (basedir/'parkind_mod.F90').write_text(fcode_parkind_mod) + (tmp_path/'driver.F90').write_text(fcode_driver) + (tmp_path/'kernel_mod.F90').write_text(fcode_kernel) + (tmp_path/'parkind_mod.F90').write_text(fcode_parkind_mod) config = { 'default': { @@ -903,7 +892,7 @@ def test_pool_allocator_temporaries_kernel_nested(frontend, block_dim, directive } } - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) if frontend == OMNI: for item in SFilter(scheduler.sgraph, item_filter=ProcedureItem): normalize_range_indexing(item.ir) @@ -1086,12 +1075,10 @@ def test_pool_allocator_temporaries_kernel_nested(frontend, block_dim, directive assert fcode.lower().count('if (ylstack_l > ylstack_u)') == 2 assert fcode.lower().count('stop') == 2 - rmtree(basedir) - @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('cray_ptr_loc_rhs', [False, True]) -def test_pool_allocator_more_call_checks(frontend, block_dim, caplog, cray_ptr_loc_rhs): +def test_pool_allocator_more_call_checks(tmp_path, frontend, block_dim, caplog, cray_ptr_loc_rhs): fcode = """ module kernel_mod type point @@ -1137,9 +1124,7 @@ def test_pool_allocator_more_call_checks(frontend, block_dim, caplog, cray_ptr_l end module kernel_mod """.strip() - basedir = gettempdir()/'test_pool_allocator_inline_call' - basedir.mkdir(exist_ok=True) - (basedir/'kernel.F90').write_text(fcode) + (tmp_path/'kernel.F90').write_text(fcode) config = { 'default': { @@ -1153,7 +1138,7 @@ def test_pool_allocator_more_call_checks(frontend, block_dim, caplog, cray_ptr_l 'kernel': {} } } - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) if frontend == OMNI: for item in SFilter(scheduler.sgraph, item_filter=ProcedureItem): normalize_range_indexing(item.ir) @@ -1196,12 +1181,11 @@ def test_pool_allocator_more_call_checks(frontend, block_dim, caplog, cray_ptr_l assert relevant_call.kwarguments == expected_kwarguments assert 'Derived-type vars in Subroutine:: kernel not supported in pool allocator' in caplog.text - rmtree(basedir) @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('cray_ptr_loc_rhs', [False, True]) -def test_pool_allocator_args_vs_kwargs(frontend, block_dim_alt, cray_ptr_loc_rhs): +def test_pool_allocator_args_vs_kwargs(tmp_path, frontend, block_dim_alt, cray_ptr_loc_rhs): fcode_module = """ module geom_mod implicit none @@ -1294,11 +1278,9 @@ def test_pool_allocator_args_vs_kwargs(frontend, block_dim_alt, cray_ptr_loc_rhs end module kernel_mod """.strip() - basedir = gettempdir() / 'test_pool_allocator_args_vs_kwargs' - basedir.mkdir(exist_ok=True) - (basedir / 'driver.F90').write_text(fcode_driver) - (basedir / 'kernel.F90').write_text(fcode_kernel) - (basedir / 'module.F90').write_text(fcode_module) + (tmp_path / 'driver.F90').write_text(fcode_driver) + (tmp_path / 'kernel.F90').write_text(fcode_kernel) + (tmp_path / 'module.F90').write_text(fcode_module) config = { 'default': { @@ -1313,7 +1295,7 @@ def test_pool_allocator_args_vs_kwargs(frontend, block_dim_alt, cray_ptr_loc_rhs 'driver': {'role': 'driver'} } } - scheduler = Scheduler(paths=[basedir], config=SchedulerConfig.from_dict(config), frontend=frontend) + scheduler = Scheduler(paths=[tmp_path], config=SchedulerConfig.from_dict(config), frontend=frontend) if frontend == OMNI: for item in scheduler.items: @@ -1362,5 +1344,3 @@ def test_pool_allocator_args_vs_kwargs(frontend, block_dim_alt, cray_ptr_loc_rhs # check stack size allocation allocations = FindNodes(Allocation).visit(driver.body) assert len(allocations) == 1 and 'zstack(istsz,geom%blk_dim%nb)' in allocations[0].variables - - rmtree(basedir) diff --git a/loki/transformations/tests/test_transform_derived_types.py b/loki/transformations/tests/test_transform_derived_types.py index 168884053..63cb2e469 100644 --- a/loki/transformations/tests/test_transform_derived_types.py +++ b/loki/transformations/tests/test_transform_derived_types.py @@ -7,11 +7,10 @@ from pathlib import Path from itertools import zip_longest -from shutil import rmtree import pytest from loki import ( - Sourcefile, Scheduler, ProcedureItem, as_tuple, gettempdir, + Sourcefile, Scheduler, ProcedureItem, as_tuple, ProcedureDeclaration, BasicType, CaseInsensitiveDict, ) from loki.expression import Scalar, Array, FindVariables, FindInlineCalls @@ -1314,7 +1313,7 @@ def test_transform_derived_type_arguments_non_array(frontend): @pytest.mark.parametrize('duplicate', [False,True]) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_typebound_procedure_calls(frontend, config, duplicate): +def test_transform_typebound_procedure_calls(tmp_path, frontend, config, duplicate): fcode1 = """ module typebound_procedure_calls_mod implicit none @@ -1438,15 +1437,13 @@ def test_transform_typebound_procedure_calls(frontend, config, duplicate): end subroutine driver """.strip() - workdir = gettempdir()/'test_transform_typebound_procedure_calls' - workdir.mkdir(exist_ok=True) - (workdir/'typebound_procedure_calls_mod.F90').write_text(fcode1) - (workdir/'other_typebound_procedure_calls_mod.F90').write_text(fcode2) - (workdir/'function_mod.F90').write_text(fcode3) - (workdir/'driver.F90').write_text(fcode4) + (tmp_path/'typebound_procedure_calls_mod.F90').write_text(fcode1) + (tmp_path/'other_typebound_procedure_calls_mod.F90').write_text(fcode2) + (tmp_path/'function_mod.F90').write_text(fcode3) + (tmp_path/'driver.F90').write_text(fcode4) scheduler = Scheduler( - paths=[workdir], config=config, seed_routines=['driver'], frontend=frontend + paths=[tmp_path], config=config, seed_routines=['driver'], frontend=frontend ) transformation = TypeboundProcedureCallTransformation(duplicate_typebound_kernels=duplicate) @@ -1525,5 +1522,3 @@ def test_transform_typebound_procedure_calls(frontend, config, duplicate): assert [ str(call.name) for call in FindNodes(CallStatement).visit(other_mod['init_'].ir) ] == ['this%stuff(i)%arr(j)%reset', 'this%stuff(i)%arr(j)%add'] - - rmtree(workdir) diff --git a/loki/transformations/tests/test_transform_region.py b/loki/transformations/tests/test_transform_region.py index 5320a730a..203018bff 100644 --- a/loki/transformations/tests/test_transform_region.py +++ b/loki/transformations/tests/test_transform_region.py @@ -13,7 +13,7 @@ Module, Subroutine, Section, as_tuple, FindNodes, Loop, Assignment, CallStatement, Intrinsic ) -from loki.build import jit_compile, jit_compile_lib, clean_test, Builder +from loki.build import jit_compile, jit_compile_lib, Builder, Obj from loki.expression import symbols as sym from loki.frontend import available_frontends @@ -23,18 +23,14 @@ ) -@pytest.fixture(scope='module', name='here') -def fixture_here(): - return Path(__file__).parent - - -@pytest.fixture(scope='module', name='builder') -def fixture_builder(here): - return Builder(source_dirs=here, build_dir=here/'build') +@pytest.fixture(scope='function', name='builder') +def fixture_builder(tmp_path): + yield Builder(source_dirs=tmp_path, build_dir=tmp_path/'build') + Obj.clear_cache() @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_hoist(here, frontend): +def test_transform_region_hoist(tmp_path, frontend): """ A very simple hoisting example """ @@ -56,7 +52,7 @@ def test_transform_region_hoist(here, frontend): end subroutine transform_region_hoist """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) # Test the reference solution @@ -65,19 +61,16 @@ def test_transform_region_hoist(here, frontend): # Apply transformation region_hoist(routine) - hoisted_filepath = here/(f'{routine.name}_hoisted_{frontend}.f90') + hoisted_filepath = tmp_path/(f'{routine.name}_hoisted_{frontend}.f90') hoisted_function = jit_compile(routine, filepath=hoisted_filepath, objname=routine.name) # Test transformation a, b, c = hoisted_function() assert a == 1 and b == 5 and c == 6 - clean_test(filepath) - clean_test(hoisted_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_hoist_inlined_pragma(here, frontend): +def test_transform_region_hoist_inlined_pragma(tmp_path, frontend): """ Hoisting when pragmas are potentially inlined into other nodes. """ @@ -113,7 +106,7 @@ def test_transform_region_hoist_inlined_pragma(here, frontend): end subroutine transform_region_hoist_inlined_pragma """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) klon, klev = 32, 100 ref_a = np.array([[jl + 1] * klev for jl in range(klon)], order='F') @@ -137,7 +130,7 @@ def test_transform_region_hoist_inlined_pragma(here, frontend): assert len(loops) == 6 assert [str(loop.variable) for loop in loops] == ['jk', 'jl', 'jk', 'jl', 'jk', 'jl'] - hoisted_filepath = here/(f'{routine.name}_hoisted_{frontend}.f90') + hoisted_filepath = tmp_path/(f'{routine.name}_hoisted_{frontend}.f90') hoisted_function = jit_compile(routine, filepath=hoisted_filepath, objname=routine.name) # Test transformation @@ -148,12 +141,9 @@ def test_transform_region_hoist_inlined_pragma(here, frontend): assert np.all(a == ref_a) assert np.all(b == ref_b) - clean_test(filepath) - clean_test(hoisted_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_hoist_multiple(here, frontend): +def test_transform_region_hoist_multiple(tmp_path, frontend): """ Test hoisting with multiple groups and multiple regions per group """ @@ -183,7 +173,7 @@ def test_transform_region_hoist_multiple(here, frontend): end subroutine transform_region_hoist_multiple """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) # Test the reference solution @@ -192,19 +182,16 @@ def test_transform_region_hoist_multiple(here, frontend): # Apply transformation region_hoist(routine) - hoisted_filepath = here/(f'{routine.name}_hoisted_{frontend}.f90') + hoisted_filepath = tmp_path/(f'{routine.name}_hoisted_{frontend}.f90') hoisted_function = jit_compile(routine, filepath=hoisted_filepath, objname=routine.name) # Test transformation a, b, c = hoisted_function() assert a == 5 and b == 1 and c == 3 - clean_test(filepath) - clean_test(hoisted_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_hoist_collapse(here, frontend): +def test_transform_region_hoist_collapse(tmp_path, frontend): """ Use collapse with region-hoist. """ @@ -240,7 +227,7 @@ def test_transform_region_hoist_collapse(here, frontend): end subroutine transform_region_hoist_collapse """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) klon, klev = 32, 100 ref_a = np.array([[jl + 1] * klev for jl in range(klon)], order='F') @@ -264,7 +251,7 @@ def test_transform_region_hoist_collapse(here, frontend): assert len(loops) == 7 assert [str(loop.variable) for loop in loops] == ['jk', 'jl', 'jk', 'jl', 'jk', 'jk', 'jl'] - hoisted_filepath = here/(f'{routine.name}_hoisted_{frontend}.f90') + hoisted_filepath = tmp_path/(f'{routine.name}_hoisted_{frontend}.f90') hoisted_function = jit_compile(routine, filepath=hoisted_filepath, objname=routine.name) # Test transformation @@ -275,12 +262,9 @@ def test_transform_region_hoist_collapse(here, frontend): assert np.all(a == ref_a) assert np.all(b == ref_b) - clean_test(filepath) - clean_test(hoisted_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_hoist_promote(here, frontend): +def test_transform_region_hoist_promote(tmp_path, frontend): """ Use collapse with region-hoist. """ @@ -321,7 +305,7 @@ def test_transform_region_hoist_promote(here, frontend): end subroutine transform_region_hoist_promote """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) klon, klev = 32, 100 ref_a = np.array([[jl + 1] * klev for jl in range(klon)], order='F') @@ -351,7 +335,7 @@ def test_transform_region_hoist_promote(here, frontend): assert isinstance(b_tmp, sym.Array) and len(b_tmp.type.shape) == 1 assert str(b_tmp.type.shape[0]) == 'klev' - hoisted_filepath = here/(f'{routine.name}_hoisted_{frontend}.f90') + hoisted_filepath = tmp_path/(f'{routine.name}_hoisted_{frontend}.f90') hoisted_function = jit_compile(routine, filepath=hoisted_filepath, objname=routine.name) # Test transformation @@ -362,17 +346,14 @@ def test_transform_region_hoist_promote(here, frontend): assert np.all(a == ref_a) assert np.all(b == ref_b) - clean_test(filepath) - clean_test(hoisted_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_to_call(here, frontend): +def test_transform_region_to_call(tmp_path, frontend): """ A very simple region-to-call test case """ fcode = """ -subroutine transform_region_to_call(a, b, c) +subroutine reg_to_call(a, b, c) integer, intent(out) :: a, b, c a = 5 @@ -383,10 +364,10 @@ def test_transform_region_to_call(here, frontend): !$loki end region-to-call c = a + b -end subroutine transform_region_to_call +end subroutine reg_to_call """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) # Test the reference solution @@ -407,24 +388,21 @@ def test_transform_region_to_call(here, frontend): # Test transformation contains = Section(body=as_tuple([Intrinsic('CONTAINS'), *routines, routine])) module = Module(name=f'{routine.name}_mod', spec=None, contains=contains) - mod_filepath = here/(f'{module.name}_converted_{frontend}.f90') + mod_filepath = tmp_path/(f'{module.name}_converted_{frontend}.f90') mod = jit_compile(module, filepath=mod_filepath, objname=module.name) mod_function = getattr(mod, routine.name) a, b, c = mod_function() assert a == 1 and b == 1 and c == 2 - clean_test(filepath) - clean_test(mod_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_to_call_multiple(here, frontend): +def test_transform_region_to_call_multiple(tmp_path, frontend): """ Test hoisting with multiple groups and multiple regions per group """ fcode = """ -subroutine transform_region_to_call_multiple(a, b, c) +subroutine reg_to_call_mult(a, b, c) integer, intent(out) :: a, b, c a = 1 @@ -442,10 +420,10 @@ def test_transform_region_to_call_multiple(here, frontend): !$loki region-to-call in(a,b) out(c) c = a + b !$loki end region-to-call -end subroutine transform_region_to_call_multiple +end subroutine reg_to_call_mult """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) # Test the reference solution @@ -468,25 +446,22 @@ def test_transform_region_to_call_multiple(here, frontend): # Test transformation contains = Section(body=as_tuple([Intrinsic('CONTAINS'), *routines, routine])) module = Module(name=f'{routine.name}_mod', spec=None, contains=contains) - mod_filepath = here/(f'{module.name}_converted_{frontend}.f90') + mod_filepath = tmp_path/(f'{module.name}_converted_{frontend}.f90') mod = jit_compile(module, filepath=mod_filepath, objname=module.name) mod_function = getattr(mod, routine.name) a, b, c = mod_function() assert a == 5 and b == 5 and c == 10 - clean_test(filepath) - clean_test(mod_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_to_call_arguments(here, frontend): +def test_transform_region_to_call_arguments(tmp_path, frontend): """ Test hoisting with multiple groups and multiple regions per group and automatic derivation of arguments """ fcode = """ -subroutine transform_region_to_call_arguments(a, b, c) +subroutine reg_to_call_args(a, b, c) integer, intent(out) :: a, b, c a = 1 @@ -505,10 +480,10 @@ def test_transform_region_to_call_arguments(here, frontend): !$loki region-to-call name(func_c) inout(b) c = a + b !$loki end region-to-call -end subroutine transform_region_to_call_arguments +end subroutine reg_to_call_args """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) # Test the reference solution @@ -542,24 +517,21 @@ def test_transform_region_to_call_arguments(here, frontend): # Test transformation contains = Section(body=as_tuple([Intrinsic('CONTAINS'), *routines, routine])) module = Module(name=f'{routine.name}_mod', spec=None, contains=contains) - mod_filepath = here/(f'{module.name}_converted_{frontend}.f90') + mod_filepath = tmp_path/(f'{module.name}_converted_{frontend}.f90') mod = jit_compile(module, filepath=mod_filepath, objname=module.name) mod_function = getattr(mod, routine.name) a, b, c = mod_function() assert a == 5 and b == 5 and c == 10 - clean_test(filepath) - clean_test(mod_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_to_call_arrays(here, frontend): +def test_transform_region_to_call_arrays(tmp_path, frontend): """ Test hoisting with array variables """ fcode = """ -subroutine transform_region_to_call_arrays(a, b, n) +subroutine reg_to_call_arr(a, b, n) integer, intent(out) :: a(n), b(n) integer, intent(in) :: n integer :: j @@ -582,12 +554,12 @@ def test_transform_region_to_call_arrays(here, frontend): end do b(n) = 1 !$loki end region-to-call -end subroutine transform_region_to_call_arrays +end subroutine reg_to_call_arr """ routine = Subroutine.from_source(fcode, frontend=frontend) normalize_range_indexing(routine) - filepath = here/(f'{routine.name}_{frontend}.f90') + filepath = tmp_path/(f'{routine.name}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname=routine.name) # Test the reference solution @@ -617,7 +589,7 @@ def test_transform_region_to_call_arrays(here, frontend): # Test transformation contains = Section(body=as_tuple([Intrinsic('CONTAINS'), *routines, routine])) module = Module(name=f'{routine.name}_mod', spec=None, contains=contains) - mod_filepath = here/(f'{module.name}_converted_{frontend}.f90') + mod_filepath = tmp_path/(f'{module.name}_converted_{frontend}.f90') mod = jit_compile(module, filepath=mod_filepath, objname=module.name) mod_function = getattr(mod, routine.name) @@ -627,12 +599,9 @@ def test_transform_region_to_call_arrays(here, frontend): assert np.all(a == range(1,n+1)) assert np.all(b == [1] * n) - clean_test(filepath) - clean_test(mod_filepath) - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transform_region_to_call_imports(here, builder, frontend): +def test_transform_region_to_call_imports(tmp_path, builder, frontend): """ Test hoisting with correct treatment of imports """ @@ -646,10 +615,10 @@ def test_transform_region_to_call_imports(here, builder, frontend): """.strip() fcode = """ -module transform_region_to_call_imports_mod +module reg_to_call_imps_mod implicit none contains - subroutine transform_region_to_call_imports(a, b) + subroutine reg_to_call_imps(a, b) use region_to_call_mod, only: param, arr1, arr2 integer, intent(out) :: a(10), b(10) integer :: j @@ -673,14 +642,14 @@ def test_transform_region_to_call_imports(here, builder, frontend): b(j) = arr2(j) - a(j) end do !$loki end region-to-call - end subroutine transform_region_to_call_imports -end module transform_region_to_call_imports_mod + end subroutine reg_to_call_imps +end module reg_to_call_imps_mod """ ext_module = Module.from_source(fcode_module, frontend=frontend) module = Module.from_source(fcode, frontend=frontend, definitions=ext_module) normalize_range_indexing(module.subroutines[0]) refname = f'ref_{module.name}_{frontend}' - reference = jit_compile_lib([module, ext_module], path=here, name=refname, builder=builder) + reference = jit_compile_lib([module, ext_module], path=tmp_path, name=refname, builder=builder) function = getattr(getattr(reference, module.name), module.subroutines[0].name) # Test the reference solution @@ -689,7 +658,7 @@ def test_transform_region_to_call_imports(here, builder, frontend): function(a, b) assert np.all(a == [1] * 10) assert np.all(b == range(1,11)) - (here/f'{module.name}.f90').unlink() + (tmp_path/f'{module.name}.f90').unlink() assert len(FindNodes(Assignment).visit(module.subroutines[0].body)) == 4 assert len(FindNodes(CallStatement).visit(module.subroutines[0].body)) == 0 @@ -709,7 +678,7 @@ def test_transform_region_to_call_imports(here, builder, frontend): # Insert created routines into module module.contains.append(routines) - obj = jit_compile_lib([module, ext_module], path=here, name=f'{module.name}_{frontend}', builder=builder) + obj = jit_compile_lib([module, ext_module], path=tmp_path, name=f'{module.name}_{frontend}', builder=builder) mod_function = getattr(getattr(obj, module.name), module.subroutines[0].name) # Test transformation @@ -718,5 +687,3 @@ def test_transform_region_to_call_imports(here, builder, frontend): mod_function(a, b) assert np.all(a == [1] * 10) assert np.all(b == range(1,11)) - (here/f'{module.name}.f90').unlink() - (here/f'{ext_module.name}.f90').unlink() diff --git a/loki/transformations/transpile/tests/test_maxeler/test_maxeler.py b/loki/transformations/transpile/tests/test_maxeler/test_maxeler.py index ab2aa16e1..1e29fa859 100644 --- a/loki/transformations/transpile/tests/test_maxeler/test_maxeler.py +++ b/loki/transformations/transpile/tests/test_maxeler/test_maxeler.py @@ -101,7 +101,8 @@ def fixture_here(): @pytest.fixture(scope='module', name='builder') def fixture_builder(here): include_dirs = get_max_includes() if is_maxeler_available() else [] - return Builder(source_dirs=here, include_dirs=include_dirs, build_dir=here/'build') + yield Builder(source_dirs=here, include_dirs=include_dirs, build_dir=here/'build') + Obj.clear_cache() def max_transpile(routine, path, builder, frontend, objects=None, wrap=None): diff --git a/loki/transformations/transpile/tests/test_sdfg.py b/loki/transformations/transpile/tests/test_sdfg.py index 97767ab18..af568f449 100644 --- a/loki/transformations/transpile/tests/test_sdfg.py +++ b/loki/transformations/transpile/tests/test_sdfg.py @@ -5,14 +5,15 @@ # granted to it by virtue of its status as an intergovernmental organisation # nor does it submit to any jurisdiction. -import itertools import importlib +import itertools +from shutil import rmtree from pathlib import Path import numpy as np import pytest from loki import Subroutine -from loki.build import jit_compile, clean_test +from loki.build import jit_compile from loki.frontend import available_frontends from loki.transformations.transpile import FortranPythonTransformation @@ -30,10 +31,6 @@ ) ] -@pytest.fixture(scope='module', name='here') -def fixture_here(): - return Path(__file__).parent - def load_module(path): path = Path(path) @@ -47,9 +44,9 @@ def load_module(path): return importlib.import_module(path.stem) -def create_sdfg(routine, here): +def create_sdfg(routine, tmp_path): trafo = FortranPythonTransformation(with_dace=True, suffix='_py') - routine.apply(trafo, path=here) + routine.apply(trafo, path=tmp_path) mod = load_module(trafo.py_path) function = getattr(mod, routine.name) @@ -57,7 +54,7 @@ def create_sdfg(routine, here): @pytest.mark.parametrize('frontend', available_frontends()) -def test_sdfg_routine_copy(here, frontend): +def test_sdfg_routine_copy(tmp_path, frontend): fcode = """ subroutine routine_copy(n, x, y) @@ -77,7 +74,7 @@ def test_sdfg_routine_copy(here, frontend): routine = Subroutine.from_source(fcode, frontend=frontend) # Test the reference solution - filepath = here/(f'routine_copy_{frontend}.f90') + filepath = tmp_path/(f'routine_copy_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='routine_copy') n = 64 @@ -89,7 +86,7 @@ def test_sdfg_routine_copy(here, frontend): assert all(x_ref == y) # Create and compile the SDFG - sdfg = create_sdfg(routine, here) + sdfg = create_sdfg(routine, tmp_path) assert sdfg.validate() is None csdfg = sdfg.compile() @@ -100,14 +97,11 @@ def test_sdfg_routine_copy(here, frontend): csdfg(n=np.int32(n), x=x, y=y) assert all(x_ref == y) - clean_test(filepath) - (here / (routine.name + '.py')).unlink() - @pytest.mark.xfail(reason='Scalar inout arguments do not work in dace') @pytest.mark.filterwarnings('ignore:The value of the smallest subnormal.*class \'numpy.float64\':UserWarning') @pytest.mark.parametrize('frontend', available_frontends()) -def test_sdfg_routine_axpy_scalar(here, frontend): +def test_sdfg_routine_axpy_scalar(tmp_path, frontend): fcode = """ subroutine routine_axpy_scalar(a, x, y) @@ -124,7 +118,7 @@ def test_sdfg_routine_axpy_scalar(here, frontend): routine = Subroutine.from_source(fcode, frontend=frontend) # Test the reference solution - filepath = here/(f'sdfg_routine_axpy_scalar_{frontend}.f90') + filepath = tmp_path/(f'sdfg_routine_axpy_scalar_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='routine_axpy_scalar') a = np.float64(23) @@ -135,7 +129,7 @@ def test_sdfg_routine_axpy_scalar(here, frontend): assert x_out == a * x + y # Create and compile the SDFG - sdfg = create_sdfg(routine, here) + sdfg = create_sdfg(routine, tmp_path) assert sdfg.validate() is None csdfg = sdfg.compile() @@ -146,12 +140,9 @@ def test_sdfg_routine_axpy_scalar(here, frontend): csdfg(a=a, x=x_out, y=y) assert x_out == a * x + y - clean_test(filepath) - (here / (routine.name + '.py')).unlink() - @pytest.mark.parametrize('frontend', available_frontends()) -def test_sdfg_routine_copy_stream(here, frontend): +def test_sdfg_routine_copy_stream(tmp_path, frontend): fcode = """ subroutine routine_copy_stream(length, alpha, vector_in, vector_out) @@ -172,7 +163,7 @@ def test_sdfg_routine_copy_stream(here, frontend): # TODO: make alpha a true scalar, which doesn't seem to work with SDFG at the moment??? # Test the reference solution - filepath = here/(f'sdfg_routine_copy_stream_{frontend}.f90') + filepath = tmp_path/(f'sdfg_routine_copy_stream_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='routine_copy_stream') length = 32 @@ -183,7 +174,7 @@ def test_sdfg_routine_copy_stream(here, frontend): assert np.all(vector_out == np.array(range(length)) + alpha) # Create and compile the SDFG - sdfg = create_sdfg(routine, here) + sdfg = create_sdfg(routine, tmp_path) assert sdfg.validate() is None csdfg = sdfg.compile() @@ -195,12 +186,9 @@ def test_sdfg_routine_copy_stream(here, frontend): csdfg(length=length, alpha=alpha, vector_in=vec_in, vector_out=vec_out) assert np.all(vec_out == np.array(range(length)) + alpha) - clean_test(filepath) - (here / (routine.name + '.py')).unlink() - @pytest.mark.parametrize('frontend', available_frontends()) -def test_sdfg_routine_fixed_loop(here, frontend): +def test_sdfg_routine_fixed_loop(tmp_path, frontend): fcode = """ subroutine routine_fixed_loop(scalar, vector, vector_out, tensor, tensor_out) @@ -224,7 +212,7 @@ def test_sdfg_routine_fixed_loop(here, frontend): end subroutine routine_fixed_loop """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'sdfg_routine_fixed_loop_{frontend}.f90') + filepath = tmp_path/(f'sdfg_routine_fixed_loop_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='routine_fixed_loop') # Test the reference solution @@ -240,7 +228,7 @@ def test_sdfg_routine_fixed_loop(here, frontend): assert np.all(tensor_out == ref_tensor) # Create and compile the SDFG - sdfg = create_sdfg(routine, here) + sdfg = create_sdfg(routine, tmp_path) assert sdfg.validate() is None csdfg = sdfg.compile() @@ -257,15 +245,12 @@ def test_sdfg_routine_fixed_loop(here, frontend): assert np.all(vector == ref_vector) assert np.all(tensor_out == ref_tensor) - clean_test(filepath) - (here / (routine.name + '.py')).unlink() - @pytest.mark.skip(reason=('This translates successfully but the generated OpenMP code does not ' 'honour the loop-carried dependency, thus creating data races for more ' 'than 1 thread.')) @pytest.mark.parametrize('frontend', available_frontends()) -def test_sdfg_routine_loop_carried_dependency(here, frontend): +def test_sdfg_routine_loop_carried_dependency(tmp_path, frontend): fcode = """ subroutine routine_loop_carried_dependency(vector) @@ -281,7 +266,7 @@ def test_sdfg_routine_loop_carried_dependency(here, frontend): end subroutine routine_loop_carried_dependency """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'sdfg_routine_loop_carried_dependency_{frontend}.f90') + filepath = tmp_path/(f'sdfg_routine_loop_carried_dependency_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='routine_loop_carried_dependency') # Test the reference solution @@ -292,7 +277,7 @@ def test_sdfg_routine_loop_carried_dependency(here, frontend): assert np.all(vector == ref_vector) # Create and compile the SDFG - sdfg = create_sdfg(routine, here) + sdfg = create_sdfg(routine, tmp_path) assert sdfg.validate() is None csdfg = sdfg.compile() @@ -305,16 +290,13 @@ def test_sdfg_routine_loop_carried_dependency(here, frontend): csdfg(vector=vector) assert np.all(vector == ref_vector) - clean_test(filepath) - (here / (routine.name + '.py')).unlink() - @pytest.mark.parametrize('frontend', available_frontends()) -def test_sdfg_routine_moving_average(here, frontend): +def test_sdfg_routine_moving_average(tmp_path, frontend): # TODO: This needs more work to properly handle boundary values. # In the current form, these values seem to be handled in a way # that causes race conditions. Either this is a DaCe bug or we are - # using DaCe wrong here. + # using DaCe wrong tmp_path. fcode = """ subroutine routine_moving_average(length, data_in, data_out) @@ -354,7 +336,7 @@ def test_sdfg_routine_moving_average(here, frontend): end subroutine routine_moving_average """ routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'sdfg_routine_moving_average_{frontend}.f90') + filepath = tmp_path/(f'sdfg_routine_moving_average_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='routine_moving_average') # Create random input data @@ -373,7 +355,7 @@ def test_sdfg_routine_moving_average(here, frontend): assert np.all(data_out[1:-1] == expected[1:-1]) # Create and compile the SDFG - sdfg = create_sdfg(routine, here) + sdfg = create_sdfg(routine, tmp_path) assert sdfg.validate() is None csdfg = sdfg.compile() @@ -383,6 +365,3 @@ def test_sdfg_routine_moving_average(here, frontend): data_out = np.zeros(shape=(n,), order='F') csdfg(length=n, data_in=data_in, data_out=data_out) assert np.all(data_out[1:-1] == expected[1:-1]) - - clean_test(filepath) - (here / (routine.name + '.py')).unlink() diff --git a/loki/transformations/transpile/tests/test_transpile.py b/loki/transformations/transpile/tests/test_transpile.py index 563b3085a..a39f7026f 100644 --- a/loki/transformations/transpile/tests/test_transpile.py +++ b/loki/transformations/transpile/tests/test_transpile.py @@ -5,12 +5,12 @@ # granted to it by virtue of its status as an intergovernmental organisation # nor does it submit to any jurisdiction. -from pathlib import Path +from shutil import rmtree import pytest import numpy as np from loki import Subroutine, Module, cgen -from loki.build import jit_compile, jit_compile_lib, clean_test, Builder +from loki.build import jit_compile, jit_compile_lib, clean_test, Builder, Obj import loki.expression.symbols as sym from loki.frontend import available_frontends, OFP import loki.ir as ir @@ -19,19 +19,15 @@ from loki.transformations.transpile import FortranCTransformation -@pytest.fixture(scope='module', name='here') -def fixture_here(): - return Path(__file__).parent - - -@pytest.fixture(scope='module', name='builder') -def fixture_builder(here): - return Builder(source_dirs=here, build_dir=here/'build') +@pytest.fixture(scope='function', name='builder') +def fixture_builder(tmp_path): + yield Builder(source_dirs=tmp_path, build_dir=tmp_path) + Obj.clear_cache() @pytest.mark.parametrize('case_sensitive', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_case_sensitivity(here, frontend, case_sensitive): +def test_transpile_case_sensitivity(tmp_path, frontend, case_sensitive): """ A simple test for testing lowering the case and case-sensitivity for specific symbols. @@ -62,25 +58,23 @@ def convert_case(_str, case_sensitive): routine.body = (routine.body, assignment, call, inline_call_assignment) f2c = FortranCTransformation() - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) ccode = f2c.c_path.read_text().replace(' ', '').replace('\n', ' ').replace('\r', '').replace('\t', '') assert convert_case('transpile_case_sensitivity_c(inta,intsOmE_vAr,intoTher_VaR)', case_sensitive) in ccode assert convert_case('a=threadIdx%x;', case_sensitive) in ccode assert convert_case('somE_cALl(a);', case_sensitive) in ccode assert convert_case('a=somE_InlINeCaLl(1);', case_sensitive) in ccode - f2c.wrapperpath.unlink() - f2c.c_path.unlink() @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_simple_loops(here, builder, frontend, use_c_ptr): +def test_transpile_simple_loops(tmp_path, builder, frontend, use_c_ptr): """ A simple test routine to test C transpilation of loops """ fcode = """ -subroutine transpile_simple_loops(n, m, scalar, vector, tensor) +subroutine simple_loops(n, m, scalar, vector, tensor) use iso_fortran_env, only: real64 implicit none integer, intent(in) :: n, m @@ -99,14 +93,14 @@ def test_transpile_simple_loops(here, builder, frontend, use_c_ptr): tensor(i, j) = 10.* j + i end do end do -end subroutine transpile_simple_loops +end subroutine simple_loops """ # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) normalize_range_indexing(routine) # Fix OMNI nonsense - filepath = here/(f'transpile_simple_loops{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') - function = jit_compile(routine, filepath=filepath, objname='transpile_simple_loops') + filepath = tmp_path/(f'simple_loops{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + function = jit_compile(routine, filepath=filepath, objname='simple_loops') n, m = 3, 4 scalar = 2.0 @@ -121,10 +115,10 @@ def test_transpile_simple_loops(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transpile_simple_loops_fc_mod.transpile_simple_loops_fc + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_function = c_kernel.simple_loops_fc_mod.simple_loops_fc # check the generated F2C wrapper with open(f2c.wrapperpath, 'r') as f2c_f: @@ -151,15 +145,10 @@ def test_transpile_simple_loops(here, builder, frontend, use_c_ptr): [12., 22., 32., 42.], [13., 23., 33., 43.]]) - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_arguments(here, builder, frontend, use_c_ptr): +def test_transpile_arguments(tmp_path, builder, frontend, use_c_ptr): """ A test the correct exchange of arguments with varying intents """ @@ -209,7 +198,7 @@ def test_transpile_arguments(here, builder, frontend, use_c_ptr): # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) normalize_range_indexing(routine) # Fix OMNI nonsense - filepath = here/(f'transpile_arguments{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + filepath = tmp_path/(f'transpile_arguments{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='transpile_arguments') a, b, c = function(n, array, array_io, a_io, b_io, c_io) @@ -220,9 +209,9 @@ def test_transpile_arguments(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) fc_function = c_kernel.transpile_arguments_fc_mod.transpile_arguments_fc # check the generated F2C wrapper @@ -251,15 +240,10 @@ def test_transpile_arguments(here, builder, frontend, use_c_ptr): assert a_io[0] == 3. and np.isclose(b_io[0], 5.2) and np.isclose(c_io[0], 7.1) assert a == 8 and np.isclose(b, 3.2) and np.isclose(c, 4.1) - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_derived_type(here, builder, frontend, use_c_ptr): +def test_transpile_derived_type(tmp_path, builder, frontend, use_c_ptr): """ Tests handling and type-conversion of various argument types """ @@ -278,7 +262,7 @@ def test_transpile_derived_type(here, builder, frontend, use_c_ptr): """ fcode_routine = """ -subroutine transpile_derived_type(a_struct) +subroutine transp_der_type(a_struct) use transpile_type_mod, only: my_struct implicit none type(my_struct), intent(inout) :: a_struct @@ -286,65 +270,58 @@ def test_transpile_derived_type(here, builder, frontend, use_c_ptr): a_struct%a = a_struct%a + 4 a_struct%b = a_struct%b + 5. a_struct%c = a_struct%c + 6. -end subroutine transpile_derived_type +end subroutine transp_der_type """ builder.clean() module = Module.from_source(fcode_type, frontend=frontend) routine = Subroutine.from_source(fcode_routine, definitions=module, frontend=frontend) refname = f'ref_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - reference = jit_compile_lib([module, routine], path=here, name=refname, builder=builder) + reference = jit_compile_lib([module, routine], path=tmp_path, name=refname, builder=builder) # Test the reference solution a_struct = reference.transpile_type_mod.my_struct() a_struct.a = 4 a_struct.b = 5. a_struct.c = 6. - reference.transpile_derived_type(a_struct) + reference.transp_der_type(a_struct) assert a_struct.a == 8 assert a_struct.b == 10. assert a_struct.c == 12. # Translate the header module to expose parameters mod2c = FortranCTransformation(use_c_ptr=use_c_ptr) - mod2c.apply(source=module, path=here, role='header') + mod2c.apply(source=module, path=tmp_path, role='header') # Create transformation object and apply f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here, role='kernel') + f2c.apply(source=routine, path=tmp_path, role='kernel') # Build and wrap the cross-compiled library sources = [module, f2c.wrapperpath, f2c.c_path] libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib(sources=sources, path=here, name=libname, builder=builder) + c_kernel = jit_compile_lib(sources=sources, path=tmp_path, name=libname, builder=builder) a_struct = c_kernel.transpile_type_mod.my_struct() a_struct.a = 4 a_struct.b = 5. a_struct.c = 6. - function = c_kernel.transpile_derived_type_fc_mod.transpile_derived_type_fc + function = c_kernel.transp_der_type_fc_mod.transp_der_type_fc function(a_struct) assert a_struct.a == 8 assert a_struct.b == 10. assert a_struct.c == 12. - builder.clean() - mod2c.wrapperpath.unlink() - mod2c.c_path.unlink() - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - (here/f'{module.name}.f90').unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_associates(here, builder, frontend, use_c_ptr): +def test_transpile_associates(tmp_path, builder, frontend, use_c_ptr): """ Tests C-transpilation of associate statements """ fcode_type = """ -module transpile_type_mod +module assoc_type_mod use iso_fortran_env, only: real32, real64 implicit none @@ -353,12 +330,12 @@ def test_transpile_associates(here, builder, frontend, use_c_ptr): real(kind=real32) :: b real(kind=real64) :: c end type my_struct -end module transpile_type_mod +end module assoc_type_mod """ fcode_routine = """ -subroutine transpile_associates(a_struct) - use transpile_type_mod, only: my_struct +subroutine transp_assoc(a_struct) + use assoc_type_mod, only: my_struct implicit none type(my_struct), intent(inout) :: a_struct @@ -368,55 +345,47 @@ def test_transpile_associates(here, builder, frontend, use_c_ptr): a_struct_b = a_struct%b + 5. a_struct_c = a_struct_a + a_struct%b + a_struct_c end associate -end subroutine transpile_associates +end subroutine transp_assoc """ - builder.clean() module = Module.from_source(fcode_type, frontend=frontend) routine = Subroutine.from_source(fcode_routine, definitions=module, frontend=frontend) refname = f'ref_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - reference = jit_compile_lib([module, routine], path=here, name=refname, builder=builder) + reference = jit_compile_lib([module, routine], path=tmp_path, name=refname, builder=builder) # Test the reference solution - a_struct = reference.transpile_type_mod.my_struct() + a_struct = reference.assoc_type_mod.my_struct() a_struct.a = 4 a_struct.b = 5. a_struct.c = 6. - reference.transpile_associates(a_struct) + reference.transp_assoc(a_struct) assert a_struct.a == 8 assert a_struct.b == 10. assert a_struct.c == 24. # Translate the header module to expose parameters mod2c = FortranCTransformation() - mod2c.apply(source=module, path=here, role='header') + mod2c.apply(source=module, path=tmp_path, role='header') # Create transformation object and apply f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here, role='kernel') + f2c.apply(source=routine, path=tmp_path, role='kernel') # Build and wrap the cross-compiled library sources = [module, f2c.wrapperpath, f2c.c_path] libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib(sources=sources, path=here, name=libname, builder=builder) + c_kernel = jit_compile_lib(sources=sources, path=tmp_path, name=libname, builder=builder) - a_struct = c_kernel.transpile_type_mod.my_struct() + a_struct = c_kernel.assoc_type_mod.my_struct() a_struct.a = 4 a_struct.b = 5. a_struct.c = 6. - function = c_kernel.transpile_associates_fc_mod.transpile_associates_fc + function = c_kernel.transp_assoc_fc_mod.transp_assoc_fc function(a_struct) assert a_struct.a == 8 assert a_struct.b == 10. assert a_struct.c == 24. - builder.clean() - mod2c.wrapperpath.unlink() - mod2c.c_path.unlink() - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - (here/f'{module.name}.f90').unlink() - @pytest.mark.skip(reason='More thought needed on how to test structs-of-arrays') def test_transpile_derived_type_array(): @@ -448,15 +417,16 @@ def test_transpile_derived_type_array(): ! end subroutine transpile_derived_type_array """ + @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_module_variables(here, builder, frontend, use_c_ptr): +def test_transpile_module_variables(tmp_path, builder, frontend, use_c_ptr): """ Tests the use of imported module variables (via getter routines in C) """ fcode_type = """ -module transpile_type_mod +module mod_var_type_mod use iso_fortran_env, only: real32, real64 implicit none @@ -465,13 +435,13 @@ def test_transpile_module_variables(here, builder, frontend, use_c_ptr): integer :: PARAM1 real(kind=real32) :: param2 real(kind=real64) :: param3 -end module transpile_type_mod +end module mod_var_type_mod """ fcode_routine = """ -subroutine transpile_module_variables(a, b, c) +subroutine transp_mod_var(a, b, c) use iso_fortran_env, only: real32, real64 - use transpile_type_mod, only: PARAM1, param2, param3 + use mod_var_type_mod, only: PARAM1, param2, param3 integer, intent(out) :: a real(kind=real32), intent(out) :: b @@ -480,57 +450,50 @@ def test_transpile_module_variables(here, builder, frontend, use_c_ptr): a = 1 + PARAM1 ! Ensure downcasing is done right b = 1. + param2 c = 1. + param3 -end subroutine transpile_module_variables +end subroutine transp_mod_var """ module = Module.from_source(fcode_type, frontend=frontend) routine = Subroutine.from_source(fcode_routine, definitions=module, frontend=frontend) refname = f'ref_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - reference = jit_compile_lib([module, routine], path=here, name=refname, builder=builder) + reference = jit_compile_lib([module, routine], path=tmp_path, name=refname, builder=builder) - reference.transpile_type_mod.param1 = 2 - reference.transpile_type_mod.param2 = 4. - reference.transpile_type_mod.param3 = 3. - a, b, c = reference.transpile_module_variables() + reference.mod_var_type_mod.param1 = 2 + reference.mod_var_type_mod.param2 = 4. + reference.mod_var_type_mod.param3 = 3. + a, b, c = reference.transp_mod_var() assert a == 3 and b == 5. and c == 4. # Translate the header module to expose parameters mod2c = FortranCTransformation(use_c_ptr=use_c_ptr) - mod2c.apply(source=module, path=here, role='header') + mod2c.apply(source=module, path=tmp_path, role='header') # Create transformation object and apply f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here, role='kernel') + f2c.apply(source=routine, path=tmp_path, role='kernel') # Build and wrap the cross-compiled library sources = [module, mod2c.wrapperpath, f2c.wrapperpath, f2c.c_path] - wrap = [here/'transpile_type_mod.f90', f2c.wrapperpath.name] + wrap = [tmp_path/'mod_var_type_mod.f90', f2c.wrapperpath.name] libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib(sources=sources, wrap=wrap, path=here, name=libname, builder=builder) + c_kernel = jit_compile_lib(sources=sources, wrap=wrap, path=tmp_path, name=libname, builder=builder) - c_kernel.transpile_type_mod.param1 = 2 - c_kernel.transpile_type_mod.param2 = 4. - c_kernel.transpile_type_mod.param3 = 3. - a, b, c = c_kernel.transpile_module_variables_fc_mod.transpile_module_variables_fc() + c_kernel.mod_var_type_mod.param1 = 2 + c_kernel.mod_var_type_mod.param2 = 4. + c_kernel.mod_var_type_mod.param3 = 3. + a, b, c = c_kernel.transp_mod_var_fc_mod.transp_mod_var_fc() assert a == 3 and b == 5. and c == 4. - builder.clean() - mod2c.wrapperpath.unlink() - mod2c.c_path.unlink() - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - (here/f'{module.name}.f90').unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_vectorization(here, builder, frontend, use_c_ptr): +def test_transpile_vectorization(tmp_path, builder, frontend, use_c_ptr): """ Tests vector-notation conversion and local multi-dimensional arrays. """ fcode = """ -subroutine transpile_vectorization(n, m, scalar, v1, v2) +subroutine transp_vect(n, m, scalar, v1, v2) use iso_fortran_env, only: real64 implicit none integer, intent(in) :: n, m @@ -545,13 +508,13 @@ def test_transpile_vectorization(here, builder, frontend, use_c_ptr): matrix(:, :) = scalar + 2. v2(:) = matrix(:, 2) v2(1) = 1. -end subroutine transpile_vectorization +end subroutine transp_vect """ # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'transpile_vectorization{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') - function = jit_compile(routine, filepath=filepath, objname='transpile_vectorization') + filepath = tmp_path/(f'transp_vect{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + function = jit_compile(routine, filepath=filepath, objname='transp_vect') n, m = 3, 4 scalar = 2.0 @@ -564,10 +527,10 @@ def test_transpile_vectorization(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transpile_vectorization_fc_mod.transpile_vectorization_fc + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_function = c_kernel.transp_vect_fc_mod.transp_vect_fc # Test the trnapiled C kernel n, m = 3, 4 @@ -579,15 +542,10 @@ def test_transpile_vectorization(here, builder, frontend, use_c_ptr): assert np.all(v1 == 3.) assert v2[0] == 1. and np.all(v2[1:] == 4.) - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_intrinsics(here, builder, frontend, use_c_ptr): +def test_transpile_intrinsics(tmp_path, builder, frontend, use_c_ptr): """ A simple test routine to test supported intrinsic functions """ @@ -609,7 +567,7 @@ def test_transpile_intrinsics(here, builder, frontend, use_c_ptr): # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'transpile_intrinsics{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + filepath = tmp_path/(f'transpile_intrinsics{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') function = jit_compile(routine, filepath=filepath, objname='transpile_intrinsics') # Test the reference solution @@ -620,30 +578,25 @@ def test_transpile_intrinsics(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) fc_function = c_kernel.transpile_intrinsics_fc_mod.transpile_intrinsics_fc vmin, vmax, vabs, vmin_nested, vmax_nested = fc_function(v1, v2, v3, v4) assert vmin == 2. and vmax == 4. and vabs == 2. assert vmin_nested == 1. and vmax_nested == 5. - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_loop_indices(here, builder, frontend, use_c_ptr): +def test_transpile_loop_indices(tmp_path, builder, frontend, use_c_ptr): """ Test to ensure loop indexing translates correctly """ fcode = """ -subroutine transpile_loop_indices(n, idx, mask1, mask2, mask3) +subroutine transp_loop_ind(n, idx, mask1, mask2, mask3) ! Test to ensure loop indexing translates correctly use iso_fortran_env, only: real64 integer, intent(in) :: n, idx @@ -664,13 +617,13 @@ def test_transpile_loop_indices(here, builder, frontend, use_c_ptr): mask2(i) = i end do mask3(n) = 3.0 -end subroutine transpile_loop_indices +end subroutine transp_loop_ind """ # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'transpile_loop_indices{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') - function = jit_compile(routine, filepath=filepath, objname='transpile_loop_indices') + filepath = tmp_path/(f'transp_loop_ind{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + function = jit_compile(routine, filepath=filepath, objname='transp_loop_ind') # Test the reference solution n = 6 @@ -689,10 +642,10 @@ def test_transpile_loop_indices(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transpile_loop_indices_fc_mod.transpile_loop_indices_fc + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_function = c_kernel.transp_loop_ind_fc_mod.transp_loop_ind_fc mask1 = np.zeros(shape=(n,), order='F', dtype=np.int32) mask2 = np.zeros(shape=(n,), order='F', dtype=np.int32) @@ -705,21 +658,16 @@ def test_transpile_loop_indices(here, builder, frontend, use_c_ptr): assert np.all(mask3[:-1] == 0.) assert mask3[-1] == 3. - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_logical_statements(here, builder, frontend, use_c_ptr): +def test_transpile_logical_statements(tmp_path, builder, frontend, use_c_ptr): """ A simple test routine to test logical statements """ fcode = """ -subroutine transpile_logical_statements(v1, v2, v_xor, v_xnor, v_nand, v_neqv, v_val) +subroutine logical_stmts(v1, v2, v_xor, v_xnor, v_nand, v_neqv, v_val) logical, intent(in) :: v1, v2 logical, intent(out) :: v_xor, v_nand, v_xnor, v_neqv, v_val(2) @@ -730,13 +678,13 @@ def test_transpile_logical_statements(here, builder, frontend, use_c_ptr): v_val(1) = .true. v_val(2) = .false. -end subroutine transpile_logical_statements +end subroutine logical_stmts """ # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'transpile_logical_statements{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') - function = jit_compile(routine, filepath=filepath, objname='transpile_logical_statements') + filepath = tmp_path/(f'logical_stmts{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + function = jit_compile(routine, filepath=filepath, objname='logical_stmts') # Test the reference solution for v1 in range(2): @@ -751,10 +699,10 @@ def test_transpile_logical_statements(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transpile_logical_statements_fc_mod.transpile_logical_statements_fc + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_function = c_kernel.logical_stmts_fc_mod.logical_stmts_fc for v1 in range(2): for v2 in range(2): @@ -766,20 +714,15 @@ def test_transpile_logical_statements(here, builder, frontend, use_c_ptr): assert v_neqv == ((not (v1 and v2)) and (v1 or v2)) assert v_val[0] and not v_val[1] - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_multibody_conditionals(here, builder, frontend, use_c_ptr): +def test_transpile_multibody_conditionals(tmp_path, builder, frontend, use_c_ptr): """ Test correct transformation of multi-body conditionals. """ fcode = """ -subroutine transpile_multibody_conditionals(in1, out1, out2) +subroutine multibody_cond(in1, out1, out2) integer, intent(in) :: in1 integer, intent(out) :: out1, out2 @@ -799,12 +742,12 @@ def test_transpile_multibody_conditionals(here, builder, frontend, use_c_ptr): else out2 = in1 end if -end subroutine transpile_multibody_conditionals +end subroutine multibody_cond """ # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/(f'transpile_multibody_conditionals{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') - function = jit_compile(routine, filepath=filepath, objname='transpile_multibody_conditionals') + filepath = tmp_path/(f'multibody_cond{"_c_ptr" if use_c_ptr else ""}_{frontend}.f90') + function = jit_compile(routine, filepath=filepath, objname='multibody_cond') out1, out2 = function(5) assert out1 == 1 and out2 == 4 @@ -822,10 +765,10 @@ def test_transpile_multibody_conditionals(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transpile_multibody_conditionals_fc_mod.transpile_multibody_conditionals_fc + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_function = c_kernel.multibody_cond_fc_mod.multibody_cond_fc out1, out2 = fc_function(5) assert out1 == 1 and out2 == 4 @@ -838,16 +781,13 @@ def test_transpile_multibody_conditionals(here, builder, frontend, use_c_ptr): out1, out2 = fc_function(10) assert out1 == 5 and out2 == 5 - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends( skip=[(OFP, 'Prefix/elemental support not implemented')] )) -def test_transpile_inline_elemental_functions(here, builder, frontend, use_c_ptr): +def test_transpile_inline_elemental_functions(tmp_path, builder, frontend, use_c_ptr): """ Test correct inlining of elemental functions in C transpilation. """ @@ -867,7 +807,7 @@ def test_transpile_inline_elemental_functions(here, builder, frontend, use_c_ptr """ fcode = """ -subroutine transpile_inline_elemental_functions(v1, v2, v3) +subroutine inline_elemental(v1, v2, v3) use iso_fortran_env, only: real64 use multiply_mod_c, only: multiply real(kind=real64), intent(in) :: v1 @@ -875,44 +815,40 @@ def test_transpile_inline_elemental_functions(here, builder, frontend, use_c_ptr v2 = multiply(v1, 6._real64) v3 = 600. + multiply(6._real64, 11._real64) -end subroutine transpile_inline_elemental_functions +end subroutine inline_elemental """ # Generate reference code, compile run and verify module = Module.from_source(fcode_module, frontend=frontend) routine = Subroutine.from_source(fcode, frontend=frontend) refname = f'ref_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - reference = jit_compile_lib([module, routine], path=here, name=refname, builder=builder) + reference = jit_compile_lib([module, routine], path=tmp_path, name=refname, builder=builder) - v2, v3 = reference.transpile_inline_elemental_functions(11.) + v2, v3 = reference.inline_elemental(11.) assert v2 == 66. assert v3 == 666. - (here/f'{module.name}.f90').unlink() - (here/f'{routine.name}.f90').unlink() + (tmp_path/f'{module.name}.f90').unlink() + (tmp_path/f'{routine.name}.f90').unlink() # Now transpile with supplied elementals but without module routine = Subroutine.from_source(fcode, definitions=module, frontend=frontend) f2c = FortranCTransformation(inline_elementals=True, use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_mod = c_kernel.transpile_inline_elemental_functions_fc_mod + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_mod = c_kernel.inline_elemental_fc_mod - v2, v3 = fc_mod.transpile_inline_elemental_functions_fc(11.) + v2, v3 = fc_mod.inline_elemental_fc(11.) assert v2 == 66. assert v3 == 666. - builder.clean() - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends( skip=[(OFP, 'Prefix/elemental support not implemented')] )) -def test_transpile_inline_elementals_recursive(here, builder, frontend, use_c_ptr): +def test_transpile_inline_elementals_recursive(tmp_path, builder, frontend, use_c_ptr): """ Test correct inlining of nested elemental functions. """ @@ -946,7 +882,7 @@ def test_transpile_inline_elementals_recursive(here, builder, frontend, use_c_pt """ fcode = """ -subroutine transpile_inline_elementals_recursive(v1, v2, v3) +subroutine inline_elementals_rec(v1, v2, v3) use iso_fortran_env, only: real64 use multiply_plus_one_mod, only: multiply_plus_one real(kind=real64), intent(in) :: v1 @@ -954,42 +890,38 @@ def test_transpile_inline_elementals_recursive(here, builder, frontend, use_c_pt v2 = multiply_plus_one(v1, 6._real64) v3 = 600. + multiply_plus_one(5._real64, 11._real64) -end subroutine transpile_inline_elementals_recursive +end subroutine inline_elementals_rec """ # Generate reference code, compile run and verify module = Module.from_source(fcode_module, frontend=frontend) routine = Subroutine.from_source(fcode, frontend=frontend) refname = f'ref_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - reference = jit_compile_lib([module, routine], path=here, name=refname, builder=builder) + reference = jit_compile_lib([module, routine], path=tmp_path, name=refname, builder=builder) - v2, v3 = reference.transpile_inline_elementals_recursive(10.) + v2, v3 = reference.inline_elementals_rec(10.) assert v2 == 66. assert v3 == 666. - (here/f'{module.name}.f90').unlink() - (here/f'{routine.name}.f90').unlink() + (tmp_path/f'{module.name}.f90').unlink() + (tmp_path/f'{routine.name}.f90').unlink() # Now transpile with supplied elementals but without module routine = Subroutine.from_source(fcode, definitions=module, frontend=frontend) f2c = FortranCTransformation(inline_elementals=True, use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_mod = c_kernel.transpile_inline_elementals_recursive_fc_mod + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_mod = c_kernel.inline_elementals_rec_fc_mod - v2, v3 = fc_mod.transpile_inline_elementals_recursive_fc(10.) + v2, v3 = fc_mod.inline_elementals_rec_fc(10.) assert v2 == 66. assert v3 == 666. - builder.clean() - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_expressions(here, builder, frontend, use_c_ptr): +def test_transpile_expressions(tmp_path, builder, frontend, use_c_ptr): """ A simple test to verify expression parenthesis and resolution of minus sign @@ -1014,7 +946,7 @@ def test_transpile_expressions(here, builder, frontend, use_c_ptr): # Generate reference code, compile run and verify routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/f'{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend!s}.f90' + filepath = tmp_path/f'{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend!s}.f90' function = jit_compile(routine, filepath=filepath, objname=routine.name) n = 10 @@ -1026,9 +958,9 @@ def test_transpile_expressions(here, builder, frontend, use_c_ptr): # Generate and test the transpiled C kernel f2c = FortranCTransformation(use_c_ptr=use_c_ptr) - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) libname = f'fc_{routine.name}{"_c_ptr" if use_c_ptr else ""}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) fc_function = c_kernel.transpile_expressions_fc_mod.transpile_expressions_fc # Make sure minus signs are represented correctly in the C code @@ -1045,15 +977,10 @@ def test_transpile_expressions(here, builder, frontend, use_c_ptr): assert np.all(vector == [i * scalar for i in range(1, n+1)]) - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('use_c_ptr', (False, True)) @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_call(here, frontend, use_c_ptr): +def test_transpile_call(tmp_path, frontend, use_c_ptr): fcode_module = """ module transpile_call_kernel_mod implicit none @@ -1082,15 +1009,12 @@ def test_transpile_call(here, frontend, use_c_ptr): call transpile_call_kernel(a, b, arr2(1, 1), arr1, len) end subroutine transpile_call_driver """ - unlink_paths = [] module = Module.from_source(fcode_module, frontend=frontend) routine = Subroutine.from_source(fcode, frontend=frontend, definitions=module) - f2c = FortranCTransformation(use_c_ptr=use_c_ptr, path=here) - f2c.apply(source=module.subroutine_map['transpile_call_kernel'], path=here, role='kernel') - unlink_paths.extend([f2c.wrapperpath, f2c.c_path]) + f2c = FortranCTransformation(use_c_ptr=use_c_ptr, path=tmp_path) + f2c.apply(source=module.subroutine_map['transpile_call_kernel'], path=tmp_path, role='kernel') ccode_kernel = f2c.c_path.read_text().replace(' ', '').replace('\n', '') - f2c.apply(source=routine, path=here, role='kernel') - unlink_paths.extend([f2c.wrapperpath, f2c.c_path]) + f2c.apply(source=routine, path=tmp_path, role='kernel') ccode_driver = f2c.c_path.read_text().replace(' ', '').replace('\n', '') assert "int*a,intb,int*c" in ccode_kernel @@ -1100,13 +1024,10 @@ def test_transpile_call(here, frontend, use_c_ptr): # check for applied Reference assert "transpile_call_kernel((&a),b,(&arr2[" in ccode_driver - for path in unlink_paths: - path.unlink() - @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('f_type', ['integer', 'real']) -def test_transpile_inline_functions(here, frontend, f_type): +def test_transpile_inline_functions(tmp_path, frontend, f_type): """ Test correct transpilation of functions in C transpilation. """ @@ -1122,7 +1043,7 @@ def test_transpile_inline_functions(here, frontend, f_type): routine = Subroutine.from_source(fcode, frontend=frontend) f2c = FortranCTransformation() - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) f_type_map = {'integer': 'int', 'real': 'double'} c_routine = cgen(routine) @@ -1132,7 +1053,7 @@ def test_transpile_inline_functions(here, frontend, f_type): @pytest.mark.parametrize('frontend', available_frontends()) @pytest.mark.parametrize('f_type', ['integer', 'real']) -def test_transpile_inline_functions_return(here, frontend, f_type): +def test_transpile_inline_functions_return(tmp_path, frontend, f_type): """ Test correct transpilation of functions in C transpilation. """ @@ -1148,7 +1069,7 @@ def test_transpile_inline_functions_return(here, frontend, f_type): routine = Subroutine.from_source(fcode, frontend=frontend) f2c = FortranCTransformation() - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) f_type_map = {'integer': 'int', 'real': 'double'} c_routine = cgen(routine) @@ -1157,13 +1078,13 @@ def test_transpile_inline_functions_return(here, frontend, f_type): @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_multiconditional(here, builder, frontend): +def test_transpile_multiconditional(tmp_path, builder, frontend): """ A simple test to verify multiconditionals/select case statements. """ fcode = """ -subroutine transpile_multi_conditional(in, out) +subroutine multi_cond(in, out) implicit none integer, intent(in) :: in integer, intent(inout) :: out @@ -1177,7 +1098,7 @@ def test_transpile_multiconditional(here, builder, frontend): out = 100 end select -end subroutine transpile_multi_conditional +end subroutine multi_cond """.strip() # for testing purposes @@ -1188,7 +1109,7 @@ def test_transpile_multiconditional(here, builder, frontend): # compile original Fortran version routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/f'{routine.name}_{frontend!s}.f90' + filepath = tmp_path/f'{routine.name}_{frontend!s}.f90' function = jit_compile(routine, filepath=filepath, objname=routine.name) # test Fortran version for i, val in enumerate(test_vals): @@ -1198,30 +1119,24 @@ def test_transpile_multiconditional(here, builder, frontend): # apply F2C trafo f2c = FortranCTransformation() - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path) # check whether 'switch' statement is within C code assert 'switch' in cgen(routine) # compile C version libname = f'fc_{routine.name}_{frontend}' - c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=here, name=libname, builder=builder) - fc_function = c_kernel.transpile_multi_conditional_fc_mod.transpile_multi_conditional_fc + c_kernel = jit_compile_lib([f2c.wrapperpath, f2c.c_path], path=tmp_path, name=libname, builder=builder) + fc_function = c_kernel.multi_cond_fc_mod.multi_cond_fc # test C version for i, val in enumerate(test_vals): in_var = val fc_function(in_var, out_var) assert out_var == expected_results[i] - # cleanup ... - builder.clean() - clean_test(filepath) - f2c.wrapperpath.unlink() - f2c.c_path.unlink() - @pytest.mark.parametrize('frontend', available_frontends()) -def test_transpile_multiconditional_range(here, frontend): +def test_transpile_multiconditional_range(tmp_path, frontend): """ A simple test to verify multiconditionals/select case statements. """ @@ -1250,7 +1165,7 @@ def test_transpile_multiconditional_range(here, frontend): # compile original Fortran version routine = Subroutine.from_source(fcode, frontend=frontend) - filepath = here/f'{routine.name}_{frontend!s}.f90' + filepath = tmp_path/f'{routine.name}_{frontend!s}.f90' function = jit_compile(routine, filepath=filepath, objname=routine.name) # test Fortran version for i, val in enumerate(test_vals): @@ -1265,4 +1180,4 @@ def test_transpile_multiconditional_range(here, frontend): # 'NotImplementedError' is raised f2c = FortranCTransformation() with pytest.raises(NotImplementedError): - f2c.apply(source=routine, path=here) + f2c.apply(source=routine, path=tmp_path)