16
16
17
17
from utils import (
18
18
PackageInfo ,
19
+ VenvInfo ,
19
20
colored ,
20
21
get_all_testcase_directories ,
21
22
get_mypy_req ,
28
29
29
30
ReturnCode : TypeAlias = int
30
31
32
+ TEST_CASES = "test_cases"
33
+ VENV_DIR = ".venv"
34
+ TYPESHED = "typeshed"
35
+
31
36
SUPPORTED_PLATFORMS = ["linux" , "darwin" , "win32" ]
32
37
SUPPORTED_VERSIONS = ["3.11" , "3.10" , "3.9" , "3.8" , "3.7" ]
33
38
@@ -36,7 +41,7 @@ def package_with_test_cases(package_name: str) -> PackageInfo:
36
41
"""Helper function for argument-parsing"""
37
42
38
43
if package_name == "stdlib" :
39
- return PackageInfo ("stdlib" , Path ("test_cases" ))
44
+ return PackageInfo ("stdlib" , Path (TEST_CASES ))
40
45
test_case_dir = testcase_dir_from_package_name (package_name )
41
46
if test_case_dir .is_dir ():
42
47
if not os .listdir (test_case_dir ):
@@ -87,90 +92,102 @@ def package_with_test_cases(package_name: str) -> PackageInfo:
87
92
)
88
93
89
94
90
- def run_testcases (
91
- package : PackageInfo , flags : list [str ], tmpdir_path : Path , python_minor_version : int
92
- ) -> tuple [Path , subprocess .CompletedProcess [str ]]:
93
- python_exe = sys .executable
94
- new_test_case_dir = tmpdir_path / "test_cases"
95
+ def setup_testcase_dir (package : PackageInfo , tempdir : Path , new_test_case_dir : Path ) -> None :
96
+ # --warn-unused-ignores doesn't work for files inside typeshed.
97
+ # SO, to work around this, we copy the test_cases directory into a TemporaryDirectory,
98
+ # and run the test cases inside of that.
95
99
shutil .copytree (package .test_case_directory , new_test_case_dir )
100
+ if package .is_stdlib :
101
+ return
102
+
103
+ # HACK: we want to run these test cases in an isolated environment --
104
+ # we want mypy to see all stub packages listed in the "requires" field of METADATA.toml
105
+ # (and all stub packages required by those stub packages, etc. etc.),
106
+ # but none of the other stubs in typeshed.
107
+ #
108
+ # The best way of doing that without stopping --warn-unused-ignore from working
109
+ # seems to be to create a "new typeshed" directory in a tempdir
110
+ # that has only the required stubs copied over.
111
+ new_typeshed = tempdir / TYPESHED
112
+ new_typeshed .mkdir ()
113
+ shutil .copytree (Path ("stdlib" ), new_typeshed / "stdlib" )
114
+ requirements = get_recursive_requirements (package .name )
115
+ # mypy refuses to consider a directory a "valid typeshed directory"
116
+ # unless there's a stubs/mypy-extensions path inside it,
117
+ # so add that to the list of stubs to copy over to the new directory
118
+ for requirement in {package .name , * requirements .typeshed_pkgs , "mypy-extensions" }:
119
+ shutil .copytree (Path ("stubs" , requirement ), new_typeshed / "stubs" / requirement )
120
+
121
+ if requirements .external_pkgs :
122
+ pip_exe = make_venv (tempdir / VENV_DIR ).pip_exe
123
+ pip_command = [pip_exe , "install" , get_mypy_req (), * requirements .external_pkgs ]
124
+ try :
125
+ subprocess .run (pip_command , check = True , capture_output = True , text = True )
126
+ except subprocess .CalledProcessError as e :
127
+ print (e .stderr )
128
+ raise
129
+
130
+
131
+ def run_testcases (package : PackageInfo , version : str , platform : str , * , tempdir : Path ) -> subprocess .CompletedProcess [str ]:
96
132
env_vars = dict (os .environ )
133
+ new_test_case_dir = tempdir / TEST_CASES
134
+ testcasedir_already_setup = new_test_case_dir .exists () and new_test_case_dir .is_dir ()
135
+
136
+ if not testcasedir_already_setup :
137
+ setup_testcase_dir (package , tempdir = tempdir , new_test_case_dir = new_test_case_dir )
138
+
139
+ # "--enable-error-code ignore-without-code" is purposefully ommited. See https://github.com/python/typeshed/pull/8083
140
+ flags = [
141
+ "--python-version" ,
142
+ version ,
143
+ "--show-traceback" ,
144
+ "--show-error-codes" ,
145
+ "--no-error-summary" ,
146
+ "--platform" ,
147
+ platform ,
148
+ "--strict" ,
149
+ "--pretty" ,
150
+ "--no-incremental" ,
151
+ ]
152
+
97
153
if package .is_stdlib :
98
- flags .extend (["--no-site-packages" , "--custom-typeshed-dir" , str (Path (__file__ ).parent .parent )])
154
+ python_exe = sys .executable
155
+ custom_typeshed = Path (__file__ ).parent .parent
156
+ flags .append ("--no-site-packages" )
99
157
else :
100
- # HACK: we want to run these test cases in an isolated environment --
101
- # we want mypy to see all stub packages listed in the "requires" field of METADATA.toml
102
- # (and all stub packages required by those stub packages, etc. etc.),
103
- # but none of the other stubs in typeshed.
104
- #
105
- # The best way of doing that without stopping --warn-unused-ignore from working
106
- # seems to be to create a "new typeshed" directory in a tempdir
107
- # that has only the required stubs copied over.
108
- new_typeshed = tmpdir_path / "typeshed"
109
- new_typeshed .mkdir ()
110
- shutil .copytree (Path ("stdlib" ), new_typeshed / "stdlib" )
111
- requirements = get_recursive_requirements (package .name )
112
- # mypy refuses to consider a directory a "valid typeshed directory"
113
- # unless there's a stubs/mypy-extensions path inside it,
114
- # so add that to the list of stubs to copy over to the new directory
115
- for requirement in {package .name , * requirements .typeshed_pkgs , "mypy-extensions" }:
116
- shutil .copytree (Path ("stubs" , requirement ), new_typeshed / "stubs" / requirement )
117
-
118
- if requirements .external_pkgs :
119
- pip_exe , python_exe = make_venv (tmpdir_path / ".venv" )
120
- pip_command = [pip_exe , "install" , get_mypy_req (), * requirements .external_pkgs ]
121
- try :
122
- subprocess .run (pip_command , check = True , capture_output = True , text = True )
123
- except subprocess .CalledProcessError as e :
124
- print (e .stderr )
125
- raise
158
+ custom_typeshed = tempdir / TYPESHED
159
+ env_vars ["MYPYPATH" ] = os .pathsep .join (map (str , custom_typeshed .glob ("stubs/*" )))
160
+ has_non_types_dependencies = (tempdir / VENV_DIR ).exists ()
161
+ if has_non_types_dependencies :
162
+ python_exe = VenvInfo .of_existing_venv (tempdir / VENV_DIR ).python_exe
126
163
else :
164
+ python_exe = sys .executable
127
165
flags .append ("--no-site-packages" )
128
166
129
- env_vars ["MYPYPATH" ] = os .pathsep .join (map (str , new_typeshed .glob ("stubs/*" )))
130
- flags .extend (["--custom-typeshed-dir" , str (new_typeshed )])
167
+ flags .extend (["--custom-typeshed-dir" , str (custom_typeshed )])
131
168
132
169
# If the test-case filename ends with -py39,
133
170
# only run the test if --python-version was set to 3.9 or higher (for example)
134
171
for path in new_test_case_dir .rglob ("*.py" ):
135
172
if match := re .fullmatch (r".*-py3(\d{1,2})" , path .stem ):
136
173
minor_version_required = int (match [1 ])
137
174
assert f"3.{ minor_version_required } " in SUPPORTED_VERSIONS
138
- if minor_version_required <= python_minor_version :
139
- flags . append ( str ( path ))
140
- else :
141
- flags .append (str (path ))
175
+ python_minor_version = int ( version . split ( "." )[ 1 ])
176
+ if minor_version_required > python_minor_version :
177
+ continue
178
+ flags .append (str (path ))
142
179
143
180
mypy_command = [python_exe , "-m" , "mypy" ] + flags
144
- result = subprocess .run (mypy_command , capture_output = True , text = True , env = env_vars )
145
- return new_test_case_dir , result
181
+ return subprocess .run (mypy_command , capture_output = True , text = True , env = env_vars )
146
182
147
183
148
- def test_testcase_directory (package : PackageInfo , version : str , platform : str , quiet : bool ) -> ReturnCode :
184
+ def test_testcase_directory (package : PackageInfo , version : str , platform : str , * , quiet : bool , tempdir : Path ) -> ReturnCode :
149
185
msg = f"Running mypy --platform { platform } --python-version { version } on the "
150
186
msg += "standard library test cases..." if package .is_stdlib else f"test cases for { package .name !r} ..."
151
187
if not quiet :
152
188
print (msg , end = " " , flush = True )
153
189
154
- # "--enable-error-code ignore-without-code" is purposefully ommited. See https://github.com/python/typeshed/pull/8083
155
- flags = [
156
- "--python-version" ,
157
- version ,
158
- "--show-traceback" ,
159
- "--show-error-codes" ,
160
- "--no-error-summary" ,
161
- "--platform" ,
162
- platform ,
163
- "--strict" ,
164
- "--pretty" ,
165
- ]
166
-
167
- # --warn-unused-ignores doesn't work for files inside typeshed.
168
- # SO, to work around this, we copy the test_cases directory into a TemporaryDirectory,
169
- # and run the test cases inside of that.
170
- with tempfile .TemporaryDirectory () as td :
171
- new_test_case_dir , result = run_testcases (
172
- package = package , flags = flags , tmpdir_path = Path (td ), python_minor_version = int (version .split ("." )[1 ])
173
- )
190
+ result = run_testcases (package = package , version = version , platform = platform , tempdir = tempdir )
174
191
175
192
if result .returncode :
176
193
if quiet :
@@ -179,7 +196,7 @@ def test_testcase_directory(package: PackageInfo, version: str, platform: str, q
179
196
# If there are errors, the output is inscrutable if this isn't printed.
180
197
print (msg , end = " " )
181
198
print_error ("failure\n " )
182
- replacements = (str (new_test_case_dir ), str (package .test_case_directory ))
199
+ replacements = (str (tempdir / TEST_CASES ), str (package .test_case_directory ))
183
200
if result .stderr :
184
201
print_error (result .stderr , fix_path = replacements )
185
202
if result .stdout :
@@ -204,8 +221,12 @@ def main() -> ReturnCode:
204
221
versions_to_test = args .versions_to_test or [f"3.{ sys .version_info [1 ]} " ]
205
222
206
223
code = 0
207
- for platform , version , directory in product (platforms_to_test , versions_to_test , testcase_directories ):
208
- code = max (code , test_testcase_directory (directory , version , platform , args .quiet ))
224
+ for testcase_dir in testcase_directories :
225
+ with tempfile .TemporaryDirectory () as td :
226
+ tempdir = Path (td )
227
+ for platform , version in product (platforms_to_test , versions_to_test ):
228
+ this_code = test_testcase_directory (testcase_dir , version , platform , quiet = args .quiet , tempdir = tempdir )
229
+ code = max (code , this_code )
209
230
if code :
210
231
print_error ("\n Test completed with errors" )
211
232
else :
0 commit comments