@@ -25,13 +25,13 @@ def test_find_downstream_modules(module, as_package, expected_result):
2525
2626 graph .add_module (external , is_squashed = True )
2727
28- graph .add_import (imported = a , importer = b )
29- graph .add_import (imported = a , importer = c )
30- graph .add_import (imported = c , importer = d )
31- graph .add_import (imported = d , importer = e )
32- graph .add_import (imported = f , importer = b )
33- graph .add_import (imported = f , importer = g )
34- graph .add_import (imported = external , importer = d )
28+ graph .add_import (importer = b , imported = a )
29+ graph .add_import (importer = c , imported = a )
30+ graph .add_import (importer = d , imported = c )
31+ graph .add_import (importer = e , imported = d )
32+ graph .add_import (importer = b , imported = f )
33+ graph .add_import (importer = g , imported = f )
34+ graph .add_import (importer = d , imported = external )
3535
3636 assert expected_result == graph .find_downstream_modules (module , as_package = as_package )
3737
@@ -41,6 +41,7 @@ def test_find_downstream_modules(module, as_package, expected_result):
4141 (
4242 ("foo.d" , False , {"foo.d.c" , "foo.a" }),
4343 ("foo.b.g" , False , set ()),
44+ # Note: foo.d.c is not included in the upstreams because that's internal to the package.
4445 ("foo.d" , True , {"foo.a" , "foo.a.f" , "foo.b.g" }),
4546 ("foo.b.g" , True , set ()),
4647 ("bar" , True , {"foo.a.f" , "foo.b.g" }),
@@ -56,13 +57,13 @@ def test_find_upstream_modules(module, as_package, expected_result):
5657
5758 graph .add_module (external , is_squashed = True )
5859
59- graph .add_import (imported = a , importer = b )
60- graph .add_import (imported = a , importer = c )
61- graph .add_import (imported = c , importer = d )
62- graph .add_import (imported = d , importer = e )
63- graph .add_import (imported = f , importer = b )
64- graph .add_import (imported = g , importer = f )
65- graph .add_import (imported = f , importer = external )
60+ graph .add_import (importer = b , imported = a )
61+ graph .add_import (importer = c , imported = a )
62+ graph .add_import (importer = d , imported = c )
63+ graph .add_import (importer = e , imported = d )
64+ graph .add_import (importer = b , imported = f )
65+ graph .add_import (importer = f , imported = g )
66+ graph .add_import (importer = external , imported = f )
6667
6768 assert expected_result == graph .find_upstream_modules (module , as_package = as_package )
6869
@@ -169,6 +170,44 @@ def test_demonstrate_nondeterminism_of_equal_chains(self):
169170
170171
171172class TestFindShortestChains :
173+ @pytest .mark .parametrize (
174+ "importer, imported" ,
175+ (
176+ ("green" , "green.one" ),
177+ ("green.one" , "green" ),
178+ ),
179+ )
180+ def test_modules_with_shared_descendants_raises_value_error_when_as_packages_true (
181+ self , importer : str , imported : str
182+ ):
183+ graph = ImportGraph ()
184+ graph .add_module (importer )
185+ graph .add_module (imported )
186+
187+ with pytest .raises (ValueError , match = "Modules have shared descendants." ):
188+ graph .find_shortest_chains (importer = importer , imported = imported , as_packages = True )
189+
190+ @pytest .mark .parametrize (
191+ "importer, imported" ,
192+ (
193+ ("green" , "green.one" ),
194+ ("green.one" , "green" ),
195+ ),
196+ )
197+ def test_modules_with_shared_descendants_allowed_when_as_packages_false (
198+ self , importer : str , imported : str
199+ ):
200+ graph = ImportGraph ()
201+ middle = "middle"
202+ graph .add_import (importer = importer , imported = middle )
203+ graph .add_import (importer = middle , imported = imported )
204+
205+ result = graph .find_shortest_chains (
206+ importer = importer , imported = imported , as_packages = False
207+ )
208+
209+ assert result == {(importer , middle , imported )}
210+
172211 @pytest .mark .parametrize ("as_packages" , (False , True ))
173212 def test_top_level_import (self , as_packages : bool ):
174213 graph = ImportGraph ()
@@ -340,6 +379,25 @@ def test_long_indirect_import(self, as_packages: bool, expected_result: Set[Tupl
340379
341380 assert result == expected_result
342381
382+ def test_chains_within_packages_are_not_included (self ):
383+ graph = ImportGraph ()
384+
385+ graph .add_module ("importer_package" )
386+ graph .add_module ("imported_package" )
387+
388+ # Chain via importer package.
389+ graph .add_import (importer = "importer_package.one" , imported = "importer_package.two" )
390+ graph .add_import (importer = "importer_package.two" , imported = "importer_package.three" )
391+ graph .add_import (importer = "importer_package.three" , imported = "imported_package.four" )
392+ graph .add_import (importer = "imported_package.four" , imported = "imported_package.five" )
393+ graph .add_import (importer = "imported_package.five" , imported = "imported_package.six" )
394+
395+ result = graph .find_shortest_chains (
396+ importer = "importer_package" , imported = "imported_package"
397+ )
398+
399+ assert result == {("importer_package.three" , "imported_package.four" )}
400+
343401 def test_chains_via_importer_package_dont_stop_longer_chains_being_included (self ):
344402 graph = ImportGraph ()
345403
@@ -501,6 +559,9 @@ def test_doesnt_change_import_count(self, as_packages: bool):
501559 # Importer is child of imported (but doesn't import). This doesn't
502560 # make sense if as_packages is True, so it should raise an exception.
503561 ("b.two" , "b" , True , ValueError ()),
562+ # Importer is child of imported (but doesn't import). This doesn't
563+ # make sense if as_packages is True, so it should raise an exception.
564+ ("b" , "b.two" , True , ValueError ()),
504565 # Importer's child imports imported's child (b.two.green -> a.one.green).
505566 ("b.two" , "a.one" , True , True ),
506567 # Importer's grandchild directly imports imported's grandchild
0 commit comments