Skip to content

Commit 37bc54a

Browse files
gh-152548: Convert the test_signal wakeup tests to @runInSubprocess()
WakeupSignalTests and WakeupSocketSignalTests ran each test as a script string via assert_python_ok('-c', ...), using a subprocess to have a single thread for signal delivery. Decorate them with @runInSubprocess() instead, so the same code runs as a normal method body using self.assert*. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bde526d commit 37bc54a

1 file changed

Lines changed: 87 additions & 146 deletions

File tree

Lib/test/test_signal.py

Lines changed: 87 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import time
1414
import unittest
1515
from test import support
16+
from test.support import isolation
1617
from test.support import (
1718
force_not_colorized, is_apple, is_apple_mobile, os_helper, threading_helper
1819
)
@@ -312,59 +313,51 @@ def test_set_wakeup_fd_blocking(self):
312313

313314
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
314315
class WakeupSignalTests(unittest.TestCase):
315-
@unittest.skipIf(_testcapi is None, 'need _testcapi')
316-
def check_wakeup(self, test_body, *signals, ordered=True):
317-
# use a subprocess to have only one thread
318-
code = """if 1:
319-
import _testcapi
320-
import os
321-
import signal
316+
def check_wakeup(self, test, *signals, ordered=True):
317+
# The caller is decorated with @runInSubprocess(), so this runs in a fresh
318+
# subprocess that has only one thread for the signal to be delivered to.
322319
import struct
323320

324-
signals = {!r}
321+
signals = tuple(int(s) for s in signals)
325322

326323
def handler(signum, frame):
327324
pass
328325

329-
def check_signum(signals):
330-
data = os.read(read, len(signals)+1)
331-
raised = struct.unpack('%uB' % len(data), data)
332-
if not {!r}:
333-
raised = set(raised)
334-
signals = set(signals)
335-
if raised != signals:
336-
raise Exception("%r != %r" % (raised, signals))
337-
338-
{}
339-
340326
signal.signal(signal.SIGALRM, handler)
341327
read, write = os.pipe()
342328
os.set_blocking(write, False)
343329
signal.set_wakeup_fd(write)
344330

345-
test()
346-
check_signum(signals)
331+
test(read)
332+
333+
data = os.read(read, len(signals) + 1)
334+
raised = struct.unpack('%uB' % len(data), data)
335+
if ordered:
336+
self.assertEqual(raised, signals)
337+
else:
338+
self.assertEqual(set(raised), set(signals))
347339

348340
os.close(read)
349341
os.close(write)
350-
""".format(tuple(map(int, signals)), ordered, test_body)
351-
352-
assert_python_ok('-c', code)
353342

354343
@unittest.skipIf(_testcapi is None, 'need _testcapi')
355344
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
356345
@force_not_colorized
346+
@isolation.runInSubprocess()
357347
def test_wakeup_write_error(self):
358348
# Issue #16105: write() errors in the C signal handler should not
359349
# pass silently.
360-
# Use a subprocess to have only one thread.
361-
code = """if 1:
362-
import _testcapi
363-
import errno
364-
import os
365-
import signal
366-
import sys
367-
from test.support import captured_stderr
350+
# Use @runInSubprocess() to run in a subprocess with only one thread.
351+
r, w = os.pipe()
352+
try:
353+
os.write(r, b'x')
354+
except OSError:
355+
pass
356+
else:
357+
self.skipTest("OS doesn't report write() error on the read end of a pipe")
358+
finally:
359+
os.close(r)
360+
os.close(w)
368361

369362
def handler(signum, frame):
370363
1/0
@@ -375,38 +368,23 @@ def handler(signum, frame):
375368

376369
# Set wakeup_fd a read-only file descriptor to trigger the error
377370
signal.set_wakeup_fd(r)
378-
try:
379-
with captured_stderr() as err:
371+
with support.captured_stderr() as err:
372+
with self.assertRaises(ZeroDivisionError):
380373
signal.raise_signal(signal.SIGALRM)
381-
except ZeroDivisionError:
382-
# An ignored exception should have been printed out on stderr
383-
err = err.getvalue()
384-
if ('Exception ignored while trying to write to the signal wakeup fd'
385-
not in err):
386-
raise AssertionError(err)
387-
if ('OSError: [Errno %d]' % errno.EBADF) not in err:
388-
raise AssertionError(err)
389-
else:
390-
raise AssertionError("ZeroDivisionError not raised")
374+
# An ignored exception should have been printed out on stderr
375+
err = err.getvalue()
376+
self.assertIn(
377+
'Exception ignored while trying to write to the signal wakeup fd',
378+
err)
379+
self.assertIn('OSError: [Errno %d]' % errno.EBADF, err)
391380

392381
os.close(r)
393382
os.close(w)
394-
"""
395-
r, w = os.pipe()
396-
try:
397-
os.write(r, b'x')
398-
except OSError:
399-
pass
400-
else:
401-
self.skipTest("OS doesn't report write() error on the read end of a pipe")
402-
finally:
403-
os.close(r)
404-
os.close(w)
405-
406-
assert_python_ok('-c', code)
407383

