aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/asyncio/tools.py162
-rw-r--r--Lib/configparser.py11
-rw-r--r--Lib/email/header.py17
-rw-r--r--Lib/html/parser.py41
-rw-r--r--Lib/http/server.py2
-rw-r--r--Lib/logging/config.py2
-rw-r--r--Lib/test/_code_definitions.py10
-rw-r--r--Lib/test/test_asyncio/test_tools.py1560
-rw-r--r--Lib/test/test_code.py5
-rw-r--r--Lib/test/test_email/test_email.py12
-rw-r--r--Lib/test/test_external_inspection.py395
-rw-r--r--Lib/test/test_fstring.py2
-rw-r--r--Lib/test/test_htmlparser.py97
-rw-r--r--Lib/test/test_logging.py2
-rw-r--r--Lib/test/test_memoryio.py19
-rw-r--r--Lib/test/test_unittest/testmock/testhelpers.py21
-rw-r--r--Lib/unittest/mock.py13
-rw-r--r--Lib/xmlrpc/server.py2
18 files changed, 1755 insertions, 618 deletions
diff --git a/Lib/asyncio/tools.py b/Lib/asyncio/tools.py
index 3fc4524c008..2683f34cc71 100644
--- a/Lib/asyncio/tools.py
+++ b/Lib/asyncio/tools.py
@@ -1,11 +1,10 @@
"""Tools to analyze tasks running in asyncio programs."""
-from collections import defaultdict
+from collections import defaultdict, namedtuple
from itertools import count
from enum import Enum
import sys
-from _remote_debugging import RemoteUnwinder
-
+from _remote_debugging import RemoteUnwinder, FrameInfo
class NodeType(Enum):
COROUTINE = 1
@@ -26,51 +25,75 @@ class CycleFoundException(Exception):
# ─── indexing helpers ───────────────────────────────────────────
-def _format_stack_entry(elem: tuple[str, str, int] | str) -> str:
- if isinstance(elem, tuple):
- fqname, path, line_no = elem
- return f"{fqname} {path}:{line_no}"
-
+def _format_stack_entry(elem: str|FrameInfo) -> str:
+ if not isinstance(elem, str):
+ if elem.lineno == 0 and elem.filename == "":
+ return f"{elem.funcname}"
+ else:
+ return f"{elem.funcname} {elem.filename}:{elem.lineno}"
return elem
def _index(result):
- id2name, awaits = {}, []
- for _thr_id, tasks in result:
- for tid, tname, awaited in tasks:
- id2name[tid] = tname
- for stack, parent_id in awaited:
- stack = [_format_stack_entry(elem) for elem in stack]
- awaits.append((parent_id, stack, tid))
- return id2name, awaits
-
-
-def _build_tree(id2name, awaits):
+ id2name, awaits, task_stacks = {}, [], {}
+ for awaited_info in result:
+ for task_info in awaited_info.awaited_by:
+ task_id = task_info.task_id
+ task_name = task_info.task_name
+ id2name[task_id] = task_name
+
+ # Store the internal coroutine stack for this task
+ if task_info.coroutine_stack:
+ for coro_info in task_info.coroutine_stack:
+ call_stack = coro_info.call_stack
+ internal_stack = [_format_stack_entry(frame) for frame in call_stack]
+ task_stacks[task_id] = internal_stack
+
+ # Add the awaited_by relationships (external dependencies)
+ if task_info.awaited_by:
+ for coro_info in task_info.awaited_by:
+ call_stack = coro_info.call_stack
+ parent_task_id = coro_info.task_name
+ stack = [_format_stack_entry(frame) for frame in call_stack]
+ awaits.append((parent_task_id, stack, task_id))
+ return id2name, awaits, task_stacks
+
+
+def _build_tree(id2name, awaits, task_stacks):
id2label = {(NodeType.TASK, tid): name for tid, name in id2name.items()}
children = defaultdict(list)
- cor_names = defaultdict(dict) # (parent) -> {frame: node}
- cor_id_seq = count(1)
-
- def _cor_node(parent_key, frame_name):
- """Return an existing or new (NodeType.COROUTINE, …) node under *parent_key*."""
- bucket = cor_names[parent_key]
- if frame_name in bucket:
- return bucket[frame_name]
- node_key = (NodeType.COROUTINE, f"c{next(cor_id_seq)}")
- id2label[node_key] = frame_name
- children[parent_key].append(node_key)
- bucket[frame_name] = node_key
+ cor_nodes = defaultdict(dict) # Maps parent -> {frame_name: node_key}
+ next_cor_id = count(1)
+
+ def get_or_create_cor_node(parent, frame):
+ """Get existing coroutine node or create new one under parent"""
+ if frame in cor_nodes[parent]:
+ return cor_nodes[parent][frame]
+
+ node_key = (NodeType.COROUTINE, f"c{next(next_cor_id)}")
+ id2label[node_key] = frame
+ children[parent].append(node_key)
+ cor_nodes[parent][frame] = node_key
return node_key
- # lay down parent ➜ …frames… ➜ child paths
+ # Build task dependency tree with coroutine frames
for parent_id, stack, child_id in awaits:
cur = (NodeType.TASK, parent_id)
- for frame in reversed(stack): # outer-most → inner-most
- cur = _cor_node(cur, frame)
+ for frame in reversed(stack):
+ cur = get_or_create_cor_node(cur, frame)
+
child_key = (NodeType.TASK, child_id)
if child_key not in children[cur]:
children[cur].append(child_key)
+ # Add coroutine stacks for leaf tasks
+ awaiting_tasks = {parent_id for parent_id, _, _ in awaits}
+ for task_id in id2name:
+ if task_id not in awaiting_tasks and task_id in task_stacks:
+ cur = (NodeType.TASK, task_id)
+ for frame in reversed(task_stacks[task_id]):
+ cur = get_or_create_cor_node(cur, frame)
+
return id2label, children
@@ -129,12 +152,12 @@ def build_async_tree(result, task_emoji="(T)", cor_emoji=""):
The call tree is produced by `get_all_async_stacks()`, prefixing tasks
with `task_emoji` and coroutine frames with `cor_emoji`.
"""
- id2name, awaits = _index(result)
+ id2name, awaits, task_stacks = _index(result)
g = _task_graph(awaits)
cycles = _find_cycles(g)
if cycles:
raise CycleFoundException(cycles, id2name)
- labels, children = _build_tree(id2name, awaits)
+ labels, children = _build_tree(id2name, awaits, task_stacks)
def pretty(node):
flag = task_emoji if node[0] == NodeType.TASK else cor_emoji
@@ -154,35 +177,40 @@ def build_async_tree(result, task_emoji="(T)", cor_emoji=""):
def build_task_table(result):
- id2name, awaits = _index(result)
+ id2name, _, _ = _index(result)
table = []
- for tid, tasks in result:
- for task_id, task_name, awaited in tasks:
- if not awaited:
- table.append(
- [
- tid,
- hex(task_id),
- task_name,
- "",
- "",
- "0x0"
- ]
- )
- for stack, awaiter_id in awaited:
- stack = [elem[0] if isinstance(elem, tuple) else elem for elem in stack]
- coroutine_chain = " -> ".join(stack)
- awaiter_name = id2name.get(awaiter_id, "Unknown")
- table.append(
- [
- tid,
- hex(task_id),
- task_name,
- coroutine_chain,
- awaiter_name,
- hex(awaiter_id),
- ]
- )
+
+ for awaited_info in result:
+ thread_id = awaited_info.thread_id
+ for task_info in awaited_info.awaited_by:
+ # Get task info
+ task_id = task_info.task_id
+ task_name = task_info.task_name
+
+ # Build coroutine stack string
+ frames = [frame for coro in task_info.coroutine_stack
+ for frame in coro.call_stack]
+ coro_stack = " -> ".join(_format_stack_entry(x).split(" ")[0]
+ for x in frames)
+
+ # Handle tasks with no awaiters
+ if not task_info.awaited_by:
+ table.append([thread_id, hex(task_id), task_name, coro_stack,
+ "", "", "0x0"])
+ continue
+
+ # Handle tasks with awaiters
+ for coro_info in task_info.awaited_by:
+ parent_id = coro_info.task_name
+ awaiter_frames = [_format_stack_entry(x).split(" ")[0]
+ for x in coro_info.call_stack]
+ awaiter_chain = " -> ".join(awaiter_frames)
+ awaiter_name = id2name.get(parent_id, "Unknown")
+ parent_id_str = (hex(parent_id) if isinstance(parent_id, int)
+ else str(parent_id))
+
+ table.append([thread_id, hex(task_id), task_name, coro_stack,
+ awaiter_chain, awaiter_name, parent_id_str])
return table
@@ -211,11 +239,11 @@ def display_awaited_by_tasks_table(pid: int) -> None:
table = build_task_table(tasks)
# Print the table in a simple tabular format
print(
- f"{'tid':<10} {'task id':<20} {'task name':<20} {'coroutine chain':<50} {'awaiter name':<20} {'awaiter id':<15}"
+ f"{'tid':<10} {'task id':<20} {'task name':<20} {'coroutine stack':<50} {'awaiter chain':<50} {'awaiter name':<15} {'awaiter id':<15}"
)
- print("-" * 135)
+ print("-" * 180)
for row in table:
- print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<20} {row[5]:<15}")
+ print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<50} {row[5]:<15} {row[6]:<15}")
def display_awaited_by_tasks_tree(pid: int) -> None:
diff --git a/Lib/configparser.py b/Lib/configparser.py
index 239fda60a02..18af1eadaad 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -1218,11 +1218,14 @@ class RawConfigParser(MutableMapping):
def _validate_key_contents(self, key):
"""Raises an InvalidWriteError for any keys containing
- delimiters or that match the section header pattern"""
+ delimiters or that begins with the section header pattern"""
if re.match(self.SECTCRE, key):
- raise InvalidWriteError("Cannot write keys matching section pattern")
- if any(delim in key for delim in self._delimiters):
- raise InvalidWriteError("Cannot write key that contains delimiters")
+ raise InvalidWriteError(
+ f"Cannot write key {key}; begins with section pattern")
+ for delim in self._delimiters:
+ if delim in key:
+ raise InvalidWriteError(
+ f"Cannot write key {key}; contains delimiter {delim}")
def _validate_value_types(self, *, section="", option="", value=""):
"""Raises a TypeError for illegal non-string values.
diff --git a/Lib/email/header.py b/Lib/email/header.py
index 113a81f4131..220a84a7454 100644
--- a/Lib/email/header.py
+++ b/Lib/email/header.py
@@ -59,16 +59,22 @@ _max_append = email.quoprimime._max_append
def decode_header(header):
"""Decode a message header value without converting charset.
- Returns a list of (string, charset) pairs containing each of the decoded
- parts of the header. Charset is None for non-encoded parts of the header,
- otherwise a lower-case string containing the name of the character set
- specified in the encoded string.
+ For historical reasons, this function may return either:
+
+ 1. A list of length 1 containing a pair (str, None).
+ 2. A list of (bytes, charset) pairs containing each of the decoded
+ parts of the header. Charset is None for non-encoded parts of the header,
+ otherwise a lower-case string containing the name of the character set
+ specified in the encoded string.
header may be a string that may or may not contain RFC2047 encoded words,
or it may be a Header object.
An email.errors.HeaderParseError may be raised when certain decoding error
occurs (e.g. a base64 decoding exception).
+
+ This function exists for backwards compatibility only. For new code, we
+ recommend using email.headerregistry.HeaderRegistry instead.
"""
# If it is a Header object, we can just return the encoded chunks.
if hasattr(header, '_chunks'):
@@ -161,6 +167,9 @@ def make_header(decoded_seq, maxlinelen=None, header_name=None,
This function takes one of those sequence of pairs and returns a Header
instance. Optional maxlinelen, header_name, and continuation_ws are as in
the Header constructor.
+
+ This function exists for backwards compatibility only, and is not
+ recommended for use in new code.
"""
h = Header(maxlinelen=maxlinelen, header_name=header_name,
continuation_ws=continuation_ws)
diff --git a/Lib/html/parser.py b/Lib/html/parser.py
index 1e30956fe24..ba416e7fa6e 100644
--- a/Lib/html/parser.py
+++ b/Lib/html/parser.py
@@ -27,6 +27,7 @@ charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
attr_charref = re.compile(r'&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*)[;=]?')
starttagopen = re.compile('<[a-zA-Z]')
+endtagopen = re.compile('</[a-zA-Z]')
piclose = re.compile('>')
commentclose = re.compile(r'--\s*>')
# Note:
@@ -195,7 +196,7 @@ class HTMLParser(_markupbase.ParserBase):
k = self.parse_pi(i)
elif startswith("<!", i):
k = self.parse_html_declaration(i)
- elif (i + 1) < n:
+ elif (i + 1) < n or end:
self.handle_data("<")
k = i + 1
else:
@@ -203,17 +204,35 @@ class HTMLParser(_markupbase.ParserBase):
if k < 0:
if not end:
break
- k = rawdata.find('>', i + 1)
- if k < 0:
- k = rawdata.find('<', i + 1)
- if k < 0:
- k = i + 1
- else:
- k += 1
- if self.convert_charrefs and not self.cdata_elem:
- self.handle_data(unescape(rawdata[i:k]))
+ if starttagopen.match(rawdata, i): # < + letter
+ pass
+ elif startswith("</", i):
+ if i + 2 == n:
+ self.handle_data("</")
+ elif endtagopen.match(rawdata, i): # </ + letter
+ pass
+ else:
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<!--", i):
+ j = n
+ for suffix in ("--!", "--", "-"):
+ if rawdata.endswith(suffix, i+4):
+ j -= len(suffix)
+ break
+ self.handle_comment(rawdata[i+4:j])
+ elif startswith("<![CDATA[", i):
+ self.unknown_decl(rawdata[i+3:])
+ elif rawdata[i:i+9].lower() == '<!doctype':
+ self.handle_decl(rawdata[i+2:])
+ elif startswith("<!", i):
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<?", i):
+ self.handle_pi(rawdata[i+2:])
else:
- self.handle_data(rawdata[i:k])
+ raise AssertionError("we should not get here!")
+ k = n
i = self.updatepos(i, k)
elif startswith("&#", i):
match = charref.match(rawdata, i)
diff --git a/Lib/http/server.py b/Lib/http/server.py
index ef10d185932..a2ffbe2e44d 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -115,7 +115,7 @@ DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8"
class HTTPServer(socketserver.TCPServer):
allow_reuse_address = True # Seems to make sense in testing environment
- allow_reuse_port = True
+ allow_reuse_port = False
def server_bind(self):
"""Override server_bind to store the server name."""
diff --git a/Lib/logging/config.py b/Lib/logging/config.py
index c994349fd6e..3d9aa00fa52 100644
--- a/Lib/logging/config.py
+++ b/Lib/logging/config.py
@@ -1018,7 +1018,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
"""
allow_reuse_address = True
- allow_reuse_port = True
+ allow_reuse_port = False
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
handler=None, ready=None, verify=None):
diff --git a/Lib/test/_code_definitions.py b/Lib/test/_code_definitions.py
index 274beb65a6d..70c44da2ec6 100644
--- a/Lib/test/_code_definitions.py
+++ b/Lib/test/_code_definitions.py
@@ -57,6 +57,13 @@ def spam_with_globals_and_builtins():
print(res)
+def spam_with_global_and_attr_same_name():
+ try:
+ spam_minimal.spam_minimal
+ except AttributeError:
+ pass
+
+
def spam_full_args(a, b, /, c, d, *args, e, f, **kwargs):
return (a, b, c, d, e, f, args, kwargs)
@@ -190,6 +197,7 @@ TOP_FUNCTIONS = [
spam_minimal,
spam_with_builtins,
spam_with_globals_and_builtins,
+ spam_with_global_and_attr_same_name,
spam_full_args,
spam_full_args_with_defaults,
spam_args_attrs_and_builtins,
@@ -258,6 +266,7 @@ STATELESS_CODE = [
script_with_globals,
spam_full_args_with_defaults,
spam_with_globals_and_builtins,
+ spam_with_global_and_attr_same_name,
spam_full,
]
@@ -275,6 +284,7 @@ SCRIPT_FUNCTIONS = [
*PURE_SCRIPT_FUNCTIONS,
script_with_globals,
spam_with_globals_and_builtins,
+ spam_with_global_and_attr_same_name,
]
diff --git a/Lib/test/test_asyncio/test_tools.py b/Lib/test/test_asyncio/test_tools.py
index ba36e759ccd..34e94830204 100644
--- a/Lib/test/test_asyncio/test_tools.py
+++ b/Lib/test/test_asyncio/test_tools.py
@@ -2,6 +2,13 @@ import unittest
from asyncio import tools
+from collections import namedtuple
+
+FrameInfo = namedtuple('FrameInfo', ['funcname', 'filename', 'lineno'])
+CoroInfo = namedtuple('CoroInfo', ['call_stack', 'task_name'])
+TaskInfo = namedtuple('TaskInfo', ['task_id', 'task_name', 'coroutine_stack', 'awaited_by'])
+AwaitedInfo = namedtuple('AwaitedInfo', ['thread_id', 'awaited_by'])
+
# mock output of get_all_awaited_by function.
TEST_INPUTS_TREE = [
@@ -10,81 +17,151 @@ TEST_INPUTS_TREE = [
# different subtasks part of a TaskGroup (root1 and root2) which call
# awaiter functions.
(
- (
- 1,
- [
- (2, "Task-1", []),
- (
- 3,
- "timer",
- [
- [[("awaiter3", "/path/to/app.py", 130),
- ("awaiter2", "/path/to/app.py", 120),
- ("awaiter", "/path/to/app.py", 110)], 4],
- [[("awaiterB3", "/path/to/app.py", 190),
- ("awaiterB2", "/path/to/app.py", 180),
- ("awaiterB", "/path/to/app.py", 170)], 5],
- [[("awaiterB3", "/path/to/app.py", 190),
- ("awaiterB2", "/path/to/app.py", 180),
- ("awaiterB", "/path/to/app.py", 170)], 6],
- [[("awaiter3", "/path/to/app.py", 130),
- ("awaiter2", "/path/to/app.py", 120),
- ("awaiter", "/path/to/app.py", 110)], 7],
- ],
- ),
- (
- 8,
- "root1",
- [[["_aexit", "__aexit__", "main"], 2]],
- ),
- (
- 9,
- "root2",
- [[["_aexit", "__aexit__", "main"], 2]],
- ),
- (
- 4,
- "child1_1",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 8,
- ]
- ],
- ),
- (
- 6,
- "child2_1",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 8,
- ]
- ],
- ),
- (
- 7,
- "child1_2",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 9,
- ]
- ],
- ),
- (
- 5,
- "child2_2",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 9,
- ]
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
),
- ],
+ TaskInfo(
+ task_id=3,
+ task_name="timer",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "/path/to/app.py", 130),
+ FrameInfo("awaiter2", "/path/to/app.py", 120),
+ FrameInfo("awaiter", "/path/to/app.py", 110)
+ ],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiterB3", "/path/to/app.py", 190),
+ FrameInfo("awaiterB2", "/path/to/app.py", 180),
+ FrameInfo("awaiterB", "/path/to/app.py", 170)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiterB3", "/path/to/app.py", 190),
+ FrameInfo("awaiterB2", "/path/to/app.py", 180),
+ FrameInfo("awaiterB", "/path/to/app.py", 170)
+ ],
+ task_name=6
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "/path/to/app.py", 130),
+ FrameInfo("awaiter2", "/path/to/app.py", 120),
+ FrameInfo("awaiter", "/path/to/app.py", 110)
+ ],
+ task_name=7
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="root1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=9,
+ task_name="root2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="child1_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="child2_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="child1_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="child2_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ )
+ ]
),
- (0, []),
+ AwaitedInfo(thread_id=0, awaited_by=[])
),
(
[
@@ -130,26 +207,96 @@ TEST_INPUTS_TREE = [
[
# test case containing two roots
(
- (
- 9,
- [
- (5, "Task-5", []),
- (6, "Task-6", [[["main2"], 5]]),
- (7, "Task-7", [[["main2"], 5]]),
- (8, "Task-8", [[["main2"], 5]]),
- ],
+ AwaitedInfo(
+ thread_id=9,
+ awaited_by=[
+ TaskInfo(
+ task_id=5,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-6",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-7",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="Task-8",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ )
+ ]
),
- (
- 10,
- [
- (1, "Task-1", []),
- (2, "Task-2", [[["main"], 1]]),
- (3, "Task-3", [[["main"], 1]]),
- (4, "Task-4", [[["main"], 1]]),
- ],
+ AwaitedInfo(
+ thread_id=10,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ )
+ ]
),
- (11, []),
- (0, []),
+ AwaitedInfo(thread_id=11, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
),
(
[
@@ -174,18 +321,63 @@ TEST_INPUTS_TREE = [
# test case containing two roots, one of them without subtasks
(
[
- (1, [(2, "Task-5", [])]),
- (
- 3,
- [
- (4, "Task-1", []),
- (5, "Task-2", [[["main"], 4]]),
- (6, "Task-3", [[["main"], 4]]),
- (7, "Task-4", [[["main"], 4]]),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
),
- (8, []),
- (0, []),
+ AwaitedInfo(
+ thread_id=3,
+ awaited_by=[
+ TaskInfo(
+ task_id=4,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=8, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
]
),
(
@@ -208,19 +400,44 @@ TEST_INPUTS_CYCLES_TREE = [
# this test case contains a cycle: two tasks awaiting each other.
(
[
- (
- 1,
- [
- (2, "Task-1", []),
- (
- 3,
- "a",
- [[["awaiter2"], 4], [["main"], 2]],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
),
- (4, "b", [[["awaiter"], 3]]),
- ],
+ TaskInfo(
+ task_id=3,
+ task_name="a",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter2", "", 0)],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="b",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
),
- (0, []),
+ AwaitedInfo(thread_id=0, awaited_by=[])
]
),
([[4, 3, 4]]),
@@ -229,32 +446,85 @@ TEST_INPUTS_CYCLES_TREE = [
# this test case contains two cycles
(
[
- (
- 1,
- [
- (2, "Task-1", []),
- (
- 3,
- "A",
- [[["nested", "nested", "task_b"], 4]],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
),
- (
- 4,
- "B",
- [
- [["nested", "nested", "task_c"], 5],
- [["nested", "nested", "task_a"], 3],
- ],
+ TaskInfo(
+ task_id=3,
+ task_name="A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
),
- (5, "C", [[["nested", "nested"], 6]]),
- (
- 6,
- "Task-2",
- [[["nested", "nested", "task_b"], 4]],
+ TaskInfo(
+ task_id=4,
+ task_name="B",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_c", "", 0)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_a", "", 0)
+ ],
+ task_name=3
+ )
+ ]
),
- ],
+ TaskInfo(
+ task_id=5,
+ task_name="C",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0)
+ ],
+ task_name=6
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
+ )
+ ]
),
- (0, []),
+ AwaitedInfo(thread_id=0, awaited_by=[])
]
),
([[4, 3, 4], [4, 6, 5, 4]]),
@@ -267,81 +537,160 @@ TEST_INPUTS_TABLE = [
# different subtasks part of a TaskGroup (root1 and root2) which call
# awaiter functions.
(
- (
- 1,
- [
- (2, "Task-1", []),
- (
- 3,
- "timer",
- [
- [["awaiter3", "awaiter2", "awaiter"], 4],
- [["awaiter1_3", "awaiter1_2", "awaiter1"], 5],
- [["awaiter1_3", "awaiter1_2", "awaiter1"], 6],
- [["awaiter3", "awaiter2", "awaiter"], 7],
- ],
- ),
- (
- 8,
- "root1",
- [[["_aexit", "__aexit__", "main"], 2]],
- ),
- (
- 9,
- "root2",
- [[["_aexit", "__aexit__", "main"], 2]],
- ),
- (
- 4,
- "child1_1",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 8,
- ]
- ],
- ),
- (
- 6,
- "child2_1",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 8,
- ]
- ],
- ),
- (
- 7,
- "child1_2",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 9,
- ]
- ],
- ),
- (
- 5,
- "child2_2",
- [
- [
- ["_aexit", "__aexit__", "blocho_caller", "bloch"],
- 9,
- ]
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
),
- ],
+ TaskInfo(
+ task_id=3,
+ task_name="timer",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "", 0),
+ FrameInfo("awaiter2", "", 0),
+ FrameInfo("awaiter", "", 0)
+ ],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter1_3", "", 0),
+ FrameInfo("awaiter1_2", "", 0),
+ FrameInfo("awaiter1", "", 0)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter1_3", "", 0),
+ FrameInfo("awaiter1_2", "", 0),
+ FrameInfo("awaiter1", "", 0)
+ ],
+ task_name=6
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("awaiter3", "", 0),
+ FrameInfo("awaiter2", "", 0),
+ FrameInfo("awaiter", "", 0)
+ ],
+ task_name=7
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="root1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=9,
+ task_name="root2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("main", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="child1_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="child2_1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=8
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="child1_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="child2_2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("_aexit", "", 0),
+ FrameInfo("__aexit__", "", 0),
+ FrameInfo("blocho_caller", "", 0),
+ FrameInfo("bloch", "", 0)
+ ],
+ task_name=9
+ )
+ ]
+ )
+ ]
),
- (0, []),
+ AwaitedInfo(thread_id=0, awaited_by=[])
),
(
[
- [1, "0x2", "Task-1", "", "", "0x0"],
+ [1, "0x2", "Task-1", "", "", "", "0x0"],
[
1,
"0x3",
"timer",
+ "",
"awaiter3 -> awaiter2 -> awaiter",
"child1_1",
"0x4",
@@ -350,6 +699,7 @@ TEST_INPUTS_TABLE = [
1,
"0x3",
"timer",
+ "",
"awaiter1_3 -> awaiter1_2 -> awaiter1",
"child2_2",
"0x5",
@@ -358,6 +708,7 @@ TEST_INPUTS_TABLE = [
1,
"0x3",
"timer",
+ "",
"awaiter1_3 -> awaiter1_2 -> awaiter1",
"child2_1",
"0x6",
@@ -366,6 +717,7 @@ TEST_INPUTS_TABLE = [
1,
"0x3",
"timer",
+ "",
"awaiter3 -> awaiter2 -> awaiter",
"child1_2",
"0x7",
@@ -374,6 +726,7 @@ TEST_INPUTS_TABLE = [
1,
"0x8",
"root1",
+ "",
"_aexit -> __aexit__ -> main",
"Task-1",
"0x2",
@@ -382,6 +735,7 @@ TEST_INPUTS_TABLE = [
1,
"0x9",
"root2",
+ "",
"_aexit -> __aexit__ -> main",
"Task-1",
"0x2",
@@ -390,6 +744,7 @@ TEST_INPUTS_TABLE = [
1,
"0x4",
"child1_1",
+ "",
"_aexit -> __aexit__ -> blocho_caller -> bloch",
"root1",
"0x8",
@@ -398,6 +753,7 @@ TEST_INPUTS_TABLE = [
1,
"0x6",
"child2_1",
+ "",
"_aexit -> __aexit__ -> blocho_caller -> bloch",
"root1",
"0x8",
@@ -406,6 +762,7 @@ TEST_INPUTS_TABLE = [
1,
"0x7",
"child1_2",
+ "",
"_aexit -> __aexit__ -> blocho_caller -> bloch",
"root2",
"0x9",
@@ -414,6 +771,7 @@ TEST_INPUTS_TABLE = [
1,
"0x5",
"child2_2",
+ "",
"_aexit -> __aexit__ -> blocho_caller -> bloch",
"root2",
"0x9",
@@ -424,37 +782,107 @@ TEST_INPUTS_TABLE = [
[
# test case containing two roots
(
- (
- 9,
- [
- (5, "Task-5", []),
- (6, "Task-6", [[["main2"], 5]]),
- (7, "Task-7", [[["main2"], 5]]),
- (8, "Task-8", [[["main2"], 5]]),
- ],
+ AwaitedInfo(
+ thread_id=9,
+ awaited_by=[
+ TaskInfo(
+ task_id=5,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-6",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-7",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=8,
+ task_name="Task-8",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main2", "", 0)],
+ task_name=5
+ )
+ ]
+ )
+ ]
),
- (
- 10,
- [
- (1, "Task-1", []),
- (2, "Task-2", [[["main"], 1]]),
- (3, "Task-3", [[["main"], 1]]),
- (4, "Task-4", [[["main"], 1]]),
- ],
+ AwaitedInfo(
+ thread_id=10,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=1
+ )
+ ]
+ )
+ ]
),
- (11, []),
- (0, []),
+ AwaitedInfo(thread_id=11, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
),
(
[
- [9, "0x5", "Task-5", "", "", "0x0"],
- [9, "0x6", "Task-6", "main2", "Task-5", "0x5"],
- [9, "0x7", "Task-7", "main2", "Task-5", "0x5"],
- [9, "0x8", "Task-8", "main2", "Task-5", "0x5"],
- [10, "0x1", "Task-1", "", "", "0x0"],
- [10, "0x2", "Task-2", "main", "Task-1", "0x1"],
- [10, "0x3", "Task-3", "main", "Task-1", "0x1"],
- [10, "0x4", "Task-4", "main", "Task-1", "0x1"],
+ [9, "0x5", "Task-5", "", "", "", "0x0"],
+ [9, "0x6", "Task-6", "", "main2", "Task-5", "0x5"],
+ [9, "0x7", "Task-7", "", "main2", "Task-5", "0x5"],
+ [9, "0x8", "Task-8", "", "main2", "Task-5", "0x5"],
+ [10, "0x1", "Task-1", "", "", "", "0x0"],
+ [10, "0x2", "Task-2", "", "main", "Task-1", "0x1"],
+ [10, "0x3", "Task-3", "", "main", "Task-1", "0x1"],
+ [10, "0x4", "Task-4", "", "main", "Task-1", "0x1"],
]
),
],
@@ -462,27 +890,72 @@ TEST_INPUTS_TABLE = [
# test case containing two roots, one of them without subtasks
(
[
- (1, [(2, "Task-5", [])]),
- (
- 3,
- [
- (4, "Task-1", []),
- (5, "Task-2", [[["main"], 4]]),
- (6, "Task-3", [[["main"], 4]]),
- (7, "Task-4", [[["main"], 4]]),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-5",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
),
- (8, []),
- (0, []),
+ AwaitedInfo(
+ thread_id=3,
+ awaited_by=[
+ TaskInfo(
+ task_id=4,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=5,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=6,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=7,
+ task_name="Task-4",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=4
+ )
+ ]
+ )
+ ]
+ ),
+ AwaitedInfo(thread_id=8, awaited_by=[]),
+ AwaitedInfo(thread_id=0, awaited_by=[])
]
),
(
[
- [1, "0x2", "Task-5", "", "", "0x0"],
- [3, "0x4", "Task-1", "", "", "0x0"],
- [3, "0x5", "Task-2", "main", "Task-1", "0x4"],
- [3, "0x6", "Task-3", "main", "Task-1", "0x4"],
- [3, "0x7", "Task-4", "main", "Task-1", "0x4"],
+ [1, "0x2", "Task-5", "", "", "", "0x0"],
+ [3, "0x4", "Task-1", "", "", "", "0x0"],
+ [3, "0x5", "Task-2", "", "main", "Task-1", "0x4"],
+ [3, "0x6", "Task-3", "", "main", "Task-1", "0x4"],
+ [3, "0x7", "Task-4", "", "main", "Task-1", "0x4"],
]
),
],
@@ -491,27 +964,52 @@ TEST_INPUTS_TABLE = [
# this test case contains a cycle: two tasks awaiting each other.
(
[
- (
- 1,
- [
- (2, "Task-1", []),
- (
- 3,
- "a",
- [[["awaiter2"], 4], [["main"], 2]],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
),
- (4, "b", [[["awaiter"], 3]]),
- ],
+ TaskInfo(
+ task_id=3,
+ task_name="a",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter2", "", 0)],
+ task_name=4
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="b",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("awaiter", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
),
- (0, []),
+ AwaitedInfo(thread_id=0, awaited_by=[])
]
),
(
[
- [1, "0x2", "Task-1", "", "", "0x0"],
- [1, "0x3", "a", "awaiter2", "b", "0x4"],
- [1, "0x3", "a", "main", "Task-1", "0x2"],
- [1, "0x4", "b", "awaiter", "a", "0x3"],
+ [1, "0x2", "Task-1", "", "", "", "0x0"],
+ [1, "0x3", "a", "", "awaiter2", "b", "0x4"],
+ [1, "0x3", "a", "", "main", "Task-1", "0x2"],
+ [1, "0x4", "b", "", "awaiter", "a", "0x3"],
]
),
],
@@ -519,41 +1017,95 @@ TEST_INPUTS_TABLE = [
# this test case contains two cycles
(
[
- (
- 1,
- [
- (2, "Task-1", []),
- (
- 3,
- "A",
- [[["nested", "nested", "task_b"], 4]],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
),
- (
- 4,
- "B",
- [
- [["nested", "nested", "task_c"], 5],
- [["nested", "nested", "task_a"], 3],
- ],
+ TaskInfo(
+ task_id=4,
+ task_name="B",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_c", "", 0)
+ ],
+ task_name=5
+ ),
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_a", "", 0)
+ ],
+ task_name=3
+ )
+ ]
),
- (5, "C", [[["nested", "nested"], 6]]),
- (
- 6,
- "Task-2",
- [[["nested", "nested", "task_b"], 4]],
+ TaskInfo(
+ task_id=5,
+ task_name="C",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0)
+ ],
+ task_name=6
+ )
+ ]
),
- ],
+ TaskInfo(
+ task_id=6,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("nested", "", 0),
+ FrameInfo("nested", "", 0),
+ FrameInfo("task_b", "", 0)
+ ],
+ task_name=4
+ )
+ ]
+ )
+ ]
),
- (0, []),
+ AwaitedInfo(thread_id=0, awaited_by=[])
]
),
(
[
- [1, "0x2", "Task-1", "", "", "0x0"],
+ [1, "0x2", "Task-1", "", "", "", "0x0"],
[
1,
"0x3",
"A",
+ "",
"nested -> nested -> task_b",
"B",
"0x4",
@@ -562,6 +1114,7 @@ TEST_INPUTS_TABLE = [
1,
"0x4",
"B",
+ "",
"nested -> nested -> task_c",
"C",
"0x5",
@@ -570,6 +1123,7 @@ TEST_INPUTS_TABLE = [
1,
"0x4",
"B",
+ "",
"nested -> nested -> task_a",
"A",
"0x3",
@@ -578,6 +1132,7 @@ TEST_INPUTS_TABLE = [
1,
"0x5",
"C",
+ "",
"nested -> nested",
"Task-2",
"0x6",
@@ -586,6 +1141,7 @@ TEST_INPUTS_TABLE = [
1,
"0x6",
"Task-2",
+ "",
"nested -> nested -> task_b",
"B",
"0x4",
@@ -600,7 +1156,8 @@ class TestAsyncioToolsTree(unittest.TestCase):
def test_asyncio_utils(self):
for input_, tree in TEST_INPUTS_TREE:
with self.subTest(input_):
- self.assertEqual(tools.build_async_tree(input_), tree)
+ result = tools.build_async_tree(input_)
+ self.assertEqual(result, tree)
def test_asyncio_utils_cycles(self):
for input_, cycles in TEST_INPUTS_CYCLES_TREE:
@@ -615,7 +1172,8 @@ class TestAsyncioToolsTable(unittest.TestCase):
def test_asyncio_utils(self):
for input_, table in TEST_INPUTS_TABLE:
with self.subTest(input_):
- self.assertEqual(tools.build_task_table(input_), table)
+ result = tools.build_task_table(input_)
+ self.assertEqual(result, table)
class TestAsyncioToolsBasic(unittest.TestCase):
@@ -632,26 +1190,67 @@ class TestAsyncioToolsBasic(unittest.TestCase):
self.assertEqual(tools.build_task_table(result), expected_output)
def test_only_independent_tasks_tree(self):
- input_ = [(1, [(10, "taskA", []), (11, "taskB", [])])]
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=10,
+ task_name="taskA",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=11,
+ task_name="taskB",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
expected = [["└── (T) taskA"], ["└── (T) taskB"]]
result = tools.build_async_tree(input_)
self.assertEqual(sorted(result), sorted(expected))
def test_only_independent_tasks_table(self):
- input_ = [(1, [(10, "taskA", []), (11, "taskB", [])])]
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=10,
+ task_name="taskA",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=11,
+ task_name="taskB",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
self.assertEqual(
tools.build_task_table(input_),
- [[1, "0xa", "taskA", "", "", "0x0"], [1, "0xb", "taskB", "", "", "0x0"]],
+ [[1, '0xa', 'taskA', '', '', '', '0x0'], [1, '0xb', 'taskB', '', '', '', '0x0']]
)
def test_single_task_tree(self):
"""Test build_async_tree with a single task and no awaits."""
result = [
- (
- 1,
- [
- (2, "Task-1", []),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
)
]
expected_output = [
@@ -664,25 +1263,50 @@ class TestAsyncioToolsBasic(unittest.TestCase):
def test_single_task_table(self):
"""Test build_task_table with a single task and no awaits."""
result = [
- (
- 1,
- [
- (2, "Task-1", []),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
)
]
- expected_output = [[1, "0x2", "Task-1", "", "", "0x0"]]
+ expected_output = [[1, '0x2', 'Task-1', '', '', '', '0x0']]
self.assertEqual(tools.build_task_table(result), expected_output)
def test_cycle_detection(self):
"""Test build_async_tree raises CycleFoundException for cyclic input."""
result = [
- (
- 1,
- [
- (2, "Task-1", [[["main"], 3]]),
- (3, "Task-2", [[["main"], 2]]),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ )
+ ]
)
]
with self.assertRaises(tools.CycleFoundException) as context:
@@ -692,13 +1316,38 @@ class TestAsyncioToolsBasic(unittest.TestCase):
def test_complex_tree(self):
"""Test build_async_tree with a more complex tree structure."""
result = [
- (
- 1,
- [
- (2, "Task-1", []),
- (3, "Task-2", [[["main"], 2]]),
- (4, "Task-3", [[["main"], 3]]),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
)
]
expected_output = [
@@ -715,30 +1364,76 @@ class TestAsyncioToolsBasic(unittest.TestCase):
def test_complex_table(self):
"""Test build_task_table with a more complex tree structure."""
result = [
- (
- 1,
- [
- (2, "Task-1", []),
- (3, "Task-2", [[["main"], 2]]),
- (4, "Task-3", [[["main"], 3]]),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=2,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=4,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("main", "", 0)],
+ task_name=3
+ )
+ ]
+ )
+ ]
)
]
expected_output = [
- [1, "0x2", "Task-1", "", "", "0x0"],
- [1, "0x3", "Task-2", "main", "Task-1", "0x2"],
- [1, "0x4", "Task-3", "main", "Task-2", "0x3"],
+ [1, '0x2', 'Task-1', '', '', '', '0x0'],
+ [1, '0x3', 'Task-2', '', 'main', 'Task-1', '0x2'],
+ [1, '0x4', 'Task-3', '', 'main', 'Task-2', '0x3']
]
self.assertEqual(tools.build_task_table(result), expected_output)
def test_deep_coroutine_chain(self):
input_ = [
- (
- 1,
- [
- (10, "leaf", [[["c1", "c2", "c3", "c4", "c5"], 11]]),
- (11, "root", []),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=10,
+ task_name="leaf",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("c1", "", 0),
+ FrameInfo("c2", "", 0),
+ FrameInfo("c3", "", 0),
+ FrameInfo("c4", "", 0),
+ FrameInfo("c5", "", 0)
+ ],
+ task_name=11
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=11,
+ task_name="root",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
)
]
expected = [
@@ -757,13 +1452,47 @@ class TestAsyncioToolsBasic(unittest.TestCase):
def test_multiple_cycles_same_node(self):
input_ = [
- (
- 1,
- [
- (1, "Task-A", [[["call1"], 2]]),
- (2, "Task-B", [[["call2"], 3]]),
- (3, "Task-C", [[["call3"], 1], [["call4"], 2]]),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("call1", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-B",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("call2", "", 0)],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-C",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("call3", "", 0)],
+ task_name=1
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("call4", "", 0)],
+ task_name=2
+ )
+ ]
+ )
+ ]
)
]
with self.assertRaises(tools.CycleFoundException) as ctx:
@@ -772,19 +1501,43 @@ class TestAsyncioToolsBasic(unittest.TestCase):
self.assertTrue(any(set(c) == {1, 2, 3} for c in cycles))
def test_table_output_format(self):
- input_ = [(1, [(1, "Task-A", [[["foo"], 2]]), (2, "Task-B", [])])]
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("foo", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-B",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
table = tools.build_task_table(input_)
for row in table:
- self.assertEqual(len(row), 6)
+ self.assertEqual(len(row), 7)
self.assertIsInstance(row[0], int) # thread ID
self.assertTrue(
isinstance(row[1], str) and row[1].startswith("0x")
) # hex task ID
self.assertIsInstance(row[2], str) # task name
- self.assertIsInstance(row[3], str) # coroutine chain
- self.assertIsInstance(row[4], str) # awaiter name
+ self.assertIsInstance(row[3], str) # coroutine stack
+ self.assertIsInstance(row[4], str) # coroutine chain
+ self.assertIsInstance(row[5], str) # awaiter name
self.assertTrue(
- isinstance(row[5], str) and row[5].startswith("0x")
+ isinstance(row[6], str) and row[6].startswith("0x")
) # hex awaiter ID
@@ -792,28 +1545,86 @@ class TestAsyncioToolsEdgeCases(unittest.TestCase):
def test_task_awaits_self(self):
"""A task directly awaits itself - should raise a cycle."""
- input_ = [(1, [(1, "Self-Awaiter", [[["loopback"], 1]])])]
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Self-Awaiter",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("loopback", "", 0)],
+ task_name=1
+ )
+ ]
+ )
+ ]
+ )
+ ]
with self.assertRaises(tools.CycleFoundException) as ctx:
tools.build_async_tree(input_)
self.assertIn([1, 1], ctx.exception.cycles)
def test_task_with_missing_awaiter_id(self):
"""Awaiter ID not in task list - should not crash, just show 'Unknown'."""
- input_ = [(1, [(1, "Task-A", [[["coro"], 999]])])] # 999 not defined
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-A",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("coro", "", 0)],
+ task_name=999
+ )
+ ]
+ )
+ ]
+ )
+ ]
table = tools.build_task_table(input_)
self.assertEqual(len(table), 1)
- self.assertEqual(table[0][4], "Unknown")
+ self.assertEqual(table[0][5], "Unknown")
def test_duplicate_coroutine_frames(self):
"""Same coroutine frame repeated under a parent - should deduplicate."""
input_ = [
- (
- 1,
- [
- (1, "Task-1", [[["frameA"], 2], [["frameA"], 3]]),
- (2, "Task-2", []),
- (3, "Task-3", []),
- ],
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="Task-1",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("frameA", "", 0)],
+ task_name=2
+ ),
+ CoroInfo(
+ call_stack=[FrameInfo("frameA", "", 0)],
+ task_name=3
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="Task-2",
+ coroutine_stack=[],
+ awaited_by=[]
+ ),
+ TaskInfo(
+ task_id=3,
+ task_name="Task-3",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
)
]
tree = tools.build_async_tree(input_)
@@ -830,14 +1641,63 @@ class TestAsyncioToolsEdgeCases(unittest.TestCase):
def test_task_with_no_name(self):
"""Task with no name in id2name - should still render with fallback."""
- input_ = [(1, [(1, "root", [[["f1"], 2]]), (2, None, [])])]
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="root",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[FrameInfo("f1", "", 0)],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name=None,
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
# If name is None, fallback to string should not crash
tree = tools.build_async_tree(input_)
self.assertIn("(T) None", "\n".join(tree[0]))
def test_tree_rendering_with_custom_emojis(self):
"""Pass custom emojis to the tree renderer."""
- input_ = [(1, [(1, "MainTask", [[["f1", "f2"], 2]]), (2, "SubTask", [])])]
+ input_ = [
+ AwaitedInfo(
+ thread_id=1,
+ awaited_by=[
+ TaskInfo(
+ task_id=1,
+ task_name="MainTask",
+ coroutine_stack=[],
+ awaited_by=[
+ CoroInfo(
+ call_stack=[
+ FrameInfo("f1", "", 0),
+ FrameInfo("f2", "", 0)
+ ],
+ task_name=2
+ )
+ ]
+ ),
+ TaskInfo(
+ task_id=2,
+ task_name="SubTask",
+ coroutine_stack=[],
+ awaited_by=[]
+ )
+ ]
+ )
+ ]
tree = tools.build_async_tree(input_, task_emoji="🧵", cor_emoji="🔁")
flat = "\n".join(tree[0])
self.assertIn("🧵 MainTask", flat)
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index 9fc2b047bef..655f5a9be7f 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -701,6 +701,7 @@ class CodeTest(unittest.TestCase):
'checks': CO_FAST_LOCAL,
'res': CO_FAST_LOCAL,
},
+ defs.spam_with_global_and_attr_same_name: {},
defs.spam_full_args: {
'a': POSONLY,
'b': POSONLY,
@@ -955,6 +956,10 @@ class CodeTest(unittest.TestCase):
purelocals=5,
globalvars=6,
),
+ defs.spam_with_global_and_attr_same_name: new_var_counts(
+ globalvars=2,
+ attrs=1,
+ ),
defs.spam_full_args: new_var_counts(
posonly=2,
posorkw=2,
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 8765d121fd0..b8116d073a2 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -2568,6 +2568,18 @@ Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?=
self.assertEqual(str(make_header(decode_header(s))),
'"Müller T" <T.Mueller@xxx.com>')
+ def test_unencoded_ascii(self):
+ # bpo-22833/gh-67022: returns [(str, None)] rather than [(bytes, None)]
+ s = 'header without encoded words'
+ self.assertEqual(decode_header(s),
+ [('header without encoded words', None)])
+
+ def test_unencoded_utf8(self):
+ # bpo-22833/gh-67022: returns [(str, None)] rather than [(bytes, None)]
+ s = 'header with unexpected non ASCII caract\xe8res'
+ self.assertEqual(decode_header(s),
+ [('header with unexpected non ASCII caract\xe8res', None)])
+
# Test the MIMEMessage class
class TestMIMEMessage(TestEmailBase):
diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py
index 303af25fc7a..90214e814f2 100644
--- a/Lib/test/test_external_inspection.py
+++ b/Lib/test/test_external_inspection.py
@@ -5,7 +5,7 @@ import importlib
import sys
import socket
import threading
-from asyncio import staggered, taskgroups
+from asyncio import staggered, taskgroups, base_events, tasks
from unittest.mock import ANY
from test.support import os_helper, SHORT_TIMEOUT, busy_retry
from test.support.script_helper import make_script
@@ -18,8 +18,12 @@ PROCESS_VM_READV_SUPPORTED = False
try:
from _remote_debugging import PROCESS_VM_READV_SUPPORTED
from _remote_debugging import RemoteUnwinder
+ from _remote_debugging import FrameInfo, CoroInfo, TaskInfo
except ImportError:
- raise unittest.SkipTest("Test only runs when _remote_debugging is available")
+ raise unittest.SkipTest(
+ "Test only runs when _remote_debugging is available"
+ )
+
def _make_test_script(script_dir, script_basename, source):
to_return = make_script(script_dir, script_basename, source)
@@ -28,7 +32,11 @@ def _make_test_script(script_dir, script_basename, source):
skip_if_not_supported = unittest.skipIf(
- (sys.platform != "darwin" and sys.platform != "linux" and sys.platform != "win32"),
+ (
+ sys.platform != "darwin"
+ and sys.platform != "linux"
+ and sys.platform != "win32"
+ ),
"Test only runs on Linux, Windows and MacOS",
)
@@ -101,11 +109,16 @@ class TestGetStackTrace(unittest.TestCase):
client_socket, _ = server_socket.accept()
server_socket.close()
response = b""
- while b"ready:main" not in response or b"ready:thread" not in response:
+ while (
+ b"ready:main" not in response
+ or b"ready:thread" not in response
+ ):
response += client_socket.recv(1024)
stack_trace = get_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -114,17 +127,17 @@ class TestGetStackTrace(unittest.TestCase):
p.wait(timeout=SHORT_TIMEOUT)
thread_expected_stack_trace = [
- (script_name, 15, "foo"),
- (script_name, 12, "baz"),
- (script_name, 9, "bar"),
- (threading.__file__, ANY, 'Thread.run')
+ FrameInfo([script_name, 15, "foo"]),
+ FrameInfo([script_name, 12, "baz"]),
+ FrameInfo([script_name, 9, "bar"]),
+ FrameInfo([threading.__file__, ANY, "Thread.run"]),
]
# Is possible that there are more threads, so we check that the
# expected stack traces are in the result (looking at you Windows!)
self.assertIn((ANY, thread_expected_stack_trace), stack_trace)
# Check that the main thread stack trace is in the result
- frame = (script_name, 19, "<module>")
+ frame = FrameInfo([script_name, 19, "<module>"])
for _, stack in stack_trace:
if frame in stack:
break
@@ -189,8 +202,12 @@ class TestGetStackTrace(unittest.TestCase):
):
script_dir = os.path.join(work_dir, "script_pkg")
os.mkdir(script_dir)
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ server_socket = socket.socket(
+ socket.AF_INET, socket.SOCK_STREAM
+ )
+ server_socket.setsockopt(
+ socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
+ )
server_socket.bind(("localhost", port))
server_socket.settimeout(SHORT_TIMEOUT)
server_socket.listen(1)
@@ -208,7 +225,9 @@ class TestGetStackTrace(unittest.TestCase):
self.assertEqual(response, b"ready")
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -219,79 +238,49 @@ class TestGetStackTrace(unittest.TestCase):
# sets are unordered, so we want to sort "awaited_by"s
stack_trace[2].sort(key=lambda x: x[1])
- root_task = "Task-1"
expected_stack_trace = [
[
- (script_name, 10, "c5"),
- (script_name, 14, "c4"),
- (script_name, 17, "c3"),
- (script_name, 20, "c2"),
+ FrameInfo([script_name, 10, "c5"]),
+ FrameInfo([script_name, 14, "c4"]),
+ FrameInfo([script_name, 17, "c3"]),
+ FrameInfo([script_name, 20, "c2"]),
],
"c2_root",
[
- [
- [
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup._aexit"
- ),
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup.__aexit__"
- ),
- (script_name, 26, "main"),
- ],
- "Task-1",
- [],
- ],
- [
- [(script_name, 23, "c1")],
- "sub_main_1",
+ CoroInfo(
[
[
- [
- (
+ FrameInfo(
+ [
taskgroups.__file__,
ANY,
- "TaskGroup._aexit"
- ),
- (
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
taskgroups.__file__,
ANY,
- "TaskGroup.__aexit__"
- ),
- (script_name, 26, "main"),
- ],
- "Task-1",
- [],
- ]
- ],
- ],
- [
- [(script_name, 23, "c1")],
- "sub_main_2",
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo([script_name, 26, "main"]),
+ ],
+ "Task-1",
+ ]
+ ),
+ CoroInfo(
[
- [
- [
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup._aexit"
- ),
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup.__aexit__"
- ),
- (script_name, 26, "main"),
- ],
- "Task-1",
- [],
- ]
- ],
- ],
+ [FrameInfo([script_name, 23, "c1"])],
+ "sub_main_1",
+ ]
+ ),
+ CoroInfo(
+ [
+ [FrameInfo([script_name, 23, "c1"])],
+ "sub_main_2",
+ ]
+ ),
],
]
self.assertEqual(stack_trace, expected_stack_trace)
@@ -350,7 +339,9 @@ class TestGetStackTrace(unittest.TestCase):
self.assertEqual(response, b"ready")
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -363,9 +354,9 @@ class TestGetStackTrace(unittest.TestCase):
expected_stack_trace = [
[
- (script_name, 10, "gen_nested_call"),
- (script_name, 16, "gen"),
- (script_name, 19, "main"),
+ FrameInfo([script_name, 10, "gen_nested_call"]),
+ FrameInfo([script_name, 16, "gen"]),
+ FrameInfo([script_name, 19, "main"]),
],
"Task-1",
[],
@@ -427,7 +418,9 @@ class TestGetStackTrace(unittest.TestCase):
self.assertEqual(response, b"ready")
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -439,9 +432,12 @@ class TestGetStackTrace(unittest.TestCase):
stack_trace[2].sort(key=lambda x: x[1])
expected_stack_trace = [
- [(script_name, 11, "deep"), (script_name, 15, "c1")],
+ [
+ FrameInfo([script_name, 11, "deep"]),
+ FrameInfo([script_name, 15, "c1"]),
+ ],
"Task-2",
- [[[(script_name, 21, "main")], "Task-1", []]],
+ [CoroInfo([[FrameInfo([script_name, 21, "main"])], "Task-1"])],
]
self.assertEqual(stack_trace, expected_stack_trace)
@@ -503,7 +499,9 @@ class TestGetStackTrace(unittest.TestCase):
self.assertEqual(response, b"ready")
stack_trace = get_async_stack_trace(p.pid)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -515,20 +513,29 @@ class TestGetStackTrace(unittest.TestCase):
stack_trace[2].sort(key=lambda x: x[1])
expected_stack_trace = [
[
- (script_name, 11, "deep"),
- (script_name, 15, "c1"),
- (staggered.__file__, ANY, "staggered_race.<locals>.run_one_coro"),
+ FrameInfo([script_name, 11, "deep"]),
+ FrameInfo([script_name, 15, "c1"]),
+ FrameInfo(
+ [
+ staggered.__file__,
+ ANY,
+ "staggered_race.<locals>.run_one_coro",
+ ]
+ ),
],
"Task-2",
[
- [
+ CoroInfo(
[
- (staggered.__file__, ANY, "staggered_race"),
- (script_name, 21, "main"),
- ],
- "Task-1",
- [],
- ]
+ [
+ FrameInfo(
+ [staggered.__file__, ANY, "staggered_race"]
+ ),
+ FrameInfo([script_name, 21, "main"]),
+ ],
+ "Task-1",
+ ]
+ )
],
]
self.assertEqual(stack_trace, expected_stack_trace)
@@ -659,62 +666,174 @@ class TestGetStackTrace(unittest.TestCase):
# expected: at least 1000 pending tasks
self.assertGreaterEqual(len(entries), 1000)
# the first three tasks stem from the code structure
- self.assertIn((ANY, "Task-1", []), entries)
main_stack = [
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup._aexit",
+ FrameInfo([taskgroups.__file__, ANY, "TaskGroup._aexit"]),
+ FrameInfo(
+ [taskgroups.__file__, ANY, "TaskGroup.__aexit__"]
),
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup.__aexit__",
- ),
- (script_name, 60, "main"),
+ FrameInfo([script_name, 60, "main"]),
]
self.assertIn(
- (ANY, "server task", [[main_stack, ANY]]),
+ TaskInfo(
+ [ANY, "Task-1", [CoroInfo([main_stack, ANY])], []]
+ ),
entries,
)
self.assertIn(
- (ANY, "echo client spam", [[main_stack, ANY]]),
+ TaskInfo(
+ [
+ ANY,
+ "server task",
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ base_events.__file__,
+ ANY,
+ "Server.serve_forever",
+ ]
+ )
+ ],
+ ANY,
+ ]
+ )
+ ],
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo(
+ [script_name, ANY, "main"]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ],
+ ]
+ ),
+ entries,
+ )
+ self.assertIn(
+ TaskInfo(
+ [
+ ANY,
+ "Task-4",
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [tasks.__file__, ANY, "sleep"]
+ ),
+ FrameInfo(
+ [
+ script_name,
+ 38,
+ "echo_client",
+ ]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ],
+ [
+ CoroInfo(
+ [
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo(
+ [
+ script_name,
+ 41,
+ "echo_client_spam",
+ ]
+ ),
+ ],
+ ANY,
+ ]
+ )
+ ],
+ ]
+ ),
entries,
)
- expected_stack = [
- [
+ expected_awaited_by = [
+ CoroInfo(
[
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup._aexit",
- ),
- (
- taskgroups.__file__,
- ANY,
- "TaskGroup.__aexit__",
- ),
- (script_name, 41, "echo_client_spam"),
- ],
- ANY,
- ]
+ [
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup._aexit",
+ ]
+ ),
+ FrameInfo(
+ [
+ taskgroups.__file__,
+ ANY,
+ "TaskGroup.__aexit__",
+ ]
+ ),
+ FrameInfo(
+ [script_name, 41, "echo_client_spam"]
+ ),
+ ],
+ ANY,
+ ]
+ )
]
- tasks_with_stack = [
- task for task in entries if task[2] == expected_stack
+ tasks_with_awaited = [
+ task
+ for task in entries
+ if task.awaited_by == expected_awaited_by
]
- self.assertGreaterEqual(len(tasks_with_stack), 1000)
+ self.assertGreaterEqual(len(tasks_with_awaited), 1000)
# the final task will have some random number, but it should for
# sure be one of the echo client spam horde (In windows this is not true
# for some reason)
if sys.platform != "win32":
self.assertEqual(
- expected_stack,
- entries[-1][2],
+ tasks_with_awaited[-1].awaited_by,
+ entries[-1].awaited_by,
)
except PermissionError:
- self.skipTest("Insufficient permissions to read the stack trace")
+ self.skipTest(
+ "Insufficient permissions to read the stack trace"
+ )
finally:
if client_socket is not None:
client_socket.close()
@@ -740,17 +859,21 @@ class TestGetStackTrace(unittest.TestCase):
self.assertEqual(
stack[:2],
[
- (
- __file__,
- get_stack_trace.__code__.co_firstlineno + 2,
- "get_stack_trace",
+ FrameInfo(
+ [
+ __file__,
+ get_stack_trace.__code__.co_firstlineno + 2,
+ "get_stack_trace",
+ ]
),
- (
- __file__,
- self.test_self_trace.__code__.co_firstlineno + 6,
- "TestGetStackTrace.test_self_trace",
+ FrameInfo(
+ [
+ __file__,
+ self.test_self_trace.__code__.co_firstlineno + 6,
+ "TestGetStackTrace.test_self_trace",
+ ]
),
- ]
+ ],
)
diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py
index dd58e032a8b..f5455705678 100644
--- a/Lib/test/test_fstring.py
+++ b/Lib/test/test_fstring.py
@@ -1380,7 +1380,7 @@ x = (
for conv in ' s', ' s ':
self.assertAllRaise(SyntaxError,
"f-string: conversion type must come right after the"
- " exclamanation mark",
+ " exclamation mark",
["f'{3!" + conv + "}'"])
self.assertAllRaise(SyntaxError,
diff --git a/Lib/test/test_htmlparser.py b/Lib/test/test_htmlparser.py
index 61fa24fab57..65a4bee72b9 100644
--- a/Lib/test/test_htmlparser.py
+++ b/Lib/test/test_htmlparser.py
@@ -5,6 +5,7 @@ import pprint
import unittest
from unittest.mock import patch
+from test import support
class EventCollector(html.parser.HTMLParser):
@@ -430,28 +431,34 @@ text
('data', '<'),
('starttag', 'bc<', [('a', None)]),
('endtag', 'html'),
- ('data', '\n<img src="URL>'),
- ('comment', '/img'),
- ('endtag', 'html<')])
+ ('data', '\n')])
def test_starttag_junk_chars(self):
+ self._run_check("<", [('data', '<')])
+ self._run_check("<>", [('data', '<>')])
+ self._run_check("< >", [('data', '< >')])
+ self._run_check("< ", [('data', '< ')])
self._run_check("</>", [])
+ self._run_check("<$>", [('data', '<$>')])
self._run_check("</$>", [('comment', '$')])
self._run_check("</", [('data', '</')])
- self._run_check("</a", [('data', '</a')])
+ self._run_check("</a", [])
+ self._run_check("</ a>", [('endtag', 'a')])
+ self._run_check("</ a", [('comment', ' a')])
self._run_check("<a<a>", [('starttag', 'a<a', [])])
self._run_check("</a<a>", [('endtag', 'a<a')])
- self._run_check("<!", [('data', '<!')])
- self._run_check("<a", [('data', '<a')])
- self._run_check("<a foo='bar'", [('data', "<a foo='bar'")])
- self._run_check("<a foo='bar", [('data', "<a foo='bar")])
- self._run_check("<a foo='>'", [('data', "<a foo='>'")])
- self._run_check("<a foo='>", [('data', "<a foo='>")])
+ self._run_check("<!", [('comment', '')])
+ self._run_check("<a", [])
+ self._run_check("<a foo='bar'", [])
+ self._run_check("<a foo='bar", [])
+ self._run_check("<a foo='>'", [])
+ self._run_check("<a foo='>", [])
self._run_check("<a$>", [('starttag', 'a$', [])])
self._run_check("<a$b>", [('starttag', 'a$b', [])])
self._run_check("<a$b/>", [('startendtag', 'a$b', [])])
self._run_check("<a$b >", [('starttag', 'a$b', [])])
self._run_check("<a$b />", [('startendtag', 'a$b', [])])
+ self._run_check("</a$b>", [('endtag', 'a$b')])
def test_slashes_in_starttag(self):
self._run_check('<a foo="var"/>', [('startendtag', 'a', [('foo', 'var')])])
@@ -576,21 +583,50 @@ text
for html, expected in data:
self._run_check(html, expected)
- def test_EOF_in_comments_or_decls(self):
+ def test_eof_in_comments(self):
data = [
- ('<!', [('data', '<!')]),
- ('<!-', [('data', '<!-')]),
- ('<!--', [('data', '<!--')]),
- ('<![', [('data', '<![')]),
- ('<![CDATA[', [('data', '<![CDATA[')]),
- ('<![CDATA[x', [('data', '<![CDATA[x')]),
- ('<!DOCTYPE', [('data', '<!DOCTYPE')]),
- ('<!DOCTYPE HTML', [('data', '<!DOCTYPE HTML')]),
+ ('<!--', [('comment', '')]),
+ ('<!---', [('comment', '')]),
+ ('<!----', [('comment', '')]),
+ ('<!-----', [('comment', '-')]),
+ ('<!------', [('comment', '--')]),
+ ('<!----!', [('comment', '')]),
+ ('<!---!', [('comment', '-!')]),
+ ('<!---!>', [('comment', '-!>')]),
+ ('<!--foo', [('comment', 'foo')]),
+ ('<!--foo-', [('comment', 'foo')]),
+ ('<!--foo--', [('comment', 'foo')]),
+ ('<!--foo--!', [('comment', 'foo')]),
+ ('<!--<!--', [('comment', '<!')]),
+ ('<!--<!--!', [('comment', '<!')]),
]
for html, expected in data:
self._run_check(html, expected)
+
+ def test_eof_in_declarations(self):
+ data = [
+ ('<!', [('comment', '')]),
+ ('<!-', [('comment', '-')]),
+ ('<![', [('comment', '[')]),
+ ('<![CDATA[', [('unknown decl', 'CDATA[')]),
+ ('<![CDATA[x', [('unknown decl', 'CDATA[x')]),
+ ('<![CDATA[x]', [('unknown decl', 'CDATA[x]')]),
+ ('<![CDATA[x]]', [('unknown decl', 'CDATA[x]]')]),
+ ('<!DOCTYPE', [('decl', 'DOCTYPE')]),
+ ('<!DOCTYPE ', [('decl', 'DOCTYPE ')]),
+ ('<!DOCTYPE html', [('decl', 'DOCTYPE html')]),
+ ('<!DOCTYPE html ', [('decl', 'DOCTYPE html ')]),
+ ('<!DOCTYPE html PUBLIC', [('decl', 'DOCTYPE html PUBLIC')]),
+ ('<!DOCTYPE html PUBLIC "foo', [('decl', 'DOCTYPE html PUBLIC "foo')]),
+ ('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo',
+ [('decl', 'DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "foo')]),
+ ]
+ for html, expected in data:
+ self._run_check(html, expected)
+
def test_bogus_comments(self):
- html = ('<! not really a comment >'
+ html = ('<!ELEMENT br EMPTY>'
+ '<! not really a comment >'
'<! not a comment either -->'
'<! -- close enough -->'
'<!><!<-- this was an empty comment>'
@@ -604,6 +640,7 @@ text
'<![CDATA]]>' # required '[' after CDATA
)
expected = [
+ ('comment', 'ELEMENT br EMPTY'),
('comment', ' not really a comment '),
('comment', ' not a comment either --'),
('comment', ' -- close enough --'),
@@ -684,6 +721,26 @@ text
('endtag', 'a'), ('data', ' bar & baz')]
)
+ @support.requires_resource('cpu')
+ def test_eof_no_quadratic_complexity(self):
+ # Each of these examples used to take about an hour.
+ # Now they take a fraction of a second.
+ def check(source):
+ parser = html.parser.HTMLParser()
+ parser.feed(source)
+ parser.close()
+ n = 120_000
+ check("<a " * n)
+ check("<a a=" * n)
+ check("</a " * 14 * n)
+ check("</a a=" * 11 * n)
+ check("<!--" * 4 * n)
+ check("<!" * 60 * n)
+ check("<?" * 19 * n)
+ check("</$" * 15 * n)
+ check("<![CDATA[" * 9 * n)
+ check("<!doctype" * 35 * n)
+
class AttributesTestCase(TestCaseBase):
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index fa5b1e43816..e672dfcbb46 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -1036,7 +1036,7 @@ class TestTCPServer(ControlMixin, ThreadingTCPServer):
"""
allow_reuse_address = True
- allow_reuse_port = True
+ allow_reuse_port = False
def __init__(self, addr, handler, poll_interval=0.5,
bind_and_activate=True):
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index 249e0f3ba32..63998a86c45 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -5,7 +5,6 @@ BytesIO -- for bytes
import unittest
from test import support
-from test.support import threading_helper
import gc
import io
@@ -13,7 +12,6 @@ import _pyio as pyio
import pickle
import sys
import weakref
-import threading
class IntLike:
def __init__(self, num):
@@ -725,22 +723,6 @@ class TextIOTestMixin:
for newline in (None, "", "\n", "\r", "\r\n"):
self.ioclass(newline=newline)
- @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful under free-threading")
- @threading_helper.requires_working_threading()
- def test_concurrent_use(self):
- memio = self.ioclass("")
-
- def use():
- memio.write("x" * 10)
- memio.readlines()
-
- threads = [threading.Thread(target=use) for _ in range(8)]
- with threading_helper.catch_threading_exception() as cm:
- with threading_helper.start_threads(threads):
- pass
-
- self.assertIsNone(cm.exc_value)
-
class PyStringIOTest(MemoryTestMixin, MemorySeekTestMixin,
TextIOTestMixin, unittest.TestCase):
@@ -908,7 +890,6 @@ class CStringIOTest(PyStringIOTest):
self.assertRaises(ValueError, memio.__setstate__, ("closed", "", 0, None))
-
class CStringIOPickleTest(PyStringIOPickleTest):
UnsupportedOperation = io.UnsupportedOperation
diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py
index d1e48bde982..0e82c723ec3 100644
--- a/Lib/test/test_unittest/testmock/testhelpers.py
+++ b/Lib/test/test_unittest/testmock/testhelpers.py
@@ -1050,6 +1050,7 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithPostInit()),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithPostInit)
self.assertIsInstance(mock.a, int)
self.assertIsInstance(mock.b, int)
@@ -1072,6 +1073,7 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithDefault(1)),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithDefault)
self.assertIsInstance(mock.a, int)
self.assertIsInstance(mock.b, int)
@@ -1087,6 +1089,7 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithMethod(1)),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithMethod)
self.assertIsInstance(mock.a, int)
mock.b.assert_not_called()
@@ -1102,11 +1105,29 @@ class SpecSignatureTest(unittest.TestCase):
create_autospec(WithNonFields(1)),
]:
with self.subTest(mock=mock):
+ self.assertIsInstance(mock, WithNonFields)
with self.assertRaisesRegex(AttributeError, msg):
mock.a
with self.assertRaisesRegex(AttributeError, msg):
mock.b
+ def test_dataclass_special_attrs(self):
+ @dataclass
+ class Description:
+ name: str
+
+ for mock in [
+ create_autospec(Description, instance=True),
+ create_autospec(Description(1)),
+ ]:
+ with self.subTest(mock=mock):
+ self.assertIsInstance(mock, Description)
+ self.assertIs(mock.__class__, Description)
+ self.assertIsInstance(mock.__dataclass_fields__, MagicMock)
+ self.assertIsInstance(mock.__dataclass_params__, MagicMock)
+ self.assertIsInstance(mock.__match_args__, MagicMock)
+ self.assertIsInstance(mock.__hash__, MagicMock)
+
class TestCallList(unittest.TestCase):
def test_args_list_contains_call_list(self):
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 55cb4b1f6af..e370aa48b7c 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -569,6 +569,11 @@ class NonCallableMock(Base):
__dict__['_mock_methods'] = spec
__dict__['_spec_asyncs'] = _spec_asyncs
+ def _mock_extend_spec_methods(self, spec_methods):
+ methods = self.__dict__.get('_mock_methods') or []
+ methods.extend(spec_methods)
+ self.__dict__['_mock_methods'] = methods
+
def __get_return_value(self):
ret = self._mock_return_value
if self._mock_delegate is not None:
@@ -2766,14 +2771,16 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
raise InvalidSpecError(f'Cannot autospec a Mock object. '
f'[object={spec!r}]')
is_async_func = _is_async_func(spec)
+ _kwargs = {'spec': spec}
entries = [(entry, _missing) for entry in dir(spec)]
if is_type and instance and is_dataclass(spec):
+ is_dataclass_spec = True
dataclass_fields = fields(spec)
entries.extend((f.name, f.type) for f in dataclass_fields)
- _kwargs = {'spec': [f.name for f in dataclass_fields]}
+ dataclass_spec_list = [f.name for f in dataclass_fields]
else:
- _kwargs = {'spec': spec}
+ is_dataclass_spec = False
if spec_set:
_kwargs = {'spec_set': spec}
@@ -2810,6 +2817,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name,
name=_name, **_kwargs)
+ if is_dataclass_spec:
+ mock._mock_extend_spec_methods(dataclass_spec_list)
if isinstance(spec, FunctionTypes):
# should only happen at the top level because we don't
diff --git a/Lib/xmlrpc/server.py b/Lib/xmlrpc/server.py
index 90a356fbb8e..8130c739af2 100644
--- a/Lib/xmlrpc/server.py
+++ b/Lib/xmlrpc/server.py
@@ -578,7 +578,7 @@ class SimpleXMLRPCServer(socketserver.TCPServer,
"""
allow_reuse_address = True
- allow_reuse_port = True
+ allow_reuse_port = False
# Warning: this is for debugging purposes only! Never set this to True in
# production code, as will be sending out sensitive information (exception