@@ -6,6 +6,9 @@ import sys
66import platform
77import argparse
88import re
9+ import threading
10+ import multiprocessing
11+ from multiprocessing .pool import ThreadPool
912from glob import glob
1013
1114# Tests require at least CPython 3.3. If your default python3 executable
@@ -197,13 +200,27 @@ def run_micropython(pyb, args, test_file, is_special=False):
197200def run_feature_check (pyb , args , base_path , test_file ):
198201 return run_micropython (pyb , args , base_path + "/feature_check/" + test_file , is_special = True )
199202
203+ class ThreadSafeCounter :
204+ def __init__ (self , start = 0 ):
205+ self ._value = start
206+ self ._lock = threading .Lock ()
200207
201- def run_tests (pyb , tests , args , base_path = "." ):
202- test_count = 0
203- testcase_count = 0
204- passed_count = 0
205- failed_tests = []
206- skipped_tests = []
208+ def add (self , to_add ):
209+ with self ._lock : self ._value += to_add
210+
211+ def append (self , arg ):
212+ self .add ([arg ])
213+
214+ @property
215+ def value (self ):
216+ return self ._value
217+
218+ def run_tests (pyb , tests , args , base_path = "." , num_threads = 1 ):
219+ test_count = ThreadSafeCounter ()
220+ testcase_count = ThreadSafeCounter ()
221+ passed_count = ThreadSafeCounter ()
222+ failed_tests = ThreadSafeCounter ([])
223+ skipped_tests = ThreadSafeCounter ([])
207224
208225 skip_tests = set ()
209226 skip_native = False
@@ -354,7 +371,7 @@ def run_tests(pyb, tests, args, base_path="."):
354371 skip_tests .add ('micropython/heapalloc_iter.py' ) # requires generators
355372 skip_tests .add ('micropython/schedule.py' ) # native code doesn't check pending events
356373
357- for test_file in tests :
374+ def run_one_test ( test_file ) :
358375 test_file = test_file .replace ('\\ ' , '/' )
359376 test_basename = os .path .basename (test_file )
360377 test_name = os .path .splitext (test_basename )[0 ]
@@ -377,7 +394,7 @@ def run_tests(pyb, tests, args, base_path="."):
377394 if skip_it :
378395 print ("skip " , test_file )
379396 skipped_tests .append (test_name )
380- continue
397+ return
381398
382399 # get expected output
383400 test_file_expected = test_file + '.exp'
@@ -405,24 +422,24 @@ def run_tests(pyb, tests, args, base_path="."):
405422 output_expected = output_expected .replace (b'\r \n ' , b'\n ' )
406423
407424 if args .write_exp :
408- continue
425+ return
409426
410427 # run MicroPython
411428 output_mupy = run_micropython (pyb , args , test_file )
412429
413430 if output_mupy == b'SKIP\n ' :
414431 print ("skip " , test_file )
415432 skipped_tests .append (test_name )
416- continue
433+ return
417434
418- testcase_count += len (output_expected .splitlines ())
435+ testcase_count . add ( len (output_expected .splitlines () ))
419436
420437 filename_expected = test_basename + ".exp"
421438 filename_mupy = test_basename + ".out"
422439
423440 if output_expected == output_mupy :
424441 print ("pass " , test_file )
425- passed_count += 1
442+ passed_count . add ( 1 )
426443 rm_f (filename_expected )
427444 rm_f (filename_mupy )
428445 else :
@@ -437,15 +454,22 @@ def run_tests(pyb, tests, args, base_path="."):
437454 print ("FAIL " , test_file )
438455 failed_tests .append (test_name )
439456
440- test_count += 1
457+ test_count .add (1 )
458+
459+ if num_threads > 1 :
460+ pool = ThreadPool (num_threads )
461+ pool .map (run_one_test , tests )
462+ else :
463+ for test in tests :
464+ run_one_test (test )
441465
442- print ("{} tests performed ({} individual testcases)" .format (test_count , testcase_count ))
443- print ("{} tests passed" .format (passed_count ))
466+ print ("{} tests performed ({} individual testcases)" .format (test_count . value , testcase_count . value ))
467+ print ("{} tests passed" .format (passed_count . value ))
444468
445- if len (skipped_tests ) > 0 :
446- print ("{} tests skipped: {}" .format (len (skipped_tests ), ' ' .join (skipped_tests )))
447- if len (failed_tests ) > 0 :
448- print ("{} tests failed: {}" .format (len (failed_tests ), ' ' .join (failed_tests )))
469+ if len (skipped_tests . value ) > 0 :
470+ print ("{} tests skipped: {}" .format (len (skipped_tests . value ), ' ' .join (sorted ( skipped_tests . value ) )))
471+ if len (failed_tests . value ) > 0 :
472+ print ("{} tests failed: {}" .format (len (failed_tests . value ), ' ' .join (sorted ( failed_tests . value ) )))
449473 return False
450474
451475 # all tests succeeded
@@ -464,11 +488,14 @@ def main():
464488 cmd_parser .add_argument ('--heapsize' , help = 'heapsize to use (use default if not specified)' )
465489 cmd_parser .add_argument ('--via-mpy' , action = 'store_true' , help = 'compile .py files to .mpy first' )
466490 cmd_parser .add_argument ('--keep-path' , action = 'store_true' , help = 'do not clear MICROPYPATH when running tests' )
491+ cmd_parser .add_argument ('-j' , '--jobs' , default = 1 , metavar = 'N' , type = int , help = 'Number of tests to run simultaneously' )
492+ cmd_parser .add_argument ('--auto-jobs' , action = 'store_const' , dest = 'jobs' , const = multiprocessing .cpu_count (), help = 'Set the -j values to the CPU (thread) count' )
467493 cmd_parser .add_argument ('files' , nargs = '*' , help = 'input test files' )
468494 args = cmd_parser .parse_args ()
469495
470496 EXTERNAL_TARGETS = ('pyboard' , 'wipy' , 'esp8266' , 'minimal' )
471497 if args .target in EXTERNAL_TARGETS :
498+ args .jobs = 1
472499 import pyboard
473500 pyb = pyboard .Pyboard (args .device , args .baudrate , args .user , args .password )
474501 pyb .enter_raw_repl ()
@@ -507,7 +534,7 @@ def main():
507534 # run-tests script itself.
508535 base_path = os .path .dirname (sys .argv [0 ]) or "."
509536 try :
510- res = run_tests (pyb , tests , args , base_path )
537+ res = run_tests (pyb , tests , args , base_path , args . jobs )
511538 finally :
512539 if pyb :
513540 pyb .close ()
0 commit comments