Beispiel #1
0
    def test_equality(self):
        test_unit = CSymbolID('salt', Path('pepper'))
        with pytest.raises(TypeError):
            _ = test_unit == 'Not a CSymbolID'

        other = CSymbolID('salt', Path('pepper'))
        assert test_unit == other
        assert other == test_unit

        other = CSymbolID('stew', Path('dumplings'))
        assert test_unit != other
        assert other != test_unit
Beispiel #2
0
    def test_equality(self):
        test_unit \
            = CInfo(CSymbolID('argle',
                              Path('bargle/wargle.gargle')),
                    ['beef', 'cheese'])
        with pytest.raises(TypeError):
            _ = test_unit == 'not a CInfo'

        other = CInfo(CSymbolID('argle', Path('bargle/wargle.gargle')),
                      ['beef', 'cheese'])
        assert test_unit == other
        assert other == test_unit

        other = CInfo(CSymbolID('argle', Path('bargle/wargle.gargle')))
        assert test_unit != other
        assert other != test_unit
Beispiel #3
0
 def test_default_constructor(self):
     test_unit \
         = CInfo(CSymbolID('argle',
                           Path('bargle/wargle.gargle')))
     assert test_unit.symbol.name == 'argle'
     assert test_unit.symbol.found_in == Path('bargle/wargle.gargle')
     assert test_unit.depends_on == []
Beispiel #4
0
 def test_prereq_constructor(self):
     test_unit \
         = CInfo(CSymbolID('argle',
                           Path('bargle/wargle.gargle')),
                 ['cheese'])
     assert test_unit.symbol.name == 'argle'
     assert test_unit.symbol.found_in == Path('bargle/wargle.gargle')
     assert test_unit.depends_on == ['cheese']
Beispiel #5
0
    def test_add_prerequisite(self):
        test_unit \
            = CInfo(CSymbolID('argle',
                              Path('bargle/wargle.gargle')))
        assert test_unit.depends_on == []

        test_unit.add_prerequisite('cheese')
        assert test_unit.depends_on == ['cheese']
Beispiel #6
0
    def test_analyser_symbols(self, caplog, tmp_path):
        """
        Tests that symbols are identified, and calls are
        picked up provided they come from internal headers.
        """
        caplog.set_level(logging.DEBUG)

        test_file: Path = tmp_path / 'test.c'
        test_file.write_text(
            dedent('''
                  #pragma FAB UsrIncludeStart
                  void foo();
                  #pragma FAB UsrIncludeEnd

                  #pragma FAB UsrIncludeStart
                  void bar();
                  #pragma FAB UsrIncludeEnd

                  #pragma FAB SysIncludeStart
                  void baz();
                  #pragma FAB SysIncludeEnd

                  void foo() {
                      bar();
                      baz();
                  }
                   '''))

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

        # Confirm database is updated
        working_state = CWorkingState(database)
        assert list(working_state) \
            == [CInfo(CSymbolID('foo', test_file),
                      ['bar'])]

        # Confirm returned Artifact is updated
        assert len(output_artifacts) == 1
        assert output_artifacts[0].defines == ['foo']
        assert output_artifacts[0].depends_on == ['bar']
        assert output_artifacts[0].location == test_file
        assert output_artifacts[0].filetype is CSource
        assert output_artifacts[0].state is Analysed
Beispiel #7
0
 def test_hash(self):
     test_unit = CSymbolID('grumper', Path('bumper'))
     similar = CSymbolID('grumper', Path('bumper'))
     different = CSymbolID('bumper', Path('grumper'))
     assert hash(test_unit) == hash(similar)
     assert hash(test_unit) != hash(different)
Beispiel #8
0
 def test_constructor(self):
     test_unit = CSymbolID('beef', Path('cheese'))
     assert test_unit.name == 'beef'
     assert test_unit.found_in == Path('cheese')
