blob: fabb6631e97ff257913edf809154394e43a81652 [file] [edit]
# Copyright 2025 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
"""Stress test versions of some existing tests from test_core.py
These don't run in test_core.py itself because that is already run in parallel and these
stress tests each saturate the CPU cores.
TODO: Find a way to replace these tests with an `@also_with_stress_test` decorator.
Hopfully we can replace the current parallelism with `taskset -u 0` to force the test
only run on a single core (would limit the tests to linux-only).
"""
import multiprocessing
import subprocess
import threading
from common import RunnerCore, node_pthreads, is_slow_test, also_with_modularize, NON_ZERO
class stress(RunnerCore):
def parallel_stress_test_js_file(self, js_file, assert_returncode=None, expected=None, not_expected=None):
# If no expectations were passed, expect a successful run exit code
if assert_returncode is None and expected is None and not_expected is None:
assert_returncode = 0
# We will use Python multithreading, so prepare the command to run in advance, and keep the threading kernel
# compact to avoid accessing unexpected data/functions across threads.
cmd = self.get_engine_with_args() + [js_file]
exception_thrown = threading.Event()
error_lock = threading.Lock()
error_exception = None
def test_run():
nonlocal error_exception
try:
# Each thread repeatedly runs the test case in a tight loop, which is critical to coax out timing related issues
for _ in range(16):
# Early out from the test, if error was found
if exception_thrown.is_set():
return
result = subprocess.run(cmd, capture_output=True, text=True)
output = f'\n----------------------------\n{result.stdout}{result.stderr}\n----------------------------'
if not_expected is not None and not_expected in output:
raise Exception(f'\n\nWhen running command "{cmd}",\nexpected string "{not_expected}" to NOT be present in output:{output}')
if expected is not None and expected not in output:
raise Exception(f'\n\nWhen running command "{cmd}",\nexpected string "{expected}" was not found in output:{output}')
if assert_returncode is not None:
if assert_returncode == NON_ZERO:
if result.returncode != 0:
raise Exception(f'\n\nCommand "{cmd}" was expected to fail, but did not (returncode=0). Output:{output}')
elif assert_returncode != result.returncode:
raise Exception(f'\n\nWhen running command "{cmd}",\nreturn code {result.returncode} does not match expected return code {assert_returncode}. Output:{output}')
except Exception as e:
if not exception_thrown.is_set():
exception_thrown.set()
with error_lock:
error_exception = e
return
threads = []
# Oversubscribe hardware threads to make sure scheduling becomes erratic
while len(threads) < 2 * multiprocessing.cpu_count() and not exception_thrown.is_set():
threads += [threading.Thread(target=test_run)]
threads[-1].start()
for t in threads:
t.join()
if error_exception:
raise error_exception
# This is a stress test version that focuses on https://github.com/emscripten-core/emscripten/issues/20067
@node_pthreads
@is_slow_test
def test_stress_proxy_to_pthread_hello_world(self):
self.skipTest('Occassionally hangs. https://github.com/emscripten-core/emscripten/issues/20067')
self.set_setting('ABORT_ON_WASM_EXCEPTIONS')
self.set_setting('PROXY_TO_PTHREAD')
self.set_setting('EXIT_RUNTIME')
js_file = self.build('core/test_hello_world.c')
self.parallel_stress_test_js_file(js_file, assert_returncode=0, expected='hello, world!', not_expected='error')
# This is a stress test to verify that the Node.js postMessage() vs uncaughtException
# race does not affect Emscripten execution.
@node_pthreads
@is_slow_test
def test_stress_pthread_abort(self):
self.set_setting('PROXY_TO_PTHREAD')
# Add the onAbort handler at runtime during preRun. This means that onAbort
# handler will only be present in the main thread (much like it would if it
# was passed in by pre-populating the module object on prior to loading).
self.add_pre_run("Module.onAbort = () => console.log('My custom onAbort called');")
self.cflags += ['-sINCOMING_MODULE_JS_API=preRun,onAbort']
js_file = self.build('pthread/test_pthread_abort.c')
self.parallel_stress_test_js_file(js_file, expected='My custom onAbort called')
# TODO: investigate why adding assert_returncode=NON_ZERO to above doesn't work.
# Is the test test_pthread_abort still flaky?
@node_pthreads
@also_with_modularize
@is_slow_test
def test_stress_pthread_proxying(self):
if '-sMODULARIZE' in self.cflags:
if self.get_setting('WASM') == 0:
self.skipTest('MODULARIZE + WASM=0 + pthreads does not work (#16794)')
self.set_setting('EXPORT_NAME=ModuleFactory')
self.set_setting('PROXY_TO_PTHREAD')
if not self.has_changed_setting('INITIAL_MEMORY'):
self.set_setting('INITIAL_MEMORY=32mb')
js_file = self.build('pthread/test_pthread_proxying_reduced_stress_test_case.c')
self.parallel_stress_test_js_file(js_file, not_expected='pthread_self() == unknown', expected='pthread_self() == worker2', assert_returncode=0)