Skip to content

Commit c48fe56

Browse files
CPython Devleopersyouknowone
authored andcommitted
Update test.support from CPython 3.11.2
1 parent c4b6789 commit c48fe56

File tree

9 files changed

+410
-89
lines changed

9 files changed

+410
-89
lines changed

Lib/test/support/__init__.py

Lines changed: 187 additions & 54 deletions
Large diffs are not rendered by default.

Lib/test/support/import_helper.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import _imp
23
import importlib
34
import importlib.util
45
import os
@@ -90,7 +91,24 @@ def _save_and_remove_modules(names):
9091
return orig_modules
9192

9293

93-
def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
94+
@contextlib.contextmanager
95+
def frozen_modules(enabled=True):
96+
"""Force frozen modules to be used (or not).
97+
98+
This only applies to modules that haven't been imported yet.
99+
Also, some essential modules will always be imported frozen.
100+
"""
101+
_imp._override_frozen_modules_for_tests(1 if enabled else -1)
102+
try:
103+
yield
104+
finally:
105+
_imp._override_frozen_modules_for_tests(0)
106+
107+
108+
def import_fresh_module(name, fresh=(), blocked=(), *,
109+
deprecated=False,
110+
usefrozen=False,
111+
):
94112
"""Import and return a module, deliberately bypassing sys.modules.
95113
96114
This function imports and returns a fresh copy of the named Python module
@@ -115,6 +133,9 @@ def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
115133
116134
This function will raise ImportError if the named module cannot be
117135
imported.
136+
137+
If "usefrozen" is False (the default) then the frozen importer is
138+
disabled (except for essential modules like importlib._bootstrap).
118139
"""
119140
# NOTE: test_heapq, test_json and test_warnings include extra sanity checks
120141
# to make sure that this utility function is working as expected
@@ -129,13 +150,14 @@ def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
129150
sys.modules[modname] = None
130151

