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
16 changes: 10 additions & 6 deletions Lib/_android_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ def init_streams(android_log_write, stdout_prio, stderr_prio):

global logcat
logcat = Logcat(android_log_write)

sys.stdout = TextLogStream(
stdout_prio, "python.stdout", sys.stdout.fileno())
sys.stderr = TextLogStream(
stderr_prio, "python.stderr", sys.stderr.fileno())
sys.stdout = TextLogStream(stdout_prio, "python.stdout", sys.stdout)
sys.stderr = TextLogStream(stderr_prio, "python.stderr", sys.stderr)


class TextLogStream(io.TextIOWrapper):
def __init__(self, prio, tag, fileno=None, **kwargs):
def __init__(self, prio, tag, original=None, **kwargs):
# Respect the -u option.
if original:
kwargs.setdefault("write_through", original.write_through)
fileno = original.fileno()
else:
fileno = None

# The default is surrogateescape for stdout and backslashreplace for
# stderr, but in the context of an Android log, readability is more
# important than reversibility.
Expand Down
14 changes: 9 additions & 5 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ def __getitem__(self, item):
new_args = (t_args, t_result)
return _CallableGenericAlias(Callable, tuple(new_args))

# TODO: RUSTPYTHON patch for common call
# TODO: RUSTPYTHON; patch for common call
def __or__(self, other):
super().__or__(other)

Expand Down Expand Up @@ -1087,7 +1087,7 @@ def __new__(cls, name, bases, namespace, **kwargs):

warnings._deprecated(
"collections.abc.ByteString",
remove=(3, 14),
remove=(3, 17),
)
return super().__new__(cls, name, bases, namespace, **kwargs)

Expand All @@ -1096,14 +1096,18 @@ def __instancecheck__(cls, instance):

warnings._deprecated(
"collections.abc.ByteString",
remove=(3, 14),
remove=(3, 17),
)
return super().__instancecheck__(instance)

class ByteString(Sequence, metaclass=_DeprecateByteStringMeta):
"""This unifies bytes and bytearray.
"""Deprecated ABC serving as a common supertype of ``bytes`` and ``bytearray``.

XXX Should add all their methods.
This ABC is scheduled for removal in Python 3.17.
Use ``isinstance(obj, collections.abc.Buffer)`` to test if ``obj``
implements the buffer protocol at runtime. For use in type annotations,
either use ``Buffer`` or a union that explicitly specifies the types your
code supports (e.g., ``bytes | bytearray | memoryview``).
"""

__slots__ = ()
Expand Down
24 changes: 21 additions & 3 deletions Lib/html/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def reset(self):
self.lasttag = '???'
self.interesting = interesting_normal
self.cdata_elem = None
self._support_cdata = True
self._escapable = True
super().reset()

Expand Down Expand Up @@ -183,6 +184,19 @@ def clear_cdata_mode(self):
self.cdata_elem = None
self._escapable = True

def _set_support_cdata(self, flag=True):
"""Enable or disable support of the CDATA sections.
If enabled, "<[CDATA[" starts a CDATA section which ends with "]]>".
If disabled, "<[CDATA[" starts a bogus comments which ends with ">".

