Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 008fe2e

Browse files
authored
mypy_test.py: rework filter, exclude and --dir arguments (#8711)
1 parent b3db49a commit 008fe2e

3 files changed

Lines changed: 87 additions & 89 deletions

File tree

tests/check_consistent.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from packaging.requirements import Requirement
1616
from packaging.specifiers import SpecifierSet
1717
from packaging.version import Version
18-
from utils import get_all_testcase_directories
18+
from utils import VERSIONS_RE, get_all_testcase_directories, strip_comments
1919

2020
metadata_keys = {"version", "requires", "extra_description", "obsolete_since", "no_longer_updated", "tool"}
2121
tool_keys = {"stubtest": {"skip", "apt_dependencies", "extras", "ignore_missing_stub"}}
@@ -85,13 +85,6 @@ def check_no_symlinks() -> None:
8585
raise ValueError(no_symlink.format(file))
8686

8787

88-
_VERSIONS_RE = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_.]*): [23]\.\d{1,2}-(?:[23]\.\d{1,2})?$")
89-
90-
91-
def strip_comments(text: str) -> str:
92-
return text.split("#")[0].strip()
93-
94-
9588
def check_versions() -> None:
9689
versions = set()
9790
with open("stdlib/VERSIONS") as f:
@@ -100,7 +93,7 @@ def check_versions() -> None:
10093
line = strip_comments(line)
10194
if line == "":
10295
continue
103-
m = _VERSIONS_RE.match(line)
96+
m = VERSIONS_RE.match(line)
10497
if not m:
10598
raise AssertionError(f"Bad line in VERSIONS: {line}")
10699
module = m.group(1)

tests/mypy_test.py

Lines changed: 71 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -21,53 +21,55 @@
2121
from typing_extensions import Annotated, TypeAlias
2222

2323
import tomli
24-
from utils import colored, print_error, print_success_msg, read_dependencies
24+
from utils import VERSIONS_RE as VERSION_LINE_RE, colored, print_error, print_success_msg, read_dependencies, strip_comments
2525

26-
SUPPORTED_VERSIONS = [(3, 11), (3, 10), (3, 9), (3, 8), (3, 7)]
26+
SUPPORTED_VERSIONS = ["3.11", "3.10", "3.9", "3.8", "3.7"]
2727
SUPPORTED_PLATFORMS = ("linux", "win32", "darwin")
28-
TYPESHED_DIRECTORIES = frozenset({"stdlib", "stubs"})
28+
DIRECTORIES_TO_TEST = [Path("stdlib"), Path("stubs")]
2929

3030
ReturnCode: TypeAlias = int
31-
MajorVersion: TypeAlias = int
32-
MinorVersion: TypeAlias = int
33-
MinVersion: TypeAlias = tuple[MajorVersion, MinorVersion]
34-
MaxVersion: TypeAlias = tuple[MajorVersion, MinorVersion]
31+
VersionString: TypeAlias = Annotated[str, "Must be one of the entries in SUPPORTED_VERSIONS"]
32+
VersionTuple: TypeAlias = tuple[int, int]
3533
Platform: TypeAlias = Annotated[str, "Must be one of the entries in SUPPORTED_PLATFORMS"]
36-
Directory: TypeAlias = Annotated[str, "Must be one of the entries in TYPESHED_DIRECTORIES"]
37-
38-
39-
def python_version(arg: str) -> tuple[MajorVersion, MinorVersion]:
40-
version = tuple(map(int, arg.split("."))) # This will naturally raise TypeError if it's not in the form "{major}.{minor}"
41-
if version not in SUPPORTED_VERSIONS:
42-
raise ValueError
43-
# mypy infers the return type as tuple[int, ...]
44-
return version # type: ignore[return-value]
4534

4635

4736
class CommandLineArgs(argparse.Namespace):
4837
verbose: int
49-
exclude: list[str] | None
50-
python_version: list[tuple[MajorVersion, MinorVersion]] | None
51-
dir: list[Directory] | None
38+
filter: list[Path]
39+
exclude: list[Path] | None
40+
python_version: list[VersionString] | None
5241
platform: list[Platform] | None
53-
filter: list[str]
42+
43+
44+
def valid_path(cmd_arg: str) -> Path:
45+
"""Helper function for argument-parsing"""
46+
path = Path(cmd_arg)
47+
if not path.exists():
48+
raise argparse.ArgumentTypeError(f'"{path}" does not exist in typeshed!')
49+
if not (path in DIRECTORIES_TO_TEST or any(directory in path.parents for directory in DIRECTORIES_TO_TEST)):
50+
raise argparse.ArgumentTypeError('mypy_test.py only tests the stubs found in the "stdlib" and "stubs" directories')
51+
return path
5452

5553

