def test_top_level_import(self): graph = ImportGraph() graph.add_import(importer="green", imported="blue") result = graph.find_shortest_chains(importer="green", imported="blue") assert result == {("green", "blue")}
def test_add_module(): graph = ImportGraph() module = "foo" graph.add_module(module) assert graph.modules == {module}
def test_count_imports(imports, expected_count): graph = ImportGraph() for importer, imported in imports: graph.add_import(importer=importer, imported=imported) assert expected_count == graph.count_imports()
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_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 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_find_shortest_chain_returns_none_if_not_exists(): graph = ImportGraph() a, b, c = "foo", "bar", "baz" graph.add_import(importer=a, imported=b) graph.add_import(importer=b, imported=c) assert None is graph.find_shortest_chain(importer=c, imported=a)
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_returns_empty_list_when_import_but_no_available_details(self): graph = ImportGraph() importer, imported = "foo", "bar" graph.add_import(importer=importer, imported=imported), assert [] == graph.get_import_details(importer=importer, imported=imported)
def test_finds_two_step_path(self): graph = ImportGraph() self._add_chain(graph, "foo", "bar", "baz") result = tuple( graph.find_all_simple_chains(importer="foo", imported="baz")) assert (("foo", "bar", "baz"), ) == result
def test_finds_no_paths_if_wrong_direction(self): graph = ImportGraph() self._add_chain(graph, "foo", "bar") result = list( graph.find_all_simple_chains(importer="bar", imported="foo")) assert [] == result
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_long_path(self): graph = ImportGraph() long_path = ("one", "two", "three", "four", "five") self._add_chain(graph, *long_path) result = tuple( graph.find_all_simple_chains(importer="one", imported="five")) assert (long_path, ) == 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_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_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_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_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_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_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_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_untruncated(self): modules = ["one", "two", "three", "four", "five"] graph = ImportGraph() for module in modules: graph.add_module(module) # Assert something in the form <ImportGraph: 'one', 'two', 'three', 'four', 'five'> # (but not necessarily in that order). re_part = "(" + "|".join(modules) + ")" assert re.match( f"<ImportGraph: '{re_part}', '{re_part}', '{re_part}', '{re_part}', '{re_part}'>", repr(graph), )
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_module(): graph = ImportGraph() for module in ("mypackage", "mypackage.foo"): graph.add_module(module) contract = IndependenceContract( name="Independence contract", session_options={"root_packages": ["mypackage"]}, contract_options={"modules": ["mypackage.foo", "mypackage.bar"]}, ) with pytest.raises(ValueError, match=("Module 'mypackage.bar' does not exist.")): contract.check(graph=graph)
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 test_finds_multiple_paths(self): graph = ImportGraph() paths = ( ("foo", "blue", "bar"), ("foo", "green", "bar"), ("foo", "red", "yellow", "bar"), ("foo", "brown", "purple", "grey", "pink", "bar"), ) for path in paths: self._add_chain(graph, *path) result = set( graph.find_all_simple_chains(importer="foo", imported="bar")) assert set(paths) == result
def test_find_modules_that_directly_import(): graph = ImportGraph() a, b, c = "foo", "bar", "baz" d, e, f = "foo.one", "bar.one", "baz.one" graph.add_import(importer=a, imported=b) graph.add_import(importer=a, imported=c) graph.add_import(importer=a, imported=d) graph.add_import(importer=b, imported=e) graph.add_import(importer=f, imported=b) assert {a, f} == graph.find_modules_that_directly_import("bar")
def test_find_modules_directly_imported_by(): graph = ImportGraph() a, b, c = "foo", "bar", "baz" d, e, f = "foo.one", "bar.one", "baz.one" graph.add_import(importer=a, imported=b) graph.add_import(importer=a, imported=c) graph.add_import(importer=a, imported=d) graph.add_import(importer=b, imported=e) graph.add_import(importer=f, imported=a) assert {b, c, d} == graph.find_modules_directly_imported_by("foo")
def test_includes_redundant_paths(self): # I'm not definitely sure we want this behaviour, but this is what networkx's # all_simple_paths gives us at the moment. graph = ImportGraph() paths = ( ("foo", "blue", "bar"), ("foo", "green", "bar"), ("foo", "green", "blue", "bar"), ) for path in paths: self._add_chain(graph, *path) result = set( graph.find_all_simple_chains(importer="foo", imported="bar")) assert set(paths) == result
def _build_default_graph(self): graph = ImportGraph() for module in ( "mypackage", "mypackage.blue", "mypackage.blue.alpha", "mypackage.blue.beta", "mypackage.blue.beta.foo", "mypackage.green", "mypackage.yellow", "mypackage.yellow.gamma", "mypackage.yellow.delta", "mypackage.other", ): graph.add_module(module) return graph