Skip to content

Commit 3f89645

Browse files
Merge branch '3.13' into backport-b6b99bf-3.13
2 parents 41fd1aa + ad5bd4a commit 3f89645

File tree

62 files changed

+715
-161
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+715
-161
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,11 +557,14 @@ jobs:
557557
matrix:
558558
sanitizer:
559559
- address
560-
- undefined
561-
- memory
562560
oss-fuzz-project-name:
563561
- cpython3
564562
- python3-libraries
563+
include:
564+
- sanitizer: undefined
565+
oss-fuzz-project-name: cpython3
566+
- sanitizer: memory
567+
oss-fuzz-project-name: cpython3
565568
exclude:
566569
# Note that the 'no-exclude' sentinel below is to prevent
567570
# an empty string value from excluding all jobs and causing

Android/android.py

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from asyncio import wait_for
1616
from contextlib import asynccontextmanager
1717
from datetime import datetime, timezone
18+
from enum import IntEnum, auto
1819
from glob import glob
1920
from os.path import abspath, basename, relpath
2021
from pathlib import Path
@@ -61,6 +62,19 @@
6162
hidden_output = []
6263

6364

65+
# Based on android/log.h in the NDK.
66+
class LogPriority(IntEnum):
67+
UNKNOWN = 0
68+
DEFAULT = auto()
69+
VERBOSE = auto()
70+
DEBUG = auto()
71+
INFO = auto()
72+
WARN = auto()
73+
ERROR = auto()
74+
FATAL = auto()
75+
SILENT = auto()
76+
77+
6478
def log_verbose(context, line, stream=sys.stdout):
6579
if context.verbose:
6680
stream.write(line)
@@ -505,47 +519,47 @@ async def logcat_task(context, initial_devices):
505519
pid = await wait_for(find_pid(serial), startup_timeout)
506520

507521
# `--pid` requires API level 24 or higher.
508-
args = [adb, "-s", serial, "logcat", "--pid", pid, "--format", "tag"]
522+
#
523+
# `--binary` mode is used in order to detect which messages end with a
524+
# newline, which most of the other modes don't indicate (except `--format
525+
# long`). For example, every time pytest runs a test, it prints a "." and
526+
# flushes the stream. Each "." becomes a separate log message, but we should
527+
# show them all on the same line.
528+
args = [adb, "-s", serial, "logcat", "--pid", pid, "--binary"]
509529
logcat_started = False
510530
async with async_process(
511-
*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
531+
*args, stdout=subprocess.PIPE, stderr=None
512532
) as process:
513-
while line := (await process.stdout.readline()).decode(*DECODE_ARGS):
514-
if match := re.fullmatch(r"([A-Z])/(.*)", line, re.DOTALL):
533+
while True:
534+
try:
535+
priority, tag, message = await read_logcat(process.stdout)
515536
logcat_started = True
516-
level, message = match.groups()
517-
else:
518-
# If the regex doesn't match, this is either a logcat startup
519-
# error, or the second or subsequent line of a multi-line
520-
# message. Python won't produce multi-line messages, but other
521-
# components might.
522-
level, message = None, line
537+
except asyncio.IncompleteReadError:
538+
break
523539

524540
# Exclude high-volume messages which are rarely useful.
525541
if context.verbose < 2 and "from python test_syslog" in message:
526542
continue
527543

528544
# Put high-level messages on stderr so they're highlighted in the
529545
# buildbot logs. This will include Python's own stderr.
530-
stream = (
531-
sys.stderr
532-
if level in ["W", "E", "F"] # WARNING, ERROR, FATAL (aka ASSERT)
533-
else sys.stdout
534-
)
535-
536-
# To simplify automated processing of the output, e.g. a buildbot
537-
# posting a failure notice on a GitHub PR, we strip the level and
538-
# tag indicators from Python's stdout and stderr.
539-
for prefix in ["python.stdout: ", "python.stderr: "]:
540-
if message.startswith(prefix):
541-
global python_started
542-
python_started = True
543-
stream.write(message.removeprefix(prefix))
544-
break
546+
stream = sys.stderr if priority >= LogPriority.WARN else sys.stdout
547+
548+
# The app's stdout and stderr should be passed through transparently
549+
# to our own corresponding streams.
550+
if tag in ["python.stdout", "python.stderr"]:
551+
global python_started
552+
python_started = True
553+
stream.write(message)
554+
stream.flush()
545555
else:
546556
# Non-Python messages add a lot of noise, but they may
547-
# sometimes help explain a failure.
548-
log_verbose(context, line, stream)
557+
# sometimes help explain a failure. Format them in the same way
558+
# as `logcat --format tag`.
559+
formatted = f"{priority.name[0]}/{tag}: {message}"
560+
if not formatted.endswith("\n"):
561+
formatted += "\n"
562+
log_verbose(context, formatted, stream)
549563