5654
parser = argparse.ArgumentParser(
5755
description="Typecheck typeshed's stubs with mypy. Patterns are unanchored regexps on the full path."
5856
)
59-
parser.add_argument("-v", "--verbose", action="count", default=0, help="More output")
60-
parser.add_argument("-x", "--exclude", type=str, nargs="*", help="Exclude pattern")
6157
parser.add_argument(
62-
"-p", "--python-version", type=python_version, nargs="*", action="extend", help="These versions only (major[.minor])"
58+
"filter",
59+
type=valid_path,
60+
nargs="*",
61+
help='Test these files and directories (defaults to all files in the "stdlib" and "stubs" directories)',
6362
)
63+
parser.add_argument("-x", "--exclude", type=valid_path, nargs="*", help="Exclude these files and directories")
64+
parser.add_argument("-v", "--verbose", action="count", default=0, help="More output")
6465
parser.add_argument(
65-
"-d",
66-
"--dir",
67-
choices=TYPESHED_DIRECTORIES,
66+
"-p",
67+
"--python-version",
68+
type=str,
69+
choices=SUPPORTED_VERSIONS,
6870
nargs="*",
6971
action="extend",
70-
help="Test only these top-level typeshed directories (defaults to all typeshed directories)",
72+
help="These versions only (major[.minor])",
7173
)
7274
parser.add_argument(
7375
"--platform",
@@ -76,20 +78,17 @@ class CommandLineArgs(argparse.Namespace):
7678
action="extend",
7779
help="Run mypy for certain OS platforms (defaults to sys.platform only)",
7880
)
79-
parser.add_argument("filter", type=str, nargs="*", help="Include pattern (default all)")
8081

8182

8283
@dataclass
8384
class TestConfig:
8485
"""Configuration settings for a single run of the `test_typeshed` function."""
8586

8687
verbose: int
87-
exclude: list[str] | None
88-
major: MajorVersion
89-
minor: MinorVersion
90-
directories: frozenset[Directory]
88+
filter: list[Path]
89+
exclude: list[Path]
90+
version: VersionString
9191
platform: Platform
92-
filter: list[str]
9392

9493

9594
def log(args: TestConfig, *varargs: object) -> None:
@@ -98,39 +97,35 @@ def log(args: TestConfig, *varargs: object) -> None:
9897

9998

10099
def match(path: Path, args: TestConfig) -> bool:
101-
fn = str(path)
102-
if not args.filter and not args.exclude:
103-
log(args, fn, "accept by default")
104-
return True
105-
if args.exclude:
106-
for f in args.exclude:
107-
if re.search(f, fn):
108-
log(args, fn, "excluded by pattern", f)
109-
return False
110-
if args.filter:
111-
for f in args.filter:
112-
if re.search(f, fn):
113-
log(args, fn, "accepted by pattern", f)
114-
return True
115-
if args.filter:
116-
log(args, fn, "rejected (no pattern matches)")
117-
return False
118-
log(args, fn, "accepted (no exclude pattern matches)")
119-
return True
120-
121-
122-
_VERSION_LINE_RE = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_.]*): ([23]\.\d{1,2})-([23]\.\d{1,2})?$")
123-
124-
125-
def parse_versions(fname: StrPath) -> dict[str, tuple[MinVersion, MaxVersion]]:
100+
for excluded_path in args.exclude:
101+
if path == excluded_path:
102+
log(args, path, "explicitly excluded")
103+
return False
104+
if excluded_path in path.parents:
105+
log(args, path, f'is in an explicitly excluded directory "{excluded_path}"')
106+
return False
107+
for included_path in args.filter:
108+
if path == included_path:
109+
log(args, path, "was explicitly included")
110+
return True
111+
if included_path in path.parents:
112+
log(args, path, f'is in an explicitly included directory "{included_path}"')
113+
return True
114+
log_msg = (
115+
f'is implicitly excluded: was not in any of the directories or paths specified on the command line: "{args.filter!r}"'
116+
)
117+
log(args, path, log_msg)
118+
return False
119+
120+
121+
def parse_versions(fname: StrPath) -> dict[str, tuple[VersionTuple, VersionTuple]]:
126122
result = {}
127123
with open(fname) as f:
128124
for line in f:
129-
# Allow having some comments or empty lines.
130-
line = line.split("#")[0].strip()
125+
line = strip_comments(line)
131126
if line == "":
132127
continue
133-
m = _VERSION_LINE_RE.match(line)
128+
m = VERSION_LINE_RE.match(line)
134129
assert m, f"invalid VERSIONS line: {line}"
135130
mod: str = m.group(1)
136131
min_version = parse_version(m.group(2))
@@ -151,10 +146,11 @@ def parse_version(v_str: str) -> tuple[int, int]:
151146
def add_files(files: list[Path], seen: set[str], module: Path, args: TestConfig) -> None:
152147
"""Add all files in package or module represented by 'name' located in 'root'."""
153148
if module.is_file() and module.suffix == ".pyi":
154-
files.append(module)
155-
seen.add(module.stem)
149+
if match(module, args):
150+
files.append(module)
151+
seen.add(module.stem)
156152
else:
157-
to_add = sorted(module.rglob("*.pyi"))
153+
to_add = sorted(file for file in module.rglob("*.pyi") if match(file, args))
158154
files.extend(to_add)
159155
seen.update(path.stem for path in to_add)
160156