384+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
385+
@isolation.runInSubprocess()
408386
def test_wakeup_fd_early(self):
409-
self.check_wakeup("""def test():
387+
def test(read):
410388
import select
411389
import time
412390

@@ -429,18 +407,19 @@ def handler(signum, frame):
429407
except InterruptSelect:
430408
pass
431409
else:
432-
raise Exception("select() was not interrupted")
410+
self.fail("select() was not interrupted")
433411

434412
before_time = time.monotonic()
435413
select.select([read], [], [], TIMEOUT_FULL)
436414
after_time = time.monotonic()
437415
dt = after_time - before_time
438-
if dt >= TIMEOUT_HALF:
439-
raise Exception("%s >= %s" % (dt, TIMEOUT_HALF))
440-
""", signal.SIGALRM)
416+
self.assertLess(dt, TIMEOUT_HALF)
417+
self.check_wakeup(test, signal.SIGALRM)
441418

419+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
420+
@isolation.runInSubprocess()
442421
def test_wakeup_fd_during(self):
443-
self.check_wakeup("""def test():
422+
def test(read):
444423
import select
445424
import time
446425

@@ -462,27 +441,34 @@ def handler(signum, frame):
462441
except InterruptSelect:
463442
pass
464443
else:
465-
raise Exception("select() was not interrupted")
444+
self.fail("select() was not interrupted")
466445
after_time = time.monotonic()
467446
dt = after_time - before_time
468-
if dt >= TIMEOUT_HALF:
469-
raise Exception("%s >= %s" % (dt, TIMEOUT_HALF))
470-
""", signal.SIGALRM)
447+
self.assertLess(dt, TIMEOUT_HALF)
448+
self.check_wakeup(test, signal.SIGALRM)
471449

450+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
451+
@isolation.runInSubprocess()
472452
def test_signum(self):
473-
self.check_wakeup("""def test():
453+
def test(read):
454+
def handler(signum, frame):
455+
pass
474456
signal.signal(signal.SIGUSR1, handler)
475457
signal.raise_signal(signal.SIGUSR1)
476458
signal.raise_signal(signal.SIGALRM)
477-
""", signal.SIGUSR1, signal.SIGALRM)
459+
self.check_wakeup(test, signal.SIGUSR1, signal.SIGALRM)
478460

479461
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
480462
'need signal.pthread_sigmask()')
463+
@unittest.skipIf(_testcapi is None, 'need _testcapi')
464+
@isolation.runInSubprocess()
481465
def test_pending(self):
482-
self.check_wakeup("""def test():
466+
def test(read):
483467
signum1 = signal.SIGUSR1
484468
signum2 = signal.SIGUSR2
485469

470+
def handler(signum, frame):
471+
pass
486472
signal.signal(signum1, handler)
487473
signal.signal(signum2, handler)
488474

@@ -491,20 +477,17 @@ def test_pending(self):
491477
signal.raise_signal(signum2)
492478
# Unblocking the 2 signals calls the C signal handler twice
493479
signal.pthread_sigmask(signal.SIG_UNBLOCK, (signum1, signum2))
494-
""", signal.SIGUSR1, signal.SIGUSR2, ordered=False)
480+
self.check_wakeup(test, signal.SIGUSR1, signal.SIGUSR2, ordered=False)
495481

496482

497483
@unittest.skipUnless(hasattr(socket, 'socketpair'), 'need socket.socketpair')
498484
class WakeupSocketSignalTests(unittest.TestCase):
499485

500486
@unittest.skipIf(_testcapi is None, 'need _testcapi')
487+
@isolation.runInSubprocess()
501488
def test_socket(self):
502-
# use a subprocess to have only one thread
503-
code = """if 1:
504-
import signal
505-
import socket
489+
# Use @runInSubprocess() to run in a subprocess with only one thread.
506490
import struct
507-
import _testcapi
508491

509492
signum = signal.SIGINT
510493
signals = (signum,)
@@ -521,33 +504,21 @@ def handler(signum, frame):
521504
signal.raise_signal(signum)
522505

523506
data = read.recv(1)
524-
if not data:
525-
raise Exception("no signum written")
507+
self.assertTrue(data, "no signum written")
526508
raised = struct.unpack('B', data)
527-
if raised != signals:
528-
raise Exception("%r != %r" % (raised, signals))
509+
self.assertEqual(raised, signals)
529510

530511
read.close()
531512
write.close()
532-
"""
533-
534-
assert_python_ok('-c', code)
535513