550564
# If the device disconnects while logcat is running, which always
551565
# happens in --managed mode, some versions of adb return non-zero.
@@ -556,6 +570,44 @@ async def logcat_task(context, initial_devices):
556570
raise CalledProcessError(status, args)
557571

558572

573+
# Read one binary log message from the given StreamReader. The message format is
574+
# described at https://android.stackexchange.com/a/74660. All supported versions
575+
# of Android use format version 2 or later.
576+
async def read_logcat(stream):
577+
async def read_bytes(size):
578+
return await stream.readexactly(size)
579+
580+
async def read_int(size):
581+
return int.from_bytes(await read_bytes(size), "little")
582+
583+
payload_len = await read_int(2)
584+
if payload_len < 2:
585+
# 1 byte for priority, 1 byte for null terminator of tag.
586+
raise ValueError(f"payload length {payload_len} is too short")
587+
588+
header_len = await read_int(2)
589+
if header_len < 4:
590+
raise ValueError(f"header length {header_len} is too short")
591+
await read_bytes(header_len - 4) # Ignore other header fields.
592+
593+
priority_int = await read_int(1)
594+
try:
595+
priority = LogPriority(priority_int)
596+
except ValueError:
597+
priority = LogPriority.UNKNOWN
598+
599+
payload_fields = (await read_bytes(payload_len - 1)).split(b"\0")
600+
if len(payload_fields) < 2:
601+
raise ValueError(
602+
f"payload {payload!r} does not contain at least 2 "
603+
f"null-separated fields"
604+
)
605+
tag, message, *_ = [
606+
field.decode(*DECODE_ARGS) for field in payload_fields
607+
]
608+
return priority, tag, message
609+
610+
559611
def stop_app(serial):
560612
run([adb, "-s", serial, "shell", "am", "force-stop", APP_ID], log=False)
561613

Android/testbed/app/build.gradle.kts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,15 @@ android {
9292
}
9393
throw GradleException("Failed to find API level in $androidEnvFile")
9494
}
95-
targetSdk = 35
95+
96+
// This controls the API level of the maxVersion managed emulator, which is used
97+
// by CI and cibuildwheel.
98+
// * 33 has excessive buffering in the logcat client
99+
// (https://cs.android.com/android/_/android/platform/system/logging/+/d340721894f223327339010df59b0ac514308826).
100+
// * 34 consumes too much disk space on GitHub Actions (#142289).
101+
// * 35 has issues connecting to the internet (#142387).
102+
// * 36 and later are not available as aosp_atd images yet.
103+
targetSdk = 32
96104

97105
versionCode = 1
98106
versionName = "1.0"
@@ -125,9 +133,10 @@ android {
125133
path("src/main/c/CMakeLists.txt")
126134
}
127135

128-
// Set this property to something non-empty, otherwise it'll use the default
129-
// list, which ignores asset directories beginning with an underscore.
130-
aaptOptions.ignoreAssetsPattern = ".git"
136+
// Set this property to something nonexistent but non-empty. Otherwise it'll use the
137+
// default list, which ignores asset directories beginning with an underscore, and
138+
// maybe also other files required by tests.
139+
aaptOptions.ignoreAssetsPattern = "android-testbed-dont-ignore-anything"
131140

132141
compileOptions {
133142
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -229,6 +238,12 @@ androidComponents.onVariants { variant ->
229238
from(cwd)
230239
}
231240
}
241+
242+
// A filename ending with .gz will be automatically decompressed
243+
// while building the APK. Avoid this by adding a dash to the end,
244+
// and add an extra dash to any filenames that already end with one.
245+
// This will be undone in MainActivity.kt.
246+
rename(""".*(\.gz|-)""", "$0-")
232247
}
233248
}
234249

