Skip to content

Commit 2fd8829

Browse files
committed
tests: Improve master_test.ScanCodeImportsTest coverage
This covers existing behaviours of `mitogen.master.scan_code_imports()` some of which are relied on, some not, but regardless weren't tested. Notably - Explicit relative imports return level > 0 - Imports inside `class` and `def` are excluded - Imports inside other blocks are included - Python 3.x prunes impossible if/else branches (previously unknown) It also - Decouples the test results from the implementation details of the unit test. - Fixes a missing import - Fixes at least one Python 2.4 incompatibility (use of with block)
1 parent 1386529 commit 2fd8829

File tree

9 files changed

+190
-17
lines changed

9 files changed

+190
-17
lines changed

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ In progress (unreleased)
2323

2424
* :gh:issue:`1329` CI: Refactor and de-duplicate Github Actions workflow
2525
* :gh:issue:`1315` CI: macOS: Increase failed logins limit of test users
26+
* :gh:issue:`1325` tests: Improve ``master_test.ScanCodeImportsTest`` coverage
2627

2728

2829
v0.3.26 (2025-08-04)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# pyright: reportMissingImports=false
2+
# ruff: noqa: E401 E702 F401 F403
3+
4+
import a
5+
import a.b
6+
import c as d
7+
import e, e.f as g \
8+
, h; import i
9+
10+
from j import k, l, m as n
11+
from o import *
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# pyright: reportMissingImports=false
2+
# ruff: noqa: E401 E702 F401 F403
3+
4+
from . import a
5+
from .b import c, d as e
6+
from ... import (
7+
f,
8+
j as k,
9+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# pyright: reportMissingImports=false
2+
# ruff: noqa: E401 E702 F401 F403
3+
4+
from __future__ import absolute_import
5+
6+
import a
7+
import a.b
8+
import c as d
9+
import e, e.f as g \
10+
, h; import i
11+
12+
from j import k, l, m as n
13+
from o import *
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C:
2+
import in_class
3+
from in_class import x as y
4+
5+
def m(self):
6+
import in_method
7+
from in_method import x as y, z
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def f():
2+
import in_func
3+
from in_func import x as y, z
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import sys
2+
3+
4+
if True:
5+
import in_if_always_true
6+
from in_if_always_true import x as y, z
7+
else:
8+
import in_else_never_true
9+
from in_else_never_true import x as y, z
10+
11+
if sys.version >= (3, 0):
12+
import in_if_py3
13+
from in_if_py3 import x as y, z
14+
else:
15+
import in_else_py2
16+
from in_else_py2 import x as y, z
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
try:
2+
import in_try
3+
from in_try import x as y, z
4+
except ImportError:
5+
import in_except_importerror
6+
from in_except_importerror import x as y, z
7+
except Exception:
8+
import in_except_exception
9+
from in_except_exception import x as y, z

tests/master_test.py

Lines changed: 121 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,129 @@
1-
import inspect
1+
import os
2+
import sys
3+
import unittest
24

35
import testlib
46
import mitogen.master
57

68

9+
def testmod_compile(path):
10+
path = os.path.join(testlib.MODS_DIR, path)
11+
f = open(path, 'rb')
12+
co = compile(f.read(), path, 'exec')
13+
f.close()
14+
return co
15+
16+
717
class ScanCodeImportsTest(testlib.TestCase):
818
func = staticmethod(mitogen.master.scan_code_imports)
919

10-
if mitogen.core.PY3:
11-
level = 0
12-
else:
13-
level = -1
14-
15-
SIMPLE_EXPECT = [
16-
(level, 'inspect', ()),
17-
(level, 'testlib', ()),
18-
(level, 'mitogen.master', ()),
19-
]
20-
21-
def test_simple(self):
22-
source_path = inspect.getsourcefile(ScanCodeImportsTest)
23-
with open(source_path) as f:
24-
co = compile(f.read(), source_path, 'exec')
25-
self.assertEqual(list(self.func(co)), self.SIMPLE_EXPECT)
20+
@unittest.skipIf(sys.version_info < (3, 0), "Py is 2.x, would be relative")
21+
def test_default_absolute(self):
22+
co = testmod_compile('scanning/defaults.py')
23+
expected = [
24+
(0, 'a', ()), (0, 'a.b', ()), (0, 'c', ()),
25+
(0, 'e', ()), (0, 'e.f', ()), (0, 'h', ()),
26+
(0, 'i', ()),
27+
(0, 'j', ('k', 'l', 'm')),
28+
(0, 'o', ('*',)),
29+
]
30+
self.assertEqual(list(self.func(co)), expected)
31+
32+
@unittest.skipIf(sys.version_info >= (3, 0), "Py is 3.x, would be absolute")
33+
def test_default_relative(self):
34+
co = testmod_compile('scanning/defaults.py')
35+
expected = [
36+
(-1, 'a', ()), (-1, 'a.b', ()), (-1, 'c', ()),
37+
(-1, 'e', ()), (-1, 'e.f', ()), (-1, 'h', ()),
38+
(-1, 'i', ()),
39+
(-1, 'j', ('k', 'l', 'm')),
40+
(-1, 'o', ('*',)),
41+
]
42+
self.assertEqual(list(self.func(co)), expected)
43+
44+
@unittest.skipIf(sys.version_info < (2, 5), "Py is 2.4, no absolute_import")
45+
def test_explicit_absolute(self):
46+
co = testmod_compile('scanning/has_absolute_import.py')
47+
expected = [
48+
(0, '__future__', ('absolute_import',)),
49+
50+
(0, 'a', ()), (0, 'a.b', ()), (0, 'c', ()),
51+
(0, 'e', ()), (0, 'e.f', ()), (0, 'h', ()),
52+
(0, 'i', ()),
53+
(0, 'j', ('k', 'l', 'm')),
54+
(0, 'o', ('*',)),
55+
]
56+
self.assertEqual(list(self.func(co)), expected)
57+
58+
@unittest.skipIf(sys.version_info < (2, 5), "Py is 2.4, no `from . import x`")
59+
def test_explicit_relative(self):
60+
co = testmod_compile('scanning/explicit_relative.py')
61+
expected = [
62+
(1, '', ('a',)),
63+
(1, 'b', ('c', 'd')),
64+
(3, '', ('f', 'j')),
65+
]
66+
self.assertEqual(list(self.func(co)), expected)
67+
68+
def test_scoped_class(self):
69+
# Imports in `class` or `def` are ignored, a bad heuristc to detect
70+
# lazy imports and skip sending the pre-emptively.
71+
# See
72+
# - https://github.com/mitogen-hq/mitogen/issues/682
73+
# - https://github.com/mitogen-hq/mitogen/issues/1325#issuecomment-3170482014
74+
co = testmod_compile('scanning/scoped_class.py')
75+
self.assertEqual(list(self.func(co)), [])
76+
77+
pass
78+
79+
def test_scoped_function(self):
80+
co = testmod_compile('scanning/scoped_function.py')
81+
self.assertEqual(list(self.func(co)), [])
82+
83+
@unittest.skipIf(sys.version_info >= (3, 0), "Python is 3.x, which prunes")
84+
def test_scoped_if_else_unpruned(self):
85+
co = testmod_compile('scanning/scoped_if_else.py')
86+
level = (-1, 0)[int(sys.version_info >= (3, 0))]
87+
expected = [
88+
(level, 'sys', ()),
89+
(level, 'in_if_always_true', ()),
90+
(level, 'in_if_always_true', ('x', 'z')),
91+
# Python 2.x does no pruning
92+
(level, 'in_else_never_true', ()),
93+
(level, 'in_else_never_true', ('x', 'z')),
94+
(level, 'in_if_py3', ()),
95+
(level, 'in_if_py3', ('x', 'z')),
96+
(level, 'in_else_py2', ()),
97+
(level, 'in_else_py2', ('x', 'z')),
98+
]
99+
self.assertEqual(list(self.func(co)), expected)
100+
101+
@unittest.skipIf(sys.version_info < (3, 0), "Python is 2.x, which doesn't prune")
102+
def test_scoped_if_else_pruned(self):
103+
co = testmod_compile('scanning/scoped_if_else.py')
104+
level = (-1, 0)[int(sys.version_info >= (3, 0))]
105+
expected = [
106+
(level, 'sys', ()),
107+
(level, 'in_if_always_true', ()),
108+
(level, 'in_if_always_true', ('x', 'z')),
109+
# Python 3.x prunes some impossible branches ...
110+
(level, 'in_if_py3', ()),
111+
(level, 'in_if_py3', ('x', 'z')),
112+
# ... but not sys.version_info ones
113+
(level, 'in_else_py2', ()),
114+
(level, 'in_else_py2', ('x', 'z')),
115+
]
116+
self.assertEqual(list(self.func(co)), expected)
117+
118+
def test_scoped_try_except(self):
119+
co = testmod_compile('scanning/scoped_try_except.py')
120+
level = (-1, 0)[int(sys.version_info >= (3, 0))]
121+
expected = [
122+
(level, 'in_try', ()),
123+
(level, 'in_try', ('x', 'z')),
124+
(level, 'in_except_importerror', ()),
125+
(level, 'in_except_importerror', ('x', 'z')),
126+
(level, 'in_except_exception', ()),
127+
(level, 'in_except_exception', ('x', 'z')),
128+
]
129+
self.assertEqual(list(self.func(co)), expected)

0 commit comments

Comments
 (0)