Ejemplo n.º 1
0
    def test_harvested_data(self, caplog, tmp_path):
        """
        Checks that the analyser deals with rescanning a file.
        """
        caplog.set_level(logging.DEBUG)

        first_file: Path = tmp_path / 'other.F90'
        first_file.write_text(
            dedent('''
                   program betty
                     use barney_mod, only :: dino
                     implicit none
                   end program betty

                   module barney_mod
                   end module barney_mod
                   '''))
        second_file: Path = tmp_path / 'test.f90'
        second_file.write_text(
            dedent('''
                   module barney_mod
                   end module barney_mod
                   '''))

        database: SqliteStateDatabase = SqliteStateDatabase(tmp_path)
        test_unit = FortranAnalyser(tmp_path)
        first_artifact = Artifact(first_file, FortranSource, Raw)
        second_artifact = Artifact(second_file, FortranSource, Raw)
        # Not going to test returned objects this time
        _ = test_unit.run([first_artifact])
        _ = test_unit.run([second_artifact])

        # Confirm the database has been updated
        fdb = FortranWorkingState(database)
        assert list(iter(fdb)) \
            == [FortranInfo(FortranUnitID('barney_mod', first_file)),
                FortranInfo(FortranUnitID('barney_mod', second_file)),
                FortranInfo(FortranUnitID('betty', first_file),
                            ['barney_mod'])]
        assert list(fdb.depends_on(FortranUnitID('betty', first_file))) \
            == [FortranUnitID('barney_mod', tmp_path / 'other.F90'),
                FortranUnitID('barney_mod', tmp_path / 'test.f90')]

        # Repeat the scan of second_file, there should be no change.
        #
        _ = test_unit.run([second_artifact])

        fdb = FortranWorkingState(database)
        assert list(iter(fdb)) \
            == [FortranInfo(FortranUnitID('barney_mod', first_file)),
                FortranInfo(FortranUnitID('barney_mod', second_file)),
                FortranInfo(FortranUnitID('betty', first_file),
                            ['barney_mod'])]
        assert list(fdb.depends_on(FortranUnitID('betty', first_file))) \
            == [FortranUnitID('barney_mod', tmp_path / 'other.F90'),
                FortranUnitID('barney_mod', tmp_path / 'test.f90')]
Ejemplo n.º 2
0
 def test_mismatched_end_name(self, tmp_path: Path):
     """
     Ensure that the analyser handles mismatched block end names correctly.
     """
     test_file: Path = tmp_path / 'test.f90'
     test_file.write_text(
         dedent('''
                module wibble_mod
                type :: thing_type
                end type blasted_type
                end module wibble_mod
                '''))
     test_unit = FortranAnalyser(tmp_path)
     test_artifact = Artifact(test_file, FortranSource, Raw)
     with pytest.raises(TaskException):
         test_unit.run([test_artifact])
Ejemplo n.º 3
0
    def test_empty_file(self):
        mock_tree = Mock(content=[None])
        with mock.patch('fab.tasks.fortran.FortranAnalyser._parse_file',
                        return_value=mock_tree):
            result = FortranAnalyser().run(
                HashedFile(fpath=None, file_hash=None))

        assert type(result) is EmptySourceFile
