Skip to content

[3.10] bpo-44282: Fix occasional test_incremental_editing failures on buildbots (GH-26491) #26499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/idlelib/idle_test/test_colorizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ def test_long_multiline_string(self):
''')
self._assert_highlighting(source, {'STRING': [('1.0', '5.4')]})

@run_in_tk_mainloop
@run_in_tk_mainloop(delay=50)
def test_incremental_editing(self):
text = self.text
eq = self.assertEqual
Expand Down
20 changes: 10 additions & 10 deletions Lib/idlelib/idle_test/test_sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,19 +510,19 @@ def test_initial_state(self):
)
self.assert_sidebar_lines_synced()

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_single_empty_input(self):
self.do_input('\n')
yield
self.assert_sidebar_lines_end_with(['>>>', '>>>'])

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_single_line_statement(self):
self.do_input('1\n')
yield
self.assert_sidebar_lines_end_with(['>>>', None, '>>>'])

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_multi_line_statement(self):
# Block statements are not indented because IDLE auto-indents.
self.do_input(dedent('''\
Expand All @@ -540,14 +540,14 @@ def test_multi_line_statement(self):
'>>>',
])

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_single_long_line_wraps(self):
self.do_input('1' * 200 + '\n')
yield
self.assert_sidebar_lines_end_with(['>>>', None, '>>>'])
self.assert_sidebar_lines_synced()

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_squeeze_multi_line_output(self):
shell = self.shell
text = shell.text
Expand All @@ -567,7 +567,7 @@ def test_squeeze_multi_line_output(self):
self.assert_sidebar_lines_end_with(['>>>', None, None, None, '>>>'])
self.assert_sidebar_lines_synced()

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_interrupt_recall_undo_redo(self):
text = self.shell.text
# Block statements are not indented because IDLE auto-indents.
Expand Down Expand Up @@ -613,7 +613,7 @@ def test_interrupt_recall_undo_redo(self):
['>>>', '...', '...', '...', None, '>>>']
)

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_very_long_wrapped_line(self):
with swap_attr(self.shell, 'squeezer', None):
self.do_input('x = ' + '1'*10_000 + '\n')
Expand Down Expand Up @@ -678,7 +678,7 @@ def get_sidebar_colors():
sidebar.update_colors()
self.assertEqual(get_sidebar_colors(), test_colors)

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_mousewheel(self):
sidebar = self.shell.shell_sidebar
text = self.shell.text
Expand All @@ -703,7 +703,7 @@ def test_mousewheel(self):
yield
self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0')))

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_copy(self):
sidebar = self.shell.shell_sidebar
text = self.shell.text
Expand All @@ -728,7 +728,7 @@ def test_copy(self):
copied_text = text.clipboard_get()
self.assertEqual(copied_text, selected_text)

@run_in_tk_mainloop
@run_in_tk_mainloop()
def test_copy_with_prompts(self):
sidebar = self.shell.shell_sidebar
text = self.shell.text
Expand Down
70 changes: 38 additions & 32 deletions Lib/idlelib/idle_test/tkinter_testing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import functools


def run_in_tk_mainloop(test_method):
def run_in_tk_mainloop(delay=1):
"""Decorator for running a test method with a real Tk mainloop.

This starts a Tk mainloop before running the test, and stops it
Expand All @@ -13,44 +13,50 @@ def run_in_tk_mainloop(test_method):
using "yield" to allow the mainloop to process events and "after"
callbacks, and then continue the test from that point.

The delay argument is passed into root.after(...) calls as the number
of ms to wait before passing execution back to the generator function.

This also assumes that the test class has a .root attribute,
which is a tkinter.Tk object.

For example (from test_sidebar.py):

@run_test_with_tk_mainloop
@run_test_with_tk_mainloop()
def test_single_empty_input(self):
self.do_input('\n')
yield
self.assert_sidebar_lines_end_with(['>>>', '>>>'])
"""
@functools.wraps(test_method)
def new_test_method(self):
test_generator = test_method(self)
root = self.root
# Exceptions raised by self.assert...() need to be raised
# outside of the after() callback in order for the test
# harness to capture them.
exception = None
def after_callback():
nonlocal exception
try:
next(test_generator)
except StopIteration:
root.quit()
except Exception as exc:
exception = exc
root.quit()
else:
# Schedule the Tk mainloop to call this function again,
# using a robust method of ensuring that it gets a
# chance to process queued events before doing so.
# See: https://stackoverflow.com/q/18499082#comment65004099_38817470
root.after(1, root.after_idle, after_callback)
root.after(0, root.after_idle, after_callback)
root.mainloop()

if exception:
raise exception

return new_test_method
def decorator(test_method):
@functools.wraps(test_method)
def new_test_method(self):
test_generator = test_method(self)
root = self.root
# Exceptions raised by self.assert...() need to be raised
# outside of the after() callback in order for the test
# harness to capture them.
exception = None
def after_callback():
nonlocal exception
try:
next(test_generator)
except StopIteration:
root.quit()
except Exception as exc:
exception = exc
root.quit()
else:
# Schedule the Tk mainloop to call this function again,
# using a robust method of ensuring that it gets a
# chance to process queued events before doing so.
# See: https://stackoverflow.com/q/18499082#comment65004099_38817470
root.after(delay, root.after_idle, after_callback)
root.after(0, root.after_idle, after_callback)
root.mainloop()

if exception:
raise exception

return new_test_method

return decorator