Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-40280: Detect missing threading on WASM platforms (GH-32352) #32352

Merged
merged 4 commits into from Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Lib/distutils/tests/test_build_ext.py
Expand Up @@ -17,6 +17,7 @@
from test import support
from test.support import os_helper
from test.support.script_helper import assert_python_ok
from test.support import threading_helper

# http://bugs.python.org/issue4373
# Don't load the xx module more than once.
Expand Down Expand Up @@ -165,6 +166,7 @@ def test_user_site(self):
self.assertIn(lib, cmd.rpath)
self.assertIn(incl, cmd.include_dirs)

@threading_helper.requires_working_threading()
def test_optional_extension(self):

# this extension will fail, but let's ignore this failure
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/libregrtest/main.py
Expand Up @@ -20,6 +20,7 @@
from test.libregrtest.utils import removepy, count, format_duration, printlist
from test import support
from test.support import os_helper
from test.support import threading_helper


# bpo-38203: Maximum delay in seconds to exit Python (call Py_Finalize()).
Expand Down Expand Up @@ -676,7 +677,8 @@ def main(self, tests=None, **kwargs):
except SystemExit as exc:
# bpo-38203: Python can hang at exit in Py_Finalize(), especially
# on threading._shutdown() call: put a timeout
faulthandler.dump_traceback_later(EXIT_TIMEOUT, exit=True)
if threading_helper.can_start_thread:
faulthandler.dump_traceback_later(EXIT_TIMEOUT, exit=True)

sys.exit(exc.code)