This method is not called by default. Its purpose is to be called
in custom handle_starttag() and handle_endtag() methods, with
value that depends on the adjusted current node.
See https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
for details.
"""
self._support_cdata = flag

# Internal -- handle data as far as reasonable. May leave state
# and data to be processed by a subsequent call. If 'end' is
# true, force handling all data as if followed by EOF marker.
Expand Down Expand Up @@ -257,7 +271,7 @@ def goahead(self, end):
j -= len(suffix)
break
self.handle_comment(rawdata[i+4:j])
elif startswith("<![CDATA[", i):
elif startswith("<![CDATA[", i) and self._support_cdata:
self.unknown_decl(rawdata[i+3:])
elif rawdata[i:i+9].lower() == '<!doctype':
self.handle_decl(rawdata[i+2:])
Expand Down Expand Up @@ -333,8 +347,12 @@ def parse_html_declaration(self, i):
if rawdata[i:i+4] == '<!--':
# this case is actually already handled in goahead()
return self.parse_comment(i)
elif rawdata[i:i+9] == '<![CDATA[':
return self.parse_marked_section(i)
elif rawdata[i:i+9] == '<![CDATA[' and self._support_cdata:
j = rawdata.find(']]>', i+9)
if j < 0:
return -1
self.unknown_decl(rawdata[i+3: j])
return j + 3
elif rawdata[i:i+9].lower() == '<!doctype':
# find the closing >
gtpos = rawdata.find('>', i+9)
Expand Down
15 changes: 10 additions & 5 deletions Lib/multiprocessing/forkserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,13 @@ def ensure_running(self):
cmd = ('from multiprocessing.forkserver import main; ' +
'main(%d, %d, %r, **%r)')

main_kws = {}
if self._preload_modules:
desired_keys = {'main_path', 'sys_path'}
data = spawn.get_preparation_data('ignore')
data = {x: y for x, y in data.items() if x in desired_keys}
else:
data = {}
if 'sys_path' in data:
main_kws['sys_path'] = data['sys_path']
if 'init_main_from_path' in data:
main_kws['main_path'] = data['init_main_from_path']

with socket.socket(socket.AF_UNIX) as listener:
address = connection.arbitrary_address('AF_UNIX')
Expand All @@ -147,7 +148,7 @@ def ensure_running(self):
try:
fds_to_pass = [listener.fileno(), alive_r]
cmd %= (listener.fileno(), alive_r, self._preload_modules,
data)
main_kws)
exe = spawn.get_executable()
args = [exe] + util._args_from_interpreter_flags()
args += ['-c', cmd]
Expand Down Expand Up @@ -182,6 +183,10 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
except ImportError:
pass

# gh-135335: flush stdout/stderr in case any of the preloaded modules
# wrote to them, otherwise children might inherit buffered data
util._flush_std_streams()

util._close_stdin()

sig_r, sig_w = os.pipe()
Expand Down
4 changes: 4 additions & 0 deletions Lib/multiprocessing/popen_spawn_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def _launch(self, process_obj):
self._fds.extend([child_r, child_w])
self.pid = util.spawnv_passfds(spawn.get_executable(),
cmd, self._fds)
os.close(child_r)
child_r = None
os.close(child_w)
child_w = None
self.sentinel = parent_r
with open(parent_w, 'wb', closefd=False) as f:
f.write(fp.getbuffer())
Expand Down
22 changes: 13 additions & 9 deletions Lib/sysconfig/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,18 +596,22 @@ def get_platform():
isn't particularly important.

Examples of returned values:
linux-i586
linux-alpha (?)
solaris-2.6-sun4u

Windows will return one of:
win-amd64 (64-bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
win-arm64 (64-bit Windows on ARM64 (aka AArch64)
win32 (all others - specifically, sys.platform is returned)

For other non-POSIX platforms, currently just returns 'sys.platform'.
Windows:

"""
- win-amd64 (64-bit Windows on AMD64, aka x86_64, Intel64, and EM64T)
- win-arm64 (64-bit Windows on ARM64, aka AArch64)
- win32 (all others - specifically, sys.platform is returned)

POSIX based OS:

- linux-x86_64
- macosx-15.5-arm64
- macosx-26.0-universal2 (macOS on Apple Silicon or Intel)
- android-24-arm64_v8a

For other non-POSIX platforms, currently just returns :data:`sys.platform`."""
if os.name == 'nt':
if 'amd64' in sys.version.lower():
return 'win-amd64'
Expand Down
41 changes: 41 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6451,6 +6451,35 @@ def test_child_sys_path(self):
self.assertEqual(child_sys_path[1:], sys.path[1:])
self.assertIsNone(import_error, msg=f"child could not import {self._mod_name}")

def test_std_streams_flushed_after_preload(self):
# gh-135335: Check fork server flushes standard streams after
# preloading modules
if multiprocessing.get_start_method() != "forkserver":
self.skipTest("forkserver specific test")

# Create a test module in the temporary directory on the child's path
# TODO: This can all be simplified once gh-126631 is fixed and we can
# use __main__ instead of a module.
dirname = os.path.join(self._temp_dir, 'preloaded_module')
init_name = os.path.join(dirname, '__init__.py')
os.mkdir(dirname)
with open(init_name, "w") as f:
cmd = '''if 1:
import sys
print('stderr', end='', file=sys.stderr)
print('stdout', end='', file=sys.stdout)
'''
f.write(cmd)

name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py')
env = {'PYTHONPATH': self._temp_dir}
_, out, err = test.support.script_helper.assert_python_ok(name, **env)

# Check stderr first, as it is more likely to be useful to see in the
# event of a failure.
self.assertEqual(err.decode().rstrip(), 'stderr')
self.assertEqual(out.decode().rstrip(), 'stdout')


class MiscTestCase(unittest.TestCase):
def test__all__(self):
Expand Down Expand Up @@ -6516,6 +6545,18 @@ def child():
self.assertEqual(q.get_nowait(), "done")
close_queue(q)

def test_preload_main(self):
# gh-126631: Check that __main__ can be pre-loaded
if multiprocessing.get_start_method() != "forkserver":
self.skipTest("forkserver specific test")

name = os.path.join(os.path.dirname(__file__), 'mp_preload_main.py')
_, out, err = test.support.script_helper.assert_python_ok(name)
self.assertEqual(err, b'')

# The trailing empty string comes from split() on output ending with \n
out = out.decode().split("\n")
self.assertEqual(out, ['__main__', '__mp_main__', 'f', 'f', ''])

#
# Mixins
Expand Down
27 changes: 19 additions & 8 deletions Lib/test/list_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
Tests common to list and UserList.UserList
"""

