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
5 changes: 5 additions & 0 deletions Doc/library/concurrent.futures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ or separate processes, using :class:`ProcessPoolExecutor`.
Each implements the same interface, which is defined
by the abstract :class:`Executor` class.

:class:`concurrent.futures.Future` must not be confused with
:class:`asyncio.Future`, which is designed for use with :mod:`asyncio`
tasks and coroutines. See the :doc:`asyncio's Future <asyncio-future>`
documentation for a detailed comparison of the two.

.. include:: ../includes/wasm-notavail.rst

Executor Objects
Expand Down
22 changes: 20 additions & 2 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2369,7 +2369,9 @@ expression support in the :mod:`re` module).

If the string starts with the *prefix* string, return
``string[len(prefix):]``. Otherwise, return a copy of the original
string::
string:

.. doctest::

>>> 'TestHook'.removeprefix('Test')
'Hook'
Expand All @@ -2378,12 +2380,16 @@ expression support in the :mod:`re` module).

.. versionadded:: 3.9

See also :meth:`removesuffix` and :meth:`startswith`.


.. method:: str.removesuffix(suffix, /)

If the string ends with the *suffix* string and that *suffix* is not empty,
return ``string[:-len(suffix)]``. Otherwise, return a copy of the
original string::
original string:

.. doctest::

>>> 'MiscTests'.removesuffix('Tests')
'Misc'
Expand All @@ -2392,6 +2398,8 @@ expression support in the :mod:`re` module).

.. versionadded:: 3.9

See also :meth:`removeprefix` and :meth:`endswith`.


.. method:: str.replace(old, new, /, count=-1)

Expand All @@ -2416,6 +2424,16 @@ expression support in the :mod:`re` module).
Return the highest index in the string where substring *sub* is found, such
that *sub* is contained within ``s[start:end]``. Optional arguments *start*
and *end* are interpreted as in slice notation. Return ``-1`` on failure.
For example:

.. doctest::

>>> 'spam, spam, spam'.rfind('sp')
12
>>> 'spam, spam, spam'.rfind('sp', 0, 10)
6

See also :meth:`find` and :meth:`rindex`.


