Skip to content

Commit 50c1f2a

Browse files
authored
Test third-party stubs in isolation (#6229)
1 parent d5d0ba1 commit 50c1f2a

File tree

1 file changed

+79
-30
lines changed

1 file changed

+79
-30
lines changed

tests/mypy_test.py

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,7 @@ class MypyDistConf(NamedTuple):
149149
# disallow_untyped_defs = true
150150

151151

152-
def add_configuration(configurations, seen_dist_configs, distribution):
153-
if distribution in seen_dist_configs:
154-
return
155-
152+
def add_configuration(configurations: list[MypyDistConf], distribution: str) -> None:
156153
with open(os.path.join("stubs", distribution, "METADATA.toml")) as f:
157154
data = dict(tomli.loads(f.read()))
158155

@@ -173,7 +170,6 @@ def add_configuration(configurations, seen_dist_configs, distribution):
173170
assert isinstance(values, dict), "values should be a section"
174171

175172
configurations.append(MypyDistConf(module_name, values.copy()))
176-
seen_dist_configs.add(distribution)
177173

178174

179175
def run_mypy(args, configurations, major, minor, files, *, custom_typeshed=False):
@@ -225,6 +221,75 @@ def run_mypy(args, configurations, major, minor, files, *, custom_typeshed=False
225221
return 0
226222

227223

224+
def read_dependencies(distribution: str) -> list[str]:
225+
with open(os.path.join("stubs", distribution, "METADATA.toml")) as f:
226+
data = dict(tomli.loads(f.read()))
227+
requires = data.get("requires", [])
228+
assert isinstance(requires, list)
229+
dependencies = []
230+
for dependency in requires:
231+
assert isinstance(dependency, str)
232+
assert dependency.startswith("types-")
233+
dependencies.append(dependency[6:])
234+
return dependencies
235+
236+
237+
def add_third_party_files(
238+
distribution: str,
239+
major: int,
240+
files: list[str],
241+
args,
242+
exclude_list: re.Pattern[str],
243+
configurations: list[MypyDistConf],
244+
seen_dists: set[str],
245+
) -> None:
246+
if distribution in seen_dists:
247+
return
248+
seen_dists.add(distribution)
249+
250+
dependencies = read_dependencies(distribution)
251+
for dependency in dependencies:
252+
add_third_party_files(dependency, major, files, args, exclude_list, configurations, seen_dists)
253+
254+
if major == 2 and os.path.isdir(os.path.join("stubs", distribution, "@python2")):
255+
root = os.path.join("stubs", distribution, "@python2")
256+
else:
257+
root = os.path.join("stubs", distribution)
258+
for name in os.listdir(root):
259+
if name == "@python2":
260+
continue
261+
mod, _ = os.path.splitext(name)
262+
if mod.startswith("."):
263+
continue
264+
add_files(files, set(), root, name, args, exclude_list)
265+
add_configuration(configurations, distribution)
266+
267+
268+
def test_third_party_distribution(
269+
distribution: str, major: int, minor: int, args, exclude_list: re.Pattern[str]
270+
) -> tuple[int, int]:
271+
"""Test the stubs of a third-party distribution.
272+
273+
Return a tuple, where the first element indicates mypy's return code
274+
and the second element is the number of checked files.
275+
"""
276+
277+
print(f"testing {distribution} with Python {major}.{minor}...")
278+
279+
files: list[str] = []
280+
configurations: list[MypyDistConf] = []
281+
seen_dists: set[str] = set()
282+
add_third_party_files(distribution, major, files, args, exclude_list, configurations, seen_dists)
283+
284+
if not files:
285+
print("--- no files found ---")
286+
sys.exit(1)
287+
288+
# TODO: remove custom_typeshed after mypy 0.920 is released
289+
code = run_mypy(args, configurations, major, minor, files, custom_typeshed=True)
290+
return code, len(files)
291+
292+
228293
def main():
229294
args = parser.parse_args()
230295

@@ -239,11 +304,9 @@ def main():
239304
sys.exit(1)
240305

241306
code = 0
242-
runs = 0
307+
files_checked = 0
243308
for major, minor in versions:
244309
seen = {"__builtin__", "builtins", "typing"} # Always ignore these.
245-
configurations = []
246-
seen_dist_configs = set()
247310

248311
# Test standard library files.
249312
files = []
@@ -265,40 +328,26 @@ def main():
265328
add_files(files, seen, root, name, args, exclude_list)
266329

267330
if files:
268-
this_code = run_mypy(args, configurations, major, minor, files, custom_typeshed=True)
331+
this_code = run_mypy(args, [], major, minor, files, custom_typeshed=True)
269332
code = max(code, this_code)
270-
runs += 1
333+
files_checked += len(files)
271334

272335
# Test files of all third party distributions.
273-
files = []
274-
for distribution in os.listdir("stubs"):
336+
for distribution in sorted(os.listdir("stubs")):
275337
if not is_supported(distribution, major):
276338
continue
277-
if major == 2 and os.path.isdir(os.path.join("stubs", distribution, "@python2")):
278-
root = os.path.join("stubs", distribution, "@python2")
279-
else:
280-
root = os.path.join("stubs", distribution)
281-
for name in os.listdir(root):
282-
if name == "@python2":
283-
continue
284-
mod, _ = os.path.splitext(name)
285-
if mod in seen or mod.startswith("."):
286-
continue
287-
add_files(files, seen, root, name, args, exclude_list)
288-
add_configuration(configurations, seen_dist_configs, distribution)
289339

290-
if files:
291-
# TODO: remove custom_typeshed after mypy 0.920 is released
292-
this_code = run_mypy(args, configurations, major, minor, files, custom_typeshed=True)
340+
this_code, checked = test_third_party_distribution(distribution, major, minor, args, exclude_list)
293341
code = max(code, this_code)
294-
runs += 1
342+
files_checked += checked
295343

296344
if code:
297-
print("--- exit status", code, "---")
345+
print(f"--- exit status {code}, {files_checked} files checked ---")
298346
sys.exit(code)
299-
if not runs:
347+
if not files_checked:
300348
print("--- nothing to do; exit 1 ---")
301349
sys.exit(1)
350+
print(f"--- success, {files_checked} files checked ---")
302351

303352

304353
if __name__ == "__main__":

0 commit comments

Comments
 (0)