Skip to content

Commit 1a8319a

Browse files
committed
[3.9] 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 acf814a commit 1a8319a

24 files changed

+132
-64
lines changed

Lib/test/support/__init__.py

+19-11
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@
5252
"skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
5353
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
5454
"requires_IEEE_754", "skip_unless_xattr", "requires_zlib",
55+
"has_fork_support", "requires_fork",
56+
"has_subprocess_support", "requires_subprocess",
5557
"anticipate_failure", "load_package_tests", "detect_api_mismatch",
5658
"check__all__", "skip_if_buggy_ucrt_strfptime",
5759
"ignore_warnings", "check_sanitizer", "skip_if_sanitizer",
5860
# sys
59-
"is_jython", "is_android", "check_impl_detail", "unix_shell",
61+
"is_jython", "is_android", "is_apple", "is_apple_mobile",
62+
"check_impl_detail", "unix_shell",
6063
"setswitchinterval",
6164
# network
6265
"open_urlresource",
@@ -743,21 +746,26 @@ def requires_lzma(reason='requires lzma'):
743746

744747
is_android = hasattr(sys, 'getandroidapilevel')
745748

746-
if sys.platform != 'win32':
749+
if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
747750
unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
748751
else:
749752
unix_shell = None
750753

751-
# Filename used for testing
752-
if os.name == 'java':
753-
# Jython disallows @ in module names
754-
TESTFN_ASCII = '$test'
755-
else:
756-
TESTFN_ASCII = '@test'
754+
# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not
755+
# have subprocess or fork support.
756+
is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
757+
is_apple = is_apple_mobile or sys.platform == "darwin"
758+
759+
has_fork_support = hasattr(os, "fork") and not is_apple_mobile
760+
761+
def requires_fork():
762+
return unittest.skipUnless(has_fork_support, "requires working os.fork()")
763+
764+
has_subprocess_support = not is_apple_mobile
757765

758-
# Disambiguate TESTFN for parallel testing, while letting it remain a valid
759-
# module name.
760-
TESTFN_ASCII = "{}_{}_tmp".format(TESTFN_ASCII, os.getpid())
766+
def requires_subprocess():
767+
"""Used for subprocess, os.spawn calls, fd inheritance"""
768+
return unittest.skipUnless(has_subprocess_support, "requires subprocess support")
761769

762770
# Define the URL of a dedicated HTTP server for the network tests.
763771
# The URL must use clear-text HTTP: no redirection to encrypted HTTPS.

Lib/test/test_asyncio/test_events.py

+14
Original file line numberDiff line numberDiff line change
@@ -1730,6 +1730,7 @@ def check_killed(self, returncode):
17301730
else:
17311731
self.assertEqual(-signal.SIGKILL, returncode)
17321732

1733+
@support.requires_subprocess()
17331734
def test_subprocess_exec(self):
17341735
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
17351736

@@ -1750,6 +1751,7 @@ def test_subprocess_exec(self):
17501751
self.check_killed(proto.returncode)
17511752
self.assertEqual(b'Python The Winner', proto.data[1])
17521753

1754+
@support.requires_subprocess()
17531755
def test_subprocess_interactive(self):
17541756
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
17551757

@@ -1777,6 +1779,7 @@ def test_subprocess_interactive(self):
17771779
self.loop.run_until_complete(proto.completed)
17781780
self.check_killed(proto.returncode)
17791781

