Skip to content

Commit 8744743

Browse files
authored
Merge branch 'main' into multi_inputs
2 parents 008ea27 + fc3d400 commit 8744743

24 files changed

+305
-168
lines changed

Doc/library/asyncio-graph.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ and debuggers.
5959
6060
async def main():
6161
async with asyncio.TaskGroup() as g:
62-
g.create_task(test())
62+
g.create_task(test(), name='test')
6363
6464
asyncio.run(main())
6565
6666
will print::
6767

68-
* Task(name='Task-2', id=0x1039f0fe0)
68+
* Task(name='test', id=0x1039f0fe0)
6969
+ Call stack:
7070
| File 't2.py', line 4, in async test()
7171
+ Awaited by:

Lib/_pyrepl/fancy_termios.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ def as_list(self):
4040
self.lflag,
4141
self.ispeed,
4242
self.ospeed,
43-
self.cc,
43+
# Always return a copy of the control characters list to ensure
44+
# there are not any additional references to self.cc
45+
self.cc[:],
4446
]
4547

4648
def copy(self):

Lib/asyncio/staggered.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,27 @@ async def staggered_race(coro_fns, delay, *, loop=None):
6666
enum_coro_fns = enumerate(coro_fns)
6767
winner_result = None
6868
winner_index = None
69+
unhandled_exceptions = []
6970
exceptions = []
70-
running_tasks = []
71+
running_tasks = set()
72+
on_completed_fut = None
73+
74+
def task_done(task):
75+
running_tasks.discard(task)
76+
if (
77+
on_completed_fut is not None
78+
and not on_completed_fut.done()
79+
and not running_tasks
80+
):
81+
on_completed_fut.set_result(None)
82+
83+
if task.cancelled():
84+
return
85+
86+
exc = task.exception()
87+
if exc is None:
88+
return
89+
unhandled_exceptions.append(exc)
7190

7291
async def run_one_coro(ok_to_start, previous_failed) -> None:
7392
# in eager tasks this waits for the calling task to append this task
@@ -91,11 +110,11 @@ async def run_one_coro(ok_to_start, previous_failed) -> None:
91110
this_failed = locks.Event()
92111
next_ok_to_start = locks.Event()
93112
next_task = loop.create_task(run_one_coro(next_ok_to_start, this_failed))
94-
running_tasks.append(next_task)
113+
running_tasks.add(next_task)
114+
next_task.add_done_callback(task_done)
95115
# next_task has been appended to running_tasks so next_task is ok to
96116
# start.
97117
next_ok_to_start.set()
98-
assert len(running_tasks) == this_index + 2
99118
# Prepare place to put this coroutine's exceptions if not won
100119
exceptions.append(None)
101120
assert len(exceptions) == this_index + 1
@@ -120,31 +139,36 @@ async def run_one_coro(ok_to_start, previous_failed) -> None:
120139
# up as done() == True, cancelled() == False, exception() ==
121140
# asyncio.CancelledError. This behavior is specified in
122141
# https://bugs.python.org/issue30048
123-
for i, t in enumerate(running_tasks):
124-
if i != this_index:
142+
current_task = tasks.current_task(loop)
143+
for t in running_tasks:
144+
if t is not current_task:
125145
t.cancel()
126146

127-
ok_to_start = locks.Event()
128-
first_task = loop.create_task(run_one_coro(ok_to_start, None))
129-
running_tasks.append(first_task)
130-
# first_task has been appended to running_tasks so first_task is ok to start.
131-
ok_to_start.set()
147+
propagate_cancellation_error = None
132148
try:
133-
# Wait for a growing list of tasks to all finish: poor man's version of
134-
# curio's TaskGroup or trio's nursery
135-
done_count = 0
136-
while done_count != len(running_tasks):
137-
done, _ = await tasks.wait(running_tasks)
138-
done_count = len(done)
149+
ok_to_start = locks.Event()
150+
first_task = loop.create_task(run_one_coro(ok_to_start, None))
151+
running_tasks.add(first_task)
152+
first_task.add_done_callback(task_done)
153+
# first_task has been appended to running_tasks so first_task is ok to start.
154+
ok_to_start.set()
155+
propagate_cancellation_error = None
156+
# Make sure no tasks are left running if we leave this function
157+
while running_tasks:
158+
on_completed_fut = loop.create_future()
159+
try:
160+
await on_completed_fut
161+
except exceptions_mod.CancelledError as ex:
162+
propagate_cancellation_error = ex
163+
for task in running_tasks:
164+
task.cancel(*ex.args)
165+
on_completed_fut = None
166+
if __debug__ and unhandled_exceptions:
139167
# If run_one_coro raises an unhandled exception, it's probably a
140168
# programming error, and I want to see it.
141-
if __debug__:
142-
for d in done:
143-
if d.done() and not d.cancelled() and d.exception():
144-
raise d.exception()
169+
raise ExceptionGroup("staggered race failed", unhandled_exceptions)
170+
if propagate_cancellation_error is not None:
171+
raise propagate_cancellation_error
145172
return winner_result, winner_index, exceptions
146173
finally:
147-
del exceptions
148-
# Make sure no tasks are left running if we leave this function
149-
for t in running_tasks:
150-
t.cancel()
174+
del exceptions, propagate_cancellation_error, unhandled_exceptions

