forked from python/typeshed
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpytype_test.py
More file actions
executable file
·202 lines (163 loc) · 6.77 KB
/
Copy pathpytype_test.py
File metadata and controls
executable file
·202 lines (163 loc) · 6.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env python3
"""Test runner for typeshed.
Depends on pytype being installed.
If pytype is installed:
1. For every pyi, do nothing if it is in pytype_exclude_list.txt.
2. Otherwise, call 'pytype.io.parse_pyi'.
Option two will load the file and all the builtins, typeshed dependencies. This
will also discover incorrect usage of imported modules.
"""
import argparse
import os
import re
import sys
import traceback
from typing import List, Match, Optional, Sequence, Tuple
from pytype import config as pytype_config, io as pytype_io
TYPESHED_SUBDIRS = ["stdlib", "third_party"]
TYPESHED_HOME = "TYPESHED_HOME"
UNSET = object() # marker for tracking the TYPESHED_HOME environment variable
def main() -> None:
args = create_parser().parse_args()
typeshed_location = args.typeshed_location or os.getcwd()
subdir_paths = [os.path.join(typeshed_location, d) for d in TYPESHED_SUBDIRS]
check_subdirs_discoverable(subdir_paths)
files_to_test = determine_files_to_test(typeshed_location=typeshed_location, paths=args.files or subdir_paths)
run_all_tests(
files_to_test=files_to_test,
typeshed_location=typeshed_location,
print_stderr=args.print_stderr,
dry_run=args.dry_run,
)
def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Pytype/typeshed tests.")
parser.add_argument("-n", "--dry-run", action="store_true", default=False, help="Don't actually run tests")
# Default to '' so that symlinking typeshed subdirs in cwd will work.
parser.add_argument("--typeshed-location", type=str, default="", help="Path to typeshed installation.")
# Set to true to print a stack trace every time an exception is thrown.
parser.add_argument(
"--print-stderr", action="store_true", default=False, help="Print stderr every time an error is encountered."
)
parser.add_argument(
"files",
metavar="FILE",
type=str,
nargs="*",
help="Files or directories to check. (Default: Check all files.)",
)
return parser
class PathMatcher:
def __init__(self, patterns: Sequence[str]) -> None:
patterns = [re.escape(os.path.join(*x.split("/"))) for x in patterns]
self.matcher = re.compile(r"({})$".format("|".join(patterns))) if patterns else None
def search(self, path: str) -> Optional[Match[str]]:
if not self.matcher:
return None
return self.matcher.search(path)
def load_exclude_list(typeshed_location: str) -> List[str]:
filename = os.path.join(typeshed_location, "tests", "pytype_exclude_list.txt")
skip_re = re.compile(r"^\s*([^\s#]+)\s*(?:#.*)?$")
skip = []
with open(filename) as f:
for line in f:
skip_match = skip_re.match(line)
if skip_match:
skip.append(skip_match.group(1))
return skip
def run_pytype(*, filename: str, python_version: str, typeshed_location: str) -> Optional[str]:
"""Runs pytype, returning the stderr if any."""
options = pytype_config.Options.create(
filename, module_name=_get_module_name(filename), parse_pyi=True, python_version=python_version
)
old_typeshed_home = os.environ.get(TYPESHED_HOME, UNSET)
os.environ[TYPESHED_HOME] = typeshed_location
try:
pytype_io.parse_pyi(options)
except Exception:
stderr = traceback.format_exc()
else:
stderr = None
if old_typeshed_home is UNSET:
del os.environ[TYPESHED_HOME]
else:
os.environ[TYPESHED_HOME] = old_typeshed_home
return stderr
def _get_relative(filename: str) -> str:
top = 0
for d in TYPESHED_SUBDIRS:
try:
top = filename.index(d)
except ValueError:
continue
else:
break
return filename[top:]
def _get_module_name(filename: str) -> str:
"""Converts a filename {subdir}/m.n/module/foo to module.foo."""
return ".".join(_get_relative(filename).split(os.path.sep)[2:]).replace(".pyi", "").replace(".__init__", "")
def _is_version(path: str, version: str) -> bool:
return any("{}{}{}".format(d, os.path.sep, version) in path for d in TYPESHED_SUBDIRS)
def check_subdirs_discoverable(subdir_paths: List[str]) -> None:
for p in subdir_paths:
if not os.path.isdir(p):
raise SystemExit("Cannot find typeshed subdir at {} (specify parent dir via --typeshed-location)".format(p))
def determine_files_to_test(*, typeshed_location: str, paths: Sequence[str]) -> List[Tuple[str, int]]:
"""Determine all files to test, checking if it's in the exclude list and which Python versions to use.
Returns a list of pairs of the file path and Python version as an int."""
skipped = PathMatcher(load_exclude_list(typeshed_location))
filenames = find_stubs_in_paths(paths)
files = []
for f in sorted(filenames):
rel = _get_relative(f)
if skipped.search(rel):
continue
if _is_version(f, "2and3"):
files.append((f, 2))
files.append((f, 3))
elif _is_version(f, "2"):
files.append((f, 2))
elif _is_version(f, "3"):
files.append((f, 3))
else:
print("Unrecognized path: {}".format(f))
return files
def find_stubs_in_paths(paths: Sequence[str]) -> List[str]:
filenames = []
for path in paths:
if os.path.isdir(path):
for root, _, fns in os.walk(path):
filenames.extend(os.path.join(root, fn) for fn in fns if fn.endswith(".pyi"))
else:
filenames.append(path)
return filenames
def run_all_tests(*, files_to_test: Sequence[Tuple[str, int]], typeshed_location: str, print_stderr: bool, dry_run: bool) -> None:
bad = []
errors = 0
total_tests = len(files_to_test)
print("Testing files with pytype...")
for i, (f, version) in enumerate(files_to_test):
stderr = (
run_pytype(
filename=f,
python_version="2.7" if version == 2 else "{0.major}.{0.minor}".format(sys.version_info),
typeshed_location=typeshed_location,
)
if not dry_run
else None
)
if stderr:
if print_stderr:
print(stderr)
errors += 1
stacktrace_final_line = stderr.rstrip().rsplit("\n", 1)[-1]
bad.append((_get_relative(f), stacktrace_final_line))
runs = i + 1
if runs % 25 == 0:
print(" {:3d}/{:d} with {:3d} errors".format(runs, total_tests, errors))
print("Ran pytype with {:d} pyis, got {:d} errors.".format(total_tests, errors))
for f, err in bad:
print("{}: {}".format(f, err))
if errors:
raise SystemExit("\nRun again with --print-stderr to get the full stacktrace.")
if __name__ == "__main__":
main()