1212import os .path
1313import shutil
1414import sys
15+ import tempfile
1516import time
16- from shutil import which
17-
18-
19- from tests .utils import run_command
17+ from concurrent .futures import ThreadPoolExecutor
2018
19+ from tests .utils import get_output , run_command
2120
2221TEST_DIR = os .path .join (os .path .dirname (__file__ ), 'tests' )
2322TEST_COMPAT = os .path .join (TEST_DIR , "test_pythoncapi_compat.py" )
5352)
5453
5554
56- def run_tests_exe (executable , verbose , tested ):
57- tested_key = os .path .realpath (executable )
58- if tested_key in tested :
59- return
60-
55+ def get_test_command (executable , verbose , build_dir ):
6156 # Don't use realpath() for the executed command to support virtual
6257 # environments
63- cmd = [executable , TEST_COMPAT ]
58+ cmd = [executable , "-u" , TEST_COMPAT , build_dir ]
6459 if verbose :
6560 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 )
61+ return cmd
7662
7763
7864def parse_args ():
@@ -82,39 +68,88 @@ def parse_args():
8268 parser .add_argument ('-c' , '--current' , action = "store_true" ,
8369 help = "Only test the current Python executable "
8470 "(don't test multiple Python versions)" )
71+ parser .add_argument ('-j' , '--jobs' , type = int ,
72+ help = "Number of jobs run in parallel" )
8573 return parser .parse_args ()
8674
8775
88- def main ():
89- start_time = time .perf_counter ()
90- args = parse_args ()
76+ def run_tests_parallel (args ):
77+ jobs = []
78+
79+ tested = set ()
80+ tested_key = os .path .realpath (sys .executable )
81+ tested .add (tested_key )
82+ jobs .append (sys .executable )
83+
84+ for python in PYTHONS :
85+ executable = shutil .which (python )
86+ if not executable :
87+ print (f"Ignore missing Python executable: { python } " )
88+ continue
89+ tested_key = os .path .realpath (executable )
90+ if tested_key in tested :
91+ continue
92+ tested .add (tested_key )
93+ jobs .append (executable )
94+
95+ def worker (executable ):
96+ with tempfile .TemporaryDirectory () as build_dir :
97+ cmd = get_test_command (executable , args .verbose , build_dir )
98+ return get_output (cmd )
99+
100+ max_workers = args .jobs
101+ if not max_workers :
102+ if hasattr (os , 'process_cpu_count' ):
103+ max_workers = os .process_cpu_count ()
104+ else :
105+ max_workers = os .cpu_count ()
106+
107+ print ()
108+ print (f"Run { len (jobs )} jobs with { max_workers } workers (threads)" )
109+ print ()
110+
111+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
112+ exitcode = None
113+ for exitcode , stdout in executor .map (worker , jobs ):
114+ print (stdout , end = '' , flush = True )
115+ if exitcode :
116+ break
91117
92- path = os . path . join ( TEST_DIR , 'build' )
93- if os . path . exists ( path ):
94- shutil . rmtree ( path )
118+ if exitcode :
119+ executor . shutdown ( wait = True , cancel_futures = True )
120+ sys . exit ( exitcode )
95121
122+ print ()
123+ print (f"Tested: { len (jobs )} Python executables" )
124+
125+
126+ def test_upgrade_pythoncapi (args ):
96127 # 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' )
128+ print (f"Run { TEST_UPGRADE } " )
129+ cmd = [sys .executable , "-u" , TEST_UPGRADE ]
130+ if args .verbose :
131+ cmd .append ('-v' )
132+ run_command (cmd )
133+ print ()
134+
135+
136+ def test_current_version (args ):
137+ with tempfile .TemporaryDirectory () as build_dir :
138+ cmd = get_test_command (sys .executable , args .verbose , build_dir )
102139 run_command (cmd )
103- else :
104- print ("Don't test upgrade_pythoncapi.py: it requires Python 3.6" )
105140 print ()
106141
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 )
112142
113- print ()
114- print ("Tested: %s Python executables" % len (tested ))
143+ def main ():
144+ start_time = time .perf_counter ()
145+ args = parse_args ()
146+
147+ test_upgrade_pythoncapi (args )
148+
149+ if not args .current :
150+ run_tests_parallel (args )
115151 else :
116- run_tests_exe (sys .executable , args .verbose , tested )
117- print ()
152+ test_current_version (args )
118153
119154 dt = time .perf_counter () - start_time
120155 print (f"Total time: { dt :.1f} seconds" )
0 commit comments