536514
@unittest.skipIf(_testcapi is None, 'need _testcapi')
515+
@isolation.runInSubprocess()
537516
def test_send_error(self):
538-
# Use a subprocess to have only one thread.
517+
# Use @runInSubprocess() to run in a subprocess with only one thread.
539518
if os.name == 'nt':
540519
action = 'send'
541520
else:
542521
action = 'write'
543-
code = """if 1:
544-
import errno
545-
import signal
546-
import socket
547-
import sys
548-
import time
549-
import _testcapi
550-
from test.support import captured_stderr
551522

552523
signum = signal.SIGINT
553524

@@ -566,31 +537,22 @@ def handler(signum, frame):
566537
read.close()
567538
write.close()
568539

569-
with captured_stderr() as err:
540+
with support.captured_stderr() as err:
570541
signal.raise_signal(signum)
571542

572-
err = err.getvalue()
573-
if ('Exception ignored while trying to {action} to the signal wakeup fd'
574-
not in err):
575-
raise AssertionError(err)
576-
""".format(action=action)
577-
assert_python_ok('-c', code)
543+
self.assertIn(
544+
'Exception ignored while trying to %s to the signal wakeup fd'
545+
% action,
546+
err.getvalue())
578547

579548
@unittest.skipIf(_testcapi is None, 'need _testcapi')
549+
@isolation.runInSubprocess()
580550
def test_warn_on_full_buffer(self):
581-
# Use a subprocess to have only one thread.
551+
# Use @runInSubprocess() to run in a subprocess with only one thread.
582552
if os.name == 'nt':
583553
action = 'send'
584554
else:
585555
action = 'write'
586-
code = """if 1:
587-
import errno
588-
import signal
589-
import socket
590-
import sys
591-
import time
592-
import _testcapi
593-
from test.support import captured_stderr
594556

595557
signum = signal.SIGINT
596558

@@ -627,67 +589,46 @@ def handler(signum, frame):
627589
except (BlockingIOError, TimeoutError):
628590
pass
629591

630-
print(f"%s bytes written into the socketpair" % written, flush=True)
631-
632592
write.setblocking(False)
633-
try:
593+
with self.assertRaises(BlockingIOError,
594+
msg="%s bytes failed to fill the socketpair "
595+
"buffer" % written):
634596
write.send(b"x")
635-
except BlockingIOError:
636-
# The socketpair buffer seems full
637-
pass
638-
else:
639-
raise AssertionError("%s bytes failed to fill the socketpair "
640-
"buffer" % written)
641597

642598
# By default, we get a warning when a signal arrives
643-
msg = ('Exception ignored while trying to {action} '
644-
'to the signal wakeup fd')
599+
msg = ('Exception ignored while trying to %s '
600+
'to the signal wakeup fd' % action)
645601
signal.set_wakeup_fd(write.fileno())
646602

647-
with captured_stderr() as err:
603+
with support.captured_stderr() as err:
648604
signal.raise_signal(signum)
649-
650-
err = err.getvalue()
651-
if msg not in err:
652-
raise AssertionError("first set_wakeup_fd() test failed, "
653-
"stderr: %r" % err)
605+
self.assertIn(msg, err.getvalue(),
606+
"first set_wakeup_fd() test failed")
654607

655608
# And also if warn_on_full_buffer=True
656609
signal.set_wakeup_fd(write.fileno(), warn_on_full_buffer=True)
657610

658-
with captured_stderr() as err:
611+
with support.captured_stderr() as err:
659612
signal.raise_signal(signum)
660-
661-
err = err.getvalue()
662-
if msg not in err:
663-
raise AssertionError("set_wakeup_fd(warn_on_full_buffer=True) "
664-
"test failed, stderr: %r" % err)
613+
self.assertIn(msg, err.getvalue(),
614+
"set_wakeup_fd(warn_on_full_buffer=True) test failed")
665615

666616
# But not if warn_on_full_buffer=False
667617
signal.set_wakeup_fd(write.fileno(), warn_on_full_buffer=False)
668618

669-
with captured_stderr() as err:
619+
with support.captured_stderr() as err:
670620
signal.raise_signal(signum)
671-
672-
err = err.getvalue()
673-
if err != "":
674-
raise AssertionError("set_wakeup_fd(warn_on_full_buffer=False) "
675-
"test failed, stderr: %r" % err)
621+
self.assertEqual(err.getvalue(), "",
622+
"set_wakeup_fd(warn_on_full_buffer=False) test failed")
676623

677624
# And then check the default again, to make sure warn_on_full_buffer
678625
# settings don't leak across calls.
679626
signal.set_wakeup_fd(write.fileno())
680627

681-
with captured_stderr() as err:
628+
with support.captured_stderr() as err:
682629
signal.raise_signal(signum)
683-
684-
err = err.getvalue()
685-
if msg not in err:
686-
raise AssertionError("second set_wakeup_fd() test failed, "
687-
"stderr: %r" % err)
688-
689-
""".format(action=action)
690-
assert_python_ok('-c', code)
630+
self.assertIn(msg, err.getvalue(),
631+
"second set_wakeup_fd() test failed")
691632

692633

693634
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")

0 commit comments

Comments
 (0)