Skip to content

Commit

Permalink
[3.9] pythongh-114099: Add test exclusions to support running the tes…
Browse files Browse the repository at this point in the history
…t 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.
  • Loading branch information
freakboy3742 committed Sep 9, 2024
1 parent 4d24be6 commit 8dc0719
Show file tree
Hide file tree
Showing 24 changed files with 132 additions and 64 deletions.
30 changes: 19 additions & 11 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@
"skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
"requires_IEEE_754", "skip_unless_xattr", "requires_zlib",
"has_fork_support", "requires_fork",
"has_subprocess_support", "requires_subprocess",
"anticipate_failure", "load_package_tests", "detect_api_mismatch",
"check__all__", "skip_if_buggy_ucrt_strfptime",
"ignore_warnings", "check_sanitizer", "skip_if_sanitizer",
# sys
"is_jython", "is_android", "check_impl_detail", "unix_shell",
"is_jython", "is_android", "is_apple", "is_apple_mobile",
"check_impl_detail", "unix_shell",
"setswitchinterval",
# network
"open_urlresource",
Expand Down Expand Up @@ -743,21 +746,26 @@ def requires_lzma(reason='requires lzma'):

is_android = hasattr(sys, 'getandroidapilevel')

if sys.platform != 'win32':
if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
else:
unix_shell = None

# Filename used for testing
if os.name == 'java':
# Jython disallows @ in module names
TESTFN_ASCII = '$test'
else:
TESTFN_ASCII = '@test'
# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not
# have subprocess or fork support.
is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"}
is_apple = is_apple_mobile or sys.platform == "darwin"

has_fork_support = hasattr(os, "fork") and not is_apple_mobile

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

has_subprocess_support = not is_apple_mobile

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

# Define the URL of a dedicated HTTP server for the network tests.
# The URL must use clear-text HTTP: no redirection to encrypted HTTPS.
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,7 @@ def check_killed(self, returncode):
else:
self.assertEqual(-signal.SIGKILL, returncode)

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

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

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

Expand Down Expand Up @@ -1777,6 +1779,7 @@ def test_subprocess_interactive(self):
self.loop.run_until_complete(proto.completed)
self.check_killed(proto.returncode)