@@ -239,7 +235,7 @@ def run_mypy(args: TestConfig, configurations: list[MypyDistConf], files: list[P
239235
def get_mypy_flags(args: TestConfig, temp_name: str) -> list[str]:
240236
return [
241237
"--python-version",
242-
f"{args.major}.{args.minor}",
238+
args.version,
243239
"--show-traceback",
244240
"--warn-incomplete-stub",
245241
"--show-error-codes",
@@ -323,7 +319,8 @@ def test_stdlib(code: int, args: TestConfig) -> TestResults:
323319
if name == "VERSIONS" or name.startswith("."):
324320
continue
325321
module = Path(name).stem
326-
if supported_versions[module][0] <= (args.major, args.minor) <= supported_versions[module][1]:
322+
module_min_version, module_max_version = supported_versions[module]
323+
if module_min_version <= tuple(map(int, args.version.split("."))) <= module_max_version:
327324
add_files(files, seen, (stdlib / name), args)
328325

329326
if files:
@@ -354,14 +351,15 @@ def test_third_party_stubs(code: int, args: TestConfig) -> TestResults:
354351

355352

356353
def test_typeshed(code: int, args: TestConfig) -> TestResults:
357-
print(f"*** Testing Python {args.major}.{args.minor} on {args.platform}")
354+
print(f"*** Testing Python {args.version} on {args.platform}")
358355
files_checked_this_version = 0
359-
if "stdlib" in args.directories:
356+
stdlib_dir, stubs_dir = Path("stdlib"), Path("stubs")
357+
if stdlib_dir in args.filter or any(stdlib_dir in path.parents for path in args.filter):
360358
code, stdlib_files_checked = test_stdlib(code, args)
361359
files_checked_this_version += stdlib_files_checked
362360
print()
363361

364-
if "stubs" in args.directories:
362+
if stubs_dir in args.filter or any(stubs_dir in path.parents for path in args.filter):
365363
code, third_party_files_checked = test_third_party_stubs(code, args)
366364
files_checked_this_version += third_party_files_checked
367365
print()
@@ -373,19 +371,12 @@ def main() -> None:
373371
args = parser.parse_args(namespace=CommandLineArgs())
374372
versions = args.python_version or SUPPORTED_VERSIONS
375373
platforms = args.platform or [sys.platform]
376-
tested_directories = frozenset(args.dir) if args.dir else TYPESHED_DIRECTORIES
374+
filter = args.filter or DIRECTORIES_TO_TEST
375+
exclude = args.exclude or []
377376
code = 0
378377
total_files_checked = 0
379-
for (major, minor), platform in product(versions, platforms):
380-
config = TestConfig(
381-
verbose=args.verbose,
382-
exclude=args.exclude,
383-
major=major,
384-
minor=minor,
385-
directories=tested_directories,
386-
platform=platform,
387-
filter=args.filter,
388-
)
378+
for version, platform in product(versions, platforms):
379+
config = TestConfig(args.verbose, filter, exclude, version, platform)
389380
code, files_checked_this_version = test_typeshed(code, args=config)
390381
total_files_checked += files_checked_this_version
391382
if code:

tests/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
"""Utilities that are imported by multiple scripts in the tests directory."""
22

33
import os
4+
import re
45
from functools import cache
56
from pathlib import Path
67
from typing import NamedTuple
78

89
import tomli
910

11+
12+
def strip_comments(text: str) -> str:
13+
return text.split("#")[0].strip()
14+
15+
1016
try:
1117
from termcolor import colored as colored
1218
except ImportError:
@@ -46,6 +52,14 @@ def read_dependencies(distribution: str) -> tuple[str, ...]:
4652
return tuple(dependencies)
4753

4854

55+
# ====================================================================
56+
# Parsing the stdlib/VERSIONS file
57+
# ====================================================================
58+
59+
60+
VERSIONS_RE = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_.]*): ([23]\.\d{1,2})-([23]\.\d{1,2})?$")
61+
62+
4963
# ====================================================================
5064
# Getting test-case directories from package names
5165
# ====================================================================

0 commit comments

Comments
 (0)