131152
try:
132-
# Return None when one of the "fresh" modules can not be imported.
133-
try:
134-
for modname in fresh:
135-
__import__(modname)
136-
except ImportError:
137-
return None
138-
return importlib.import_module(name)
153+
with frozen_modules(usefrozen):
154+
# Return None when one of the "fresh" modules can not be imported.
155+
try:
156+
for modname in fresh:
157+
__import__(modname)
158+
except ImportError:
159+
return None
160+
return importlib.import_module(name)
139161
finally:
140162
_save_and_remove_modules(names)
141163
sys.modules.update(orig_modules)
@@ -151,9 +173,12 @@ class CleanImport(object):
151173
152174
with CleanImport("foo"):
153175
importlib.import_module("foo") # new reference
176+
177+
If "usefrozen" is False (the default) then the frozen importer is
178+
disabled (except for essential modules like importlib._bootstrap).
154179
"""
155180

156-
def __init__(self, *module_names):
181+
def __init__(self, *module_names, usefrozen=False):
157182
self.original_modules = sys.modules.copy()
158183
for module_name in module_names:
159184
if module_name in sys.modules:
@@ -165,12 +190,15 @@ def __init__(self, *module_names):
165190
if module.__name__ != module_name:
166191
del sys.modules[module.__name__]
167192
del sys.modules[module_name]
193+
self._frozen_modules = frozen_modules(usefrozen)
168194

169195
def __enter__(self):
196+
self._frozen_modules.__enter__()
170197
return self
171198

172199
def __exit__(self, *ignore_exc):
173200
sys.modules.update(self.original_modules)
201+
self._frozen_modules.__exit__(*ignore_exc)
174202

175203

176204
class DirsOnSysPath(object):

Lib/test/support/os_helper.py

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
'encoding (%s). Unicode filename tests may not be effective'
5050
% (TESTFN_UNENCODABLE, sys.getfilesystemencoding()))
5151
TESTFN_UNENCODABLE = None
52-
# Mac OS X denies unencodable filenames (invalid utf-8)
53-
elif sys.platform != 'darwin':
52+
# macOS and Emscripten deny unencodable filenames (invalid utf-8)
53+
elif sys.platform not in {'darwin', 'emscripten', 'wasi'}:
5454
try:
5555
# ascii and utf-8 cannot encode the byte 0xff
5656
b'\xff'.decode(sys.getfilesystemencoding())
@@ -171,9 +171,13 @@ def can_symlink():
171171
global _can_symlink
172172
if _can_symlink is not None:
173173
return _can_symlink
174-
symlink_path = TESTFN + "can_symlink"
174+
# WASI / wasmtime prevents symlinks with absolute paths, see man
175+
# openat2(2) RESOLVE_BENEATH. Almost all symlink tests use absolute
176+
# paths. Skip symlink tests on WASI for now.
177+
src = os.path.abspath(TESTFN)
178+
symlink_path = src + "can_symlink"
175179
try:
176-
os.symlink(TESTFN, symlink_path)
180+
os.symlink(src, symlink_path)
177181
can = True
178182
except (OSError, NotImplementedError, AttributeError):
179183
can = False
@@ -233,6 +237,84 @@ def skip_unless_xattr(test):
233237
return test if ok else unittest.skip(msg)(test)
234238

235239

240+
_can_chmod = None
241+
242+
def can_chmod():
243+
global _can_chmod
244+
if _can_chmod is not None:
245+
return _can_chmod
246+
if not hasattr(os, "chown"):
247+
_can_chmod = False
248+
return _can_chmod
249+
try:
250+
with open(TESTFN, "wb") as f:
251+
try:
252+
os.chmod(TESTFN, 0o777)
253+
mode1 = os.stat(TESTFN).st_mode
254+
os.chmod(TESTFN, 0o666)
255+
mode2 = os.stat(TESTFN).st_mode
256+
except OSError as e:
257+
can = False
258+
else:
259+
can = stat.S_IMODE(mode1) != stat.S_IMODE(mode2)
260+
finally:
261+
unlink(TESTFN)
262+
_can_chmod = can
263+
return can
264+
265+
266+
def skip_unless_working_chmod(test):
267+
"""Skip tests that require working os.chmod()
268+
269+
WASI SDK 15.0 cannot change file mode bits.
270+
"""
271+
ok = can_chmod()
272+
msg = "requires working os.chmod()"
273+
return test if ok else unittest.skip(msg)(test)
274+
275+
276+
# Check whether the current effective user has the capability to override
277+
# DAC (discretionary access control). Typically user root is able to
278+
# bypass file read, write, and execute permission checks. The capability
279+
# is independent of the effective user. See capabilities(7).
280+
_can_dac_override = None
281+
282+
def can_dac_override():
283+
global _can_dac_override
284+
285+
if not can_chmod():
286+
_can_dac_override = False
287+
if _can_dac_override is not None:
288+
return _can_dac_override
289+
290+
try:
291+
with open(TESTFN, "wb") as f:
292+
os.chmod(TESTFN, 0o400)
293+
try:
294+
with open(TESTFN, "wb"):
295+
pass
296+
except OSError:
297+
_can_dac_override = False
298+
else:
299+
_can_dac_override = True
300+
finally:
301+
unlink(TESTFN)
302+
303+
return _can_dac_override
304+
305+
306+
def skip_if_dac_override(test):
307+
ok = not can_dac_override()
308+
msg = "incompatible with CAP_DAC_OVERRIDE"
309+
return test if ok else unittest.skip(msg)(test)
310+
311+
312+
def skip_unless_dac_override(test):
313+
ok = can_dac_override()
314+
msg = "requires CAP_DAC_OVERRIDE"
315+
return test if ok else unittest.skip(msg)(test)
316+
317+
236318
def unlink(filename):
237319
try:
238320
_unlink(filename)
@@ -459,7 +541,10 @@ def create_empty_file(filename):
459541
def open_dir_fd(path):
460542
"""Open a file descriptor to a directory."""
461543
assert os.path.isdir(path)
462-
dir_fd = os.open(path, os.O_RDONLY)
544+
flags = os.O_RDONLY
545+
if hasattr(os, "O_DIRECTORY"):
546+
flags |= os.O_DIRECTORY
547+
dir_fd = os.open(path, flags)
463548
try:
464549
yield dir_fd
465550
finally:
@@ -502,7 +587,7 @@ def __fspath__(self):
502587
def fd_count():
503588
"""Count the number of open file descriptors.
504589
"""
505-
if sys.platform.startswith(('linux', 'freebsd')):
590+
if sys.platform.startswith(('linux', 'freebsd', 'emscripten')):
506591
try:
507592
names = os.listdir("/proc/self/fd")
508593
# Subtract one because listdir() internally opens a file
@@ -568,6 +653,11 @@ def temp_umask(umask):
568653
yield
569654
finally:
570655
os.umask(oldmask)
656+
else:
657+
@contextlib.contextmanager
658+
def temp_umask(umask):
659+
"""no-op on platforms without umask()"""
660+
yield
571661

572662

573663
class EnvironmentVarGuard(collections.abc.MutableMapping):
@@ -610,6 +700,10 @@ def set(self, envvar, value):
610700
def unset(self, envvar):
611701
del self[envvar]
612702

703+
def copy(self):
704+
# We do what os.environ.copy() does.
705+
return dict(self)
706+
613707
def __enter__(self):
614708
return self
615709

Lib/test/support/script_helper.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ def interpreter_requires_environment():
4242
if 'PYTHONHOME' in os.environ:
4343
__cached_interp_requires_environment = True
4444
return True
45+
# cannot run subprocess, assume we don't need it
46+
if not support.has_subprocess_support:
47+
__cached_interp_requires_environment = False
48+
return False
4549

4650
# Try running an interpreter with -E to see if it works or not.
4751
try:
@@ -87,6 +91,7 @@ def fail(self, cmd_line):
8791

8892

8993
# Executing the interpreter in a subprocess
94+
@support.requires_subprocess()
9095
def run_python_until_end(*args, **env_vars):
9196
env_required = interpreter_requires_environment()
9297
cwd = env_vars.pop('__cwd', None)
@@ -139,6 +144,7 @@ def run_python_until_end(*args, **env_vars):
139144
return _PythonRunResult(rc, out, err), cmd_line
140145

141146

147+
@support.requires_subprocess()
142148
def _assert_python(expected_success, /, *args, **env_vars):
143149
res, cmd_line = run_python_until_end(*args, **env_vars)
144150
if (res.rc and expected_success) or (not res.rc and not expected_success):
@@ -171,6 +177,7 @@ def assert_python_failure(*args, **env_vars):
171177
return _assert_python(False, *args, **env_vars)
172178

173179

180+
@support.requires_subprocess()
174181
def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw):
175182
"""Run a Python subprocess with the given arguments.
176183
@@ -273,6 +280,7 @@ def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
273280
return zip_name, os.path.join(zip_name, script_name_in_zip)
274281