Android/testbed/app/src/main/java/org/python/testbed/MainActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ class PythonTestRunner(val context: Context) {
8080
continue
8181
}
8282
input.use {
83-
File(targetSubdir, name).outputStream().use { output ->
83+
// Undo the .gz workaround from build.gradle.kts.
84+
val outputName = name.replace(Regex("""(.*)-"""), "$1")
85+
File(targetSubdir, outputName).outputStream().use { output ->
8486
input.copyTo(output)
8587
}
8688
}

Doc/c-api/module.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@ objects dynamically. Note that both ``PyModule_FromDefAndSpec`` and
529529
``PyModuleDef``, using either ``PyModule_Create`` or
530530
``PyModule_FromDefAndSpec``.
531531
532+
Return ``0`` on success.
533+
Return ``-1`` with an exception set on error.
534+
532535
.. versionadded:: 3.5
533536
534537
.. c:function:: int PyModule_AddFunctions(PyObject *module, PyMethodDef *functions)

Doc/faq/programming.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,13 +1226,13 @@ This converts the list into a set, thereby removing duplicates, and then back
12261226
into a list.
12271227

12281228

1229-
How do you remove multiple items from a list
1230-
--------------------------------------------
1229+
How do you remove multiple items from a list?
1230+
---------------------------------------------
12311231

12321232
As with removing duplicates, explicitly iterating in reverse with a
12331233
delete condition is one possibility. However, it is easier and faster
12341234
to use slice replacement with an implicit or explicit forward iteration.
1235-
Here are three variations.::
1235+
Here are three variations::
12361236

12371237
mylist[:] = filter(keep_function, mylist)
12381238
mylist[:] = (x for x in mylist if keep_condition)

Doc/howto/logging-cookbook.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ messages should not. Here's how you can achieve this::
229229
# tell the handler to use this format
230230
console.setFormatter(formatter)
231231
# add the handler to the root logger
232-
logging.getLogger('').addHandler(console)
232+
logging.getLogger().addHandler(console)
233233

234234
# Now, we can log to the root logger, or any other logger. First the root...
235235
logging.info('Jackdaws love my big sphinx of quartz.')
@@ -637,7 +637,7 @@ the receiving end. A simple way of doing this is attaching a
637637

638638
import logging, logging.handlers
639639

640-
rootLogger = logging.getLogger('')
640+
rootLogger = logging.getLogger()
641641
rootLogger.setLevel(logging.DEBUG)
642642
socketHandler = logging.handlers.SocketHandler('localhost',
643643
logging.handlers.DEFAULT_TCP_LOGGING_PORT)

Doc/library/contextvars.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ Context Variables
9393
# After the reset call the var has no value again, so
9494
# var.get() would raise a LookupError.
9595

96+
The same *token* cannot be used twice.
97+
9698

9799
.. class:: Token
98100

99101
*Token* objects are returned by the :meth:`ContextVar.set` method.
100102
They can be passed to the :meth:`ContextVar.reset` method to revert
101103
the value of the variable to what it was before the corresponding
102-
*set*.
104+
*set*. A single token cannot reset a context variable more than once.
103105

104106
.. attribute:: Token.var
105107

Doc/library/enum.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ Module Contents
153153

154154
Return a list of all power-of-two integers contained in a flag.
155155

156+
:func:`enum.bin`
157+
158+
Like built-in :func:`bin`, except negative values are represented in
159+
two's complement, and the leading bit always indicates sign
160+
(``0`` implies positive, ``1`` implies negative).
161+
156162

157163
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
158164
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``ReprEnum``, ``FlagBoundary``, ``property``, ``member``, ``nonmember``, ``global_enum``, ``show_flag_values``
@@ -1034,6 +1040,19 @@ Utilities and Decorators
10341040

10351041
.. versionadded:: 3.11
10361042

1043+
.. function:: bin(num, max_bits=None)
1044+
1045+
Like built-in :func:`bin`, except negative values are represented in
1046+
two's complement, and the leading bit always indicates sign
1047+
(``0`` implies positive, ``1`` implies negative).
1048+
1049+
>>> import enum
1050+
>>> enum.bin(10)
1051+
'0b0 1010'
1052+
>>> enum.bin(~10) # ~10 is -11
1053+
'0b1 0101'
1054+
1055+
.. versionadded:: 3.10
10371056

10381057
---------------
10391058

Doc/library/functions.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ are always available. They are listed here in alphabetical order.
138138
>>> f'{14:#b}', f'{14:b}'
139139
('0b1110', '1110')
140140

141+
See also :func:`enum.bin` to represent negative values as twos-complement.
142+
141143
See also :func:`format` for more information.
142144

143145

0 commit comments

Comments
 (0)