Skip to content

Commit aedfb47

Browse files
committed
[3.11] pythongh-114099: Add test exclusions to support running the test suite on iOS (python#114889)
Add test annotations required to run the test suite on iOS (PEP 730). The majority of the change involve annotating tests that use subprocess, but are skipped on Emscripten/WASI for other reasons, and including iOS/tvOS/watchOS under the same umbrella as macOS/darwin checks. `is_apple` and `is_apple_mobile` test helpers have been added to identify *any* Apple platform, and "any Apple platform except macOS", respectively.
1 parent a86f02e commit aedfb47

27 files changed

+156
-92
lines changed

Lib/test/support/__init__.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer",
4646
# sys
4747
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
48-
"check_impl_detail", "unix_shell", "setswitchinterval",
48+
"is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
4949
# network
5050
"open_urlresource",
5151
# processes
@@ -517,7 +517,7 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'):
517517

518518
is_android = hasattr(sys, 'getandroidapilevel')
519519

520-
if sys.platform not in ('win32', 'vxworks'):
520+
if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
521521
unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
522522
else:
523523
unix_shell = None
@@ -527,19 +527,35 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'):
527527
is_emscripten = sys.platform == "emscripten"
528528
is_wasi = sys.platform == "wasi"
529529

530-
has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi
530+
# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not
531+
# have subprocess or fork support.
532+
is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
533+
is_apple = is_apple_mobile or sys.platform == "darwin"
534+
535+
has_fork_support = hasattr(os, "fork") and not (
536+
is_emscripten
537+
or is_wasi
538+
or is_apple_mobile
539+
)
531540

532541
def requires_fork():
533542
return unittest.skipUnless(has_fork_support, "requires working os.fork()")
534543

535-
has_subprocess_support = not is_emscripten and not is_wasi
544+
has_subprocess_support = not (
545+
is_emscripten
546+
or is_wasi
547+
or is_apple_mobile
548+
)
536549

537550
def requires_subprocess():
538551
"""Used for subprocess, os.spawn calls, fd inheritance"""
539552
return unittest.skipUnless(has_subprocess_support, "requires subprocess support")
540553

541554
# Emscripten's socket emulation and WASI sockets have limitations.
542-
has_socket_support = not is_emscripten and not is_wasi
555+
has_socket_support = not (
556+
is_emscripten
557+
or is_wasi
558+
)
543559

544560
def requires_working_socket(*, module=False):
545561
"""Skip tests or modules that require working sockets

Lib/test/support/os_helper.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
# TESTFN_UNICODE is a non-ascii filename
2525
TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f"
26-
if sys.platform == 'darwin':
27-
# In Mac OS X's VFS API file names are, by definition, canonically
26+
if support.is_apple:
27+
# On Apple's VFS API file names are, by definition, canonically
2828
# decomposed Unicode, encoded using UTF-8. See QA1173:
2929
# http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
3030
import unicodedata
@@ -49,8 +49,8 @@
4949
'encoding (%s). Unicode filename tests may not be effective'
5050
% (TESTFN_UNENCODABLE, sys.getfilesystemencoding()))
5151
TESTFN_UNENCODABLE = None
52-
# macOS and Emscripten deny unencodable filenames (invalid utf-8)
53-
elif sys.platform not in {'darwin', 'emscripten', 'wasi'}:
52+
# Apple and Emscripten deny unencodable filenames (invalid utf-8)
53+
elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}:
5454
try:
5555
# ascii and utf-8 cannot encode the byte 0xff
5656
b'\xff'.decode(sys.getfilesystemencoding())

Lib/test/test_asyncio/test_events.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,6 +1867,7 @@ def check_killed(self, returncode):
18671867
else:
18681868
self.assertEqual(-signal.SIGKILL, returncode)
18691869

1870+
@support.requires_subprocess()
18701871
def test_subprocess_exec(self):
18711872
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
18721873

@@ -1888,6 +1889,7 @@ def test_subprocess_exec(self):
18881889
self.check_killed(proto.returncode)
18891890
self.assertEqual(b'Python The Winner', proto.data[1])
18901891

1892+
@support.requires_subprocess()
18911893
def test_subprocess_interactive(self):
18921894
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
18931895

@@ -1915,6 +1917,7 @@ def test_subprocess_interactive(self):
19151917
self.loop.run_until_complete(proto.completed)
19161918
self.check_killed(proto.returncode)
19171919

1920+
@support.requires_subprocess()
19181921
def test_subprocess_shell(self):
19191922
connect = self.loop.subprocess_shell(
19201923
functools.partial(MySubprocessProtocol, self.loop),
@@ -1931,6 +1934,7 @@ def test_subprocess_shell(self):
19311934
self.assertEqual(proto.data[2], b'')
19321935
transp.close()
19331936

1937+
@support.requires_subprocess()
19341938
def test_subprocess_exitcode(self):
19351939
connect = self.loop.subprocess_shell(
19361940
functools.partial(MySubprocessProtocol, self.loop),
@@ -1942,6 +1946,7 @@ def test_subprocess_exitcode(self):
19421946
self.assertEqual(7, proto.returncode)
19431947
transp.close()
19441948

1949+
@support.requires_subprocess()
19451950
def test_subprocess_close_after_finish(self):
19461951
connect = self.loop.subprocess_shell(
19471952
functools.partial(MySubprocessProtocol, self.loop),
@@ -1956,6 +1961,7 @@ def test_subprocess_close_after_finish(self):
19561961
self.assertEqual(7, proto.returncode)
19571962
self.assertIsNone(transp.close())
19581963

1964+
@support.requires_subprocess()
19591965
def test_subprocess_kill(self):
19601966
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
19611967

@@ -1972,6 +1978,7 @@ def test_subprocess_kill(self):
19721978
self.check_killed(proto.returncode)
19731979
transp.close()
19741980

1981+
@support.requires_subprocess()
19751982
def test_subprocess_terminate(self):
19761983
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
19771984

@@ -1989,6 +1996,7 @@ def test_subprocess_terminate(self):
19891996
transp.close()
19901997

19911998
@unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
1999+
@support.requires_subprocess()
19922000
def test_subprocess_send_signal(self):
19932001
# bpo-31034: Make sure that we get the default signal handler (killing
19942002
# the process). The parent process may have decided to ignore SIGHUP,
@@ -2013,6 +2021,7 @@ def test_subprocess_send_signal(self):
20132021
finally:
20142022
signal.signal(signal.SIGHUP, old_handler)
20152023

2024+
@support.requires_subprocess()
20162025
def test_subprocess_stderr(self):
20172026
prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
20182027

@@ -2034,6 +2043,7 @@ def test_subprocess_stderr(self):
20342043
self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2])
20352044
self.assertEqual(0, proto.returncode)
20362045

2046+
@support.requires_subprocess()
20372047
def test_subprocess_stderr_redirect_to_stdout(self):
20382048
prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
20392049

@@ -2059,6 +2069,7 @@ def test_subprocess_stderr_redirect_to_stdout(self):
20592069
transp.close()
20602070
self.assertEqual(0, proto.returncode)
20612071

2072+
@support.requires_subprocess()
20622073
def test_subprocess_close_client_stream(self):
20632074
prog = os.path.join(os.path.dirname(__file__), 'echo3.py')
20642075

@@ -2093,6 +2104,7 @@ def test_subprocess_close_client_stream(self):
20932104
self.loop.run_until_complete(proto.completed)
20942105
self.check_killed(proto.returncode)
20952106

2107+
@support.requires_subprocess()
20962108
def test_subprocess_wait_no_same_group(self):
20972109
# start the new process in a new session
20982110
connect = self.loop.subprocess_shell(
@@ -2105,6 +2117,7 @@ def test_subprocess_wait_no_same_group(self):
21052117
self.assertEqual(7, proto.returncode)
21062118
transp.close()
21072119

2120+
@support.requires_subprocess()
21082121
def test_subprocess_exec_invalid_args(self):
21092122
async def connect(**kwds):
21102123
await self.loop.subprocess_exec(
@@ -2118,6 +2131,7 @@ async def connect(**kwds):
21182131
with self.assertRaises(ValueError):
21192132
self.loop.run_until_complete(connect(shell=True))
21202133

2134+
@support.requires_subprocess()
21212135
def test_subprocess_shell_invalid_args(self):
21222136

21232137
async def connect(cmd=None, **kwds):

Lib/test/test_asyncio/test_streams.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import threading
1010
import unittest
1111
from unittest import mock
12-
from test.support import socket_helper
12+
from test.support import requires_subprocess, socket_helper
1313
try:
1414
import ssl
1515
except ImportError:
@@ -769,6 +769,7 @@ async def client(addr):
769769
self.assertEqual(msg2, b"hello world 2!\n")
770770

771771
@unittest.skipIf(sys.platform == 'win32', "Don't have pipes")
772+
@requires_subprocess()
772773
def test_read_all_from_pipe_reader(self):
773774
# See asyncio issue 168. This test is derived from the example
774775
# subprocess_attach_read_pipe.py, but we configure the

Lib/test/test_asyncio/test_subprocess.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def _start(self, *args, **kwargs):
4848
self._proc.pid = -1
4949

5050

51+
@support.requires_subprocess()
5152
class SubprocessTransportTests(test_utils.TestCase):
5253
def setUp(self):
5354
super().setUp()
@@ -111,6 +112,7 @@ def test_subprocess_repr(self):
111112
transport.close()
112113

113114

115+
@support.requires_subprocess()
114116
class SubprocessMixin:
115117

116118
def test_stdin_stdout(self):

Lib/test/test_cmd_line_script.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414

1515
import textwrap
1616
from test import support
17-
from test.support import import_helper
18-
from test.support import os_helper
17+
from test.support import import_helper, is_apple, os_helper
1918
from test.support.script_helper import (
2019
make_pkg, make_script, make_zip_pkg, make_zip_script,
2120
assert_python_ok, assert_python_failure, spawn_python, kill_python)
@@ -555,12 +554,17 @@ def test_pep_409_verbiage(self):
555554
self.assertTrue(text[3].startswith('NameError'))
556555

557556
def test_non_ascii(self):
558-
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
557+
# Apple platforms deny the creation of a file with an invalid UTF-8 name.
559558
# Windows allows creating a name with an arbitrary bytes name, but
560559
# Python cannot a undecodable bytes argument to a subprocess.
561-
# WASI does not permit invalid UTF-8 names.
562-
if (os_helper.TESTFN_UNDECODABLE
563-
and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
560+
# Emscripten/WASI does not permit invalid UTF-8 names.
561+
if (
562+
os_helper.TESTFN_UNDECODABLE
563+
and sys.platform not in {
564+
"win32", "emscripten", "wasi"
565+
}
566+
and not is_apple
567+
):
564568
name = os.fsdecode(os_helper.TESTFN_UNDECODABLE)
565569
elif os_helper.TESTFN_NONASCII:
566570
name = os_helper.TESTFN_NONASCII

Lib/test/test_fcntl.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77
import unittest
88
from multiprocessing import Process
9-
from test.support import verbose, cpython_only
9+
from test.support import cpython_only, requires_subprocess, verbose
1010
from test.support.import_helper import import_module
1111
from test.support.os_helper import TESTFN, unlink
1212

@@ -156,6 +156,7 @@ def test_flock(self):
156156
self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH)
157157

158158
@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
159+
@requires_subprocess()
159160
def test_lockf_exclusive(self):
160161
self.f = open(TESTFN, 'wb+')
161162
cmd = fcntl.LOCK_EX | fcntl.LOCK_NB
@@ -167,6 +168,7 @@ def test_lockf_exclusive(self):
167168
self.assertEqual(p.exitcode, 0)
168169

169170
@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
171+
@requires_subprocess()
170172
def test_lockf_share(self):
171173
self.f = open(TESTFN, 'wb+')
172174
cmd = fcntl.LOCK_SH | fcntl.LOCK_NB

Lib/test/test_ftplib.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from unittest import TestCase, skipUnless
2020
from test import support
21+
from test.support import requires_subprocess
2122
from test.support import threading_helper
2223
from test.support import socket_helper
2324
from test.support import warnings_helper
@@ -902,6 +903,7 @@ def retr():
902903

903904

904905
@skipUnless(ssl, "SSL not available")
906+
@requires_subprocess()
905907
class TestTLS_FTPClassMixin(TestFTPClass):
906908
"""Repeat TestFTPClass tests starting the TLS layer for both control
907909
and data connections first.
@@ -918,6 +920,7 @@ def setUp(self, encoding=DEFAULT_ENCODING):
918920

919921

920922
@skipUnless(ssl, "SSL not available")
923+
@requires_subprocess()
921924
class TestTLS_FTPClass(TestCase):
922925
"""Specific TLS_FTP class tests."""
923926

Lib/test/test_genericpath.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import sys
88
import unittest
99
import warnings
10-
from test.support import is_emscripten
11-
from test.support import os_helper
12-
from test.support import warnings_helper
10+
from test.support import (
11+
is_apple, is_emscripten, os_helper, warnings_helper
12+
)
1313
from test.support.script_helper import assert_python_ok
1414
from test.support.os_helper import FakePath
1515

@@ -483,12 +483,16 @@ def test_abspath_issue3426(self):
483483
self.assertIsInstance(abspath(path), str)
484484

485485
def test_nonascii_abspath(self):
486-
if (os_helper.TESTFN_UNDECODABLE
487-
# macOS and Emscripten deny the creation of a directory with an
488-
# invalid UTF-8 name. Windows allows creating a directory with an
489-
# arbitrary bytes name, but fails to enter this directory
490-
# (when the bytes name is used).
491-
and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')):
486+
if (
487+
os_helper.TESTFN_UNDECODABLE
488+
# Apple platforms and Emscripten/WASI deny the creation of a
489+
# directory with an invalid UTF-8 name. Windows allows creating a
490+
# directory with an arbitrary bytes name, but fails to enter this
491+
# directory (when the bytes name is used).
492+
and sys.platform not in {
493+
"win32", "emscripten", "wasi"
494+
} and not is_apple
495+
):
492496
name = os_helper.TESTFN_UNDECODABLE
493497
elif os_helper.TESTFN_NONASCII:
494498
name = os_helper.TESTFN_NONASCII

Lib/test/test_httpservers.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030

3131
import unittest
3232
from test import support
33-
from test.support import os_helper
34-
from test.support import threading_helper
33+
from test.support import (
34+
is_apple, os_helper, requires_subprocess, threading_helper
35+
)
3536

3637
support.requires_working_socket(module=True)
3738

@@ -410,8 +411,8 @@ def close_conn():
410411
reader.close()
411412
return body
412413

413-
@unittest.skipIf(sys.platform == 'darwin',
414-
'undecodable name cannot always be decoded on macOS')
414+
@unittest.skipIf(is_apple,
415+
'undecodable name cannot always be decoded on Apple platforms')
415416
@unittest.skipIf(sys.platform == 'win32',
416417
'undecodable name cannot be decoded on win32')
417418
@unittest.skipUnless(os_helper.TESTFN_UNDECODABLE,
@@ -422,11 +423,11 @@ def test_undecodable_filename(self):
422423
with open(os.path.join(self.tempdir, filename), 'wb') as f:
423424
f.write(os_helper.TESTFN_UNDECODABLE)
424425
response = self.request(self.base_url + '/')
425-
if sys.platform == 'darwin':
426-
# On Mac OS the HFS+ filesystem replaces bytes that aren't valid
427-
# UTF-8 into a percent-encoded value.
426+
if is_apple:
427+
# On Apple platforms the HFS+ filesystem replaces bytes that
428+
# aren't valid UTF-8 into a percent-encoded value.
428429
for name in os.listdir(self.tempdir):
429-
if name != 'test': # Ignore a filename created in setUp().
430+
if name != 'test': # Ignore a filename created in setUp().
430431
filename = name
431432
break
432433
body = self.check_status_and_reason(response, HTTPStatus.OK)
@@ -697,6 +698,7 @@ def test_html_escape_filename(self):
697698

698699
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
699700
"This test can't be run reliably as root (issue #13308).")
701+
@requires_subprocess()
700702
class CGIHTTPServerTestCase(BaseTestCase):
701703
class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
702704
pass

0 commit comments

Comments
 (0)