Lib/idlelib/idle_test/test_configdialog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ def test_click_help(self):
9898
dialog.buttons['Help'].invoke()
9999
title, contents = view.kwds['title'], view.kwds['contents']
100100
self.assertEqual(title, 'Help for IDLE preferences')
101-
self.assertTrue(contents.startswith('When you click') and
102-
contents.endswith('a different name.\n'))
101+
self.assertStartsWith(contents, 'When you click')
102+
self.assertEndsWith(contents,'a different name.\n')
103103

104104

105105
class FontPageTest(unittest.TestCase):

Lib/idlelib/idle_test/test_debugger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def test_init(self):
256256
flist = None
257257
master_window = self.root
258258
sv = debugger.StackViewer(master_window, flist, gui)
259-
self.assertTrue(hasattr(sv, 'stack'))
259+
self.assertHasAttr(sv, 'stack')
260260

261261
def test_load_stack(self):
262262
# Test the .load_stack() method against a fixed test stack.

Lib/idlelib/idle_test/test_grep.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def test_found(self):
143143
self.assertIn(pat, lines[0])
144144
self.assertIn('py: 1:', lines[1]) # line number 1
145145
self.assertIn('2', lines[3]) # hits found 2
146-
self.assertTrue(lines[4].startswith('(Hint:'))
146+
self.assertStartsWith(lines[4], '(Hint:')
147147

148148

149149
class Default_commandTest(unittest.TestCase):

Lib/idlelib/idle_test/test_multicall.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def tearDownClass(cls):
2727
def test_creator(self):
2828
mc = self.mc
2929
self.assertIs(multicall._multicall_dict[Text], mc)
30-
self.assertTrue(issubclass(mc, Text))
30+
self.assertIsSubclass(mc, Text)
3131
mc2 = multicall.MultiCallCreator(Text)
3232
self.assertIs(mc, mc2)
3333

Lib/idlelib/idle_test/test_query.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ def test_c_source_name(self):
134134

135135
def test_good_module_name(self):
136136
dialog = self.Dummy_ModuleName('idlelib')
137-
self.assertTrue(dialog.entry_ok().endswith('__init__.py'))
137+
self.assertEndsWith(dialog.entry_ok(), '__init__.py')
138138
self.assertEqual(dialog.entry_error['text'], '')
139139
dialog = self.Dummy_ModuleName('idlelib.idle')
140-
self.assertTrue(dialog.entry_ok().endswith('idle.py'))
140+
self.assertEndsWith(dialog.entry_ok(), 'idle.py')
141141
self.assertEqual(dialog.entry_error['text'], '')
142142

143143

@@ -389,7 +389,7 @@ def test_click_module_name(self):
389389
self.assertEqual(dialog.text0, 'idlelib')
390390
self.assertEqual(dialog.entry.get(), 'idlelib')
391391
dialog.button_ok.invoke()
392-
self.assertTrue(dialog.result.endswith('__init__.py'))
392+
self.assertEndsWith(dialog.result, '__init__.py')
393393
root.destroy()
394394

395395

Lib/idlelib/idle_test/test_redirector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_close(self):
3434
redir.register('insert', Func)
3535
redir.close()
3636
self.assertEqual(redir._operations, {})
37-
self.assertFalse(hasattr(self.text, 'widget'))
37+
self.assertNotHasAttr(self.text, 'widget')
3838

3939

4040
class WidgetRedirectorTest(unittest.TestCase):

Lib/idlelib/idle_test/test_sidebar.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ def test_copy(self):
725725

726726
text.tag_add('sel', f'{first_line}.0', 'end-1c')
727727
selected_text = text.get('sel.first', 'sel.last')
728-
self.assertTrue(selected_text.startswith('if True:\n'))
728+
self.assertStartsWith(selected_text, 'if True:\n')
729729
self.assertIn('\n1\n', selected_text)
730730

731731
text.event_generate('<<copy>>')
@@ -749,7 +749,7 @@ def test_copy_with_prompts(self):
749749

750750
text.tag_add('sel', f'{first_line}.3', 'end-1c')
751751
selected_text = text.get('sel.first', 'sel.last')
752-
self.assertTrue(selected_text.startswith('True:\n'))
752+
self.assertStartsWith(selected_text, 'True:\n')
753753

754754
selected_lines_text = text.get('sel.first linestart', 'sel.last')
755755
selected_lines = selected_lines_text.split('\n')

0 commit comments

Comments
 (0)