run-tests: optionally parallelize tests

When requested via 'run-tests -j', more than one test will be run
at a time.  On my system, (i5-3320m with 4 threads / 2 cores), this
reduces elapsed time by over 50% when testing pots/unix/micropython.

Elapsed time, seconds, best of 3 runs with each -j value:

before patchset: 18.1
            -j1: 18.1
            -j2: 11.3  (-37%)
            -j4:  8.7  (-52%)
            -j6:  8.4  (-54%)

In all cases the final output is identical:
    651 tests performed (18932 individual testcases)
    651 tests passed
    23 tests skipped: buffered_writer...
though the individual pass/fail messages can be different/interleaved.
This commit is contained in:
Jeff Epler 2018-03-31 09:32:20 -05:00
parent b9dd6a5bb4
commit a3309ebb80
1 changed files with 10 additions and 4 deletions

View File

@ -7,6 +7,7 @@ import platform
import argparse import argparse
import re import re
import threading import threading
from multiprocessing.pool import ThreadPool
from glob import glob from glob import glob
# Tests require at least CPython 3.3. If your default python3 executable # Tests require at least CPython 3.3. If your default python3 executable
@ -213,7 +214,7 @@ class ThreadSafeCounter:
def value(self): def value(self):
return self._value return self._value
def run_tests(pyb, tests, args, base_path="."): def run_tests(pyb, tests, args, base_path=".", num_threads=1):
test_count = ThreadSafeCounter() test_count = ThreadSafeCounter()
testcase_count = ThreadSafeCounter() testcase_count = ThreadSafeCounter()
passed_count = ThreadSafeCounter() passed_count = ThreadSafeCounter()
@ -454,8 +455,12 @@ def run_tests(pyb, tests, args, base_path="."):
test_count.add(1) test_count.add(1)
for test_file in tests: if num_threads > 1:
run_one_test(test_file) pool = ThreadPool(num_threads)
pool.map(run_one_test, tests)
else:
for test in tests:
run_one_test(test)
print("{} tests performed ({} individual testcases)".format(test_count.value, testcase_count.value)) print("{} tests performed ({} individual testcases)".format(test_count.value, testcase_count.value))
print("{} tests passed".format(passed_count.value)) print("{} tests passed".format(passed_count.value))
@ -482,6 +487,7 @@ def main():
cmd_parser.add_argument('--heapsize', help='heapsize to use (use default if not specified)') cmd_parser.add_argument('--heapsize', help='heapsize to use (use default if not specified)')
cmd_parser.add_argument('--via-mpy', action='store_true', help='compile .py files to .mpy first') cmd_parser.add_argument('--via-mpy', action='store_true', help='compile .py files to .mpy first')
cmd_parser.add_argument('--keep-path', action='store_true', help='do not clear MICROPYPATH when running tests') cmd_parser.add_argument('--keep-path', action='store_true', help='do not clear MICROPYPATH when running tests')
cmd_parser.add_argument('-j', '--jobs', default=1, metavar='N', type=int, help='Number of tests to run simultaneously')
cmd_parser.add_argument('files', nargs='*', help='input test files') cmd_parser.add_argument('files', nargs='*', help='input test files')
args = cmd_parser.parse_args() args = cmd_parser.parse_args()
@ -525,7 +531,7 @@ def main():
# run-tests script itself. # run-tests script itself.
base_path = os.path.dirname(sys.argv[0]) or "." base_path = os.path.dirname(sys.argv[0]) or "."
try: try:
res = run_tests(pyb, tests, args, base_path) res = run_tests(pyb, tests, args, base_path, args.jobs)
finally: finally:
if pyb: if pyb:
pyb.close() pyb.close()