Beispiel #9
0
    def test_get_symbol(self, tmp_path: Path):
        database = SqliteStateDatabase(tmp_path)
        test_unit = CWorkingState(database)

        # Test on an empty list
        #
        with pytest.raises(WorkingStateException):
            _ = test_unit.get_symbol('tigger')

        # Test we can retrieve an item from a single element list
        test_unit.add_c_symbol(CSymbolID('tigger', Path('tigger.c')))
        assert test_unit.get_symbol('tigger') \
            == [CInfo(CSymbolID('tigger', Path('tigger.c')))]
        with pytest.raises(WorkingStateException):
            _ = test_unit.get_symbol('eeor')

        # Test retrieval from a multi-element list and with prerequisites.
        #
        test_unit.add_c_symbol(CSymbolID('eeor', Path('eeor.c')))
        test_unit.add_c_dependency(CSymbolID('eeor', Path('eeor.c')), 'pooh')
        test_unit.add_c_dependency(CSymbolID('eeor', Path('eeor.c')), 'piglet')
        assert test_unit.get_symbol('tigger') \
            == [CInfo(CSymbolID('tigger', Path('tigger.c')))]
        assert test_unit.get_symbol('eeor') \
            == [CInfo(CSymbolID('eeor', Path('eeor.c')),
                      ['piglet', 'pooh'])]
        with pytest.raises(WorkingStateException):
            _ = test_unit.get_symbol('pooh')

        # Test a multiply defined program unit.
        #
        test_unit.add_c_symbol(CSymbolID('tigger', Path('hundred.c')))
        assert test_unit.get_symbol('tigger') \
            == [CInfo(CSymbolID('tigger', Path('hundred.c'))),
                CInfo(CSymbolID('tigger', Path('tigger.c')))]
        assert test_unit.get_symbol('eeor') \
            == [CInfo(CSymbolID('eeor', Path('eeor.c')),
                      ['piglet', 'pooh'])]
        with pytest.raises(WorkingStateException):
            _ = test_unit.get_symbol('pooh')
Beispiel #10
0
    def test_add_remove_sequence(self, tmp_path: Path):
        database = SqliteStateDatabase(tmp_path)
        test_unit = CWorkingState(database)
        assert list(iter(test_unit)) == []

        # Add a file containing a program unit and an unsatisfied dependency.
        #
        test_unit.add_c_symbol(CSymbolID('foo', Path('foo.c')))
        test_unit.add_c_dependency(CSymbolID('foo', Path('foo.c')), 'bar')
        assert list(iter(test_unit)) \
            == [CInfo(CSymbolID('foo', Path('foo.c')),
                      ['bar'])]
        assert list(test_unit.depends_on(CSymbolID('foo',
                                                   Path('foo.c')))) \
            == [CSymbolUnresolvedID('bar')]

        # Add a second file containing a second program unit.
        #
        # This satisfies the previously dangling dependency and adds a new
        # one.
        #
        test_unit.add_c_symbol(CSymbolID('bar', Path('bar.c')))
        test_unit.add_c_dependency(CSymbolID('bar', Path('bar.c')), 'baz')
        assert list(iter(test_unit)) \
            == [CInfo(CSymbolID('bar', Path('bar.c')),
                      ['baz']),
                CInfo(CSymbolID('foo', Path('foo.c')),
                      ['bar'])]
        assert list(test_unit.depends_on(CSymbolID('foo',
                                                   Path('foo.c')))) \
            == [CSymbolID('bar', Path('bar.c'))]
        assert list(test_unit.depends_on(CSymbolID('bar',
                                                   Path('bar.c')))) \
            == [CSymbolUnresolvedID('baz')]

        # Add a third file also containing a third program unit and another
        # copy of the first.
        #
        # The new unit depends on two other units.
        #
        test_unit.add_c_symbol(CSymbolID('baz', Path('baz.c')))
        test_unit.add_c_symbol(CSymbolID('foo', Path('baz.c')))
        test_unit.add_c_dependency(CSymbolID('baz', Path('baz.c')), 'qux')
        test_unit.add_c_dependency(CSymbolID('baz', Path('baz.c')), 'cheese')
        assert list(iter(test_unit)) \
            == [CInfo(CSymbolID('bar', Path('bar.c')),
                      ['baz']),
                CInfo(CSymbolID('baz', Path('baz.c')),
                      ['cheese', 'qux']),
                CInfo(CSymbolID('foo', Path('baz.c'))),
                CInfo(CSymbolID('foo', Path('foo.c')),
                      ['bar'])]
        assert list(test_unit.depends_on(CSymbolID('foo',
                                                   Path('foo.c')))) \
            == [CSymbolID('bar', Path('bar.c'))]
        assert list(test_unit.depends_on(CSymbolID('foo',
                                                   Path('baz.c')))) \
            == []
        assert list(test_unit.depends_on(CSymbolID('bar',
                                                   Path('bar.c')))) \
            == [CSymbolID('baz', Path('baz.c'))]
        assert list(test_unit.depends_on(CSymbolID('baz',
                                                   Path('baz.c')))) \
            == [CSymbolUnresolvedID('qux'),
                CSymbolUnresolvedID('cheese')]

        # Remove a previously added file
        #
        test_unit.remove_c_file(Path('baz.c'))
        assert list(iter(test_unit)) \
            == [CInfo(CSymbolID('bar', Path('bar.c')),
                      ['baz']),
                CInfo(CSymbolID('foo', Path('foo.c')),
                      ['bar'])]
        assert list(test_unit.depends_on(CSymbolID('foo',
                                                   Path('foo.c')))) \
            == [CSymbolID('bar', Path('bar.c'))]
        assert list(test_unit.depends_on(CSymbolID('bar',
                                                   Path('bar.c')))) \
            == [CSymbolUnresolvedID('baz')]
