Skip to content

Commit 2b16a08

Browse files
tiranbrettcannon
andauthored
bpo-40280: Detect missing threading on WASM platforms (GH-32352)
Co-authored-by: Brett Cannon <[email protected]>
1 parent 5aee46b commit 2b16a08

33 files changed

+103
-21
lines changed

Lib/distutils/tests/test_build_ext.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from test import support
1818
from test.support import os_helper
1919
from test.support.script_helper import assert_python_ok
20+
from test.support import threading_helper
2021

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

169+
@threading_helper.requires_working_threading()
168170
def test_optional_extension(self):
169171

170172
# this extension will fail, but let's ignore this failure

Lib/test/libregrtest/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from test.libregrtest.utils import removepy, count, format_duration, printlist
2121
from test import support
2222
from test.support import os_helper
23+
from test.support import threading_helper
2324

2425

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

681683
sys.exit(exc.code)
682684

Lib/test/pickletester.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,7 @@ def test_truncated_data(self):
13801380
self.check_unpickling_error(self.truncated_errors, p)
13811381

13821382
@threading_helper.reap_threads
1383+
@threading_helper.requires_working_threading()
13831384
def test_unpickle_module_race(self):
13841385
# https://bugs.python.org/issue34572
13851386
locker_module = dedent("""

Lib/test/support/threading_helper.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
import threading
66
import time
7+
import unittest
78

89
from test import support
910

@@ -210,7 +211,7 @@ def __exit__(self, *exc_info):
210211

211212

212213
def _can_start_thread() -> bool:
213-
"""Detect if Python can start new threads.
214+
"""Detect whether Python can start new threads.
214215
215216
Some WebAssembly platforms do not provide a working pthread
216217
implementation. Thread support is stubbed and any attempt
@@ -234,3 +235,15 @@ def _can_start_thread() -> bool:
234235
return True
235236

236237
can_start_thread = _can_start_thread()
238+
239+
def requires_working_threading(*, module=False):
240+
"""Skip tests or modules that require working threading.
241+
242+
Can be used as a function/class decorator or to skip an entire module.
243+
"""
244+
msg = "requires threading support"
245+
if module:
246+
if not can_start_thread:
247+
raise unittest.SkipTest(msg)
248+
else:
249+
return unittest.skipUnless(can_start_thread, msg)

Lib/test/test_bz2.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ def testContextProtocol(self):
496496
else:
497497
self.fail("1/0 didn't raise an exception")
498498

499+
@threading_helper.requires_working_threading()
499500
def testThreading(self):
500501
# Issue #7205: Using a BZ2File from several threads shouldn't deadlock.
501502
data = b"1" * 2**20

Lib/test/test_capi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ def pendingcalls_wait(self, l, n, context = None):
710710
if False and support.verbose:
711711
print("(%i)"%(len(l),))
712712

713+
@threading_helper.requires_working_threading()
713714
def test_pendingcalls_threaded(self):
714715

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

842843
@threading_helper.reap_threads
844+
@threading_helper.requires_working_threading()
843845
def test_thread_state(self):
844846
# some extra thread-state tests driven via _testcapi
845847
def target():

Lib/test/test_context.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
import unittest
88
import weakref
9+
from test.support import threading_helper
910

1011
try:
1112
from _testcapi import hamt
@@ -341,6 +342,7 @@ def ctx2_fun():
341342
ctx1.run(ctx1_fun)
342343

343344
@isolated_context
345+
@threading_helper.requires_working_threading()
344346
def test_context_threads_1(self):
345347
cvar = contextvars.ContextVar('cvar')
346348

Lib/test/test_decimal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
run_with_locale, cpython_only,
4040
darwin_malloc_err_warning)
4141
from test.support.import_helper import import_fresh_module
42+
from test.support import threading_helper
4243
from test.support import warnings_helper
4344
import random
4445
import inspect
@@ -1591,6 +1592,8 @@ def thfunc2(cls):
15911592
for sig in Overflow, Underflow, DivisionByZero, InvalidOperation:
15921593
cls.assertFalse(thiscontext.flags[sig])
15931594

1595+
1596+
@threading_helper.requires_working_threading()
15941597
class ThreadingTest(unittest.TestCase):
15951598
'''Unit tests for thread local contexts in Decimal.'''
15961599

Lib/test/test_email/test_email.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3285,6 +3285,7 @@ def test_getaddresses_header_obj(self):
32853285
addrs = utils.getaddresses([Header('Al Person <[email protected]>')])
32863286
self.assertEqual(addrs[0][1], '[email protected]')
32873287

3288+
@threading_helper.requires_working_threading()
32883289
def test_make_msgid_collisions(self):
32893290
# Test make_msgid uniqueness, even with multiple threads
32903291
class MsgidsThread(Thread):

Lib/test/test_enum.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,6 +2949,7 @@ class Color(StrMixin, AllMixin, Flag):
29492949
self.assertEqual(str(Color.BLUE), 'blue')
29502950

29512951
@threading_helper.reap_threads
2952+
@threading_helper.requires_working_threading()
29522953
def test_unique_composite(self):
29532954
# override __eq__ to be identity only
29542955
class TestFlag(Flag):
@@ -3481,6 +3482,7 @@ class Color(StrMixin, AllMixin, IntFlag):
34813482
self.assertEqual(str(Color.BLUE), 'blue')
34823483

34833484
@threading_helper.reap_threads
3485+
@threading_helper.requires_working_threading()
34843486
def test_unique_composite(self):
34853487
# override __eq__ to be identity only
34863488
class TestFlag(IntFlag):

Lib/test/test_functools.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,7 @@ def f(zomg: 'zomg_annotation'):
16371637
for attr in self.module.WRAPPER_ASSIGNMENTS:
16381638
self.assertEqual(getattr(g, attr), getattr(f, attr))
16391639

1640+
@threading_helper.requires_working_threading()
16401641
def test_lru_cache_threaded(self):
16411642
n, m = 5, 11
16421643
def orig(x, y):
@@ -1685,6 +1686,7 @@ def clear():
16851686
finally:
16861687
sys.setswitchinterval(orig_si)
16871688

1689+
@threading_helper.requires_working_threading()
16881690
def test_lru_cache_threaded2(self):
16891691
# Simultaneous call with the same arguments
16901692
n, m = 5, 7
@@ -1712,6 +1714,7 @@ def test():
17121714
pause.reset()
17131715
self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1))
17141716

1717+
@threading_helper.requires_working_threading()
17151718
def test_lru_cache_threaded3(self):
17161719
@self.module.lru_cache(maxsize=2)
17171720
def f(x):
@@ -2914,6 +2917,7 @@ def test_cached_attribute_name_differs_from_func_name(self):
29142917
self.assertEqual(item.get_cost(), 4)
29152918
self.assertEqual(item.cached_cost, 3)
29162919

2920+
@threading_helper.requires_working_threading()
29172921
def test_threaded(self):
29182922
go = threading.Event()
29192923
item = CachedCostItemWait(go)

Lib/test/test_gc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ def __del__(self):
365365
v = {1: v, 2: Ouch()}
366366
gc.disable()
367367

368+
@threading_helper.requires_working_threading()
368369
def test_trashcan_threads(self):
369370
# Issue #13992: trashcan mechanism should be thread-safe
370371
NESTING = 60

Lib/test/test_hashlib.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ def test_gil(self):
915915
)
916916

917917
@threading_helper.reap_threads
918+
@threading_helper.requires_working_threading()
918919
def test_threaded_hashing(self):
919920
# Updating the same hash object from several threads at once
920921
# using data chunk sizes containing the same byte sequences.

Lib/test/test_import/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def test_issue31492(self):
448448
with self.assertRaises(AttributeError):
449449
os.does_not_exist
450450

451+
@threading_helper.requires_working_threading()
451452
def test_concurrency(self):
452453
# bpo 38091: this is a hack to slow down the code that calls
453454
# has_deadlock(); the logic was itself sometimes deadlocking.

Lib/test/test_importlib/test_locks.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from test import lock_tests
1313

1414

15+
threading_helper.requires_working_threading(module=True)
16+
17+
1518
class ModuleLockAsRLockTests:
1619
locktype = classmethod(lambda cls: cls.LockType("some_lock"))
1720

@@ -146,4 +149,4 @@ def setUpModule():
146149

147150

148151
if __name__ == '__main__':
149-
unittets.main()
152+
unittest.main()

Lib/test/test_importlib/test_threaded_import.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from test.support.os_helper import (TESTFN, unlink, rmtree)
2020
from test.support import script_helper, threading_helper
2121

22+
threading_helper.requires_working_threading(module=True)
23+
2224
def task(N, done, done_tasks, errors):
2325
try:
2426
# We don't use modulefinder but still import it in order to stress

Lib/test/test_io.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,7 @@ def test_read_all(self):
14511451
self.assertEqual(b"abcdefg", bufio.read())
14521452

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

18271828
@support.requires_resource('cpu')
1829+
@threading_helper.requires_working_threading()
18281830
def test_threads(self):
18291831
try:
18301832
# Write out many bytes from many threads and test they were
@@ -1895,6 +1897,7 @@ def bad_write(b):
18951897
self.assertRaises(OSError, b.close) # exception not swallowed
18961898
self.assertTrue(b.closed)
18971899

1900+
@threading_helper.requires_working_threading()
18981901
def test_slow_close_from_thread(self):
18991902
# Issue #31976
19001903
rawio = self.SlowFlushRawIO()
@@ -3287,6 +3290,7 @@ def test_errors_property(self):
32873290
self.assertEqual(f.errors, "replace")
32883291

32893292
@support.no_tracing
3293+
@threading_helper.requires_working_threading()
32903294
def test_threads_write(self):
32913295
# Issue6750: concurrent writes could duplicate data
32923296
event = threading.Event()
@@ -4362,9 +4366,11 @@ def run():
43624366
else:
43634367
self.assertFalse(err.strip('.!'))
43644368

4369+
@threading_helper.requires_working_threading()
43654370
def test_daemon_threads_shutdown_stdout_deadlock(self):
43664371
self.check_daemon_threads_shutdown_deadlock('stdout')
43674372

4373+
@threading_helper.requires_working_threading()
43684374
def test_daemon_threads_shutdown_stderr_deadlock(self):
43694375
self.check_daemon_threads_shutdown_deadlock('stderr')
43704376

Lib/test/test_itertools.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import doctest
22
import unittest
33
from test import support
4+
from test.support import threading_helper
45
from itertools import *
56
import weakref
67
from decimal import Decimal
@@ -1533,6 +1534,7 @@ def __next__(self):
15331534
with self.assertRaisesRegex(RuntimeError, "tee"):
15341535
next(a)
15351536

1537+
@threading_helper.requires_working_threading()
15361538
def test_tee_concurrent(self):
15371539
start = threading.Event()
15381540
finish = threading.Event()

Lib/test/test_logging.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ def test_path_objects(self):
630630
@unittest.skipIf(
631631
support.is_emscripten, "Emscripten cannot fstat unlinked files."
632632
)
633+
@threading_helper.requires_working_threading()
633634
def test_race(self):
634635
# Issue #14632 refers.
635636
def remove_loop(fname, tries):
@@ -679,6 +680,7 @@ def remove_loop(fname, tries):
679680
# This helps ensure that when fork exists (the important concept) that the
680681
# register_at_fork mechanism is also present and used.
681682
@support.requires_fork()
683+
@threading_helper.requires_working_threading()
682684
def test_post_fork_child_no_deadlock(self):
683685
"""Ensure child logging locks are not held; bpo-6721 & bpo-36533."""
684686
class _OurHandler(logging.Handler):
@@ -1063,6 +1065,7 @@ class TestUnixDatagramServer(TestUDPServer):
10631065
# - end of server_helper section
10641066

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

1178+
@threading_helper.requires_working_threading()
11751179
def test_race_between_set_target_and_flush(self):
11761180
class MockRaceConditionHandler:
11771181
def __init__(self, mem_hdlr):
@@ -1687,6 +1691,7 @@ def test_defaults_do_no_interpolation(self):
16871691

16881692

16891693
@support.requires_working_socket()
1694+
@threading_helper.requires_working_threading()
16901695
class SocketHandlerTest(BaseTest):
16911696

16921697
"""Test for SocketHandler objects."""
@@ -1802,6 +1807,7 @@ def tearDown(self):
18021807
os_helper.unlink(self.address)
18031808

18041809
@support.requires_working_socket()
1810+
@threading_helper.requires_working_threading()
18051811
class DatagramHandlerTest(BaseTest):
18061812

18071813
"""Test for DatagramHandler."""
@@ -1884,6 +1890,7 @@ def tearDown(self):
18841890
os_helper.unlink(self.address)
18851891

18861892
@support.requires_working_socket()
1893+
@threading_helper.requires_working_threading()
18871894
class SysLogHandlerTest(BaseTest):
18881895

18891896
"""Test for SysLogHandler using UDP."""
@@ -1994,6 +2001,7 @@ def tearDown(self):
19942001
super(IPv6SysLogHandlerTest, self).tearDown()
19952002

19962003
@support.requires_working_socket()
2004+
@threading_helper.requires_working_threading()
19972005
class HTTPHandlerTest(BaseTest):
19982006
"""Test for HTTPHandler."""
19992007

@@ -3575,6 +3583,7 @@ def test_logrecord_class(self):
35753583
])
35763584

35773585

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

3696+
@threading_helper.requires_working_threading()
36873697
class QueueListenerTest(BaseTest):
36883698
"""
36893699
Tests based on patch submitted for issue #27930. Ensure that

0 commit comments

Comments
 (0)