Skip to content

Commit cf52c7f

Browse files
authored
Combine all tests into a single suite to increase parallelism. NFC (#26365)
My motivation for this change is to allow for increased parallelism that is not limited to how many tests are in a give module. I'm hoping to do more splitting up of test_other.py, for example, and I don't want to loose parallism. After this change all tests are combined into a single parallel test suite. For example: ``` $ ./test/runner jslib other.test_a* core0.test_a* --skip-slow Test suites: ['test_core', 'test_jslib', 'test_other'] Running test_core: (88 tests) Using 88 parallel test processes [88/88] test_asan_vector (test_core.core0.test_asan_vector) ... ok ---------------------------------------------------------------------- Ran 88 tests in 3.296s OK (skipped=5) Running test_jslib: (54 tests) Using 54 parallel test processes [54/54] test_jslib_aliases_closure_wasm64 (test_jslib.jslib.test_jslib_aliases_closure_wasm64) ... ok ---------------------------------------------------------------------- Ran 54 tests in 5.287s OK Running test_other: (41 tests) Using 41 parallel test processes [41/41] test_abspaths (test_other.other.test_abspaths) ... ok ---------------------------------------------------------------------- Ran 41 tests in 5.210s OK Total core time: 228.881s. Wallclock time: 13.795s. Parallelization: 16.59x. ==================== TEST SUMMARY test_core: 88 run, 0 errors, 0 failures, 5 skipped test_jslib: 54 run, 0 errors, 0 failures, 0 skipped test_other: 41 run, 0 errors, 0 failures, 0 skipped ``` After: ``` $ ./test/runner jslib other.test_a* core0.test_a* --skip-slow Running 183 tests Using 128 parallel test processes [183/183] test_abspaths (test_other.other.test_abspaths) ... ok ---------------------------------------------------------------------- Ran 183 tests in 7.459s OK (skipped=5) Total core time: 301.490s. Wallclock time: 7.459s. Parallelization: 40.42x. ``` Note the wall clock time is less since we ran all the tests in parallel. If you try to mix parallel and non-parallel test module you now get an error. e.g.: ``` $ ./test/runner core0 benchmark runner: error: attempt to mix parallel and non-parallel test modules ```
1 parent cfb4642 commit cf52c7f

3 files changed

Lines changed: 45 additions & 47 deletions

File tree

.circleci/config.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,9 +1097,12 @@ jobs:
10971097
- run-tests-linux:
10981098
# some native-dependent tests fail because of the lack of native
10991099
# headers on emsdk-bundled clang
1100+
test_targets: "other jslib skip:other.test_native_link_error_message"
1101+
- run-tests-linux:
11001102
# Include a single test from test_benchmark.py to ensure it's always
1101-
# kept functional.
1102-
test_targets: "other jslib benchmark.test_primes skip:other.test_native_link_error_message"
1103+
# kept functional. Must be run separately from above because we cannot
1104+
# run non-parallel tests in the same run as parallel tests.
1105+
test_targets: "benchmark.test_primes"
11031106
test-browser-chrome:
11041107
executor: ubuntu-lts
11051108
environment:

test/runner.py

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,30 @@ def sort_tests_failing_and_slowest_first_comparator(x, y):
368368
return sort_tests_failing_and_slowest_first_comparator
369369

370370

371-
def load_test_suites(args, modules, options):
371+
def use_parallel_suite(module):
372+
suite_supported = module.__name__ not in ('test_sanity', 'test_benchmark', 'test_sockets', 'test_interactive', 'test_stress')
373+
if not common.EMTEST_SAVE_DIR and not shared.DEBUG:
374+
has_multiple_cores = parallel_testsuite.num_cores() > 1
375+
if suite_supported and has_multiple_cores:
376+
return True
377+
return False
378+
379+
380+
def create_test_suite(is_parallel, options):
381+
if is_parallel:
382+
return parallel_testsuite.ParallelTestSuite(options)
383+
else:
384+
return unittest.TestSuite()
385+
386+
387+
def load_test_suite(args, modules, options):
372388
found_start = not options.start_at
373389

374390
loader = unittest.TestLoader()
375391
error_on_legacy_suite_names(args)
376392
unmatched_test_names = set(args)
377-
suites = []
393+
suite = None
394+
using_parallel_suite = False
378395

379396
total_tests = 0
380397
for m in modules:
@@ -394,7 +411,15 @@ def load_test_suites(args, modules, options):
394411

395412
loaded_tests = loader.loadTestsFromNames(sorted(names_in_module), m)
396413
tests = flattened_tests(loaded_tests)
397-
suite = suite_for_module(m, tests, options)
414+
is_parallel_module = use_parallel_suite(m)
415+
if not suite:
416+
# The first module we encounter dictates whether we use parallel test suite
417+
suite = create_test_suite(is_parallel_module, options)
418+
using_parallel_suite = is_parallel_module
419+
else:
420+
# All the following modules must match in their support for the parallel runner.
421+
if is_parallel_module != using_parallel_suite:
422+
utils.exit_with_error(f'attempt to mix parallel and non-parallel test modules ({m.__name__})')
398423
if options.failing_and_slow_first:
399424
tests = sorted(tests, key=cmp_to_key(create_test_run_sorter(options.max_failures < len(tests) / 2)))
400425
for test in tests:
@@ -407,10 +432,9 @@ def load_test_suites(args, modules, options):
407432
for _x in range(options.repeat):
408433
total_tests += 1
409434
suite.addTest(test)
410-
suites.append((m.__name__, suite))
411435
if not found_start:
412436
utils.exit_with_error(f'unable to find --start-at test: {options.start_at}')
413-
return suites, unmatched_test_names
437+
return suite, unmatched_test_names
414438

415439

416440
def flattened_tests(loaded_tests):
@@ -420,24 +444,8 @@ def flattened_tests(loaded_tests):
420444
return tests
421445

422446

423-
def suite_for_module(module, tests, options):
424-
suite_supported = module.__name__ not in ('test_sanity', 'test_benchmark', 'test_sockets', 'test_interactive', 'test_stress')
425-
if not common.EMTEST_SAVE_DIR and not shared.DEBUG:
426-
has_multiple_tests = len(tests) > 1
427-
has_multiple_cores = parallel_testsuite.num_cores() > 1
428-
if suite_supported and has_multiple_tests and has_multiple_cores:
429-
return parallel_testsuite.ParallelTestSuite(options)
430-
return unittest.TestSuite()
431-
432-
433-
def run_tests(options, suites):
434-
resultMessages = []
435-
num_failures = 0
436-
437-
if len(suites) > 1:
438-
print('Test suites:', [s[0] for s in suites])
447+
def run_tests(options, suite):
439448
# Run the discovered tests
440-
441449
if os.getenv('CI'):
442450
# output fd must remain open until after testRunner.run() below
443451
output = open('out/test-results.xml', 'wb')
@@ -456,27 +464,15 @@ def run_tests(options, suites):
456464
print('using verbose test runner (verbose output requested)')
457465
testRunner = ColorTextRunner(failfast=options.failfast)
458466

459-
total_core_time = 0
460467
run_start_time = time.perf_counter()
461-
for mod_name, suite in suites:
462-
errlog('Running %s: (%s tests)' % (mod_name, suite.countTestCases()))
463-
res = testRunner.run(suite)
464-
msg = ('%s: %s run, %s errors, %s failures, %s skipped' %
465-
(mod_name, res.testsRun, len(res.errors), len(res.failures), len(res.skipped)))
466-
num_failures += len(res.errors) + len(res.failures) + len(res.unexpectedSuccesses)
467-
resultMessages.append(msg)
468-
if hasattr(res, 'core_time'):
469-
total_core_time += res.core_time
470-
total_run_time = time.perf_counter() - run_start_time
471-
if total_core_time > 0:
472-
errlog('Total core time: %.3fs. Wallclock time: %.3fs. Parallelization: %.2fx.' % (total_core_time, total_run_time, total_core_time / total_run_time))
473468

474-
if len(resultMessages) > 1:
475-
errlog('====================')
476-
errlog()
477-
errlog('TEST SUMMARY')
478-
for msg in resultMessages:
479-
errlog(' ' + msg)
469+
errlog('Running %s tests' % suite.countTestCases())
470+
res = testRunner.run(suite)
471+
num_failures = len(res.errors) + len(res.failures) + len(res.unexpectedSuccesses)
472+
473+
total_run_time = time.perf_counter() - run_start_time
474+
if hasattr(res, 'core_time'):
475+
errlog('Total core time: %.3fs. Wallclock time: %.3fs. Parallelization: %.2fx.' % (res.core_time, total_run_time, res.core_time / total_run_time))
480476

481477
if options.bell:
482478
sys.stdout.write('\a')
@@ -746,12 +742,12 @@ def prepend_default(arg):
746742
if os.path.exists(common.LAST_TEST):
747743
options.start_at = utils.read_file(common.LAST_TEST).strip()
748744

749-
suites, unmatched_tests = load_test_suites(tests, modules, options)
745+
suite, unmatched_tests = load_test_suite(tests, modules, options)
750746
if unmatched_tests:
751747
errlog('ERROR: could not find the following tests: ' + ' '.join(unmatched_tests))
752748
return 1
753749

754-
num_failures = run_tests(options, suites)
750+
num_failures = run_tests(options, suite)
755751
# Return the number of failures as the process exit code
756752
# for automating success/failure reporting. Return codes
757753
# over 125 are not well supported on UNIX.

test/test_sanity.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
path_from_root,
2525
test_file,
2626
)
27-
from decorators import crossplatform, no_windows, parameterized, with_env_modify
27+
from decorators import no_windows, parameterized, with_env_modify
2828

2929
from tools import cache, ports, response_file, shared, utils
3030
from tools.config import EM_CONFIG
@@ -182,7 +182,6 @@ def check_working(self, command, expected=None, env=None):
182182
return output
183183

184184
# this should be the very first thing that runs. if this fails, everything else is irrelevant!
185-
@crossplatform
186185
def test_aaa_normal(self):
187186
# Your existing EM_CONFIG should work!
188187
restore_and_set_up()

0 commit comments

Comments
 (0)