Beispiel #11
0
    def run(self, artifacts: List[Artifact]) -> List[Artifact]:
        logger = logging.getLogger(__name__)

        if len(artifacts) == 1:
            artifact = artifacts[0]
        else:
            msg = ('Fortran Analyser expects only one Artifact, '
                   f'but was given {len(artifacts)}')
            raise TaskException(msg)

        reader = FileTextReader(artifact.location)

        new_artifact = Artifact(artifact.location, artifact.filetype, Analysed)

        state = FortranWorkingState(self.database)
        state.remove_fortran_file(reader.filename)
        logger.debug('Analysing: %s', reader.filename)

        # If this file defines any C symbol bindings it may also
        # end up with an entry in the C part of the database
        cstate = CWorkingState(self.database)
        cstate.remove_c_file(reader.filename)

        normalised_source = FortranNormaliser(reader)
        scope: List[Tuple[str, str]] = []
        for line in normalised_source.line_by_line():
            logger.debug(scope)
            logger.debug('Considering: %s', line)

            if len(scope) == 0:
                unit_match: Optional[Match] \
                    = self._program_unit_pattern.match(line)
                if unit_match is not None:
                    unit_type: str = unit_match.group(1).lower()
                    unit_name: str = unit_match.group(2).lower()
                    logger.debug('Found %s called "%s"', unit_type, unit_name)
                    unit_id = FortranUnitID(unit_name, reader.filename)
                    state.add_fortran_program_unit(unit_id)
                    new_artifact.add_definition(unit_name)
                    scope.append((unit_type, unit_name))
                    continue
            use_match: Optional[Match] \
                = self._use_pattern.match(line)
            if use_match is not None:
                use_name: str = use_match.group(3).lower()
                if use_name in self._intrinsic_modules:
                    logger.debug('Ignoring intrinsic module "%s"', use_name)
                else:
                    if len(scope) == 0:
                        use_message \
                            = '"use" statement found outside program unit'
                        raise TaskException(use_message)
                    logger.debug('Found usage of "%s"', use_name)
                    unit_id = FortranUnitID(scope[0][1], reader.filename)
                    state.add_fortran_dependency(unit_id, use_name)
                    new_artifact.add_dependency(use_name)
                continue

            block_match: Optional[Match] = self._scoping_pattern.match(line)
            if block_match is not None:
                # Beware we want the value of a different group to the one we
                # check the presence of.
                #
                block_name: str = block_match.group(1) \
                                  and block_match.group(2).lower()
                block_nature: str = block_match.group(3).lower()
                logger.debug('Found %s called "%s"', block_nature, block_name)
                scope.append((block_nature, block_name))
                continue

            proc_match: Optional[Match] \
                = self._procedure_pattern.match(line)
            if proc_match is not None:
                proc_nature = proc_match.group(1).lower()
                proc_name = proc_match.group(2).lower()
                logger.debug('Found %s called "%s"', proc_nature, proc_name)
                scope.append((proc_nature, proc_name))

                # Check for the procedure being symbol-bound to C
                cbind_match: Optional[Match] \
                    = self._cbind_pattern.match(line)
                if cbind_match is not None:
                    cbind_name = cbind_match.group(2)
                    # The name keyword on the bind statement is optional.
                    # If it doesn't exist, the procedure name is used
                    if cbind_name is None:
                        cbind_name = proc_name
                    cbind_name = cbind_name.lower().strip("'\"")
                    logger.debug('Bound to C symbol "%s"', cbind_name)
                    # A bind within an interface block means this is
                    # exposure of a C-defined function to Fortran,
                    # otherwise it is going the other way (allowing C
                    # code to call the Fortran procedure)
                    if any([stype == "interface" for stype, _ in scope]):
                        # TODO: This is sort of hijacking the mechanism used
                        # for Fortran module dependencies, only using the
                        # symbol name. Longer term we probably need a more
                        # elegant solution
                        logger.debug('In an interface block; so a dependency')
                        unit_id = FortranUnitID(scope[0][1], reader.filename)
                        state.add_fortran_dependency(unit_id, cbind_name)
                        new_artifact.add_dependency(cbind_name)
                    else:
                        # Add to the C database
                        logger.debug('Not an interface block; so a definition')
                        symbol_id = CSymbolID(cbind_name, reader.filename)
                        cstate.add_c_symbol(symbol_id)
                        new_artifact.add_definition(cbind_name)
                continue

            cbind_match = self._cbind_pattern.match(line)
            if cbind_match is not None:
                # This should be a line binding from C to a variable definition
                # (procedure binds are dealt with above)
                cbind_name = cbind_match.group(2)

                # The name keyword on the bind statement is optional.
                # If it doesn't exist, the Fortran variable name is used
                if cbind_name is None:
                    var_search = re.search(r'.*::\s*(\w+)', line)
                    if var_search:
                        cbind_name = var_search.group(1)
                    else:
                        cbind_message \
                            = 'failed to find variable name ' \
                              'on C bound variable'
                        raise TaskException(cbind_message)

                cbind_name = cbind_name.lower().strip("'\"")
                logger.debug('Found C bound variable called "%s"', cbind_name)

                # Add to the C database
                symbol_id = CSymbolID(cbind_name, reader.filename)
                cstate.add_c_symbol(symbol_id)
                new_artifact.add_definition(cbind_name)

            iface_match: Optional[Match] = self._interface_pattern.match(line)
            if iface_match is not None:
                iface_name = iface_match.group(1) \
                             and iface_match.group(1).lower()
                logger.debug('Found interface called "%s"', iface_name)
                scope.append(('interface', iface_name))
                continue

            type_match: Optional[Match] = self._type_pattern.match(line)
            if type_match is not None:
                type_name = type_match.group(3).lower()
                logger.debug('Found type called "%s"', type_name)
                scope.append(('type', type_name))
                continue

            end_match: Optional[Match] = self._end_block_pattern.match(line)
            if end_match is not None:
                end_nature: str = end_match.group(1) \
                    and end_match.group(1).lower()
                end_name: str = end_match.group(2) \
                    and end_match.group(2).lower()
                logger.debug('Found end of %s called %s', end_nature, end_name)
                exp: Tuple[str, str] = scope.pop()

                if end_nature is not None:
                    if end_nature != exp[0]:
                        end_message = 'Expected end of {exp} "{name}" ' \
                                      'but found {found}'
                        end_values = {
                            'exp': exp[0],
                            'name': exp[1],
                            'found': end_nature
                        }
                        raise TaskException(end_message.format(**end_values))
                if end_name is not None:
                    if end_name != exp[1]:
                        end_message = 'Expected end of {exp} "{name}" ' \
                                      'but found end of {found}'
                        end_values = {
                            'exp': exp[0],
                            'name': exp[1],
                            'found': end_name
                        }
                        raise TaskException(end_message.format(**end_values))

        return [new_artifact]
Beispiel #12
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