Expand Down
1 change: 1 addition & 0 deletions Lib/test/pickletester.py
Expand Up @@ -1380,6 +1380,7 @@ def test_truncated_data(self):
self.check_unpickling_error(self.truncated_errors, p)

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_unpickle_module_race(self):
# https://bugs.python.org/issue34572
locker_module = dedent("""
Expand Down
15 changes: 14 additions & 1 deletion Lib/test/support/threading_helper.py
Expand Up @@ -4,6 +4,7 @@
import sys
import threading
import time
import unittest

from test import support

Expand Down Expand Up @@ -210,7 +211,7 @@ def __exit__(self, *exc_info):


def _can_start_thread() -> bool:
"""Detect if Python can start new threads.
"""Detect whether Python can start new threads.

Some WebAssembly platforms do not provide a working pthread
implementation. Thread support is stubbed and any attempt
Expand All @@ -234,3 +235,15 @@ def _can_start_thread() -> bool:
return True

can_start_thread = _can_start_thread()

def requires_working_threading(*, module=False):
tiran marked this conversation as resolved.
Show resolved Hide resolved
"""Skip tests or modules that require working threading.

Can be used as a function/class decorator or to skip an entire module.
"""
msg = "requires threading support"
if module:
if not can_start_thread:
raise unittest.SkipTest(msg)
else:
return unittest.skipUnless(can_start_thread, msg)
1 change: 1 addition & 0 deletions Lib/test/test_bz2.py
Expand Up @@ -496,6 +496,7 @@ def testContextProtocol(self):
else:
self.fail("1/0 didn't raise an exception")

@threading_helper.requires_working_threading()
def testThreading(self):
# Issue #7205: Using a BZ2File from several threads shouldn't deadlock.
data = b"1" * 2**20
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_capi.py
Expand Up @@ -710,6 +710,7 @@ def pendingcalls_wait(self, l, n, context = None):
if False and support.verbose:
print("(%i)"%(len(l),))

@threading_helper.requires_working_threading()
def test_pendingcalls_threaded(self):

#do every callback on a separate thread
Expand Down Expand Up @@ -840,6 +841,7 @@ def test_module_state_shared_in_global(self):
class TestThreadState(unittest.TestCase):

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_thread_state(self):
# some extra thread-state tests driven via _testcapi
def target():
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_context.py
Expand Up @@ -6,6 +6,7 @@
import time
import unittest
import weakref
from test.support import threading_helper

try:
from _testcapi import hamt
Expand Down Expand Up @@ -341,6 +342,7 @@ def ctx2_fun():
ctx1.run(ctx1_fun)

@isolated_context
@threading_helper.requires_working_threading()
def test_context_threads_1(self):
cvar = contextvars.ContextVar('cvar')

Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_decimal.py
Expand Up @@ -39,6 +39,7 @@
run_with_locale, cpython_only,
darwin_malloc_err_warning)
from test.support.import_helper import import_fresh_module
from test.support import threading_helper
from test.support import warnings_helper
import random
import inspect
Expand Down Expand Up @@ -1591,6 +1592,8 @@ def thfunc2(cls):
for sig in Overflow, Underflow, DivisionByZero, InvalidOperation:
cls.assertFalse(thiscontext.flags[sig])


@threading_helper.requires_working_threading()
class ThreadingTest(unittest.TestCase):
'''Unit tests for thread local contexts in Decimal.'''

Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_email/test_email.py
Expand Up @@ -3285,6 +3285,7 @@ def test_getaddresses_header_obj(self):
addrs = utils.getaddresses([Header('Al Person <aperson@dom.ain>')])
self.assertEqual(addrs[0][1], 'aperson@dom.ain')

@threading_helper.requires_working_threading()
def test_make_msgid_collisions(self):
# Test make_msgid uniqueness, even with multiple threads
class MsgidsThread(Thread):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_enum.py
Expand Up @@ -2949,6 +2949,7 @@ class Color(StrMixin, AllMixin, Flag):
self.assertEqual(str(Color.BLUE), 'blue')

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_unique_composite(self):
# override __eq__ to be identity only
class TestFlag(Flag):
Expand Down Expand Up @@ -3481,6 +3482,7 @@ class Color(StrMixin, AllMixin, IntFlag):
self.assertEqual(str(Color.BLUE), 'blue')

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_unique_composite(self):
# override __eq__ to be identity only
class TestFlag(IntFlag):
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_functools.py
Expand Up @@ -1637,6 +1637,7 @@ def f(zomg: 'zomg_annotation'):
for attr in self.module.WRAPPER_ASSIGNMENTS:
self.assertEqual(getattr(g, attr), getattr(f, attr))

@threading_helper.requires_working_threading()
def test_lru_cache_threaded(self):
n, m = 5, 11
def orig(x, y):
Expand Down Expand Up @@ -1685,6 +1686,7 @@ def clear():
finally:
sys.setswitchinterval(orig_si)

@threading_helper.requires_working_threading()
def test_lru_cache_threaded2(self):
# Simultaneous call with the same arguments
n, m = 5, 7
Expand Down Expand Up @@ -1712,6 +1714,7 @@ def test():
pause.reset()
self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1))

@threading_helper.requires_working_threading()
def test_lru_cache_threaded3(self):
@self.module.lru_cache(maxsize=2)
def f(x):
Expand Down Expand Up @@ -2914,6 +2917,7 @@ def test_cached_attribute_name_differs_from_func_name(self):
self.assertEqual(item.get_cost(), 4)
self.assertEqual(item.cached_cost, 3)

@threading_helper.requires_working_threading()
def test_threaded(self):
go = threading.Event()
item = CachedCostItemWait(go)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_gc.py
Expand Up @@ -365,6 +365,7 @@ def __del__(self):
v = {1: v, 2: Ouch()}
gc.disable()

@threading_helper.requires_working_threading()
def test_trashcan_threads(self):
# Issue #13992: trashcan mechanism should be thread-safe
NESTING = 60
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_hashlib.py
Expand Up @@ -915,6 +915,7 @@ def test_gil(self):
)

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_threaded_hashing(self):
# Updating the same hash object from several threads at once
# using data chunk sizes containing the same byte sequences.
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_import/__init__.py
Expand Up @@ -448,6 +448,7 @@ def test_issue31492(self):
with self.assertRaises(AttributeError):
os.does_not_exist

@threading_helper.requires_working_threading()
def test_concurrency(self):
# bpo 38091: this is a hack to slow down the code that calls
# has_deadlock(); the logic was itself sometimes deadlocking.
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_importlib/test_locks.py
Expand Up @@ -12,6 +12,9 @@
from test import lock_tests


threading_helper.requires_working_threading(module=True)


class ModuleLockAsRLockTests:
locktype = classmethod(lambda cls: cls.LockType("some_lock"))

Expand Down Expand Up @@ -146,4 +149,4 @@ def setUpModule():


if __name__ == '__main__':
unittets.main()
unittest.main()
2 changes: 2 additions & 0 deletions Lib/test/test_importlib/test_threaded_import.py
Expand Up @@ -19,6 +19,8 @@
from test.support.os_helper import (TESTFN, unlink, rmtree)
from test.support import script_helper, threading_helper

threading_helper.requires_working_threading(module=True)

def task(N, done, done_tasks, errors):
try:
# We don't use modulefinder but still import it in order to stress
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_io.py
Expand Up @@ -1451,6 +1451,7 @@ def test_read_all(self):
self.assertEqual(b"abcdefg", bufio.read())

@support.requires_resource('cpu')
@threading_helper.requires_working_threading()
def test_threads(self):
try:
# Write out many bytes with exactly the same number of 0's,
Expand Down Expand Up @@ -1825,6 +1826,7 @@ def test_truncate_after_write(self):
self.assertEqual(f.tell(), buffer_size + 2)

@support.requires_resource('cpu')
@threading_helper.requires_working_threading()
def test_threads(self):
try:
# Write out many bytes from many threads and test they were
Expand Down Expand Up @@ -1895,6 +1897,7 @@ def bad_write(b):
self.assertRaises(OSError, b.close) # exception not swallowed
self.assertTrue(b.closed)

@threading_helper.requires_working_threading()
def test_slow_close_from_thread(self):
# Issue #31976
rawio = self.SlowFlushRawIO()
Expand Down Expand Up @@ -3287,6 +3290,7 @@ def test_errors_property(self):
self.assertEqual(f.errors, "replace")

@support.no_tracing
@threading_helper.requires_working_threading()
def test_threads_write(self):
# Issue6750: concurrent writes could duplicate data
event = threading.Event()
Expand Down Expand Up @@ -4362,9 +4366,11 @@ def run():
else:
self.assertFalse(err.strip('.!'))

@threading_helper.requires_working_threading()
def test_daemon_threads_shutdown_stdout_deadlock(self):
self.check_daemon_threads_shutdown_deadlock('stdout')

@threading_helper.requires_working_threading()
def test_daemon_threads_shutdown_stderr_deadlock(self):
self.check_daemon_threads_shutdown_deadlock('stderr')

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_itertools.py
@@ -1,6 +1,7 @@
import doctest
import unittest
from test import support
from test.support import threading_helper
from itertools import *
import weakref
from decimal import Decimal
Expand Down Expand Up @@ -1533,6 +1534,7 @@ def __next__(self):
with self.assertRaisesRegex(RuntimeError, "tee"):
next(a)

@threading_helper.requires_working_threading()
def test_tee_concurrent(self):
start = threading.Event()
finish = threading.Event()
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_logging.py
Expand Up @@ -630,6 +630,7 @@ def test_path_objects(self):
@unittest.skipIf(
support.is_emscripten, "Emscripten cannot fstat unlinked files."
)
@threading_helper.requires_working_threading()
def test_race(self):
# Issue #14632 refers.
def remove_loop(fname, tries):
Expand Down Expand Up @@ -679,6 +680,7 @@ def remove_loop(fname, tries):
# This helps ensure that when fork exists (the important concept) that the
# register_at_fork mechanism is also present and used.
@support.requires_fork()
@threading_helper.requires_working_threading()
def test_post_fork_child_no_deadlock(self):
"""Ensure child logging locks are not held; bpo-6721 & bpo-36533."""
class _OurHandler(logging.Handler):
Expand Down Expand Up @@ -1063,6 +1065,7 @@ class TestUnixDatagramServer(TestUDPServer):
# - end of server_helper section

@support.requires_working_socket()
@threading_helper.requires_working_threading()
class SMTPHandlerTest(BaseTest):
# bpo-14314, bpo-19665, bpo-34092: don't wait forever
TIMEOUT = support.LONG_TIMEOUT
Expand Down Expand Up @@ -1172,6 +1175,7 @@ def test_flush_on_close(self):
# assert that no new lines have been added
self.assert_log_lines(lines) # no change

@threading_helper.requires_working_threading()
def test_race_between_set_target_and_flush(self):
class MockRaceConditionHandler:
def __init__(self, mem_hdlr):
Expand Down Expand Up @@ -1687,6 +1691,7 @@ def test_defaults_do_no_interpolation(self):


@support.requires_working_socket()
@threading_helper.requires_working_threading()
class SocketHandlerTest(BaseTest):

"""Test for SocketHandler objects."""
Expand Down Expand Up @@ -1802,6 +1807,7 @@ def tearDown(self):
os_helper.unlink(self.address)

@support.requires_working_socket()
@threading_helper.requires_working_threading()
class DatagramHandlerTest(BaseTest):

"""Test for DatagramHandler."""
Expand Down Expand Up @@ -1884,6 +1890,7 @@ def tearDown(self):
os_helper.unlink(self.address)

@support.requires_working_socket()
@threading_helper.requires_working_threading()
class SysLogHandlerTest(BaseTest):

"""Test for SysLogHandler using UDP."""
Expand Down Expand Up @@ -1994,6 +2001,7 @@ def tearDown(self):
super(IPv6SysLogHandlerTest, self).tearDown()

@support.requires_working_socket()
@threading_helper.requires_working_threading()
class HTTPHandlerTest(BaseTest):
"""Test for HTTPHandler."""

Expand Down Expand Up @@ -3575,6 +3583,7 @@ def test_logrecord_class(self):
])


@threading_helper.requires_working_threading()
class QueueHandlerTest(BaseTest):
# Do not bother with a logger name group.
expected_log_pat = r"^[\w.]+ -> (\w+): (\d+)$"
Expand Down Expand Up @@ -3684,6 +3693,7 @@ def test_queue_listener_with_multiple_handlers(self):
import multiprocessing
from unittest.mock import patch

@threading_helper.requires_working_threading()
class QueueListenerTest(BaseTest):
"""
Tests based on patch submitted for issue #27930. Ensure that
Expand Down