Ejemplo n.º 4
0
    def __init__(self, workspace: Path, target: str, exec_name: str,
                 fpp_flags: str, fc_flags: str, ld_flags: str, n_procs: int):

        self._workspace = workspace
        if not workspace.exists():
            workspace.mkdir(parents=True)

        self._state = SqliteStateDatabase(workspace)

        # Path maps tell the engine what filetype and starting state
        # the Artifacts representing any files encountered by the
        # initial descent should have
        path_maps = [
            PathMap(r'.*\.f90', FortranSource, Raw),
            PathMap(r'.*\.F90', FortranSource, Seen),
            PathMap(r'.*\.c', CSource, Seen),
            PathMap(r'.*\.h', CHeader, Seen),
        ]

        # Initialise the required Tasks, providing them with any static
        # properties such as flags to use, workspace location etc
        # TODO: Eventually the tasks may instead access many of these
        # properties via the configuration (at Task runtime, to allow for
        # file-specific overrides?)
        fortran_preprocessor = FortranPreProcessor(
            'cpp', ['-traditional-cpp', '-P'] + fpp_flags.split(), workspace)
        fortran_analyser = FortranAnalyser(workspace)
        fortran_compiler = FortranCompiler(
            'gfortran', ['-c', '-J', str(workspace)] + fc_flags.split(),
            workspace)

        header_analyser = HeaderAnalyser(workspace)
        c_pragma_injector = CPragmaInjector(workspace)
        c_preprocessor = CPreProcessor('cpp', [], workspace)
        c_analyser = CAnalyser(workspace)
        c_compiler = CCompiler('gcc', ['-c'], workspace)

        linker = Linker('gcc', ['-lc', '-lgfortran'] + ld_flags.split(),
                        workspace, exec_name)

        # The Task map tells the engine what Task it should be using
        # to deal with Artifacts depending on their type and state
        task_map = {
            (FortranSource, Seen): fortran_preprocessor,
            (FortranSource, Raw): fortran_analyser,
            (FortranSource, Analysed): fortran_compiler,
            (CSource, Seen): header_analyser,
            (CHeader, Seen): header_analyser,
            (CSource, HeadersAnalysed): c_pragma_injector,
            (CHeader, HeadersAnalysed): c_pragma_injector,
            (CSource, Modified): c_preprocessor,
            (CSource, Raw): c_analyser,
            (CSource, Analysed): c_compiler,
            (BinaryObject, Compiled): linker,
        }

        engine = Engine(workspace, target, path_maps, task_map)
        self._queue = QueueManager(n_procs - 1, engine)
Ejemplo n.º 5
0
    def test_naked_use(self, tmp_path):
        """
        Ensures that an exception is raised if a "use" is found outside a
        program unit.
        """
        test_file: Path = tmp_path / 'test.f90'
        test_file.write_text(
            dedent('''
                   use beef_mod

                   module test_mod
                   end module test_mod
                   '''))

        test_unit = FortranAnalyser(tmp_path)
        test_artifact = Artifact(test_file, FortranSource, Raw)
        with pytest.raises(TaskException):
            test_unit.run([test_artifact])
Ejemplo n.º 6
0
    def __init__(
            self,
            source: ArtefactsGetter = None,
            root_symbol: Optional[Union[
                str, List[str]]] = None,  # todo: iterable is more correct
            std="f2008",
            special_measure_analysis_results=None,
            unreferenced_deps=None,
            ignore_mod_deps=None,
            name='analyser'):
        """
        If no artefact getter is specified in *source*, a default is used which provides input files
        from multiple artefact collections, including the default C and Fortran preprocessor outputs
        and any source files with a 'little' *.f90* extension.

        A build tree is produced for every root symbol specified in *root_symbol*, which can be a string or list of.
        This is how we create executable files. If no root symbol is specified, a single tree of the entire source
        is produced (with a root symbol of `None`). This is how we create shared and static libraries.

        :param source:
            An :class:`~fab.util.ArtefactsGetter` to get the source files.
        :param root_symbol:
            When building an executable, provide the Fortran Program name(s), or 'main' for C.
            If None, build tree extraction will not be performed and the entire source will be used
            as the build tree - for building a shared or static library.
        :param std:
            The fortran standard, passed through to fparser2. Defaults to 'f2008'.
        :param special_measure_analysis_results:
            When fparser2 cannot parse a "valid" Fortran file,
            we can manually provide the expected analysis results with this argument.
            Only the symbol definitions and dependencies need be provided.
        :param unreferenced_deps:
            A list of symbols which are needed for the build, but which cannot be automatically
            determined. For example, functions that are called without a module use statement. Assuming the files
            containing these symbols are present and will be analysed, those files and all their dependencies
            will be added to the build tree(s).
        :param ignore_mod_deps:
            Third party Fortran module names to be ignored.
        :param name:
            Human friendly name for logger output, with sensible default.

        """
        super().__init__(name)
        self.source_getter = source or DEFAULT_SOURCE_GETTER
        self.root_symbols: Optional[List[str]] = [root_symbol] if isinstance(
            root_symbol, str) else root_symbol
        self.special_measure_analysis_results: List[
            AnalysedFile] = special_measure_analysis_results or []
        self.unreferenced_deps: List[str] = unreferenced_deps or []

        # todo: these seem more like functions
        self.fortran_analyser = FortranAnalyser(
            std=std, ignore_mod_deps=ignore_mod_deps)
        self.c_analyser = CAnalyser()
