示例#1
0
 def _build_import_paths(self, tuples: Tuple[Tuple[str, str]]) -> List[ImportPath]:
     import_paths = []
     for importer, imported in tuples:
         import_paths.append(
             ImportPath(importer=Module(importer), imported=Module(imported))
         )
     return import_paths
示例#2
0
    def test_find_path_direct(self):
        graph = graph_module.DependencyGraph(self.PACKAGE)

        path = graph.find_path(upstream=Module('foo.one'),
                               downstream=Module('foo.two'))

        assert path == (Module('foo.two'), Module('foo.one'))
示例#3
0
    def test_find_path_nonexistent(self):
        graph = graph_module.DependencyGraph(self.PACKAGE)

        path = graph.find_path(upstream=Module('foo.four'),
                               downstream=Module('foo.one'))

        assert path is None
示例#4
0
 def __init__(self):
     self.modules = [
         Module('foo.one'),
         Module('foo.two'),
         Module('foo.three'),
     ]
     self.module_count = len(self.modules)
     self.dependency_count = 4
示例#5
0
    def test_get_descendants_nested(self):

        graph = graph_module.DependencyGraph(self.PACKAGE)

        assert set(graph.get_descendants(Module('foo.one'))) == {
            Module('foo.one.alpha'),
            Module('foo.one.beta'),
            Module('foo.one.beta.green'),
        }
示例#6
0
 def get_modules_directly_imported_by(self, importer):
     return {
         self.modules[0]: [
             Module('foo.four.one'),
             Module('foo.four.two'),
         ],
         self.modules[1]: [],
         self.modules[2]: [
             Module('foo.five'),
         ]
     }[importer]
示例#7
0
    def test_indirect_ignore_path_is_ignored(self):
        ignore_paths = (ImportPath(
            importer=Module('foo.three'),
            imported=Module('foo.two'),
        ), )
        graph = graph_module.DependencyGraph(self.PACKAGE)

        path = graph.find_path(upstream=Module('foo.one'),
                               downstream=Module('foo.four'),
                               ignore_paths=ignore_paths)

        assert path is None
示例#8
0
    def test_success(self):
        package = Module('analyzerpackage')
        modules = self._build_modules(
            package_name=package.name,
            tuples=(
                ('analyzerpackage', '__init__.py'),
                ('analyzerpackage.utils', 'utils.py'),
                ('analyzerpackage.one', 'one/__init__.py'),
                ('analyzerpackage.one.alpha', 'one/alpha.py'),
                ('analyzerpackage.one.beta', 'one/beta.py'),
                ('analyzerpackage.one.gamma', 'one/gamma.py'),
                ('analyzerpackage.two', 'two/__init__.py'),
                ('analyzerpackage.two.alpha', 'two/alpha.py'),
                ('analyzerpackage.two.beta', 'two/beta.py'),
                ('analyzerpackage.two.gamma', 'two/gamma.py'),
            ),
        )

        analyzer = DependencyAnalyzer(modules, package)
        import_paths = analyzer.determine_import_paths()

        # N.B. We expect only the leaf imports, it's just noise to have parent packages
        # in the graph too. However, if there is a separate parent import, that should be included.
        expected_import_paths = self._build_import_paths(tuples=(
            ('analyzerpackage.utils', 'analyzerpackage.one'),
            ('analyzerpackage.utils', 'analyzerpackage.two.alpha'),
            ('analyzerpackage.one.beta', 'analyzerpackage.one.alpha'),
            ('analyzerpackage.one.gamma', 'analyzerpackage.one.beta'),
            ('analyzerpackage.two.alpha', 'analyzerpackage.one.alpha'),
            ('analyzerpackage.two.beta', 'analyzerpackage.one.alpha'),
            ('analyzerpackage.two.gamma', 'analyzerpackage.two.beta'),
            ('analyzerpackage.two.gamma', 'analyzerpackage.utils'),
        ))

        assert set(import_paths) == set(expected_import_paths)
