summaryrefslogtreecommitdiffstats
path: root/Lib/test
diff options
context:
space:
mode:
authorPablo Galindo Salgado <Pablogsal@gmail.com>2025-05-04 00:51:57 (GMT)
committerGitHub <noreply@github.com>2025-05-04 00:51:57 (GMT)
commit2bc836523105a2197a1f987cc03911bece74b35e (patch)
tree762f45a2793d0cf56aabd402c63883a1906eb561 /Lib/test
parent7363e8d24d14abf651633865ea959702ebac73d3 (diff)
downloadcpython-2bc836523105a2197a1f987cc03911bece74b35e.zip
cpython-2bc836523105a2197a1f987cc03911bece74b35e.tar.gz
cpython-2bc836523105a2197a1f987cc03911bece74b35e.tar.bz2
GH-91048: Add utils for printing the call stack for asyncio tasks (#133284)
Diffstat (limited to 'Lib/test')
-rw-r--r--Lib/test/test_asyncio/test_tools.py839
-rw-r--r--Lib/test/test_external_inspection.py45
-rw-r--r--Lib/test/test_sys.py2
3 files changed, 864 insertions, 22 deletions
diff --git a/Lib/test/test_asyncio/test_tools.py b/Lib/test/test_asyncio/test_tools.py
new file mode 100644
index 0000000..2caf561
--- /dev/null
+++ b/Lib/test/test_asyncio/test_tools.py
@@ -0,0 +1,839 @@
+import unittest
+
+from asyncio import tools
+
+
+# mock output of get_all_awaited_by function.
+TEST_INPUTS_TREE = [
+ [
+ # test case containing a task called timer being awaited in two
+ # 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,
+ ]
+ ],
+ ),
+ ],
+ ),
+ (0, []),
+ ),
+ (
+ [
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " └── __aexit__",
+ " └── _aexit",
+ " ├── (T) root1",
+ " │ └── bloch",
+ " │ └── blocho_caller",
+ " │ └── __aexit__",
+ " │ └── _aexit",
+ " │ ├── (T) child1_1",
+ " │ │ └── awaiter",
+ " │ │ └── awaiter2",
+ " │ │ └── awaiter3",
+ " │ │ └── (T) timer",
+ " │ └── (T) child2_1",
+ " │ └── awaiter1",
+ " │ └── awaiter1_2",
+ " │ └── awaiter1_3",
+ " │ └── (T) timer",
+ " └── (T) root2",
+ " └── bloch",
+ " └── blocho_caller",
+ " └── __aexit__",
+ " └── _aexit",
+ " ├── (T) child1_2",
+ " │ └── awaiter",
+ " │ └── awaiter2",
+ " │ └── awaiter3",
+ " │ └── (T) timer",
+ " └── (T) child2_2",
+ " └── awaiter1",
+ " └── awaiter1_2",
+ " └── awaiter1_3",
+ " └── (T) timer",
+ ]
+ ]
+ ),
+ ],
+ [
+ # test case containing two roots
+ (
+ (
+ 9,
+ [
+ (5, "Task-5", []),
+ (6, "Task-6", [[["main2"], 5]]),
+ (7, "Task-7", [[["main2"], 5]]),
+ (8, "Task-8", [[["main2"], 5]]),
+ ],
+ ),
+ (
+ 10,
+ [
+ (1, "Task-1", []),
+ (2, "Task-2", [[["main"], 1]]),
+ (3, "Task-3", [[["main"], 1]]),
+ (4, "Task-4", [[["main"], 1]]),
+ ],
+ ),
+ (11, []),
+ (0, []),
+ ),
+ (
+ [
+ [
+ "└── (T) Task-5",
+ " └── main2",
+ " ├── (T) Task-6",
+ " ├── (T) Task-7",
+ " └── (T) Task-8",
+ ],
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " ├── (T) Task-2",
+ " ├── (T) Task-3",
+ " └── (T) Task-4",
+ ],
+ ]
+ ),
+ ],
+ [
+ # 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]]),
+ ],
+ ),
+ (8, []),
+ (0, []),
+ ]
+ ),
+ (
+ [
+ ["└── (T) Task-5"],
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " ├── (T) Task-2",
+ " ├── (T) Task-3",
+ " └── (T) Task-4",
+ ],
+ ]
+ ),
+ ],
+]
+
+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]],
+ ),
+ (4, "b", [[["awaiter"], 3]]),
+ ],
+ ),
+ (0, []),
+ ]
+ ),
+ ([[4, 3, 4]]),
+ ],
+ [
+ # this test case contains two cycles
+ (
+ [
+ (
+ 1,
+ [
+ (2, "Task-1", []),
+ (
+ 3,
+ "A",
+ [[["nested", "nested", "task_b"], 4]],
+ ),
+ (
+ 4,
+ "B",
+ [
+ [["nested", "nested", "task_c"], 5],
+ [["nested", "nested", "task_a"], 3],
+ ],
+ ),
+ (5, "C", [[["nested", "nested"], 6]]),
+ (
+ 6,
+ "Task-2",
+ [[["nested", "nested", "task_b"], 4]],
+ ),
+ ],
+ ),
+ (0, []),
+ ]
+ ),
+ ([[4, 3, 4], [4, 6, 5, 4]]),
+ ],
+]
+
+TEST_INPUTS_TABLE = [
+ [
+ # test case containing a task called timer being awaited in two
+ # 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,
+ ]
+ ],
+ ),
+ ],
+ ),
+ (0, []),
+ ),
+ (
+ [
+ [1, "0x2", "Task-1", "", "", "0x0"],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "awaiter3 -> awaiter2 -> awaiter",
+ "child1_1",
+ "0x4",
+ ],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "awaiter1_3 -> awaiter1_2 -> awaiter1",
+ "child2_2",
+ "0x5",
+ ],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "awaiter1_3 -> awaiter1_2 -> awaiter1",
+ "child2_1",
+ "0x6",
+ ],
+ [
+ 1,
+ "0x3",
+ "timer",
+ "awaiter3 -> awaiter2 -> awaiter",
+ "child1_2",
+ "0x7",
+ ],
+ [
+ 1,
+ "0x8",
+ "root1",
+ "_aexit -> __aexit__ -> main",
+ "Task-1",
+ "0x2",
+ ],
+ [
+ 1,
+ "0x9",
+ "root2",
+ "_aexit -> __aexit__ -> main",
+ "Task-1",
+ "0x2",
+ ],
+ [
+ 1,
+ "0x4",
+ "child1_1",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root1",
+ "0x8",
+ ],
+ [
+ 1,
+ "0x6",
+ "child2_1",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root1",
+ "0x8",
+ ],
+ [
+ 1,
+ "0x7",
+ "child1_2",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root2",
+ "0x9",
+ ],
+ [
+ 1,
+ "0x5",
+ "child2_2",
+ "_aexit -> __aexit__ -> blocho_caller -> bloch",
+ "root2",
+ "0x9",
+ ],
+ ]
+ ),
+ ],
+ [
+ # test case containing two roots
+ (
+ (
+ 9,
+ [
+ (5, "Task-5", []),
+ (6, "Task-6", [[["main2"], 5]]),
+ (7, "Task-7", [[["main2"], 5]]),
+ (8, "Task-8", [[["main2"], 5]]),
+ ],
+ ),
+ (
+ 10,
+ [
+ (1, "Task-1", []),
+ (2, "Task-2", [[["main"], 1]]),
+ (3, "Task-3", [[["main"], 1]]),
+ (4, "Task-4", [[["main"], 1]]),
+ ],
+ ),
+ (11, []),
+ (0, []),
+ ),
+ (
+ [
+ [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"],
+ ]
+ ),
+ ],
+ [
+ # 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]]),
+ ],
+ ),
+ (8, []),
+ (0, []),
+ ]
+ ),
+ (
+ [
+ [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"],
+ ]
+ ),
+ ],
+ # CASES WITH CYCLES
+ [
+ # this test case contains a cycle: two tasks awaiting each other.
+ (
+ [
+ (
+ 1,
+ [
+ (2, "Task-1", []),
+ (
+ 3,
+ "a",
+ [[["awaiter2"], 4], [["main"], 2]],
+ ),
+ (4, "b", [[["awaiter"], 3]]),
+ ],
+ ),
+ (0, []),
+ ]
+ ),
+ (
+ [
+ [1, "0x2", "Task-1", "", "", "0x0"],
+ [1, "0x3", "a", "awaiter2", "b", "0x4"],
+ [1, "0x3", "a", "main", "Task-1", "0x2"],
+ [1, "0x4", "b", "awaiter", "a", "0x3"],
+ ]
+ ),
+ ],
+ [
+ # this test case contains two cycles
+ (
+ [
+ (
+ 1,
+ [
+ (2, "Task-1", []),
+ (
+ 3,
+ "A",
+ [[["nested", "nested", "task_b"], 4]],
+ ),
+ (
+ 4,
+ "B",
+ [
+ [["nested", "nested", "task_c"], 5],
+ [["nested", "nested", "task_a"], 3],
+ ],
+ ),
+ (5, "C", [[["nested", "nested"], 6]]),
+ (
+ 6,
+ "Task-2",
+ [[["nested", "nested", "task_b"], 4]],
+ ),
+ ],
+ ),
+ (0, []),
+ ]
+ ),
+ (
+ [
+ [1, "0x2", "Task-1", "", "", "0x0"],
+ [
+ 1,
+ "0x3",
+ "A",
+ "nested -> nested -> task_b",
+ "B",
+ "0x4",
+ ],
+ [
+ 1,
+ "0x4",
+ "B",
+ "nested -> nested -> task_c",
+ "C",
+ "0x5",
+ ],
+ [
+ 1,
+ "0x4",
+ "B",
+ "nested -> nested -> task_a",
+ "A",
+ "0x3",
+ ],
+ [
+ 1,
+ "0x5",
+ "C",
+ "nested -> nested",
+ "Task-2",
+ "0x6",
+ ],
+ [
+ 1,
+ "0x6",
+ "Task-2",
+ "nested -> nested -> task_b",
+ "B",
+ "0x4",
+ ],
+ ]
+ ),
+ ],
+]
+
+
+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)
+
+ def test_asyncio_utils_cycles(self):
+ for input_, cycles in TEST_INPUTS_CYCLES_TREE:
+ with self.subTest(input_):
+ try:
+ tools.build_async_tree(input_)
+ except tools.CycleFoundException as e:
+ self.assertEqual(e.cycles, cycles)
+
+
+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)
+
+
+class TestAsyncioToolsBasic(unittest.TestCase):
+ def test_empty_input_tree(self):
+ """Test build_async_tree with empty input."""
+ result = []
+ expected_output = []
+ self.assertEqual(tools.build_async_tree(result), expected_output)
+
+ def test_empty_input_table(self):
+ """Test build_task_table with empty input."""
+ result = []
+ expected_output = []
+ self.assertEqual(tools.build_task_table(result), expected_output)
+
+ def test_only_independent_tasks_tree(self):
+ input_ = [(1, [(10, "taskA", []), (11, "taskB", [])])]
+ 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", [])])]
+ self.assertEqual(
+ tools.build_task_table(input_),
+ [[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", []),
+ ],
+ )
+ ]
+ expected_output = [
+ [
+ "└── (T) Task-1",
+ ]
+ ]
+ self.assertEqual(tools.build_async_tree(result), expected_output)
+
+ def test_single_task_table(self):
+ """Test build_task_table with a single task and no awaits."""
+ result = [
+ (
+ 1,
+ [
+ (2, "Task-1", []),
+ ],
+ )
+ ]
+ 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]]),
+ ],
+ )
+ ]
+ with self.assertRaises(tools.CycleFoundException) as context:
+ tools.build_async_tree(result)
+ self.assertEqual(context.exception.cycles, [[3, 2, 3]])
+
+ 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]]),
+ ],
+ )
+ ]
+ expected_output = [
+ [
+ "└── (T) Task-1",
+ " └── main",
+ " └── (T) Task-2",
+ " └── main",
+ " └── (T) Task-3",
+ ]
+ ]
+ self.assertEqual(tools.build_async_tree(result), expected_output)
+
+ 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]]),
+ ],
+ )
+ ]
+ expected_output = [
+ [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", []),
+ ],
+ )
+ ]
+ expected = [
+ [
+ "└── (T) root",
+ " └── c5",
+ " └── c4",
+ " └── c3",
+ " └── c2",
+ " └── c1",
+ " └── (T) leaf",
+ ]
+ ]
+ result = tools.build_async_tree(input_)
+ self.assertEqual(result, expected)
+
+ 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]]),
+ ],
+ )
+ ]
+ with self.assertRaises(tools.CycleFoundException) as ctx:
+ tools.build_async_tree(input_)
+ cycles = ctx.exception.cycles
+ 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", [])])]
+ table = tools.build_task_table(input_)
+ for row in table:
+ self.assertEqual(len(row), 6)
+ 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.assertTrue(
+ isinstance(row[5], str) and row[5].startswith("0x")
+ ) # hex awaiter ID
+
+
+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]])])]
+ 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
+ table = tools.build_task_table(input_)
+ self.assertEqual(len(table), 1)
+ self.assertEqual(table[0][4], "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", []),
+ ],
+ )
+ ]
+ tree = tools.build_async_tree(input_)
+ # Both children should be under the same coroutine node
+ flat = "\n".join(tree[0])
+ self.assertIn("frameA", flat)
+ self.assertIn("Task-2", flat)
+ self.assertIn("Task-1", flat)
+
+ flat = "\n".join(tree[1])
+ self.assertIn("frameA", flat)
+ self.assertIn("Task-3", flat)
+ self.assertIn("Task-1", flat)
+
+ 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, [])])]
+ # 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", [])])]
+ tree = tools.build_async_tree(input_, task_emoji="🧵", cor_emoji="🔁")
+ flat = "\n".join(tree[0])
+ self.assertIn("🧵 MainTask", flat)
+ self.assertIn("🔁 f1", flat)
+ self.assertIn("🔁 f2", flat)
+ self.assertIn("🧵 SubTask", flat)
diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py
index aa05db9..4e82f56 100644
--- a/Lib/test/test_external_inspection.py
+++ b/Lib/test/test_external_inspection.py
@@ -4,7 +4,8 @@ import textwrap
import importlib
import sys
import socket
-from test.support import os_helper, SHORT_TIMEOUT, busy_retry
+from unittest.mock import ANY
+from test.support import os_helper, SHORT_TIMEOUT, busy_retry, requires_gil_enabled
from test.support.script_helper import make_script
from test.support.socket_helper import find_unused_port
@@ -13,13 +14,13 @@ import subprocess
PROCESS_VM_READV_SUPPORTED = False
try:
- from _testexternalinspection import PROCESS_VM_READV_SUPPORTED
- from _testexternalinspection import get_stack_trace
- from _testexternalinspection import get_async_stack_trace
- from _testexternalinspection import get_all_awaited_by
+ from _remotedebugging import PROCESS_VM_READV_SUPPORTED
+ from _remotedebugging import get_stack_trace
+ from _remotedebugging import get_async_stack_trace
+ from _remotedebugging import get_all_awaited_by
except ImportError:
raise unittest.SkipTest(
- "Test only runs when _testexternalinspection is available")
+ "Test only runs when _remotedebuggingmodule is available")
def _make_test_script(script_dir, script_basename, source):
to_return = make_script(script_dir, script_basename, source)
@@ -184,13 +185,13 @@ class TestGetStackTrace(unittest.TestCase):
root_task = "Task-1"
expected_stack_trace = [
- ["c5", "c4", "c3", "c2"],
- "c2_root",
+ ['c5', 'c4', 'c3', 'c2'],
+ 'c2_root',
[
- [["main"], root_task, []],
- [["c1"], "sub_main_1", [[["main"], root_task, []]]],
- [["c1"], "sub_main_2", [[["main"], root_task, []]]],
- ],
+ [['_aexit', '__aexit__', 'main'], root_task, []],
+ [['c1'], 'sub_main_1', [[['_aexit', '__aexit__', 'main'], root_task, []]]],
+ [['c1'], 'sub_main_2', [[['_aexit', '__aexit__', 'main'], root_task, []]]],
+ ]
]
self.assertEqual(stack_trace, expected_stack_trace)
@@ -397,12 +398,15 @@ class TestGetStackTrace(unittest.TestCase):
# sets are unordered, so we want to sort "awaited_by"s
stack_trace[2].sort(key=lambda x: x[1])
- expected_stack_trace = [
- ['deep', 'c1', 'run_one_coro'], 'Task-2', [[['main'], 'Task-1', []]]
+ expected_stack_trace = [
+ ['deep', 'c1', 'run_one_coro'],
+ 'Task-2',
+ [[['staggered_race', 'main'], 'Task-1', []]]
]
self.assertEqual(stack_trace, expected_stack_trace)
@skip_if_not_supported
+ @requires_gil_enabled("gh-133359: occasionally flaky on AMD64")
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Test only runs on Linux with process_vm_readv support")
def test_async_global_awaited_by(self):
@@ -516,19 +520,19 @@ 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(('Task-1', []), entries)
- self.assertIn(('server task', [[['main'], 'Task-1', []]]), entries)
- self.assertIn(('echo client spam', [[['main'], 'Task-1', []]]), entries)
+ self.assertIn((ANY, 'Task-1', []), entries)
+ self.assertIn((ANY, 'server task', [[['_aexit', '__aexit__', 'main'], ANY]]), entries)
+ self.assertIn((ANY, 'echo client spam', [[['_aexit', '__aexit__', 'main'], ANY]]), entries)
- expected_stack = [[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]]
- tasks_with_stack = [task for task in entries if task[1] == expected_stack]
+ expected_stack = [[['_aexit', '__aexit__', 'echo_client_spam'], ANY]]
+ tasks_with_stack = [task for task in entries if task[2] == expected_stack]
self.assertGreaterEqual(len(tasks_with_stack), 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([[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]], entries[-1][1])
+ self.assertEqual([[['_aexit', '__aexit__', 'echo_client_spam'], ANY]], entries[-1][2])
except PermissionError:
self.skipTest(
"Insufficient permissions to read the stack trace")
@@ -544,7 +548,6 @@ class TestGetStackTrace(unittest.TestCase):
"Test only runs on Linux with process_vm_readv support")
def test_self_trace(self):
stack_trace = get_stack_trace(os.getpid())
- print(stack_trace)
self.assertEqual(stack_trace[0], "test_self_trace")
if __name__ == "__main__":
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 56413d0..10c3e0e 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1960,7 +1960,7 @@ def _supports_remote_attaching():
PROCESS_VM_READV_SUPPORTED = False
try:
- from _testexternalinspection import PROCESS_VM_READV_SUPPORTED
+ from _remotedebuggingmodule import PROCESS_VM_READV_SUPPORTED
except ImportError:
pass