Ejemplo n.º 7
0
    def test_program_file(self, module_fpath, module_expected):
        # same as test_real_file() but replacing MODULE with PROGRAM
        with NamedTemporaryFile(mode='w+t', suffix='.f90') as tmp_file:
            tmp_file.write(module_fpath.open().read().replace(
                "MODULE", "PROGRAM"))
            tmp_file.flush()
            result = FortranAnalyser().run(
                HashedFile(fpath=Path(tmp_file.name), file_hash=None))

            module_expected.fpath = Path(tmp_file.name)
            module_expected.module_defs = set()
            module_expected.symbol_defs.update(
                {'internal_sub', 'internal_func'})

            assert result == module_expected
Ejemplo n.º 8
0
    def test_analyser_scope(self, caplog, tmp_path):
        """
        Tests that the analyser is able to track scope correctly.
        """
        caplog.set_level(logging.DEBUG)

        test_file: Path = tmp_path / 'test.f90'
        test_file.write_text(
            dedent('''
                   program fred

                     implicit none

                     if (something) then
                       named: do i=1, 10
                       end do named
                     endif

                   contains

                     subroutine yabadabadoo()
                     end

                   end program

                   module barney

                     implicit none

                     type betty_type
                       integer :: property
                     contains
                       procedure inspect
                     end type

                     interface betty_type
                       procedure betty_constructor
                     end

                   contains

                     function inspect(this)
                       class(betty_type), intent(in) :: this
                       integer :: inspect
                       inspect = this%property
                     end function inspect

                   end module
                   '''))

        database: SqliteStateDatabase = SqliteStateDatabase(tmp_path)
        test_unit = FortranAnalyser(tmp_path)
        test_artifact = Artifact(test_file, FortranSource, Raw)
        output_artifacts = test_unit.run([test_artifact])

        # Confirm database is updated
        working_state = FortranWorkingState(database)
        assert list(working_state) \
            == [FortranInfo(FortranUnitID('barney', tmp_path/'test.f90'), []),
                FortranInfo(FortranUnitID('fred', tmp_path/'test.f90'), [])]

        # Confirm returned Artifact is updated
        assert len(output_artifacts) == 1
        assert output_artifacts[0].defines == ['fred', 'barney']
        assert output_artifacts[0].depends_on == []
        assert output_artifacts[0].location == test_file
        assert output_artifacts[0].filetype is FortranSource
        assert output_artifacts[0].state is Analysed
