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
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'))
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
def __init__(self): self.modules = [ Module('foo.one'), Module('foo.two'), Module('foo.three'), ] self.module_count = len(self.modules) self.dependency_count = 4
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'), }
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]
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
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)
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)
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)
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)
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')), ]
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'), ]
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'
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." )
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')] ]
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')], ]
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
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
def test_get_descendants_none(self): graph = graph_module.DependencyGraph(self.PACKAGE) assert graph.get_descendants(Module('foo.two')) == []
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')], ]
def test_repr(self): import_path = ImportPath(importer=Module('foo'), imported=Module('bar')) assert repr(import_path) == '<ImportPath: foo <- bar>'
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