示例#9
0
    def test_import_from_within_init_file(self):
        # Relative imports from within __init__.py files should be interpreted as at the level
        # of the sibling modules, not the containing package.
        package = Module('initfileimports')
        modules = self._build_modules(
            package_name=package.name,
            tuples=(
                ('initfileimports', '__init__.py'),
                # This init file has ``from . import alpha``.
                ('initfileimports.one', 'one/__init__.py'),
                ('initfileimports.one.alpha', 'one/alpha.py'),  # The correct imported module.
                ('initfileimports.two', 'two/__init__.py'),
                ('initfileimports.alpha', 'alpha.py'),  # It shouldn't import this one.
            ),
        )

        analyzer = DependencyAnalyzer(modules, package)
        import_paths = analyzer.determine_import_paths()

        expected_import_paths = self._build_import_paths(
            tuples=(
                ('initfileimports.one', 'initfileimports.one.alpha'),
            )
        )

        assert set(import_paths) == set(expected_import_paths)
    def test_happy_path(self):
        self._initialize_test()

        contracts = get_contracts(self.filename_and_path,
                                  package_name='singlecontractfile')

        assert len(contracts) == 2
        expected_contracts = [
            {
                'name': 'Contract A',
                'packages':
                ['singlecontractfile.foo', 'singlecontractfile.bar'],
                'layers': ['one', 'two'],
            },
            {
                'name':
                'Contract B',
                'packages': ['singlecontractfile'],
                'layers': ['one', 'two', 'three'],
                'whitelisted_paths': [
                    ('baz.utils', 'baz.three.green'),
                    ('baz.three.blue', 'baz.two'),
                ],
            },
        ]
        sorted_contracts = sorted(contracts, key=lambda i: i.name)
        for contract_index, contract in enumerate(sorted_contracts):
            expected_data = expected_contracts[contract_index]
            assert contract.name == expected_data['name']

            for package_index, package in enumerate(contract.containers):
                expected_package_name = expected_data['packages'][
                    package_index]
                assert package == Module(expected_package_name)

            for layer_index, layer in enumerate(contract.layers):
                expected_layer_data = expected_data['layers'][layer_index]
                assert isinstance(layer, Layer)
                assert layer.name == expected_layer_data

            for whitelisted_index, whitelisted_path in enumerate(
                    contract.whitelisted_paths):
                expected_importer, expected_imported = expected_data[
                    'whitelisted_paths'][whitelisted_index]
                assert isinstance(whitelisted_path, ImportPath)
                assert whitelisted_path.importer == Module(expected_importer)
                assert whitelisted_path.imported == Module(expected_imported)
示例#11
0
    def test_all_different_import_types(self):
        """
        Test a single file with lots of different import types. Note that all of the other
        modules are empty files.
        """
        package = Module('differentimporttypes')
        modules = self._build_modules(
            package_name=package.name,
            tuples=(
                # The first module is the one with the code we'll analyze.
                ('differentimporttypes.one.importer', 'one/importer.py'),
                ('differentimporttypes', '__init__.py'),
                ('differentimporttypes.one', 'one/__init__.py'),
                ('differentimporttypes.one.alpha', 'one/alpha.py'),
                ('differentimporttypes.one.beta', 'one/beta.py'),
                ('differentimporttypes.one.gamma', 'one/gamma/__init__.py'),
                ('differentimporttypes.one.gamma.foo', 'one/gamma/foo.py'),
                ('differentimporttypes.one.delta', 'one/delta.py'),
                ('differentimporttypes.one.epsilon', 'one/epsilon.py'),
                ('differentimporttypes.two', 'two/__init__.py'),
                ('differentimporttypes.two.alpha', 'two/alpha.py'),
                ('differentimporttypes.three', 'three.py'),
                ('differentimporttypes.four', 'four/__init__.py'),
                ('differentimporttypes.four.alpha', 'four/alpha.py'),
                # Some other modules, not imported.
                ('differentimporttypes.five', 'five/__init__.py'),
                ('differentimporttypes.five.alpha', 'five/alpha.py'),
            ),
        )

        analyzer = DependencyAnalyzer(modules, package)
        import_paths = analyzer.determine_import_paths()

        expected_import_paths = self._build_import_paths(tuples=(
            ('differentimporttypes.one.importer', 'differentimporttypes.one'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.one.alpha'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.one.beta'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.one.gamma'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.one.gamma.foo'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.two.alpha'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.one.delta'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.one.epsilon'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.three'),
            ('differentimporttypes.one.importer',
             'differentimporttypes.four.alpha'),
        ))

        assert set(import_paths) == set(expected_import_paths)
示例#12
0
    def test_hash(self):
        a = ImportPath(importer=Module('foo'), imported=Module('bar'))
        b = ImportPath(importer=Module('foo'), imported=Module('bar'))
        c = ImportPath(importer=Module('bar'), imported=Module('foo'))

        assert hash(a) == hash(b)
        assert hash(a) != hash(c)
