1313import sys
1414import tempfile
1515import threading
16+ from abc import ABCMeta , abstractmethod
1617from collections .abc import Callable , Generator
1718from contextlib import ExitStack , contextmanager , suppress
1819from dataclasses import dataclass
1920from enum import IntEnum
2021from functools import partial
2122from pathlib import Path
2223from typing import TypeAlias
24+ from typing_extensions import override
2325
2426from ts_utils .metadata import get_recursive_requirements , read_metadata
2527from ts_utils .mypy import mypy_configuration_from_distribution , temporary_mypy_config_file
3335 get_all_testcase_directories ,
3436 get_mypy_req ,
3537 print_error ,
38+ print_skipped ,
3639 venv_python ,
3740)
3841
@@ -167,7 +170,7 @@ def setup_testcase_dir(package: DistributionTests, tempdir: Path, verbosity: Ver
167170
168171def run_testcases (
169172 package : DistributionTests , version : str , platform : str , * , tempdir : Path , verbosity : Verbosity
170- ) -> subprocess .CompletedProcess [str ]:
173+ ) -> subprocess .CompletedProcess [str ] | None :
171174 env_vars = dict (os .environ )
172175 new_test_case_dir = tempdir / TEST_CASES_DIR
173176
@@ -177,7 +180,6 @@ def run_testcases(
177180 configurations = mypy_configuration_from_distribution (package .name )
178181
179182 with temporary_mypy_config_file (configurations ) as temp_config :
180-
181183 # "--enable-error-code ignore-without-code" is purposefully omitted.
182184 # See https://github.com/python/typeshed/pull/8083
183185 flags = [
@@ -216,18 +218,22 @@ def run_testcases(
216218
217219 flags .extend (["--custom-typeshed-dir" , str (custom_typeshed )])
218220
219- # If the test-case filename ends with -py39,
220- # only run the test if --python-version was set to 3.9 or higher (for example)
221+ # If the test-case filename ends with e.g. -py314,
222+ # only run the test if --python-version was set to 3.14 or higher (for example)
223+ files : list [str ] = []
221224 for path in new_test_case_dir .rglob ("*.py" ):
222- if match := re .fullmatch (r".*-py3(\d{1,2} )" , path .stem ):
225+ if match := re .fullmatch (r".*-py3(\d\d )" , path .stem ):
223226 minor_version_required = int (match [1 ])
224227 assert f"3.{ minor_version_required } " in SUPPORTED_VERSIONS
225228 python_minor_version = int (version .split ("." )[1 ])
226229 if minor_version_required > python_minor_version :
227230 continue
228- flags .append (str (path ))
231+ files .append (str (path ))
232+
233+ if len (files ) == 0 :
234+ return None
229235
230- mypy_command = [python_exe , "-m" , "mypy" , * flags ]
236+ mypy_command = [python_exe , "-m" , "mypy" , * flags , * files ]
231237 if verbosity is Verbosity .VERBOSE :
232238 description = f"{ package .name } /{ version } /{ platform } "
233239 msg = f"{ description } : { mypy_command = } \n "
@@ -241,15 +247,24 @@ def run_testcases(
241247
242248
243249@dataclass (frozen = True )
244- class Result :
250+ class Result ( metaclass = ABCMeta ) :
245251 code : int
252+
253+ @abstractmethod
254+ def print_description (self , verbosity : Verbosity ) -> None :
255+ raise NotImplementedError
256+
257+
258+ @dataclass (frozen = True )
259+ class RunResult (Result ):
246260 command_run : str
247261 stderr : str
248262 stdout : str
249263 test_case_dir : Path
250264 tempdir : Path
251265
252- def print_description (self ) -> None :
266+ @override
267+ def print_description (self , verbosity : Verbosity ) -> None :
253268 if self .code :
254269 print (f"{ self .command_run } :" , end = " " )
255270 print_error ("FAILURE\n " )
@@ -260,6 +275,18 @@ def print_description(self) -> None:
260275 print_error (self .stdout , fix_path = replacements )
261276
262277
278+ @dataclass (frozen = True )
279+ class NoTestsResult (Result ):
280+ package : str
281+ version : str
282+ platform : str
283+
284+ @override
285+ def print_description (self , verbosity : Verbosity ) -> None :
286+ if verbosity != Verbosity .QUIET :
287+ print_skipped (f"No test cases found for { self .package !r} on Python { self .version } for platform { self .platform !r} ." )
288+
289+
263290def test_testcase_directory (
264291 package : DistributionTests , version : str , platform : str , * , verbosity : Verbosity , tempdir : Path
265292) -> Result :
@@ -269,7 +296,10 @@ def test_testcase_directory(
269296 _PRINT_QUEUE .put (f"Running { msg } ..." )
270297
271298 proc_info = run_testcases (package = package , version = version , platform = platform , tempdir = tempdir , verbosity = verbosity )
272- return Result (
299+ if proc_info is None :
300+ return NoTestsResult (0 , package .name , version , platform )
301+
302+ return RunResult (
273303 code = proc_info .returncode ,
274304 command_run = msg ,
275305 stderr = proc_info .stderr ,
@@ -399,7 +429,7 @@ def main() -> ReturnCode:
399429 print ()
400430
401431 for result in results :
402- result .print_description ()
432+ result .print_description (verbosity )
403433
404434 code = max (result .code for result in results )
405435
0 commit comments