Skip to content

Commit e925c86

Browse files
authored
Move test_stress to its own test file. NFC (#25199)
1 parent ba0682d commit e925c86

File tree

4 files changed

+126
-98
lines changed

4 files changed

+126
-98
lines changed

.circleci/config.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,13 @@ jobs:
637637
steps:
638638
- run-tests-linux:
639639
test_targets: "instance"
640+
test-stress:
641+
executor: focal
642+
environment:
643+
EMTEST_SKIP_NODE_CANARY: "1"
644+
steps:
645+
- run-tests-linux:
646+
test_targets: "stress"
640647
test-esm-integration:
641648
# We don't use `bionic` here since its too old to run recent node versions:
642649
# `/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found`
@@ -1117,6 +1124,9 @@ workflows:
11171124
- test-esm-integration:
11181125
requires:
11191126
- build-linux
1127+
- test-stress:
1128+
requires:
1129+
- build-linux
11201130
- test-browser-chrome
11211131
- test-browser-chrome-2gb:
11221132
requires:

test/common.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import itertools
1818
import json
1919
import logging
20-
import multiprocessing
2120
import os
2221
import re
2322
import shlex
@@ -2157,57 +2156,6 @@ def _build_and_run(self, filename, expected_output, args=None,
21572156
raise
21582157
return js_output
21592158

2160-
def parallel_stress_test_js_file(self, js_file, assert_returncode=None, expected=None, not_expected=None):
2161-
# If no expectations were passed, expect a successful run exit code
2162-
if assert_returncode is None and expected is None and not_expected is None:
2163-
assert_returncode = 0
2164-
2165-
# We will use Python multithreading, so prepare the command to run in advance, and keep the threading kernel
2166-
# compact to avoid accessing unexpected data/functions across threads.
2167-
cmd = self.get_engine_with_args() + [js_file]
2168-
2169-
exception_thrown = threading.Event()
2170-
error_lock = threading.Lock()
2171-
error_exception = None
2172-
2173-
def test_run():
2174-
nonlocal error_exception
2175-
try:
2176-
# Each thread repeatedly runs the test case in a tight loop, which is critical to coax out timing related issues
2177-
for _ in range(16):
2178-
# Early out from the test, if error was found
2179-
if exception_thrown.is_set():
2180-
return
2181-
result = subprocess.run(cmd, capture_output=True, text=True)
2182-
2183-
output = f'\n----------------------------\n{result.stdout}{result.stderr}\n----------------------------'
2184-
if not_expected is not None and not_expected in output:
2185-
raise Exception(f'\n\nWhen running command "{cmd}",\nexpected string "{not_expected}" to NOT be present in output:{output}')
2186-
if expected is not None and expected not in output:
2187-
raise Exception(f'\n\nWhen running command "{cmd}",\nexpected string "{expected}" was not found in output:{output}')
2188-
if assert_returncode is not None:
2189-
if assert_returncode == NON_ZERO:
2190-
if result.returncode != 0:
2191-
raise Exception(f'\n\nCommand "{cmd}" was expected to fail, but did not (returncode=0). Output:{output}')
2192-
elif assert_returncode != result.returncode:
2193-
raise Exception(f'\n\nWhen running command "{cmd}",\nreturn code {result.returncode} does not match expected return code {assert_returncode}. Output:{output}')
2194-
except Exception as e:
2195-
if not exception_thrown.is_set():
2196-
exception_thrown.set()
2197-
with error_lock:
2198-
error_exception = e
2199-
return
2200-
2201-
threads = []
2202-
# Oversubscribe hardware threads to make sure scheduling becomes erratic
2203-
while len(threads) < 2 * multiprocessing.cpu_count() and not exception_thrown.is_set():
2204-
threads += [threading.Thread(target=test_run)]
2205-
threads[-1].start()
2206-
for t in threads:
2207-
t.join()
2208-
if error_exception:
2209-
raise error_exception
2210-
22112159
def get_freetype_library(self):
22122160
self.cflags += [
22132161
'-Wno-misleading-indentation',

test/test_core.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,23 +2593,6 @@ def test_pthread_proxying(self):
25932593
self.set_setting('INITIAL_MEMORY=32mb')
25942594
self.do_run_in_out_file_test('pthread/test_pthread_proxying.c', interleaved_output=False)
25952595

2596-
@node_pthreads
2597-
@also_with_modularize
2598-
@is_slow_test
2599-
def test_stress_pthread_proxying(self):
2600-
self.skipTest('https://github.com/emscripten-core/emscripten/issues/25026')
2601-
if '-sMODULARIZE' in self.cflags:
2602-
if self.get_setting('WASM') == 0:
2603-
self.skipTest('MODULARIZE + WASM=0 + pthreads does not work (#16794)')
2604-
self.set_setting('EXPORT_NAME=ModuleFactory')
2605-
self.maybe_closure()
2606-
self.set_setting('PROXY_TO_PTHREAD')
2607-
if not self.has_changed_setting('INITIAL_MEMORY'):
2608-
self.set_setting('INITIAL_MEMORY=32mb')
2609-
2610-
js_file = self.build('pthread/test_pthread_proxying.c')
2611-
self.parallel_stress_test_js_file(js_file, not_expected='running widget 17 on unknown', expected='running widget 17 on worker', assert_returncode=0)
2612-
26132596
@node_pthreads
26142597
def test_pthread_proxying_cpp(self):
26152598
self.set_setting('PROXY_TO_PTHREAD')
@@ -2695,23 +2678,6 @@ def test_pthread_abort(self):
26952678
self.cflags += ['-sINCOMING_MODULE_JS_API=preRun,onAbort']
26962679
self.do_run_in_out_file_test('pthread/test_pthread_abort.c', assert_returncode=NON_ZERO)
26972680

2698-
# This is a stress test to verify that the Node.js postMessage() vs uncaughtException
2699-
# race does not affect Emscripten execution.
2700-
@node_pthreads
2701-
@is_slow_test
2702-
@no_esm_integration('TODO: WASM_ESM_INTEGRATION mode has some asynchronous behavior that causes a failure in this test. https://github.com/emscripten-core/emscripten/issues/25151')
2703-
def test_stress_pthread_abort(self):
2704-
self.set_setting('PROXY_TO_PTHREAD')
2705-
# Add the onAbort handler at runtime during preRun. This means that onAbort
2706-
# handler will only be present in the main thread (much like it would if it
2707-
# was passed in by pre-populating the module object on prior to loading).
2708-
self.add_pre_run("Module.onAbort = () => console.log('My custom onAbort called');")
2709-
self.cflags += ['-sINCOMING_MODULE_JS_API=preRun,onAbort']
2710-
js_file = self.build('pthread/test_pthread_abort.c')
2711-
self.parallel_stress_test_js_file(js_file, expected='My custom onAbort called')
2712-
# TODO: investigate why adding assert_returncode=NON_ZERO to above doesn't work.
2713-
# Is the test test_pthread_abort still flaky?
2714-
27152681
@node_pthreads
27162682
def test_pthread_abort_interrupt(self):
27172683
self.set_setting('PTHREAD_POOL_SIZE', 1)
@@ -9685,18 +9651,6 @@ def test_abort_on_exceptions_pthreads(self):
96859651
self.set_setting('EXIT_RUNTIME')
96869652
self.do_core_test('test_hello_world.c')
96879653

9688-
# This is a stress test version that focuses on https://github.com/emscripten-core/emscripten/issues/20067
9689-
@node_pthreads
9690-
@no_esm_integration('ABORT_ON_WASM_EXCEPTIONS is not compatible with WASM_ESM_INTEGRATION')
9691-
@is_slow_test
9692-
def test_stress_proxy_to_pthread_hello_world(self):
9693-
self.skipTest('Occassionally hangs. https://github.com/emscripten-core/emscripten/issues/20067')
9694-
self.set_setting('ABORT_ON_WASM_EXCEPTIONS')
9695-
self.set_setting('PROXY_TO_PTHREAD')
9696-
self.set_setting('EXIT_RUNTIME')
9697-
js_file = self.build('core/test_hello_world.c')
9698-
self.parallel_stress_test_js_file(js_file, assert_returncode=0, expected='hello, world!', not_expected='error')
9699-
97009654
@needs_dylink
97019655
@no_js_math('JS_MATH is not compatible with MAIN_MODULE')
97029656
def test_gl_main_module(self):

test/test_stress.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Copyright 2025 The Emscripten Authors. All rights reserved.
2+
# Emscripten is available under two separate licenses, the MIT license and the
3+
# University of Illinois/NCSA Open Source License. Both these licenses can be
4+
# found in the LICENSE file.
5+
6+
"""Stress test versions of some existing tests from test_core.py
7+
These don't run in test_core.py itself because that is already run in parallel and these
8+
stress tests each saturate the CPU cores.
9+
10+
TODO: Find a way to replace these tests with an `@also_with_stress_test` decorator.
11+
Hopfully we can replace the current parallelism with `taskset -u 0` to force the test
12+
only run on a single core (would limit the tests to linux-only).
13+
"""
14+
15+
import multiprocessing
16+
import subprocess
17+
import threading
18+
19+
from common import RunnerCore, node_pthreads, is_slow_test, also_with_modularize, NON_ZERO
20+
21+
22+
class stress(RunnerCore):
23+
def parallel_stress_test_js_file(self, js_file, assert_returncode=None, expected=None, not_expected=None):
24+
# If no expectations were passed, expect a successful run exit code
25+
if assert_returncode is None and expected is None and not_expected is None:
26+
assert_returncode = 0
27+
28+
# We will use Python multithreading, so prepare the command to run in advance, and keep the threading kernel
29+
# compact to avoid accessing unexpected data/functions across threads.
30+
cmd = self.get_engine_with_args() + [js_file]
31+
32+
exception_thrown = threading.Event()
33+
error_lock = threading.Lock()
34+
error_exception = None
35+
36+
def test_run():
37+
nonlocal error_exception
38+
try:
39+
# Each thread repeatedly runs the test case in a tight loop, which is critical to coax out timing related issues
40+
for _ in range(16):
41+
# Early out from the test, if error was found
42+
if exception_thrown.is_set():
43+
return
44+
result = subprocess.run(cmd, capture_output=True, text=True)
45+
46+
output = f'\n----------------------------\n{result.stdout}{result.stderr}\n----------------------------'
47+
if not_expected is not None and not_expected in output:
48+
raise Exception(f'\n\nWhen running command "{cmd}",\nexpected string "{not_expected}" to NOT be present in output:{output}')
49+
if expected is not None and expected not in output:
50+
raise Exception(f'\n\nWhen running command "{cmd}",\nexpected string "{expected}" was not found in output:{output}')
51+
if assert_returncode is not None:
52+
if assert_returncode == NON_ZERO:
53+
if result.returncode != 0:
54+
raise Exception(f'\n\nCommand "{cmd}" was expected to fail, but did not (returncode=0). Output:{output}')
55+
elif assert_returncode != result.returncode:
56+
raise Exception(f'\n\nWhen running command "{cmd}",\nreturn code {result.returncode} does not match expected return code {assert_returncode}. Output:{output}')
57+
except Exception as e:
58+
if not exception_thrown.is_set():
59+
exception_thrown.set()
60+
with error_lock:
61+
error_exception = e
62+
return
63+
64+
threads = []
65+
# Oversubscribe hardware threads to make sure scheduling becomes erratic
66+
while len(threads) < 2 * multiprocessing.cpu_count() and not exception_thrown.is_set():
67+
threads += [threading.Thread(target=test_run)]
68+
threads[-1].start()
69+
for t in threads:
70+
t.join()
71+
if error_exception:
72+
raise error_exception
73+
74+
# This is a stress test version that focuses on https://github.com/emscripten-core/emscripten/issues/20067
75+
@node_pthreads
76+
@is_slow_test
77+
def test_stress_proxy_to_pthread_hello_world(self):
78+
self.skipTest('Occassionally hangs. https://github.com/emscripten-core/emscripten/issues/20067')
79+
self.set_setting('ABORT_ON_WASM_EXCEPTIONS')
80+
self.set_setting('PROXY_TO_PTHREAD')
81+
self.set_setting('EXIT_RUNTIME')
82+
js_file = self.build('core/test_hello_world.c')
83+
self.parallel_stress_test_js_file(js_file, assert_returncode=0, expected='hello, world!', not_expected='error')
84+
85+
# This is a stress test to verify that the Node.js postMessage() vs uncaughtException
86+
# race does not affect Emscripten execution.
87+
@node_pthreads
88+
@is_slow_test
89+
def test_stress_pthread_abort(self):
90+
self.set_setting('PROXY_TO_PTHREAD')
91+
# Add the onAbort handler at runtime during preRun. This means that onAbort
92+
# handler will only be present in the main thread (much like it would if it
93+
# was passed in by pre-populating the module object on prior to loading).
94+
self.add_pre_run("Module.onAbort = () => console.log('My custom onAbort called');")
95+
self.cflags += ['-sINCOMING_MODULE_JS_API=preRun,onAbort']
96+
js_file = self.build('pthread/test_pthread_abort.c')
97+
self.parallel_stress_test_js_file(js_file, expected='My custom onAbort called')
98+
# TODO: investigate why adding assert_returncode=NON_ZERO to above doesn't work.
99+
# Is the test test_pthread_abort still flaky?
100+
101+
@node_pthreads
102+
@also_with_modularize
103+
@is_slow_test
104+
def test_stress_pthread_proxying(self):
105+
self.skipTest('https://github.com/emscripten-core/emscripten/issues/25026')
106+
if '-sMODULARIZE' in self.cflags:
107+
if self.get_setting('WASM') == 0:
108+
self.skipTest('MODULARIZE + WASM=0 + pthreads does not work (#16794)')
109+
self.set_setting('EXPORT_NAME=ModuleFactory')
110+
self.maybe_closure()
111+
self.set_setting('PROXY_TO_PTHREAD')
112+
if not self.has_changed_setting('INITIAL_MEMORY'):
113+
self.set_setting('INITIAL_MEMORY=32mb')
114+
115+
js_file = self.build('pthread/test_pthread_proxying.c')
116+
self.parallel_stress_test_js_file(js_file, not_expected='running widget 17 on unknown', expected='running widget 17 on worker', assert_returncode=0)

0 commit comments

Comments
 (0)