示例#13
0
 def __init__(self, modules, package):
     self.import_paths = [
         ImportPath(importer=Module('foo.two'), imported=Module('foo.one')),
         ImportPath(importer=Module('foo.three'),
                    imported=Module('foo.two')),
         ImportPath(importer=Module('foo.four'),
                    imported=Module('foo.three')),
     ]
示例#14
0
 def __init__(self, package):
     self.modules = [
         Module('foo'),
         Module('foo.one'),
         Module('foo.one.alpha'),
         Module('foo.one.beta'),
         Module('foo.one.beta.green'),
         Module('foo.two'),
     ]
示例#15
0
    def test_equals(self):
        a = ImportPath(importer=Module('foo'), imported=Module('bar'))
        b = ImportPath(importer=Module('foo'), imported=Module('bar'))
        c = ImportPath(importer=Module('foo'), imported=Module('foo.baz'))

        assert a == b
        assert a != c
        # Also non-ImportPath instances should not be treated as equal.
        assert a != 'foo'
示例#16
0
    def test_missing_contract(self, is_optional):
        contract = Contract(
            name='Foo contract',
            containers=(
                'foo.one',
                'foo.two',
            ),
            layers=(
                Layer('blue'),
                Layer('yellow', is_optional=is_optional),  # Missing from foo.two.
                Layer('green'),
            ),
        )
        graph = StubDependencyGraph(
            modules=[
                Module('foo.one'),
                Module('foo.one.blue'),
                Module('foo.one.blue.alpha'),
                Module('foo.one.yellow'),
                Module('foo.one.green'),
                Module('foo.two'),
                Module('foo.two.blue'),
                Module('foo.two.blue.alpha'),
                Module('foo.two.green'),
            ]
        )

        if is_optional:
            # Should pass.
            contract.check_dependencies(graph)
        else:
            with pytest.raises(ValueError) as e:
                contract.check_dependencies(graph)

            assert str(e.value) == (
                "Missing layer in container 'foo.two': module foo.two.yellow does not exist."
            )
示例#17
0
    def test_broken_contract(self):
        contract = Contract(
            name='Foo contract',
            containers=(
                Module('foo.blue'),
                Module('foo.green'),
            ),
            layers=(
                Layer('three'),
                Layer('two'),
                Layer('one'),
            ),
        )
        graph = StubDependencyGraph(
            descendants={
                Module('foo.green.one'): [
                    Module('foo.green.one.alpha'),
                    Module('foo.green.one.beta'),
                ],
                Module('foo.green.three'): [
                    Module('foo.green.three.alpha'),
                    Module('foo.green.three.beta'),
                ],
            },
            paths={
                Module('foo.blue.two'): {
                    # An allowed path: layer directly importing a layer below it.
                    Module('foo.blue.three'): [Module('foo.blue.three'), Module('foo.blue.two')],
                    # Disallowed path: layer directly importing a layer above it.
                    Module('foo.blue.one'): [Module('foo.blue.one'), Module('foo.blue.two')],
                },
                Module('foo.green.three.alpha'): {
                    # Module inside layer importing a module inside a higher layer.
                    Module('foo.green.one.alpha'): [Module('foo.green.one.alpha'),
                                                    Module('foo.green.three.alpha')],
                },
            },
            modules=[
                Module('foo.green'),
                Module('foo.green.one'),
                Module('foo.green.one.alpha'),
                Module('foo.green.one.beta'),
                Module('foo.green.two'),
                Module('foo.green.three'),
                Module('foo.blue'),
                Module('foo.blue.one'),
                Module('foo.blue.two'),
                Module('foo.blue.three'),
            ]
        )

        contract.check_dependencies(graph)

        assert contract.is_kept is False
        assert contract.illegal_dependencies == [
            [Module('foo.blue.one'), Module('foo.blue.two')],
            [Module('foo.green.one.alpha'), Module('foo.green.three.alpha')]
        ]