Ejemplo n.º 9
0
    def test_analyser_program_units(self, caplog, tmp_path):
        """
        Tests that program units and the "uses" they contain are correctly
        identified.
        """
        caplog.set_level(logging.DEBUG)

        test_file: Path = tmp_path / 'test.f90'
        test_file.write_text(
            dedent('''
                   program foo
                     use iso_fortran_env, only : output
                     use, intrinsic :: ios_c_binding
                     use beef_mod
                     implicit none
                   end program foo

                   module bar
                     use iso_fortran_env, only : output
                     use, intrinsic :: ios_c_binding
                     use cheese_mod, only : bits_n_bobs
                     implicit none
                   end module bar

                   function baz(first, second)
                     use iso_fortran_env, only : output
                     use, intrinsic :: ios_c_binding
                     use teapot_mod
                     implicit none
                   end function baz

                   subroutine qux()
                     use iso_fortran_env, only : output
                     use, intrinsic :: ios_c_binding
                     use wibble_mod
                     use wubble_mod, only: stuff_n_nonsense
                     implicit none
                   end subroutine qux
                   '''))

        database: SqliteStateDatabase = SqliteStateDatabase(tmp_path)
        test_unit = FortranAnalyser(tmp_path)
        test_artifact = Artifact(test_file, FortranSource, Raw)
        output_artifacts = test_unit.run([test_artifact])

        # Confirm database is updated
        working_state = FortranWorkingState(database)
        assert list(working_state) \
            == [FortranInfo(FortranUnitID('bar', tmp_path/'test.f90'),
                            ['cheese_mod']),
                FortranInfo(FortranUnitID('baz', tmp_path/'test.f90'),
                            ['teapot_mod']),
                FortranInfo(FortranUnitID('foo', tmp_path/'test.f90'),
                            ['beef_mod']),
                FortranInfo(FortranUnitID('qux', tmp_path/'test.f90'),
                            ['wibble_mod', 'wubble_mod'])]

        # Confirm returned Artifact is updated
        assert len(output_artifacts) == 1
        assert output_artifacts[0].defines == ['foo', 'bar', 'baz', 'qux']
        assert output_artifacts[0].depends_on == [
            'beef_mod', 'cheese_mod', 'teapot_mod', 'wibble_mod', 'wubble_mod'
        ]
        assert output_artifacts[0].location == test_file
        assert output_artifacts[0].filetype is FortranSource
        assert output_artifacts[0].state is Analysed
Ejemplo n.º 10
0
 def test_module_file(self, module_fpath, module_expected):
     result = FortranAnalyser().run(
         HashedFile(fpath=module_fpath, file_hash=None))
     assert result == module_expected
Ejemplo n.º 11
0
    def test_analyser_cbinding(self, caplog, tmp_path):
        """
        Tests that C bind procedures are correctly detected.
        """
        caplog.set_level(logging.DEBUG)

        test_file: Path = tmp_path / 'test.f90'
        test_file.write_text(
            dedent('''
                module foo

                   integer, bind(c), target, save :: quuz
                   real, bind(c, name="corge"), target, save :: varname

                   function bar() bind(c, name="bar_c")
                     implicit none
                   end function bar

                   subroutine baz(), bind(c)
                     implicit none
                   end subroutine baz

                   interface
                     function qux() bind(c, name="qux_c")
                       implicit none
                     end function qux

                     subroutine quux() bind(c)
                       implicit none
                     end subroutine quux
                   end interface
                end module foo

                   '''))

        database: SqliteStateDatabase = SqliteStateDatabase(tmp_path)
        test_unit = FortranAnalyser(tmp_path)
        test_artifact = Artifact(test_file, FortranSource, Raw)
        output_artifacts = test_unit.run([test_artifact])

        # Confirm database is updated
        # Fortran part
        working_state = FortranWorkingState(database)
        assert list(working_state) \
            == [FortranInfo(FortranUnitID('foo', tmp_path/'test.f90'),
                            ['quux', 'qux_c'])]

        # C part
        cworking_state = CWorkingState(database)
        assert list(cworking_state) \
            == [CInfo(CSymbolID('bar_c', tmp_path/'test.f90'), []),
                CInfo(CSymbolID('baz', tmp_path/'test.f90'), []),
                CInfo(CSymbolID('corge', tmp_path/'test.f90'), []),
                CInfo(CSymbolID('quuz', tmp_path/'test.f90'), [])]

        # Confirm returned Artifact is updated
        assert len(output_artifacts) == 1
        assert output_artifacts[0].defines \
            == ['foo', 'quuz', 'corge', 'bar_c', 'baz']
        assert output_artifacts[0].depends_on == ['qux_c', 'quux']
        assert output_artifacts[0].location == test_file
        assert output_artifacts[0].filetype is FortranSource
        assert output_artifacts[0].state is Analysed