@support.requires_subprocess()
def test_subprocess_shell(self):
connect = self.loop.subprocess_shell(
functools.partial(MySubprocessProtocol, self.loop),
Expand All @@ -1793,6 +1796,7 @@ def test_subprocess_shell(self):
self.assertEqual(proto.data[2], b'')
transp.close()

@support.requires_subprocess()
def test_subprocess_exitcode(self):
connect = self.loop.subprocess_shell(
functools.partial(MySubprocessProtocol, self.loop),
Expand All @@ -1804,6 +1808,7 @@ def test_subprocess_exitcode(self):
self.assertEqual(7, proto.returncode)
transp.close()

@support.requires_subprocess()
def test_subprocess_close_after_finish(self):
connect = self.loop.subprocess_shell(
functools.partial(MySubprocessProtocol, self.loop),
Expand All @@ -1817,6 +1822,7 @@ def test_subprocess_close_after_finish(self):
self.assertEqual(7, proto.returncode)
self.assertIsNone(transp.close())

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

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

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

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

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

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

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

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

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

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

Expand Down Expand Up @@ -1951,6 +1962,7 @@ def test_subprocess_close_client_stream(self):
self.loop.run_until_complete(proto.completed)
self.check_killed(proto.returncode)

@support.requires_subprocess()
def test_subprocess_wait_no_same_group(self):
# start the new process in a new session
connect = self.loop.subprocess_shell(
Expand All @@ -1963,6 +1975,7 @@ def test_subprocess_wait_no_same_group(self):
self.assertEqual(7, proto.returncode)
transp.close()

@support.requires_subprocess()
def test_subprocess_exec_invalid_args(self):
async def connect(**kwds):
await self.loop.subprocess_exec(
Expand All @@ -1976,6 +1989,7 @@ async def connect(**kwds):
with self.assertRaises(ValueError):
self.loop.run_until_complete(connect(shell=True))

@support.requires_subprocess()
def test_subprocess_shell_invalid_args(self):

async def connect(cmd=None, **kwds):
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_asyncio/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import threading
import unittest
from unittest import mock
from test.support import socket_helper
from test.support import requires_subprocess, socket_helper
try:
import ssl
except ImportError:
Expand Down Expand Up @@ -732,6 +732,7 @@ async def client(path):
self.assertEqual(messages, [])

@unittest.skipIf(sys.platform == 'win32', "Don't have pipes")
@requires_subprocess()
def test_read_all_from_pipe_reader(self):
# See asyncio issue 168. This test is derived from the example
# subprocess_attach_read_pipe.py, but we configure the
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_asyncio/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def _start(self, *args, **kwargs):
self._proc.pid = -1


@support.requires_subprocess()
class SubprocessTransportTests(test_utils.TestCase):
def setUp(self):
super().setUp()
Expand Down Expand Up @@ -103,6 +104,7 @@ def test_subprocess_repr(self):
transport.close()


@support.requires_subprocess()
class SubprocessMixin:

def test_stdin_stdout(self):
Expand Down
10 changes: 7 additions & 3 deletions Lib/test/test_cmd_line_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import textwrap
from test import support
from test.support import is_apple
from test.support.script_helper import (
make_pkg, make_script, make_zip_pkg, make_zip_script,
assert_python_ok, assert_python_failure, spawn_python, kill_python)
Expand Down Expand Up @@ -552,11 +553,14 @@ def test_pep_409_verbiage(self):
self.assertTrue(text[3].startswith('NameError'))

def test_non_ascii(self):
# Mac OS X denies the creation of a file with an invalid UTF-8 name.
# Apple platforms deny the creation of a file with an invalid UTF-8 name.
# Windows allows creating a name with an arbitrary bytes name, but
# Python cannot a undecodable bytes argument to a subprocess.
if (support.TESTFN_UNDECODABLE
and sys.platform not in ('win32', 'darwin')):
if (
support.TESTFN_UNDECODABLE
and sys.platform not in {"win32"}
and not is_apple
):
name = os.fsdecode(support.TESTFN_UNDECODABLE)
elif support.TESTFN_NONASCII:
name = support.TESTFN_NONASCII
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_fcntl.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import unittest
from multiprocessing import Process
from test.support import (verbose, TESTFN, unlink, import_module,
cpython_only)
cpython_only, requires_subprocess)

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

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

@unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError")
@requires_subprocess()
def test_lockf_share(self):
self.f = open(TESTFN, 'wb+')
cmd = fcntl.LOCK_SH | fcntl.LOCK_NB
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from unittest import TestCase, skipUnless
from test import support
from test.support import requires_subprocess
from test.support import socket_helper
from test.support.socket_helper import HOST, HOSTv6

Expand Down Expand Up @@ -897,6 +898,7 @@ def callback(data):


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


@skipUnless(ssl, "SSL not available")
@requires_subprocess()
class TestTLS_FTPClass(TestCase):
"""Specific TLS_FTP class tests."""

Expand Down
16 changes: 9 additions & 7 deletions Lib/test/test_genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import warnings
from test import support
from test.support.script_helper import assert_python_ok
from test.support import FakePath
from test.support import FakePath, is_apple


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

def test_nonascii_abspath(self):
if (support.TESTFN_UNDECODABLE
# Mac OS X denies the creation of a directory with an invalid
# UTF-8 name. Windows allows creating a directory with an
# arbitrary bytes name, but fails to enter this directory
# (when the bytes name is used).
and sys.platform not in ('win32', 'darwin')):
if (
support.TESTFN_UNDECODABLE
# Apple platforms deny the creation of a
# directory with an invalid UTF-8 name. Windows allows creating a
# directory with an arbitrary bytes name, but fails to enter this
# directory (when the bytes name is used).
and sys.platform not in {"win32"} and not is_apple
):
name = support.TESTFN_UNDECODABLE
elif support.TESTFN_NONASCII:
name = support.TESTFN_NONASCII
Expand Down
14 changes: 8 additions & 6 deletions Lib/test/test_httpservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import unittest
from test import support
from test.support import is_apple, requires_subprocess


class NoLogRequestHandler:
Expand Down Expand Up @@ -386,8 +387,8 @@ def close_conn():
reader.close()
return body

@unittest.skipIf(sys.platform == 'darwin',
'undecodable name cannot always be decoded on macOS')
@unittest.skipIf(is_apple,
'undecodable name cannot always be decoded on Apple platforms')
@unittest.skipIf(sys.platform == 'win32',
'undecodable name cannot be decoded on win32')
@unittest.skipUnless(support.TESTFN_UNDECODABLE,
Expand All @@ -398,11 +399,11 @@ def test_undecodable_filename(self):
with open(os.path.join(self.tempdir, filename), 'wb') as f:
f.write(support.TESTFN_UNDECODABLE)
response = self.request(self.base_url + '/')
if sys.platform == 'darwin':
# On Mac OS the HFS+ filesystem replaces bytes that aren't valid
# UTF-8 into a percent-encoded value.
if is_apple:
# On Apple platforms the HFS+ filesystem replaces bytes that
# aren't valid UTF-8 into a percent-encoded value.
for name in os.listdir(self.tempdir):
if name != 'test': # Ignore a filename created in setUp().
if name != 'test': # Ignore a filename created in setUp().
filename = name
break
body = self.check_status_and_reason(response, HTTPStatus.OK)
Expand Down Expand Up @@ -665,6 +666,7 @@ def test_html_escape_filename(self):

@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"This test can't be run reliably as root (issue #13308).")
@requires_subprocess()
class CGIHTTPServerTestCase(BaseTestCase):
class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler):
pass
Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from test import support
from test.support.script_helper import (
assert_python_ok, assert_python_failure, run_python_until_end)
from test.support import FakePath, skip_if_sanitizer
from test.support import FakePath, is_apple, skip_if_sanitizer

import codecs
import io # C implementation of io
Expand Down Expand Up @@ -591,10 +591,10 @@ def test_raw_bytes_io(self):
self.read_ops(f, True)

def test_large_file_ops(self):
# On Windows and Mac OSX this test consumes large resources; It takes
# a long time to build the >2 GiB file and takes >2 GiB of disk space
# therefore the resource must be enabled to run this test.
if sys.platform[:3] == 'win' or sys.platform == 'darwin':
# On Windows and Apple platforms this test consumes large resources; It
# takes a long time to build the >2 GiB file and takes >2 GiB of disk
# space therefore the resource must be enabled to run this test.
if sys.platform[:3] == 'win' or is_apple:
support.requires(
'largefile',
'test requires %s bytes and a long time to run' % self.LARGE)
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_marshal.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from test import support
from test.support import is_apple_mobile
import array
import io
import marshal
Expand Down Expand Up @@ -239,6 +240,8 @@ def test_recursion_limit(self):
#if os.name == 'nt' and hasattr(sys, 'gettotalrefcount'):
if os.name == 'nt':
MAX_MARSHAL_STACK_DEPTH = 1000
elif is_apple_mobile:
MAX_MARSHAL_STACK_DEPTH = 1500
else:
MAX_MARSHAL_STACK_DEPTH = 2000
for i in range(MAX_MARSHAL_STACK_DEPTH - 2):
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from test.support import (TESTFN, import_module, unlink,
from test.support import (TESTFN, import_module, unlink, is_apple,
requires, _2G, _4G, gc_collect, cpython_only)
import unittest
import os
Expand Down Expand Up @@ -810,7 +810,7 @@ def tearDown(self):
unlink(TESTFN)

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

0 comments on commit 8dc0719

Please sign in to comment.