示例#18
0
    def test_only_shortest_violation_is_reported(self, longer_first):
        contract = Contract(
            name='Foo contract',
            containers=(
                'foo',
            ),
            layers=(
                Layer('two'),
                Layer('one'),
            ),
        )

        # These are both dependency violations, but it's more useful just to report
        # the more direct violation.
        if longer_first:
            paths = {
                Module('foo.two'): {
                    Module('foo.one.alpha'): [
                        Module('foo.one.alpha'), Module('foo.one.alpha.green'),
                        Module('foo.another'), Module('foo.two'),
                    ],
                    Module('foo.one.alpha.green'): [
                        Module('foo.one.alpha.green'), Module('foo.another'), Module('foo.two'),
                    ],

                },
            }
        else:
            paths = {
                Module('foo.two'): {
                    Module('foo.one.alpha'): [
                        Module('foo.one.alpha'), Module('foo.another'), Module('foo.two'),
                    ],
                    Module('foo.one.alpha.green'): [
                        Module('foo.one.alpha.green'), Module('foo.one.alpha'),
                        Module('foo.another'), Module('foo.two'),
                    ],

                },
            }
        graph = StubDependencyGraph(
            descendants={
                Module('foo.one'): [Module('foo.one.alpha'), Module('foo.one.beta'),
                                    Module('foo.one.alpha.blue'), Module('foo.one.alpha.green')],
            },
            paths=paths,
            modules=[Module('foo.one'), Module('foo.two')]
        )

        contract.check_dependencies(graph)

        if longer_first:
            assert contract.illegal_dependencies == [
                [Module('foo.one.alpha.green'), Module('foo.another'), Module('foo.two')],
            ]
        else:
            assert contract.illegal_dependencies == [
                [Module('foo.one.alpha'), Module('foo.another'), Module('foo.two')],
            ]
示例#19
0
    def test_kept_contract(self):
        contract = Contract(
            name='Foo contract',
            containers=(
                Module('foo.blue'),
                Module('foo.green'),
            ),
            layers=(
                Layer('three'),
                Layer('two'),
                Layer('one'),
            ),
            whitelisted_paths=mock.sentinel.whitelisted_paths,
        )
        graph = StubDependencyGraph(
            descendants={
                Module('foo.green.one'): [
                    Module('foo.green.one.alpha'),
                    Module('foo.green.one.beta'),
                ],
                Module('foo.green.three'): [
                    Module('foo.green.three.alpha'),
                    Module('foo.green.three.beta'),
                ],
            },
            paths={
                # Include some allowed paths.
                Module('foo.blue.two'): {
                    # Layer directly importing a layer below it.
                    Module('foo.blue.three'): [Module('foo.blue.three'), Module('foo.blue.two')],
                },
                Module('foo.blue.one'): {
                    # Layer directly importing two layers below.
                    Module('foo.blue.three'): [Module('foo.blue.three'), Module('foo.blue.one')],
                },
                Module('foo.green.three'): {
                    # Layer importing higher up layer, but from another container.
                    Module('foo.blue.one'): [Module('foo.blue.one'), Module('foo.green.three')],
                },
                Module('foo.green.three.beta'): {
                    # Module inside layer importing another module in same layer.
                    Module('foo.green.three.alpha'): [Module('foo.green.three.alpha'),
                                                      Module('foo.green.three.beta')],
                },
                Module('foo.green.one.alpha'): {
                    # Module inside layer importing a module inside a lower layer.
                    Module('foo.green.three.alpha'): [Module('foo.green.three.alpha'),
                                                      Module('foo.green.one.alpha')]
                },
            },
            modules=[
                Module('foo.green'),
                Module('foo.green.one'),
                Module('foo.green.one.alpha'),
                Module('foo.green.one.beta'),
                Module('foo.green.two'),
                Module('foo.green.three'),
                Module('foo.blue'),
                Module('foo.blue.one'),
                Module('foo.blue.two'),
                Module('foo.blue.three'),
            ]
        )

        contract.check_dependencies(graph)

        assert contract.is_kept is True
示例#20
0
    def test_contains(self):
        graph = graph_module.DependencyGraph(self.PACKAGE)

        assert Module('foo.one.alpha') in graph
        assert Module('foo.one.omega') not in graph
示例#21
0
    def test_get_descendants_none(self):
        graph = graph_module.DependencyGraph(self.PACKAGE)

        assert graph.get_descendants(Module('foo.two')) == []
示例#22
0
    def test_broken_contract_via_other_layer(self):
        # If an illegal import happens via another layer, we don't want to report it
        # (as it will already be reported).

        contract = Contract(
            name='Foo contract',
            containers=(
                'foo',
            ),
            layers=(
                Layer('three'),
                Layer('two'),
                Layer('one'),
            ),
        )
        graph = StubDependencyGraph(
            descendants={},
            paths={
                Module('foo.three'): {
                    Module('foo.two'): [Module('foo.two'), Module('foo.three')],
                    Module('foo.one'): [Module('foo.one'), Module('foo.two'), Module('foo.three')],
                },
                Module('foo.two'): {
                    Module('foo.one'): [Module('foo.one'), Module('foo.two')],
                },
            },
            modules=[
                Module('foo.one'),
                Module('foo.two'),
                Module('foo.three'),
            ]
        )

        contract.check_dependencies(graph)

        assert contract.illegal_dependencies == [
            [Module('foo.one'), Module('foo.two')],
            [Module('foo.two'), Module('foo.three')],
        ]
