Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Lib/compression/zstd/_zstdfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, file, /, mode='r', *,
level=None, options=None, zstd_dict=None):
"""Open a Zstandard compressed file in binary mode.

*file* can be either an file-like object, or a file name to open.
*file* can be either a file-like object, or a file name to open.

*mode* can be 'r' for reading (default), 'w' for (over)writing, 'x'
for creating exclusively, or 'a' for appending. These can
Expand Down Expand Up @@ -246,6 +246,7 @@ def peek(self, size=-1):
return self._buffer.peek(size)

def __next__(self):
self._check_can_read()
if ret := self._buffer.readline():
return ret
raise StopIteration
Expand Down
4 changes: 3 additions & 1 deletion Lib/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,9 @@ def has_dualstack_ipv6():
try:
with socket(AF_INET6, SOCK_STREAM) as sock:
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
return True
# On some platforms (e.g. DragonFly BSD) setting IPV6_V6ONLY to 0
# silently has no effect, so check that it was actually cleared.
return sock.getsockopt(IPPROTO_IPV6, IPV6_V6ONLY) == 0
except error:
return False

Expand Down
7 changes: 7 additions & 0 deletions Lib/string/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,14 @@ def __init_subclass__(cls):
def _compile_pattern(cls):
import re # deferred import, for performance

# `pattern` may be the `_TemplatePattern` sentinel (not yet compiled), an
# already-compiled regular expression object (as documented), or a string
# regular expression. An already-compiled object is returned as-is; the
# other two are compiled and cached back on the class.
pattern = cls.__dict__.get('pattern', _TemplatePattern)
if isinstance(pattern, re.Pattern):
# re.compile() rejects flags on an already-compiled pattern.
return pattern
if pattern is _TemplatePattern:
delim = re.escape(cls.delimiter)
id = cls.idpattern
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,48 @@ def test_frame_readline(self):
# 15: . STOP
self.assertEqual(self.loads(pickled), 42)

def test_frame_ends_at_opcode_boundary(self):
# A frame may end exactly between two opcodes; the following opcodes
# are then read from outside the frame.
for pickled in [
b'\x80\x04\x95\x01\x00\x00\x00\x00\x00\x00\x00N.', # FRAME 1, NONE, STOP
b'\x80\x04\x95\x00\x00\x00\x00\x00\x00\x00\x00N.', # empty FRAME, NONE, STOP
]:
with self.subTest(pickled=pickled):
self.assertIsNone(self.loads(pickled))

def test_frame_does_not_straddle_boundary(self):
# An opcode or its argument must not cross a frame boundary
# (PEP 3154). Such a pickle must be rejected rather than silently
# reading past the declared frame length, which would make the
# meaning of the pickle diverge from its pickletools disassembly.
# See gh-154848.
for pickled in [
# FRAME 6; UNICODE argument read by readline() straddles the frame.
b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00Vhelloworld\n.',
# FRAME 6; BINUNICODE argument straddles the frame.
b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00'
b'X\x0a\x00\x00\x00helloworld.',
# FRAME 3; SHORT_BINBYTES argument straddles the frame.
b'\x80\x04\x95\x03\x00\x00\x00\x00\x00\x00\x00C\x0ahelloworld.',
# FRAME 9; GLOBAL argument (second line) straddles the frame.
b'\x80\x04\x95\x09\x00\x00\x00\x00\x00\x00\x00cbuiltins\nprint\n.',
]:
self.check_unpickling_error(self.truncated_errors, pickled)

def test_nested_frame(self):
# A new frame must not begin before the current one has ended: here
# the outer frame still has data left after the inner frame header.
pickled = (b'\x80\x04\x95\x0c\x00\x00\x00\x00\x00\x00\x00'
b'N\x95\x00\x00\x00\x00\x00\x00\x00\x00NN.')
self.check_unpickling_error(self.truncated_errors, pickled)

# But the inner frame header may lie inside the outer frame as long as
# it exactly consumes it.
pickled = (b'\x80\x04\x95\x0a\x00\x00\x00\x00\x00\x00\x00'
b'N\x95\x00\x00\x00\x00\x00\x00\x00\x00N.')
self.assertIsNone(self.loads(pickled))

def test_compat_unpickle(self):
# xrange(1, 7)
pickled = b'\x80\x02c__builtin__\nxrange\nK\x01K\x07K\x01\x87R.'
Expand Down
11 changes: 7 additions & 4 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@

from test import support
from test.support import os_helper
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, skip_if_unlimited_stack_size
from test.support import (skip_emscripten_stack_overflow,
skip_wasi_stack_overflow,
skip_if_unlimited_stack_size, skip_if_huge_c_stack)
from test.support.ast_helper import ASTTestMixin
from test.support.import_helper import ensure_lazy_imports
from test.test_ast.utils import to_tuple
Expand Down Expand Up @@ -990,7 +992,8 @@ def next(self):
enum._test_simple_enum(_Precedence, _ast_unparse._Precedence)

@support.cpython_only
@skip_if_unlimited_stack_size
@support.run_with_limited_c_stack(
100_000 if sys.platform == "android" else 500_000)
@skip_wasi_stack_overflow()
@skip_emscripten_stack_overflow()
def test_ast_recursion_limit(self):
Expand Down Expand Up @@ -1973,7 +1976,7 @@ def test_level_as_none(self):
exec(code, ns)
self.assertIn('sleep', ns)

@skip_if_unlimited_stack_size
@skip_if_huge_c_stack()
@skip_emscripten_stack_overflow()
def test_recursion_direct(self):
e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1))
Expand All @@ -1982,7 +1985,7 @@ def test_recursion_direct(self):
with support.infinite_recursion():
compile(ast.Expression(e), "<test>", "eval")

@skip_if_unlimited_stack_size
@skip_if_huge_c_stack()
@skip_emscripten_stack_overflow()
def test_recursion_indirect(self):
e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1))
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ def test_deepcopy_list(self):
self.assertIsNot(x, y)
self.assertIsNot(x[0], y[0])

@support.skip_if_huge_c_stack()
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_deepcopy_reflexive_list(self):
Expand Down Expand Up @@ -400,6 +401,7 @@ def test_deepcopy_tuple_of_immutables(self):
y = copy.deepcopy(x)
self.assertIs(x, y)

@support.skip_if_huge_c_stack()
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_deepcopy_reflexive_tuple(self):
Expand All @@ -419,6 +421,7 @@ def test_deepcopy_dict(self):
self.assertIsNot(x, y)
self.assertIsNot(x["foo"], y["foo"])

@support.skip_if_huge_c_stack()
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_deepcopy_reflexive_dict(self):
Expand Down Expand Up @@ -579,6 +582,7 @@ def __eq__(self, other):
self.assertIsNot(y, x)
self.assertIsNot(y.foo, x.foo)

@support.skip_if_huge_c_stack()
def test_deepcopy_reflexive_inst(self):
class C:
pass
Expand Down Expand Up @@ -641,6 +645,7 @@ def __eq__(self, other):
self.assertEqual(y, x)
self.assertIsNot(y.foo, x.foo)

@support.skip_if_huge_c_stack()
def test_reconstruct_reflexive(self):
class C(object):
pass
Expand Down
18 changes: 16 additions & 2 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import functools
import gc
import importlib
import importlib.util
import inspect
import io
import linecache
Expand Down Expand Up @@ -6443,6 +6444,19 @@ def test_wrapped_descriptor(self):


class TestMain(unittest.TestCase):
@staticmethod
def _expected_cached(module):
# assert_python_ok() runs the subprocess in isolated mode (-I), which
# ignores PYTHONPYCACHEPREFIX, so compute the expected cached path the
# same way (i.e. without any pycache prefix) to stay independent of the
# environment the test suite is run in. Modules without a cached path
# (e.g. frozen modules such as ntpath/importlib.machinery on Windows)
# report None, so preserve that.
if module.__spec__.cached is None:
return None
with support.swap_attr(sys, 'pycache_prefix', None):
return importlib.util.cache_from_source(module.__spec__.origin)

def test_only_source(self):
module = importlib.import_module('unittest')
rc, out, err = assert_python_ok('-m', 'inspect',
Expand Down Expand Up @@ -6482,13 +6496,13 @@ def test_details(self):
rc, out, err = assert_python_ok(*args, '-m', 'inspect',
'unittest', '--details')
output = out.decode()
cached = self._expected_cached(module)
# Just a quick sanity check on the output
self.assertIn(module.__spec__.name, output)
self.assertIn(module.__name__, output)
self.assertIn(module.__spec__.origin, output)
self.assertIn(module.__file__, output)
self.assertIn(module.__spec__.cached, output)
self.assertIn(module.__cached__, output)
self.assertIn(cached, output)
self.assertEqual(err, b'')


Expand Down
100 changes: 94 additions & 6 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ def create_file(filename, content=b'content'):
fp.write(content)


# On platforms without a native spawnv(), os.py provides a Python fallback
# built on fork()+exec*() that reports argument conversion failures from the
# child as exit status 127 instead of raising, so tests of the C
# implementation's error paths cannot run against it.
requires_native_spawnv = unittest.skipUnless(
isinstance(getattr(os, 'spawnv', None), types.BuiltinFunctionType),
'requires the native C os.spawnv')


# bpo-41625: On AIX, splice() only works with a socket, not with a pipe.
requires_splice_pipe = unittest.skipIf(sys.platform.startswith("aix"),
'on AIX, splice() only accepts sockets')
Expand Down Expand Up @@ -159,7 +168,8 @@ def test_getcwd_long_path(self):
# ("The filename or extension is too long")
break
except OSError as exc:
if exc.errno == errno.ENAMETOOLONG:
# DragonFly BSD raises EFAULT for a too long path.
if exc.errno in (errno.ENAMETOOLONG, errno.EFAULT):
break
else:
raise
Expand Down Expand Up @@ -890,11 +900,18 @@ def support_subsecond(self, filename):
or (st.st_mtime != st[8])
or (st.st_ctime != st[9]))

def support_atime(self, filename):
# Heuristic to check if the filesystem stores the access time.
# Use whole seconds, to not depend on the timestamp resolution.
os.utime(filename, (1, 2))
return os.stat(filename).st_atime == 1

def _test_utime(self, set_time, filename=None):
if not filename:
filename = self.fname

support_subsecond = self.support_subsecond(filename)
support_atime = self.support_atime(filename)
if support_subsecond:
# Timestamp with a resolution of 1 microsecond (10^-6).
#
Expand All @@ -921,18 +938,22 @@ def _test_utime(self, set_time, filename=None):
# digits worth of sub-second precision.
# Some day it would be good to fix this upstream.
delta=1e-5
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5)
if support_atime:
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5)
self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5)
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-5)
self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5)
self.assertAlmostEqual(st.st_mtime_ns, mtime_ns, delta=1e9 * 1e-5)
else:
if support_subsecond:
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
if support_atime:
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6)
else:
self.assertEqual(st.st_atime, atime_ns * 1e-9)
if support_atime:
self.assertEqual(st.st_atime, atime_ns * 1e-9)
self.assertEqual(st.st_mtime, mtime_ns * 1e-9)
self.assertEqual(st.st_atime_ns, atime_ns)
if support_atime:
self.assertEqual(st.st_atime_ns, atime_ns)
self.assertEqual(st.st_mtime_ns, mtime_ns)

def test_utime(self):
Expand Down Expand Up @@ -3763,6 +3784,26 @@ def test_spawnve_noargs(self):
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, ('',), {})
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [''], {})

@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; AssertionError: "must contain only strings" does not match "expected str, bytes or os.PathLike object, not int"')
@requires_native_spawnv
def test_spawnv_arg_conversion_errors(self):
# A non-path argv item gets a TypeError naming the argument...
with self.assertRaisesRegex(TypeError, 'must contain only strings'):
os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable, 123])
# ...but other conversion errors must not be masked as TypeError
# (gh-151416).
with self.assertRaises(ValueError):
os.spawnv(os.P_NOWAIT, sys.executable,
[sys.executable, 'embedded\0null'])

class RaisingPath:
def __fspath__(self):
raise RuntimeError('gotcha')

with self.assertRaisesRegex(RuntimeError, 'gotcha'):
os.spawnv(os.P_NOWAIT, sys.executable,
[sys.executable, RaisingPath()])

def _test_invalid_env(self, spawn):
program = sys.executable
args = self.quote_args([program, '-c', 'pass'])
Expand Down Expand Up @@ -4927,6 +4968,28 @@ def test_posix_pty_functions(self):
son_path = os.ptsname(mother_fd)
son_fd = os.open(son_path, os.O_RDWR|os.O_NOCTTY)
self.addCleanup(os.close, son_fd)
if sys.platform.startswith('sunos'):
import fcntl
I_PUSH = 0x5302
TIOCNOTTY = 0x7471
# Pushing "ptem" makes the slave a terminal, which a session
# leader without a controlling terminal then acquires as one
# despite O_NOCTTY. Note whether we already had one.
try:
os.close(os.open('/dev/tty', os.O_RDONLY|os.O_NOCTTY))
had_ctty = True
except OSError:
had_ctty = False
fcntl.ioctl(son_fd, I_PUSH, b'ptem\0')
fcntl.ioctl(son_fd, I_PUSH, b'ldterm\0')
if not had_ctty and os.getsid(0) == os.getpid():
# Disown it, otherwise closing the file descriptors sends
# SIGHUP to the session. TIOCNOTTY sends it too.
old_handler = signal.signal(signal.SIGHUP, signal.SIG_IGN)
try:
fcntl.ioctl(son_fd, TIOCNOTTY)
finally:
signal.signal(signal.SIGHUP, old_handler)
self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd))

@unittest.skipUnless(hasattr(os, 'spawnl'), "need os.spawnl()")
Expand Down Expand Up @@ -5258,6 +5321,31 @@ def test_current_directory(self):
finally:
os.chdir(old_dir)

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; FileNotFoundError")
@unittest.skipIf(sys.platform != 'win32', "Win32 specific test")
def test_windows_trailing_space_path(self):
import pathlib

filename = self.create_file("file.txt")
path = self.path + " "

self.assertTrue(os.path.exists(path))
os.stat(path)
with open(filename + " ", "rb") as file:
self.assertEqual(file.read(), b"python")

self.assertEqual(os.listdir(path), ["file.txt"])
with os.scandir(path) as entries:
self.assertEqual([entry.name for entry in entries], ["file.txt"])
pathlib_entries = list(pathlib.Path(path).iterdir())
self.assertEqual([entry.name for entry in pathlib_entries], ["file.txt"])
del pathlib_entries

extended_path = "\\\\?\\" + path
self.assertFalse(os.path.exists(extended_path))
self.assertRaises(FileNotFoundError, os.listdir, extended_path)
self.assertRaises(FileNotFoundError, os.scandir, extended_path)

def test_repr(self):
entry = self.create_file_entry()
self.assertEqual(repr(entry), "<DirEntry 'file.txt'>")
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ def test_pickler(self):
0) # Write buffer is cleared after every dump().

def test_unpickler(self):
basesize = support.calcobjsize('2P2n2P 2P2n2i5P 2P3n8P2n3i')
basesize = support.calcobjsize('2P2n2P 2P2n2i5P 2P5n8P2n3i')
unpickler = _pickle.Unpickler
P = struct.calcsize('P') # Size of memo table entry.
n = struct.calcsize('n') # Size of mark table entry.
Expand Down
Loading
Loading