def test_add_module(): graph = ImportGraph() module = "foo" graph.add_module(module) assert graph.modules == {module}
def test_chains_that_reenter_imported_package_dont_stop_longer_chains_being_included( self, ): graph = ImportGraph() graph.add_module("green") graph.add_module("blue") # Chain that reenters imported package. graph.add_import(importer="green.foo", imported="blue.foo") graph.add_import(importer="blue.foo", imported="brown") graph.add_import(importer="brown", imported="blue.bar") # Long indirect import. graph.add_import(importer="green.foo", imported="yellow.four") graph.add_import(importer="yellow.four", imported="yellow.three") graph.add_import(importer="yellow.three", imported="yellow.two") graph.add_import(importer="yellow.two", imported="yellow.one") graph.add_import(importer="yellow.one", imported="blue.bar") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == { ("green.foo", "blue.foo"), ( "green.foo", "yellow.four", "yellow.three", "yellow.two", "yellow.one", "blue.bar", ), }
def test_ignore_imports(ignore_imports, is_kept): graph = ImportGraph() graph.add_module("mypackage") graph.add_import(importer="mypackage.a", imported="mypackage.irrelevant", line_number=1, line_contents="-") graph.add_import(importer="mypackage.a", imported="mypackage.indirect", line_number=1, line_contents="-") graph.add_import(importer="mypackage.indirect", imported="mypackage.b", line_number=1, line_contents="-") contract = IndependenceContract( name="Independence contract", session_options={"root_packages": ["mypackage"]}, contract_options={ "modules": ("mypackage.a", "mypackage.b"), "ignore_imports": ignore_imports, }, ) contract_check = contract.check(graph=graph) assert is_kept == contract_check.kept
def test_ignore_imports_tolerates_duplicates(): graph = ImportGraph() graph.add_module("mypackage") graph.add_import(importer="mypackage.a", imported="mypackage.b", line_number=1, line_contents="-") graph.add_import(importer="mypackage.a", imported="mypackage.c", line_number=2, line_contents="-") contract = IndependenceContract( name="Independence contract", session_options={"root_packages": ["mypackage"]}, contract_options={ "modules": ("mypackage.a", "mypackage.b"), "ignore_imports": [ "mypackage.a -> mypackage.b", "mypackage.a -> mypackage.c", "mypackage.a -> mypackage.b", ], }, ) contract_check = contract.check(graph=graph) assert contract_check.kept
def _build_graph(self): graph = ImportGraph() for module in ( "mypackage", "mypackage.high", "mypackage.high.green", "mypackage.high.blue", "mypackage.high.yellow", "mypackage.high.yellow.alpha", "mypackage.medium", "mypackage.medium.orange", "mypackage.medium.orange.beta", "mypackage.medium.red", "mypackage.low", "mypackage.low.black", "mypackage.low.white", "mypackage.low.white.gamma", ): graph.add_module(module) # Add some 'legal' imports. graph.add_import(importer="mypackage.high.green", imported="mypackage.medium.orange") graph.add_import(importer="mypackage.high.green", imported="mypackage.low.white.gamma") graph.add_import(importer="mypackage.medium.orange", imported="mypackage.low.white") graph.add_import(importer="mypackage.high.blue", imported="mypackage.utils") graph.add_import(importer="mypackage.utils", imported="mypackage.medium.red") return graph
def test_optional_layers(include_parentheses, should_raise_exception): graph = ImportGraph() for module in ( "mypackage", "mypackage.foo", "mypackage.foo.high", "mypackage.foo.high.blue", "mypackage.foo.low", "mypackage.foo.low.alpha", ): graph.add_module(module) contract = LayersContract( name="Layer contract", session_options={"root_packages": ["mypackage"]}, contract_options={ "containers": ["mypackage.foo"], "layers": ["high", "(medium)" if include_parentheses else "medium", "low"], }, ) if should_raise_exception: with pytest.raises( ValueError, match=( "Missing layer in container 'mypackage.foo': " "module mypackage.foo.medium does not exist." ), ): contract.check(graph=graph) else: contract.check(graph=graph)
def test_invalid_container(container): graph = ImportGraph() for module in ( "mypackage", "mypackage.foo", "mypackage.foo.high", "mypackage.foo.medium", "mypackage.foo.low", "notinpackage", "mypackagebeginscorrectly", ): graph.add_module(module) contract = LayersContract( name="Layer contract", session_options={"root_packages": ["mypackage"]}, contract_options={ "containers": ["mypackage.foo", container], "layers": ["high", "medium", "low"], }, ) with pytest.raises( ValueError, match=( f"Invalid container '{container}': a container must either be a subpackage of " "mypackage, or mypackage itself." ), ): contract.check(graph=graph)
def _build_legal_graph(self, container=None): graph = ImportGraph() if container: graph.add_module(container) namespace = f"{container}." else: namespace = "" for module in ( f"{namespace}high", f"{namespace}high.green", f"{namespace}high.blue", f"{namespace}high.yellow", f"{namespace}high.yellow.alpha", f"{namespace}medium", f"{namespace}medium.orange", f"{namespace}medium.orange.beta", f"{namespace}medium.red", f"{namespace}low", f"{namespace}low.black", f"{namespace}low.white", f"{namespace}low.white.gamma", ): graph.add_module(module) # Add some 'legal' imports. graph.add_import(importer=f"{namespace}high.green", imported=f"{namespace}medium.orange") graph.add_import(importer=f"{namespace}high.green", imported=f"{namespace}low.white.gamma") graph.add_import(importer=f"{namespace}medium.orange", imported=f"{namespace}low.white") graph.add_import(importer=f"{namespace}high.blue", imported=f"{namespace}utils") graph.add_import(importer=f"{namespace}utils", imported=f"{namespace}medium.red") return graph
def test_import_between_top_level_and_child(self): graph = ImportGraph() graph.add_module("blue") graph.add_import(importer="green", imported="blue.foo") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == {("green", "blue.foo")}
def test_can_repeatedly_add_same_squashed_module(self): graph = ImportGraph() module = "foo" graph.add_module(module, is_squashed=True) graph.add_module(module, is_squashed=True) assert graph.modules == {module}
def test_does_nothing_if_module_is_already_squashed(self): graph = ImportGraph() graph.add_module("foo", is_squashed=True) graph.add_import(importer="foo", imported="bar") graph.squash_module("foo") assert graph.direct_import_exists(importer="foo", imported="bar")
def test_find_descendants_raises_exception_for_squashed_module(): graph = ImportGraph() module = "foo" graph.add_module(module, is_squashed=True) with pytest.raises(ValueError, match="Cannot find descendants of a squashed module."): graph.find_descendants(module)
def test_find_children(module, expected_result): graph = ImportGraph() foo, bar = "foo", "bar" a, b, c = "foo.a", "foo.b", "foo.c" d, e, f = "foo.a.one", "foo.b.one", "bar.g" for module_to_add in (foo, bar, a, b, c, d, e, f): graph.add_module(module_to_add) assert expected_result == graph.find_children(module)
def test_finds_no_paths_if_none_exist(self): graph = ImportGraph() graph.add_module("foo") graph.add_module("bar") result = list( graph.find_all_simple_chains(importer="foo", imported="bar")) assert [] == result
def test_finds_no_paths_if_imported_but_not_importer(self): graph = ImportGraph() self._add_chain(graph, "foo", "bar") graph.add_module("baz") result = list( graph.find_all_simple_chains(importer="baz", imported="bar")) assert [] == result
def test_no_results_in_reverse_direction(self): graph = ImportGraph() graph.add_module("green") graph.add_module("blue") graph.add_import(importer="green.foo", imported="blue.bar") result = graph.find_shortest_chains(importer="blue", imported="green") assert result == set()
def test_grandchildren_import(self): graph = ImportGraph() graph.add_module("green") graph.add_module("blue") graph.add_import(importer="green.foo.one", imported="blue.bar.two") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == {("green.foo.one", "blue.bar.two")}
def test_first_level_child_import(self): graph = ImportGraph() graph.add_module("green") graph.add_module("blue") graph.add_import(importer="green.foo", imported="blue.bar") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == {("green.foo", "blue.bar")}
def test_cannot_add_descendant_of_squashed_module(self, module_name): graph = ImportGraph() graph.add_module("mypackage.foo", is_squashed=True) with pytest.raises( ValueError, match="Module is a descendant of squashed module mypackage.foo." ): graph.add_module(module_name)
def test_short_indirect_import(self): graph = ImportGraph() graph.add_module("green") graph.add_module("blue") graph.add_import(importer="green.indirect", imported="purple") graph.add_import(importer="purple", imported="blue.foo") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == {("green.indirect", "purple", "blue.foo")}
def _build_graph(self): graph = ImportGraph() for module in ( "mypackage", "mypackage.high", "mypackage.high.green", "mypackage.high.blue", "mypackage.high.yellow", "mypackage.high.yellow.alpha", "mypackage.medium", "mypackage.medium.orange", "mypackage.medium.orange.beta", "mypackage.medium.red", "mypackage.low", "mypackage.low.black", "mypackage.low.white", "mypackage.low.white.gamma", ): graph.add_module(module) # Add some 'legal' imports. graph.add_import(importer="mypackage.high.green", imported="mypackage.medium.orange") graph.add_import(importer="mypackage.high.green", imported="mypackage.low.white.gamma") graph.add_import(importer="mypackage.medium.orange", imported="mypackage.low.white") graph.add_import(importer="mypackage.high.blue", imported="mypackage.utils") graph.add_import(importer="mypackage.utils", imported="mypackage.medium.red") # Direct illegal import. graph.add_import( importer="mypackage.low.black", imported="mypackage.medium.orange", line_number=1, line_contents="-", ) # Indirect illegal import. graph.add_import( importer="mypackage.low.white.gamma", imported="mypackage.utils.foo", line_number=1, line_contents="-", ) graph.add_import( importer="mypackage.utils.foo", imported="mypackage.utils.bar", line_number=1, line_contents="-", ) graph.add_import( importer="mypackage.utils.bar", imported="mypackage.high.yellow.alpha", line_number=1, line_contents="-", ) return graph
def test_doesnt_error_if_imports_within_module(self): graph = ImportGraph() for module in [ "foo", "foo.green", "foo.blue", ]: graph.add_module(module) graph.add_import(importer="foo.blue", imported="foo.green") graph.squash_module("foo")
def test_truncated(self): modules = ["one", "two", "three", "four", "five", "six"] graph = ImportGraph() for module in modules: graph.add_module(module) re_part = "(" + "|".join(modules) + ")" assert re.match( f"<ImportGraph: '{re_part}', '{re_part}', '{re_part}', " f"'{re_part}', '{re_part}', ...>", repr(graph), )
def test_marks_module_as_squashed(self): graph = ImportGraph() modules_to_squash = { "foo", "foo.green", } for module in modules_to_squash: graph.add_module(module) graph.squash_module("foo") assert graph.is_module_squashed("foo")
def test_contracts_import_to_descendant(self): graph = ImportGraph() for module in [ "foo", "foo.green", "bar.blue", ]: graph.add_module(module) graph.add_import(importer="bar.blue", imported="foo.green") graph.squash_module("foo") assert graph.direct_import_exists(importer="bar.blue", imported="foo")
def test_remove_module(): graph = ImportGraph() a, b = {"mypackage.blue", "mypackage.green"} graph.add_module(a) graph.add_module(b) graph.add_import(importer=a, imported=b) graph.remove_module(b) assert {a} == graph.modules # Removing a non-existent module doesn't cause an error. graph.remove_module("mypackage.yellow")
def test_missing_containerless_layers_raise_value_error(): graph = ImportGraph() for module in ("foo", "foo.blue", "bar", "bar.green"): graph.add_module(module) contract = LayersContract( name="Layer contract", session_options={"root_packages": ["foo", "bar"]}, contract_options={"containers": [], "layers": ["foo", "bar", "baz"]}, ) with pytest.raises(ValueError, match=("Missing layer 'baz': module baz does not exist.")): contract.check(graph=graph)
def test_add_import(add_module): graph = ImportGraph() a, b = "foo", "bar" # Adding the module should make no difference to the result. if add_module: graph.add_module(a) graph.add_import(importer=a, imported=b) assert {a, b} == graph.modules assert {b} == graph.find_modules_directly_imported_by(a) assert set() == graph.find_modules_directly_imported_by(b)
def test_keeps_import_of_squashed_root(self): graph = ImportGraph() for module in [ "foo", "foo.green", "bar.blue", ]: graph.add_module(module) graph.add_import(importer="bar.blue", imported="foo") graph.squash_module("foo") assert graph.direct_import_exists(importer="bar.blue", imported="foo")
def test_long_indirect_import(self): graph = ImportGraph() graph.add_module("green") graph.add_module("blue") graph.add_import(importer="green.baz", imported="yellow.three") graph.add_import(importer="yellow.three", imported="yellow.two") graph.add_import(importer="yellow.two", imported="yellow.one") graph.add_import(importer="yellow.one", imported="blue.foo") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == {("green.baz", "yellow.three", "yellow.two", "yellow.one", "blue.foo")}