示例#23
0
 def test_repr(self):
     import_path = ImportPath(importer=Module('foo'),
                              imported=Module('bar'))
     assert repr(import_path) == '<ImportPath: foo <- bar>'
示例#24
0
def test_dependency_graph():
    dirname = os.path.dirname(__file__)
    path = os.path.abspath(os.path.join(dirname, '..', '..', 'assets'))

    sys.path.append(path)

    ROOT_PACKAGE = 'dependenciespackage'
    MODULE_ONE = Module("{}.one".format(ROOT_PACKAGE))
    MODULE_TWO = Module("{}.two".format(ROOT_PACKAGE))
    MODULE_THREE = Module("{}.three".format(ROOT_PACKAGE))
    MODULE_FOUR = Module("{}.four".format(ROOT_PACKAGE))

    SUBPACKAGE = 'subpackage'
    SUBMODULE_ONE = Module("{}.{}.one".format(ROOT_PACKAGE, SUBPACKAGE))
    SUBMODULE_TWO = Module("{}.{}.two".format(ROOT_PACKAGE, SUBPACKAGE))
    SUBMODULE_THREE = Module("{}.{}.three".format(ROOT_PACKAGE, SUBPACKAGE))

    SUBSUBPACKAGE = 'subsubpackage'
    SUBSUBMODULE_ONE = Module("{}.{}.{}.one".format(ROOT_PACKAGE, SUBPACKAGE,
                                                    SUBSUBPACKAGE))
    SUBSUBMODULE_TWO = Module("{}.{}.{}.two".format(ROOT_PACKAGE, SUBPACKAGE,
                                                    SUBSUBPACKAGE))
    SUBSUBMODULE_THREE = Module("{}.{}.{}.three".format(
        ROOT_PACKAGE, SUBPACKAGE, SUBSUBPACKAGE))

    root_package = __import__(ROOT_PACKAGE)
    graph = DependencyGraph(
        SafeFilenameModule(name=ROOT_PACKAGE, filename=root_package.__file__))

    assert graph.find_path(upstream=MODULE_ONE,
                           downstream=MODULE_TWO) == (MODULE_TWO, MODULE_ONE)

    assert graph.find_path(upstream=MODULE_TWO, downstream=MODULE_ONE) is None

    assert graph.find_path(upstream=MODULE_ONE,
                           downstream=MODULE_FOUR) == (MODULE_FOUR,
                                                       MODULE_THREE,
                                                       MODULE_TWO, MODULE_ONE)

    assert graph.find_path(upstream=SUBMODULE_ONE,
                           downstream=SUBMODULE_THREE) == (SUBMODULE_THREE,
                                                           SUBMODULE_TWO,
                                                           SUBMODULE_ONE)

    assert graph.find_path(
        upstream=SUBSUBMODULE_ONE,
        downstream=SUBSUBMODULE_THREE) == (SUBSUBMODULE_THREE,
                                           SUBSUBMODULE_TWO, SUBSUBMODULE_ONE)

    # Module count should be 13 (running total in square brackets):
    # - dependenciespackage [1]
    #   - one [2]
    #   - two [3]
    #   - three [4]
    #   - four [5]
    #   - .hidden [X]
    #   - migrations [X]
    #   - subpackage [6]
    #     - one [7]
    #     - two [8]
    #     - three [9]
    #     - is [10] (treat reserved keywords as normal modules)
    #     - subsubpackage [11]
    #       - one [12]
    #       - two [13]
    #       - three [14]
    assert graph.module_count == 14
    # Dependency count should be 7:
    # dependenciespackage.two <- dependenciespackage.one
    # dependenciespackage.three <- dependenciespackage.two
    # dependenciespackage.four <- dependenciespackage.three
    # dependenciespackage.subpackage.two <- dependenciespackage.subpackage.one
    # dependenciespackage.subpackage.three <- dependenciespackage.subpackage.two
    # dependenciespackage.subpackage.is <- dependenciespackage.subpackage.three
    # dependenciespackage.subpackage.subsubpackage.two
    #           <- dependenciespackage.subpackage.subsubpackage.one
    # dependenciespackage.subpackage.subsubpackage.three
    #           <- dependenciespackage.subpackage.subsubpackage.two
    assert graph.dependency_count == 8