275282

283+
@support.requires_subprocess()
276284
def run_test_script(script):
277285
# use -u to try to get the full output if the test hangs or crash
278286
if support.verbose:

Lib/test/support/socket_helper.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import sys
66

77
from .. import support
8-
8+
from . import warnings_helper
99

1010
HOST = "localhost"
1111
HOSTv4 = "127.0.0.1"
1212
HOSTv6 = "::1"
1313

14+
# WASI SDK 15.0 does not provide gethostname, stub raises OSError ENOTSUP.
15+
has_gethostname = not support.is_wasi
16+
1417

1518
def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM):
1619
"""Returns an unused port that should be suitable for binding. This is
@@ -190,7 +193,7 @@ def get_socket_conn_refused_errs():
190193
def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()):
191194
"""Return a context manager that raises ResourceDenied when various issues
192195
with the internet connection manifest themselves as exceptions."""
193-
import nntplib
196+
nntplib = warnings_helper.import_deprecated("nntplib")
194197
import urllib.error
195198
if timeout is _NOT_SET:
196199
timeout = support.INTERNET_TIMEOUT
@@ -256,7 +259,7 @@ def filter_error(err):
256259
err = a[0]
257260
# The error can also be wrapped as args[1]:
258261
# except socket.error as msg:
259-
# raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
262+
# raise OSError('socket error', msg) from msg
260263
elif len(a) >= 2 and isinstance(a[1], OSError):
261264
err = a[1]
262265
else:

Lib/test/support/testresult.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def test_error(self):
173173
raise RuntimeError('error message')
174174

175175
suite = unittest.TestSuite()
176-
suite.addTest(unittest.makeSuite(TestTests))
176+
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestTests))
177177
stream = io.StringIO()
178178
runner_cls = get_test_runner_class(sum(a == '-v' for a in sys.argv))
179179
runner = runner_cls(sys.stdout)

Lib/test/support/threading_helper.py

Lines changed: 35 additions & 0 deletions
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

@@ -207,3 +208,37 @@ def __exit__(self, *exc_info):
207208
del self.exc_value
208209
del self.exc_traceback
209210
del self.thread
211+
212+
213+
def _can_start_thread() -> bool:
214+
"""Detect whether Python can start new threads.
215+
216+
Some WebAssembly platforms do not provide a working pthread
217+
implementation. Thread support is stubbed and any attempt
218+
to create a new thread fails.
219+
220+
- wasm32-wasi does not have threading.
221+
- wasm32-emscripten can be compiled with or without pthread
222+
support (-s USE_PTHREADS / __EMSCRIPTEN_PTHREADS__).
223+
"""
224+
if sys.platform == "emscripten":
225+
return sys._emscripten_info.pthreads
226+
elif sys.platform == "wasi":
227+
return False
228+
else:
229+
# assume all other platforms have working thread support.
230+
return True
231+
232+
can_start_thread = _can_start_thread()
233+
234+
def requires_working_threading(*, module=False):
235+
"""Skip tests or modules that require working threading.
236+
237+
Can be used as a function/class decorator or to skip an entire module.
238+
"""
239+
msg = "requires threading support"
240+
if module:
241+
if not can_start_thread:
242+
raise unittest.SkipTest(msg)
243+
else:
244+
return unittest.skipUnless(can_start_thread, msg)

0 commit comments

Comments
 (0)