1313import shutil
1414import sys
1515import time
16- from shutil import which
17-
18-
19- from tests .utils import run_command
16+ from concurrent .futures import ThreadPoolExecutor
2017
18+ from tests .utils import get_output , run_command
2119
2220TEST_DIR = os .path .join (os .path .dirname (__file__ ), 'tests' )
2321TEST_COMPAT = os .path .join (TEST_DIR , "test_pythoncapi_compat.py" )
5351)
5452
5553
56- def run_tests_exe (executable , verbose , tested ):
57- tested_key = os .path .realpath (executable )
58- if tested_key in tested :
59- return
60-
54+ def get_test_command (executable , verbose ):
6155 # Don't use realpath() for the executed command to support virtual
6256 # environments
6357 cmd = [executable , TEST_COMPAT ]
6458 if verbose :
6559 cmd .append ('-v' )
66- run_command (cmd )
67- tested .add (tested_key )
68-
69-
70- def run_tests (python , verbose , tested ):
71- executable = which (python )
72- if not executable :
73- print ("Ignore missing Python executable: %s" % python )
74- return
75- run_tests_exe (executable , verbose , tested )
60+ return cmd
7661
7762
7863def parse_args ():
@@ -85,35 +70,74 @@ def parse_args():
8570 return parser .parse_args ()
8671
8772
88- def main ():
89- start_time = time .perf_counter ()
90- args = parse_args ()
73+ def run_tests_parallel (args ):
74+ jobs = []
75+
76+ tested = set ()
77+ tested_key = os .path .realpath (sys .executable )
78+ tested .add (tested_key )
79+ jobs .append (sys .executable )
80+
81+ for python in PYTHONS :
82+ executable = shutil .which (python )
83+ if not executable :
84+ print (f"Ignore missing Python executable: { python } " )
85+ continue
86+ tested_key = os .path .realpath (executable )
87+ if tested_key in tested :
88+ continue
89+ tested .add (tested_key )
90+ jobs .append (executable )
91+
92+ def worker (executable ):
93+ cmd = get_test_command (executable , args .verbose )
94+ return get_output (cmd )
95+
96+ if hasattr (os , 'process_cpu_count' ):
97+ max_workers = os .process_cpu_count ()
98+ else :
99+ max_workers = os .cpu_count ()
100+
101+ print ()
102+ print (f"Run { len (jobs )} jobs with { max_workers } workers (threads)" )
103+ print ()
104+
105+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
106+ exitcode = None
107+ for exitcode , stdout in executor .map (worker , jobs ):
108+ print (stdout , end = '' )
109+ if exitcode :
110+ break
91111
92- path = os . path . join ( TEST_DIR , 'build' )
93- if os . path . exists ( path ):
94- shutil . rmtree ( path )
112+ if exitcode :
113+ executor . shutdown ( wait = True , cancel_futures = True )
114+ sys . exit ( exitcode )
95115
116+ print ()
117+ print ("Tested: {len(jobs)} Python executables" )
118+
119+
120+ def test_upgrade_pythoncapi (args ):
96121 # upgrade_pythoncapi.py requires Python 3.6 or newer
97- if sys .version_info >= (3 , 6 ):
98- print ("Run %s" % TEST_UPGRADE )
99- cmd = [sys .executable , TEST_UPGRADE ]
100- if args .verbose :
101- cmd .append ('-v' )
102- run_command (cmd )
103- else :
104- print ("Don't test upgrade_pythoncapi.py: it requires Python 3.6" )
122+ print (f"Run { TEST_UPGRADE } " )
123+ cmd = [sys .executable , TEST_UPGRADE ]
124+ if args .verbose :
125+ cmd .append ('-v' )
126+ run_command (cmd )
105127 print ()
106128
107- tested = set ()
108- if not args .current :
109- for python in PYTHONS :
110- run_tests (python , args .verbose , tested )
111- run_tests_exe (sys .executable , args .verbose , tested )
112129
113- print ()
114- print ("Tested: %s Python executables" % len (tested ))
130+ def main ():
131+ start_time = time .perf_counter ()
132+ args = parse_args ()
133+
134+ test_upgrade_pythoncapi (args )
135+
136+ if not args .current :
137+ run_tests_parallel (args )
115138 else :
116- run_tests_exe (sys .executable , args .verbose , tested )
139+ cmd = get_test_command (sys .executable , args .verbose )
140+ run_command (cmd )
117141 print ()
118142
119143 dt = time .perf_counter () - start_time
0 commit comments