1782+
@support.requires_subprocess()
17801783
def test_subprocess_shell(self):
17811784
connect = self.loop.subprocess_shell(
17821785
functools.partial(MySubprocessProtocol, self.loop),
@@ -1793,6 +1796,7 @@ def test_subprocess_shell(self):
17931796
self.assertEqual(proto.data[2], b'')
17941797
transp.close()
17951798

1799+
@support.requires_subprocess()
17961800
def test_subprocess_exitcode(self):
17971801
connect = self.loop.subprocess_shell(
17981802
functools.partial(MySubprocessProtocol, self.loop),
@@ -1804,6 +1808,7 @@ def test_subprocess_exitcode(self):
18041808
self.assertEqual(7, proto.returncode)
18051809
transp.close()
18061810

1811+
@support.requires_subprocess()
18071812
def test_subprocess_close_after_finish(self):
18081813
connect = self.loop.subprocess_shell(
18091814
functools.partial(MySubprocessProtocol, self.loop),
@@ -1817,6 +1822,7 @@ def test_subprocess_close_after_finish(self):
18171822
self.assertEqual(7, proto.returncode)
18181823
self.assertIsNone(transp.close())
18191824

1825+
@support.requires_subprocess()
18201826
def test_subprocess_kill(self):
18211827
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
18221828

@@ -1833,6 +1839,7 @@ def test_subprocess_kill(self):
18331839
self.check_killed(proto.returncode)
18341840
transp.close()
18351841

1842+
@support.requires_subprocess()
18361843
def test_subprocess_terminate(self):
18371844
prog = os.path.join(os.path.dirname(__file__), 'echo.py')
18381845

@@ -1850,6 +1857,7 @@ def test_subprocess_terminate(self):
18501857
transp.close()
18511858

18521859
@unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP")
1860+
@support.requires_subprocess()
18531861
def test_subprocess_send_signal(self):
18541862
# bpo-31034: Make sure that we get the default signal handler (killing
18551863
# the process). The parent process may have decided to ignore SIGHUP,
@@ -1873,6 +1881,7 @@ def test_subprocess_send_signal(self):
18731881
finally:
18741882
signal.signal(signal.SIGHUP, old_handler)
18751883

1884+
@support.requires_subprocess()
18761885
def test_subprocess_stderr(self):
18771886
prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
18781887

@@ -1894,6 +1903,7 @@ def test_subprocess_stderr(self):
18941903
self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2])
18951904
self.assertEqual(0, proto.returncode)
18961905

1906+
@support.requires_subprocess()
18971907
def test_subprocess_stderr_redirect_to_stdout(self):
18981908
prog = os.path.join(os.path.dirname(__file__), 'echo2.py')
18991909

@@ -1918,6 +1928,7 @@ def test_subprocess_stderr_redirect_to_stdout(self):
19181928
transp.close()
19191929
self.assertEqual(0, proto.returncode)
19201930

1931+
@support.requires_subprocess()
19211932
def test_subprocess_close_client_stream(self):
19221933
prog = os.path.join(os.path.dirname(__file__), 'echo3.py')
19231934

@@ -1951,6 +1962,7 @@ def test_subprocess_close_client_stream(self):
19511962
self.loop.run_until_complete(proto.completed)
19521963
self.check_killed(proto.returncode)
19531964

1965+
@support.requires_subprocess()
19541966
def test_subprocess_wait_no_same_group(self):
19551967
# start the new process in a new session
19561968
connect = self.loop.subprocess_shell(
@@ -1963,6 +1975,7 @@ def test_subprocess_wait_no_same_group(self):
19631975
self.assertEqual(7, proto.returncode)
19641976
transp.close()
19651977

1978+
@support.requires_subprocess()
19661979
def test_subprocess_exec_invalid_args(self):
19671980
async def connect(**kwds):
19681981
await self.loop.subprocess_exec(
@@ -1976,6 +1989,7 @@ async def connect(**kwds):
19761989
with self.assertRaises(ValueError):
19771990
self.loop.run_until_complete(connect(shell=True))
19781991

1992+
@support.requires_subprocess()
19791993
def test_subprocess_shell_invalid_args(self):
19801994

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

Lib/test/test_asyncio/test_streams.py

+2-1
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:
@@ -732,6 +732,7 @@ async def client(path):
732732
self.assertEqual(messages, [])
733733

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

Lib/test/test_asyncio/test_subprocess.py

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def _start(self, *args, **kwargs):
3838
self._proc.pid = -1
3939

4040

41+
@support.requires_subprocess()
4142
class SubprocessTransportTests(test_utils.TestCase):
4243
def setUp(self):
4344
super().setUp()
@@ -103,6 +104,7 @@ def test_subprocess_repr(self):
103104
transport.close()
104105

105106

107+
@support.requires_subprocess()
106108
class SubprocessMixin:
107109

108110
def test_stdin_stdout(self):

Lib/test/test_cmd_line_script.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import textwrap
1616
from test import support
17+
from test.support import is_apple
1718
from test.support.script_helper import (
1819
make_pkg, make_script, make_zip_pkg, make_zip_script,
1920
assert_python_ok, assert_python_failure, spawn_python, kill_python)
@@ -552,11 +553,14 @@ def test_pep_409_verbiage(self):
552553
self.assertTrue(text[3].startswith('NameError'))
553554

554555
def test_non_ascii(self):
555-
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
556+
# Apple platforms deny the creation of a file with an invalid UTF-8 name.
556557
# Windows allows creating a name with an arbitrary bytes name, but
557558
# Python cannot a undecodable bytes argument to a subprocess.
558-
if (support.TESTFN_UNDECODABLE
559-
and sys.platform not in ('win32', 'darwin')):
559+
if (
560+
support.TESTFN_UNDECODABLE
561+
and sys.platform not in {"win32"}
562+
and not is_apple
563+
):
560564
name = os.fsdecode(support.TESTFN_UNDECODABLE)
561565
elif support.TESTFN_NONASCII:
562566
name = support.TESTFN_NONASCII

Lib/test/test_fcntl.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import unittest
88
from multiprocessing import Process
99
from test.support import (verbose, TESTFN, unlink, import_module,
10-
cpython_only)
10+
cpython_only, requires_subprocess)
1111

1212
# Skip test if no fcntl module.
1313
fcntl = import_module('fcntl')
@@ -154,6 +154,7 @@ def test_flock(self):
154154
self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH)
155155

156156
@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
157+
@requires_subprocess()
157158
def test_lockf_exclusive(self):
158159
self.f = open(TESTFN, 'wb+')
159160
cmd = fcntl.LOCK_EX | fcntl.LOCK_NB
@@ -165,6 +166,7 @@ def test_lockf_exclusive(self):
165166
self.assertEqual(p.exitcode, 0)
166167

167168
@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
169+
@requires_subprocess()
168170
def test_lockf_share(self):
169171
self.f = open(TESTFN, 'wb+')
170172
cmd = fcntl.LOCK_SH | fcntl.LOCK_NB

Lib/test/test_ftplib.py

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from unittest import TestCase, skipUnless
2222
from test import support
23+
from test.support import requires_subprocess
2324
from test.support import socket_helper
2425
from test.support.socket_helper import HOST, HOSTv6
2526

@@ -897,6 +898,7 @@ def callback(data):
897898

898899

899900
@skipUnless(ssl, "SSL not available")
901+
@requires_subprocess()
900902
class TestTLS_FTPClassMixin(TestFTPClass):
901903
"""Repeat TestFTPClass tests starting the TLS layer for both control
902904
and data connections first.
@@ -913,6 +915,7 @@ def setUp(self, encoding=DEFAULT_ENCODING):
913915

914916

915917
@skipUnless(ssl, "SSL not available")
918+
@requires_subprocess()
916919
class TestTLS_FTPClass(TestCase):
917920
"""Specific TLS_FTP class tests."""
918921

Lib/test/test_genericpath.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import warnings
1010
from test import support
1111
from test.support.script_helper import assert_python_ok
12-
from test.support import FakePath
12+
from test.support import FakePath, is_apple
1313

1414

1515
def create_file(filename, data=b'foo'):
@@ -474,12 +474,14 @@ def test_abspath_issue3426(self):
474474
self.assertIsInstance(abspath(path), str)
475475

476476
def test_nonascii_abspath(self):
477-
if (support.TESTFN_UNDECODABLE
478-
# Mac OS X denies the creation of a directory with an invalid
479-
# UTF-8 name. Windows allows creating a directory with an
480-
# arbitrary bytes name, but fails to enter this directory
481-
# (when the bytes name is used).
482-
and sys.platform not in ('win32', 'darwin')):
477+
if (
478+
support.TESTFN_UNDECODABLE
479+
# Apple platforms deny the creation of a
480+
# directory with an invalid UTF-8 name. Windows allows creating a
481+
# directory with an arbitrary bytes name, but fails to enter this
482+
# directory (when the bytes name is used).
483+
and sys.platform not in {"win32"} and not is_apple
484+
):
483485
name = support.TESTFN_UNDECODABLE
484486
elif support.TESTFN_NONASCII:
485487
name = support.TESTFN_NONASCII

Lib/test/test_httpservers.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import unittest
3232
from test import support
33+
from test.support import is_apple, requires_subprocess
3334

3435

3536
class NoLogRequestHandler:
@@ -386,8 +387,8 @@ def close_conn():
386387
reader.close()
387388
return body
388389

389-
@unittest.skipIf(sys.platform == 'darwin',
390-
'undecodable name cannot always be decoded on macOS')
390+
@unittest.skipIf(is_apple,
391+
'undecodable name cannot always be decoded on Apple platforms')
391392
@unittest.skipIf(sys.platform == 'win32',
392393
'undecodable name cannot be decoded on win32')
393394
@unittest.skipUnless(support.TESTFN_UNDECODABLE,
@@ -398,11 +399,11 @@ def test_undecodable_filename(self):
398399
with open(os.path.join(self.tempdir, filename), 'wb') as f:
399400
f.write(support.TESTFN_UNDECODABLE)
400401
response = self.request(self.base_url + '/')
401-
if sys.platform == 'darwin':
402-
# On Mac OS the HFS+ filesystem replaces bytes that aren't valid
403-
# UTF-8 into a percent-encoded value.
402+
if is_apple:
403+
# On Apple platforms the HFS+ filesystem replaces bytes that
404+
# aren't valid UTF-8 into a percent-encoded value.
404405
for name in os.listdir(self.tempdir):
405-
if name != 'test': # Ignore a filename created in setUp().
406+
if name != 'test': # Ignore a filename created in setUp().
406407
filename = name
407408
break
408409
body = self.check_status_and_reason(response, HTTPStatus.OK)
@@ -665,6 +666,7 @@ def test_html_escape_filename(self):
665666

666667
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
667668
"This test can't be run reliably as root (issue #13308).")
669+
@requires_subprocess()
668670
class CGIHTTPServerTestCase(BaseTestCase):
669671
class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
670672
pass

Lib/test/test_io.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from test import support
4040
from test.support.script_helper import (
4141
assert_python_ok, assert_python_failure, run_python_until_end)
42-
from test.support import FakePath, skip_if_sanitizer
42+
from test.support import FakePath, is_apple, skip_if_sanitizer
4343

4444
import codecs
4545
import io # C implementation of io
@@ -591,10 +591,10 @@ def test_raw_bytes_io(self):
591591
self.read_ops(f, True)
592592

593593
def test_large_file_ops(self):
594-
# On Windows and Mac OSX this test consumes large resources; It takes
595-
# a long time to build the >2 GiB file and takes >2 GiB of disk space
596-
# therefore the resource must be enabled to run this test.
597-
if sys.platform[:3] == 'win' or sys.platform == 'darwin':
594+
# On Windows and Apple platforms this test consumes large resources; It
595+
# takes a long time to build the >2 GiB file and takes >2 GiB of disk
596+
# space therefore the resource must be enabled to run this test.
597+
if sys.platform[:3] == 'win' or is_apple:
598598
support.requires(
599599
'largefile',
600600
'test requires %s bytes and a long time to run' % self.LARGE)

Lib/test/test_marshal.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from test import support
2+
from test.support import is_apple_mobile
23
import array
34
import io
45
import marshal
@@ -239,6 +240,8 @@ def test_recursion_limit(self):
239240
#if os.name == 'nt' and hasattr(sys, 'gettotalrefcount'):
240241
if os.name == 'nt':
241242
MAX_MARSHAL_STACK_DEPTH = 1000
243+
elif is_apple_mobile:
244+
MAX_MARSHAL_STACK_DEPTH = 1500
242245
else:
243246
MAX_MARSHAL_STACK_DEPTH = 2000
244247
for i in range(MAX_MARSHAL_STACK_DEPTH - 2):

Lib/test/test_mmap.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from test.support import (TESTFN, import_module, unlink,
1+
from test.support import (TESTFN, import_module, unlink, is_apple,
22
requires, _2G, _4G, gc_collect, cpython_only)
33
import unittest
44
import os
@@ -810,7 +810,7 @@ def tearDown(self):
810810
unlink(TESTFN)
811811

812812
def _make_test_file(self, num_zeroes, tail):
813-
if sys.platform[:3] == 'win' or sys.platform == 'darwin':
813+
if sys.platform[:3] == 'win' or is_apple:
814814
requires('largefile',
815815
'test requires %s bytes and a long time to run' % str(0x180000000))
816816
f = open(TESTFN, 'w+b')

0 commit comments

Comments
 (0)