.. method:: str.rindex(sub[, start[, end]])
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_optimizer_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ extern "C" {

#include "pycore_uop.h" // UOP_MAX_TRACE_LENGTH

// Holds locals, stack, locals, stack ... co_consts (in that order)
#define MAX_ABSTRACT_INTERP_SIZE 4096
// Holds locals, stack, locals, stack ... (in that order)
#define MAX_ABSTRACT_INTERP_SIZE 512

#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * 5)

Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ typedef struct _PyJitTracerState {
_PyJitTracerInitialState initial_state;
_PyJitTracerPreviousState prev_state;
_PyJitTracerTranslatorState translator_state;
JitOptContext opt_context;
_PyUOpInstruction code_buffer[UOP_MAX_TRACE_LENGTH];
JitOptContext *opt_context;
_PyUOpInstruction *code_buffer;
} _PyJitTracerState;

#endif
Expand Down
4 changes: 2 additions & 2 deletions Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,12 +949,12 @@ def read1(self, size=-1):
return self.read(size)

def write(self, b):
if self.closed:
raise ValueError("write to closed file")
if isinstance(b, str):
raise TypeError("can't write str to binary stream")
with memoryview(b) as view:
n = view.nbytes # Size of any bytes-like object
if self.closed:
raise ValueError("write to closed file")
if n == 0:
return 0

Expand Down
1 change: 1 addition & 0 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2438,6 +2438,7 @@ def test_reduce_None(self):
with self.assertRaises(TypeError):
self.dumps(c)

@support.skip_if_unlimited_stack_size
@no_tracing
def test_bad_getattr(self):
# Issue #3514: crash when there is an infinite loop in __getattr__
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"check__all__", "skip_if_buggy_ucrt_strfptime",
"check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer",
"requires_limited_api", "requires_specialization", "thread_unsafe",
"skip_if_unlimited_stack_size",
# sys
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
"is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
Expand Down Expand Up @@ -1771,6 +1772,25 @@ def skip_if_pgo_task(test):
return test if ok else unittest.skip(msg)(test)


def skip_if_unlimited_stack_size(test):
"""Skip decorator for tests not run when an unlimited stack size is configured.

Tests using support.infinite_recursion([...]) may otherwise run into
an infinite loop, running until the memory on the system is filled and
crashing due to OOM.

See https://github.com/python/cpython/issues/143460.
"""
if is_wasi or os.name == "nt":
return test

import resource
curlim, maxlim = resource.getrlimit(resource.RLIMIT_STACK)
unlimited_stack_size_cond = curlim == maxlim and curlim in (-1, 0xFFFF_FFFF_FFFF_FFFF)
reason = "Not run due to unlimited stack size"
return unittest.skipIf(unlimited_stack_size_cond, reason)(test)


def detect_api_mismatch(ref_api, other_api, *, ignore=()):
"""Returns the set of items in ref_api not in other_api, except for a
defined list of items to be ignored in this check.
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from test import support
from test.support import os_helper
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, skip_if_unlimited_stack_size
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 @@ -989,6 +989,7 @@ def next(self):
enum._test_simple_enum(_Precedence, _ast_unparse._Precedence)

@support.cpython_only
@skip_if_unlimited_stack_size
@skip_wasi_stack_overflow()
@skip_emscripten_stack_overflow()
def test_ast_recursion_limit(self):
Expand Down Expand Up @@ -1127,6 +1128,7 @@ def test_pickling(self):
ast2 = pickle.loads(pickle.dumps(tree, protocol))
self.assertEqual(to_tuple(ast2), to_tuple(tree))

@skip_if_unlimited_stack_size
def test_copy_with_parents(self):
# gh-120108
code = """
Expand Down Expand Up @@ -1974,6 +1976,7 @@ def test_level_as_none(self):
exec(code, ns)
self.assertIn('sleep', ns)

@skip_if_unlimited_stack_size
@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,6 +1985,7 @@ def test_recursion_direct(self):
with support.infinite_recursion():
compile(ast.Expression(e), "<test>", "eval")

@skip_if_unlimited_stack_size
@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
30 changes: 30 additions & 0 deletions Lib/test/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,36 @@ def fun():

ctx.run(fun)

def test_context_eq_reentrant_contextvar_set(self):
var = contextvars.ContextVar("v")
ctx1 = contextvars.Context()
ctx2 = contextvars.Context()

class ReentrantEq:
def __eq__(self, other):
ctx1.run(lambda: var.set(object()))
return True

ctx1.run(var.set, ReentrantEq())
ctx2.run(var.set, object())
ctx1 == ctx2

def test_context_eq_reentrant_contextvar_set_in_hash(self):
var = contextvars.ContextVar("v")
ctx1 = contextvars.Context()
ctx2 = contextvars.Context()

class ReentrantHash:
def __hash__(self):
ctx1.run(lambda: var.set(object()))
return 0
def __eq__(self, other):
return isinstance(other, ReentrantHash)

ctx1.run(var.set, ReentrantHash())
ctx2.run(var.set, ReentrantHash())
ctx1 == ctx2


# HAMT Tests

Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,24 @@ class F(float, H):
value = F('nan')
self.assertEqual(hash(value), object.__hash__(value))

def test_issue_gh143006(self):
# When comparing negative non-integer float and int with the
# same number of bits in the integer part, __neg__() in the
# int subclass returning not an int caused an assertion error.
class EvilInt(int):
def __neg__(self):
return ""

i = -1 << 50
f = float(i) - 0.5
i = EvilInt(i)
self.assertFalse(f == i)
self.assertTrue(f != i)
self.assertTrue(f < i)
self.assertTrue(f <= i)
self.assertFalse(f > i)
self.assertFalse(f >= i)


@unittest.skipUnless(hasattr(float, "__getformat__"), "requires __getformat__")
class FormatFunctionsTestCase(unittest.TestCase):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ def test_setstate_subclasses(self):
self.assertIs(type(r[0]), tuple)

@support.skip_if_sanitizer("thread sanitizer crashes in __tsan::FuncEntry", thread=True)
@support.skip_if_unlimited_stack_size
@support.skip_emscripten_stack_overflow()
def test_recursive_pickle(self):
with replaced_module('functools', self.module):
Expand Down Expand Up @@ -2139,6 +2140,7 @@ def orig(a: int) -> nonexistent: ...
@support.skip_on_s390x
@unittest.skipIf(support.is_wasi, "WASI has limited C stack")
@support.skip_if_sanitizer("requires deep stack", ub=True, thread=True)
@support.skip_if_unlimited_stack_size
@support.skip_emscripten_stack_overflow()
def test_lru_recursion(self):

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ def test_created_with_capi(self):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
interp.prepare_main({'spam': True})
with self.assertRaisesRegex(ExecutionFailed, 'NameError'):
self.run_from_capi(interpid, 'assert spam is True')
self.run_from_capi(interpid, 'spam')


class TestInterpreterExec(TestBase):
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_io/test_memoryio.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,48 @@ def test_issue5449(self):
self.ioclass(initial_bytes=buf)
self.assertRaises(TypeError, self.ioclass, buf, foo=None)

def test_write_concurrent_close(self):
class B:
def __buffer__(self, flags):
memio.close()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(ValueError, memio.write, B())

# Prevent crashes when memio.write() or memio.writelines()
# concurrently mutates (e.g., closes or exports) 'memio'.
# See: https://github.com/python/cpython/issues/143378.

def test_writelines_concurrent_close(self):
class B:
def __buffer__(self, flags):
memio.close()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(ValueError, memio.writelines, [B()])

def test_write_concurrent_export(self):
class B:
buf = None
def __buffer__(self, flags):
self.buf = memio.getbuffer()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(BufferError, memio.write, B())

def test_writelines_concurrent_export(self):
class B:
buf = None
def __buffer__(self, flags):
self.buf = memio.getbuffer()
return memoryview(b"A")

memio = self.ioclass()
self.assertRaises(BufferError, memio.writelines, [B()])


class TextIOTestMixin:

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_isinstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def __bases__(self):
self.assertRaises(RecursionError, issubclass, int, X())
self.assertRaises(RecursionError, isinstance, 1, X())

@support.skip_if_unlimited_stack_size
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_infinite_recursion_via_bases_tuple(self):
Expand All @@ -328,6 +329,7 @@ def __getattr__(self, attr):
with self.assertRaises(RecursionError):
issubclass(Failure(), int)

@support.skip_if_unlimited_stack_size
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_infinite_cycle_in_bases(self):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_json/test_recursion.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def default(self, o):
self.fail("didn't raise ValueError on default recursion")


@support.skip_if_unlimited_stack_size
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_highly_nested_objects_decoding(self):
Expand All @@ -84,6 +85,7 @@ def test_highly_nested_objects_decoding(self):
with support.infinite_recursion():
self.loads('[' * very_deep + '1' + ']' * very_deep)

@support.skip_if_unlimited_stack_size
@support.skip_wasi_stack_overflow()
@support.skip_emscripten_stack_overflow()
@support.requires_resource('cpu')
Expand All @@ -99,6 +101,7 @@ def test_highly_nested_objects_encoding(self):
with support.infinite_recursion(5000):
self.dumps(d)

@support.skip_if_unlimited_stack_size
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_endless_recursion(self):
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ def test_recursive(depth, limit):
""")
script_helper.assert_python_ok("-c", code)

@support.skip_if_unlimited_stack_size
def test_recursion(self):
# Test infinite_recursion() and get_recursion_available() functions.
def recursive_function(depth):
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1350,7 +1350,7 @@ def test_disable_gil_abi(self):


@test.support.cpython_only
@force_not_colorized
@test.support.force_not_colorized_test_class
class UnraisableHookTest(unittest.TestCase):
def test_original_unraisablehook(self):
_testcapi = import_helper.import_module('_testcapi')
Expand Down Expand Up @@ -1492,6 +1492,7 @@ def hook_func(args):
def test_custom_unraisablehook_fail(self):
_testcapi = import_helper.import_module('_testcapi')
from _testcapi import err_writeunraisable

def hook_func(*args):
raise Exception("hook_func failed")

Expand Down
Loading
Loading