18
18
PackageInfo ,
19
19
colored ,
20
20
get_all_testcase_directories ,
21
+ get_mypy_req ,
21
22
get_recursive_requirements ,
23
+ make_venv ,
22
24
print_error ,
23
25
print_success_msg ,
24
26
testcase_dir_from_package_name ,
@@ -85,14 +87,66 @@ def package_with_test_cases(package_name: str) -> PackageInfo:
85
87
)
86
88
87
89
88
- def test_testcase_directory (package : PackageInfo , version : str , platform : str , quiet : bool ) -> ReturnCode :
89
- package_name , test_case_directory = package
90
- is_stdlib = package_name == "stdlib"
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
+ shutil .copytree (package .test_case_directory , new_test_case_dir )
96
+ env_vars = dict (os .environ )
97
+ if package .is_stdlib :
98
+ flags .extend (["--no-site-packages" , "--custom-typeshed-dir" , str (Path (__file__ ).parent .parent )])
99
+ 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 set (requirements .typeshed_pkgs ) | {package .name , "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
+ try :
121
+ subprocess .run ([pip_exe , "install" , get_mypy_req (), * requirements .external_pkgs ], check = True , capture_output = True )
122
+ except subprocess .CalledProcessError as e :
123
+ print (e .stderr )
124
+ raise
125
+ else :
126
+ flags .append ("--no-site-packages" )
127
+
128
+ env_vars ["MYPYPATH" ] = os .pathsep .join (map (str , new_typeshed .glob ("stubs/*" )))
129
+ flags .extend (["--custom-typeshed-dir" , str (new_typeshed )])
130
+
131
+ # If the test-case filename ends with -py39,
132
+ # only run the test if --python-version was set to 3.9 or higher (for example)
133
+ for path in new_test_case_dir .rglob ("*.py" ):
134
+ if match := re .fullmatch (r".*-py3(\d{1,2})" , path .stem ):
135
+ minor_version_required = int (match [1 ])
136
+ assert f"3.{ minor_version_required } " in SUPPORTED_VERSIONS
137
+ if minor_version_required <= python_minor_version :
138
+ flags .append (str (path ))
139
+ else :
140
+ flags .append (str (path ))
141
+
142
+ return new_test_case_dir , subprocess .run ([python_exe , "-m" , "mypy" , * flags ], capture_output = True , text = True , env = env_vars )
91
143
144
+
145
+ def test_testcase_directory (package : PackageInfo , version : str , platform : str , quiet : bool ) -> ReturnCode :
92
146
msg = f"Running mypy --platform { platform } --python-version { version } on the "
93
- msg += "standard library test cases..." if is_stdlib else f"test cases for { package_name !r} ..."
147
+ msg += "standard library test cases..." if package . is_stdlib else f"test cases for { package . name !r} ..."
94
148
if not quiet :
95
- print (msg , end = " " )
149
+ print (msg , end = " " , flush = True )
96
150
97
151
# "--enable-error-code ignore-without-code" is purposefully ommited. See https://github.com/python/typeshed/pull/8083
98
152
flags = [
@@ -103,53 +157,17 @@ def test_testcase_directory(package: PackageInfo, version: str, platform: str, q
103
157
"--no-error-summary" ,
104
158
"--platform" ,
105
159
platform ,
106
- "--no-site-packages" ,
107
160
"--strict" ,
108
161
"--pretty" ,
109
162
]
110
163
111
164
# --warn-unused-ignores doesn't work for files inside typeshed.
112
- # SO, to work around this, we copy the test_cases directory into a TemporaryDirectory.
165
+ # SO, to work around this, we copy the test_cases directory into a TemporaryDirectory,
166
+ # and run the test cases inside of that.
113
167
with tempfile .TemporaryDirectory () as td :
114
- td_path = Path (td )
115
- new_test_case_dir = td_path / "test_cases"
116
- shutil .copytree (test_case_directory , new_test_case_dir )
117
- env_vars = dict (os .environ )
118
- if is_stdlib :
119
- flags .extend (["--custom-typeshed-dir" , str (Path (__file__ ).parent .parent )])
120
- else :
121
- # HACK: we want to run these test cases in an isolated environment --
122
- # we want mypy to see all stub packages listed in the "requires" field of METADATA.toml
123
- # (and all stub packages required by those stub packages, etc. etc.),
124
- # but none of the other stubs in typeshed.
125
- #
126
- # The best way of doing that without stopping --warn-unused-ignore from working
127
- # seems to be to create a "new typeshed" directory in a tempdir
128
- # that has only the required stubs copied over.
129
- new_typeshed = td_path / "typeshed"
130
- os .mkdir (new_typeshed )
131
- shutil .copytree (Path ("stdlib" ), new_typeshed / "stdlib" )
132
- requirements = get_recursive_requirements (package_name )
133
- # mypy refuses to consider a directory a "valid typeshed directory"
134
- # unless there's a stubs/mypy-extensions path inside it,
135
- # so add that to the list of stubs to copy over to the new directory
136
- for requirement in requirements + ["mypy-extensions" ]:
137
- shutil .copytree (Path ("stubs" , requirement ), new_typeshed / "stubs" / requirement )
138
- env_vars ["MYPYPATH" ] = os .pathsep .join (map (str , new_typeshed .glob ("stubs/*" )))
139
- flags .extend (["--custom-typeshed-dir" , str (td_path / "typeshed" )])
140
-
141
- # If the test-case filename ends with -py39,
142
- # only run the test if --python-version was set to 3.9 or higher (for example)
143
- for path in new_test_case_dir .rglob ("*.py" ):
144
- if match := re .fullmatch (r".*-py3(\d{1,2})" , path .stem ):
145
- minor_version_required = int (match [1 ])
146
- assert f"3.{ minor_version_required } " in SUPPORTED_VERSIONS
147
- if minor_version_required <= int (version .split ("." )[1 ]):
148
- flags .append (str (path ))
149
- else :
150
- flags .append (str (path ))
151
-
152
- result = subprocess .run ([sys .executable , "-m" , "mypy" , * flags ], capture_output = True , env = env_vars )
168
+ new_test_case_dir , result = run_testcases (
169
+ package = package , flags = flags , tmpdir_path = Path (td ), python_minor_version = int (version .split ("." )[1 ])
170
+ )
153
171
154
172
if result .returncode :
155
173
if quiet :
@@ -158,11 +176,11 @@ def test_testcase_directory(package: PackageInfo, version: str, platform: str, q
158
176
# If there are errors, the output is inscrutable if this isn't printed.
159
177
print (msg , end = " " )
160
178
print_error ("failure\n " )
161
- replacements = (str (new_test_case_dir ), str (test_case_directory ))
179
+ replacements = (str (new_test_case_dir ), str (package . test_case_directory ))
162
180
if result .stderr :
163
- print_error (result .stderr . decode () , fix_path = replacements )
181
+ print_error (result .stderr , fix_path = replacements )
164
182
if result .stdout :
165
- print_error (result .stdout . decode () , fix_path = replacements )
183
+ print_error (result .stdout , fix_path = replacements )
166
184
elif not quiet :
167
185
print_success_msg ()
168
186
return result .returncode
0 commit comments