2525
2626import tomli
2727
28- from parse_metadata import PackageDependencies , get_recursive_requirements
28+ from parse_metadata import PackageDependencies , get_recursive_requirements , read_metadata
2929from utils import (
30+ PYTHON_VERSION ,
3031 VERSIONS_RE as VERSION_LINE_RE ,
3132 VenvInfo ,
3233 colored ,
@@ -307,6 +308,7 @@ def add_third_party_files(
307308class TestResults (NamedTuple ):
308309 exit_code : int
309310 files_checked : int
311+ packages_skipped : int = 0
310312
311313
312314def test_third_party_distribution (
@@ -393,6 +395,9 @@ def install_requirements_for_venv(venv_info: VenvInfo, args: TestConfig, externa
393395
394396def setup_virtual_environments (distributions : dict [str , PackageDependencies ], args : TestConfig , tempdir : Path ) -> None :
395397 """Logic necessary for testing stubs with non-types dependencies in isolated environments."""
398+ if not distributions :
399+ return # hooray! Nothing to do
400+
396401 # STAGE 1: Determine which (if any) stubs packages require virtual environments.
397402 # Group stubs packages according to their external-requirements sets
398403
@@ -471,6 +476,7 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
471476def test_third_party_stubs (code : int , args : TestConfig , tempdir : Path ) -> TestResults :
472477 print ("Testing third-party packages..." )
473478 files_checked = 0
479+ packages_skipped = 0
474480 gitignore_spec = get_gitignore_spec ()
475481 distributions_to_check : dict [str , PackageDependencies ] = {}
476482
@@ -480,47 +486,73 @@ def test_third_party_stubs(code: int, args: TestConfig, tempdir: Path) -> TestRe
480486 if spec_matches_path (gitignore_spec , distribution_path ):
481487 continue
482488
489+ metadata = read_metadata (distribution )
490+ if not metadata .requires_python .contains (PYTHON_VERSION ):
491+ msg = (
492+ f"skipping { distribution !r} (requires Python { metadata .requires_python } ; "
493+ f"test is being run using Python { PYTHON_VERSION } )"
494+ )
495+ print (colored (msg , "yellow" ))
496+ packages_skipped += 1
497+ continue
498+ if not metadata .requires_python .contains (args .version ):
499+ msg = f"skipping { distribution !r} for target Python { args .version } (requires Python { metadata .requires_python } )"
500+ print (colored (msg , "yellow" ))
501+ packages_skipped += 1
502+ continue
503+
483504 if (
484505 distribution_path in args .filter
485506 or Path ("stubs" ) in args .filter
486507 or any (distribution_path in path .parents for path in args .filter )
487508 ):
488509 distributions_to_check [distribution ] = get_recursive_requirements (distribution )
489510
490- # If it's the first time test_third_party_stubs() has been called during this session,
491- # setup the necessary virtual environments for testing the third-party stubs.
492- # It should only be necessary to call setup_virtual_environments() once per session.
493- if not _DISTRIBUTION_TO_VENV_MAPPING :
494- setup_virtual_environments (distributions_to_check , args , tempdir )
495-
496- assert _DISTRIBUTION_TO_VENV_MAPPING .keys () == distributions_to_check .keys ()
497-
498- for distribution , venv_info in _DISTRIBUTION_TO_VENV_MAPPING .items ():
511+ # Setup the necessary virtual environments for testing the third-party stubs.
512+ # Note that some stubs may not be tested on all Python versions
513+ # (due to version incompatibilities),
514+ # so we can't guarantee that setup_virtual_environments()
515+ # will only be called once per session.
516+ distributions_without_venv = {
517+ distribution : requirements
518+ for distribution , requirements in distributions_to_check .items ()
519+ if distribution not in _DISTRIBUTION_TO_VENV_MAPPING
520+ }
521+ setup_virtual_environments (distributions_without_venv , args , tempdir )
522+
523+ # Check that there is a venv for every distribution we're testing.
524+ # Some venvs may exist from previous runs but are skipped in this run.
525+ assert _DISTRIBUTION_TO_VENV_MAPPING .keys () >= distributions_to_check .keys ()
526+
527+ for distribution in distributions_to_check :
528+ venv_info = _DISTRIBUTION_TO_VENV_MAPPING [distribution ]
499529 non_types_dependencies = venv_info .python_exe != sys .executable
500- this_code , checked = test_third_party_distribution (
530+ this_code , checked , _ = test_third_party_distribution (
501531 distribution , args , venv_info = venv_info , non_types_dependencies = non_types_dependencies
502532 )
503533 code = max (code , this_code )
504534 files_checked += checked
505535
506- return TestResults (code , files_checked )
536+ return TestResults (code , files_checked , packages_skipped )
507537
508538
509539def test_typeshed (code : int , args : TestConfig , tempdir : Path ) -> TestResults :
510540 print (f"*** Testing Python { args .version } on { args .platform } " )
511541 files_checked_this_version = 0
542+ packages_skipped_this_version = 0
512543 stdlib_dir , stubs_dir = Path ("stdlib" ), Path ("stubs" )
513544 if stdlib_dir in args .filter or any (stdlib_dir in path .parents for path in args .filter ):
514- code , stdlib_files_checked = test_stdlib (code , args )
545+ code , stdlib_files_checked , _ = test_stdlib (code , args )
515546 files_checked_this_version += stdlib_files_checked
516547 print ()
517548
518549 if stubs_dir in args .filter or any (stubs_dir in path .parents for path in args .filter ):
519- code , third_party_files_checked = test_third_party_stubs (code , args , tempdir )
550+ code , third_party_files_checked , third_party_packages_skipped = test_third_party_stubs (code , args , tempdir )
520551 files_checked_this_version += third_party_files_checked
552+ packages_skipped_this_version = third_party_packages_skipped
521553 print ()
522554
523- return TestResults (code , files_checked_this_version )
555+ return TestResults (code , files_checked_this_version , packages_skipped_this_version )
524556
525557
526558def main () -> None :
@@ -531,19 +563,27 @@ def main() -> None:
531563 exclude = args .exclude or []
532564 code = 0
533565 total_files_checked = 0
566+ total_packages_skipped = 0
534567 with tempfile .TemporaryDirectory () as td :
535568 td_path = Path (td )
536569 for version , platform in product (versions , platforms ):
537570 config = TestConfig (args .verbose , filter , exclude , version , platform )
538- code , files_checked_this_version = test_typeshed (code , args = config , tempdir = td_path )
571+ code , files_checked_this_version , packages_skipped_this_version = test_typeshed (code , args = config , tempdir = td_path )
539572 total_files_checked += files_checked_this_version
573+ total_packages_skipped += packages_skipped_this_version
540574 if code :
541- print_error (f"--- exit status { code } , { total_files_checked } files checked ---" )
575+ plural = "" if total_files_checked == 1 else "s"
576+ print_error (f"--- exit status { code } , { total_files_checked } file{ plural } checked ---" )
542577 sys .exit (code )
543- if not total_files_checked :
578+ if total_packages_skipped :
579+ plural = "" if total_packages_skipped == 1 else "s"
580+ print (colored (f"--- { total_packages_skipped } package{ plural } skipped ---" , "yellow" ))
581+ if total_files_checked :
582+ plural = "" if total_files_checked == 1 else "s"
583+ print (colored (f"--- success, { total_files_checked } file{ plural } checked ---" , "green" ))
584+ else :
544585 print_error ("--- nothing to do; exit 1 ---" )
545586 sys .exit (1 )
546- print (colored (f"--- success, { total_files_checked } files checked ---" , "green" ))
547587
548588
549589if __name__ == "__main__" :
0 commit comments