import unittest
import sys
import os
from functools import cmp_to_key

from test import support, seq_tests
from test.support import ALWAYS_EQ, NEVER_EQ
from test import seq_tests
from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit


class CommonTest(seq_tests.CommonTest):
Expand All @@ -33,13 +31,13 @@ def test_init(self):
self.assertEqual(a, b)

def test_getitem_error(self):
a = []
a = self.type2test([])
msg = "list indices must be integers or slices"
with self.assertRaisesRegex(TypeError, msg):
a['a']

def test_setitem_error(self):
a = []
a = self.type2test([])
msg = "list indices must be integers or slices"
with self.assertRaisesRegex(TypeError, msg):
a['a'] = "python"
Expand All @@ -63,7 +61,7 @@ def test_repr(self):

def test_repr_deep(self):
a = self.type2test([])
for i in range(sys.getrecursionlimit() + 100):
for i in range(get_c_recursion_limit() + 1):
a = self.type2test([a])
self.assertRaises(RecursionError, repr, a)

Expand Down Expand Up @@ -193,6 +191,14 @@ def test_setslice(self):

self.assertRaises(TypeError, a.__setitem__)

def test_slice_assign_iterator(self):
x = self.type2test(range(5))
x[0:3] = reversed(range(3))
self.assertEqual(x, self.type2test([2, 1, 0, 3, 4]))

x[:] = reversed(range(3))
self.assertEqual(x, self.type2test([2, 1, 0]))

def test_delslice(self):
a = self.type2test([0, 1])
del a[1:2]
Expand Down Expand Up @@ -552,7 +558,7 @@ def test_constructor_exception_handling(self):
class F(object):
def __iter__(self):
raise KeyboardInterrupt
self.assertRaises(KeyboardInterrupt, list, F())
self.assertRaises(KeyboardInterrupt, self.type2test, F())

def test_exhausted_iterator(self):
a = self.type2test([1, 2, 3])
Expand All @@ -564,3 +570,8 @@ def test_exhausted_iterator(self):
self.assertEqual(list(exhit), [])
self.assertEqual(list(empit), [9])
self.assertEqual(a, self.type2test([1, 2, 3, 9]))

# gh-115733: Crash when iterating over exhausted iterator
exhit = iter(self.type2test([1, 2, 3]))
for _ in exhit:
next(exhit, 1)
14 changes: 14 additions & 0 deletions Lib/test/mp_preload_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import multiprocessing

print(f"{__name__}")

def f():
print("f")

if __name__ == "__main__":
ctx = multiprocessing.get_context("forkserver")
ctx.set_forkserver_preload(['__main__'])
for _ in range(2):
p = ctx.Process(target=f)
p.start()
p.join()
7 changes: 5 additions & 2 deletions Lib/test/seq_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ def __getitem__(self, i):
self.assertEqual(self.type2test(LyingTuple((2,))), self.type2test((1,)))
self.assertEqual(self.type2test(LyingList([2])), self.type2test([1]))

with self.assertRaises(TypeError):
self.type2test(unsupported_arg=[])

def test_truth(self):
self.assertFalse(self.type2test())
self.assertTrue(self.type2test([42]))
Expand Down Expand Up @@ -423,8 +426,8 @@ def test_pickle(self):
self.assertEqual(lst2, lst)
self.assertNotEqual(id(lst2), id(lst))

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@support.suppress_immortalization()
def test_free_after_iterating(self):
support.check_free_after_iterating(self, iter, self.type2test)
support.check_free_after_iterating(self, reversed, self.type2test)
Loading
Loading