Skip to content

Commit 6469adf

Browse files
Tests for all sorts of inconsistent history edge cases
Also fixes #371 and #375 bumps curtsies version for better testing functions
1 parent 10948f8 commit 6469adf

File tree

3 files changed

+242
-35
lines changed

3 files changed

+242
-35
lines changed

bpython/curtsiesfrontend/repl.py

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def smarter_request_reload(desc):
306306

307307
self.request_paint_to_clear_screen = False # next paint should clear screen
308308
self.inconsistent_history = False # offscreen command yields different result from history
309-
self.history_messed_up = False # history error message displayed
309+
self.history_already_messed_up = False # history error message displayed
310310
self.last_events = [None] * 50 # some commands act differently based on the prev event
311311
# this list doesn't include instances of event.Event,
312312
# only keypress-type events (no refresh screen events etc.)
@@ -1018,38 +1018,57 @@ def paint(self, about_to_exit=False, user_quit=False):
10181018
else:
10191019
arr = FSArray(0, width)
10201020
#TODO test case of current line filling up the whole screen (there aren't enough rows to show it)
1021-
if self.inconsistent_history == False and current_line_start_row >= 0:
1022-
logger.debug("start %i",current_line_start_row)
1023-
history = paint.paint_history(current_line_start_row, width, self.lines_for_display)
1024-
arr[:history.height,:history.width] = history
1025-
1026-
else:
1027-
if self.inconsistent_history == True and self.history_messed_up == False:
1028-
self.history_messed_up = True
1029-
#if INCONSISTENT_HISTORY_MSG not in self.display_lines:
1030-
logger.debug(INCONSISTENT_HISTORY_MSG)
1031-
msg = INCONSISTENT_HISTORY_MSG
1032-
arr[0, 0:min(len(msg), width)] = [msg[:width]]
1033-
# self.scroll_offset -= 1
1034-
1035-
current_line_start_row = len(self.lines_for_display )- max(-1, self.scroll_offset)
1036-
self.inconsistent_history = False
1037-
if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen
1038-
logger.debug(CONTIGUITY_BROKEN_MSG)
1039-
msg = CONTIGUITY_BROKEN_MSG
1040-
arr[0, 0:min(len(msg), width)] = [msg[:width]]
10411021

1022+
def move_screen_up(current_line_start_row):
10421023
# move screen back up a screen minus a line
1043-
while current_line_start_row < 0:
1044-
self.scroll_offset = self.scroll_offset - self.height
1045-
current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset)
1024+
while current_line_start_row < 0:
1025+
logger.debug('scroll_offset was %s, current_line_start_row was %s', self.scroll_offset, current_line_start_row)
1026+
self.scroll_offset = self.scroll_offset - self.height
1027+
current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset)
1028+
logger.debug('scroll_offset changed to %s, current_line_start_row changed to %s', self.scroll_offset, current_line_start_row)
1029+
return current_line_start_row
1030+
1031+
if self.inconsistent_history == True and not self.history_already_messed_up:
1032+
logger.debug(INCONSISTENT_HISTORY_MSG)
1033+
self.history_already_messed_up = True
1034+
msg = INCONSISTENT_HISTORY_MSG
1035+
arr[0, 0:min(len(msg), width)] = [msg[:width]]
1036+
current_line_start_row += 1 # for the message
1037+
self.scroll_offset -= 1 # to make up for the scroll we're going to receive
1038+
# after we render scrolls down a line
1039+
1040+
current_line_start_row = move_screen_up(current_line_start_row)
1041+
logger.debug('current_line_start_row: %r', current_line_start_row)
10461042

10471043
history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display)
10481044
arr[1:history.height+1,:history.width] = history
10491045

10501046
if arr.height <= min_height:
10511047
arr[min_height, 0] = ' ' # force scroll down to hide broken history message
10521048

1049+
elif current_line_start_row < 0: #if current line trying to be drawn off the top of the screen
1050+
logger.debug(CONTIGUITY_BROKEN_MSG)
1051+
msg = CONTIGUITY_BROKEN_MSG
1052+
arr[0, 0:min(len(msg), width)] = [msg[:width]]
1053+
1054+
current_line_start_row = move_screen_up(current_line_start_row)
1055+
1056+
history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display)
1057+
arr[1:history.height+1,:history.width] = history
1058+
1059+
if arr.height <= min_height:
1060+
arr[min_height, 0] = ' ' # force scroll down to hide broken history message
1061+
1062+
else:
1063+
assert current_line_start_row >= 0
1064+
logger.debug("no history issues. start %i",current_line_start_row)
1065+
history = paint.paint_history(current_line_start_row, width, self.lines_for_display)
1066+
arr[:history.height,:history.width] = history
1067+
1068+
1069+
1070+
1071+
self.inconsistent_history = False
10531072

10541073
current_line = paint.paint_current_line(min_height, width, self.current_cursor_line)
10551074
if user_quit: # quit() or exit() in interp
@@ -1077,7 +1096,7 @@ def paint(self, about_to_exit=False, user_quit=False):
10771096
assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset)
10781097
cursor_row += current_line_start_row
10791098

1080-
if self.list_win_visible:
1099+
if self.list_win_visible and not self.coderunner.running:
10811100
logger.debug('infobox display code running')
10821101
visible_space_above = history.height
10831102
visible_space_below = min_height - current_line_end_row - 1
@@ -1248,10 +1267,10 @@ def reevaluate(self, insert_into_history=False):
12481267
num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset)
12491268
display_lines_offscreen = self.display_lines[:len(self.display_lines) - num_lines_onscreen]
12501269
old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen]
1251-
logger.debug('old_display_lines_offscreen %r', old_display_lines_offscreen)
1252-
logger.debug('display_lines_offscreen %r', display_lines_offscreen)
1253-
if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and self.history_messed_up == False:
1254-
self.scroll_offset = self.scroll_offset - (len(old_display_lines)-len(self.display_lines))
1270+
logger.debug('old_display_lines_offscreen %s', '|'.join([str(x) for x in old_display_lines_offscreen]))
1271+
logger.debug(' display_lines_offscreen %s', '|'.join([str(x) for x in display_lines_offscreen]))
1272+
if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and not self.history_already_messed_up:
1273+
#self.scroll_offset = self.scroll_offset + (len(old_display_lines)-len(self.display_lines))
12551274
self.inconsistent_history = True
12561275
logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history)
12571276

bpython/test/test_curtsies_painting.py

Lines changed: 193 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ def assert_paint(self, screen, cursor_row_col):
3535
self.assertFSArraysEqual(array, screen)
3636
self.assertEqual(cursor_pos, cursor_row_col)
3737

38-
def assert_paint_ignoring_formatting(self, screen, cursor_row_col):
38+
def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None):
3939
array, cursor_pos = self.repl.paint()
4040
self.assertFSArraysEqualIgnoringFormatting(array, screen)
41-
self.assertEqual(cursor_pos, cursor_row_col)
41+
if cursor_row_col is not None:
42+
self.assertEqual(cursor_pos, cursor_row_col)
4243

4344
class TestCurtsiesPaintingSimple(TestCurtsiesPainting):
4445

@@ -180,7 +181,6 @@ def test_inconsistent_history_doesnt_happen_if_onscreen(self):
180181
u'>>> ']
181182
self.assert_paint_ignoring_formatting(screen, (2, 4))
182183

183-
@skip('for inconsistent history check')
184184
def test_rewind_inconsistent_history(self):
185185
self.enter("1 + 1")
186186
self.enter("2 + 2")
@@ -202,8 +202,172 @@ def test_rewind_inconsistent_history(self):
202202
u'4',
203203
u'>>> ',
204204
u'',
205-
u'']
206-
self.assert_paint_ignoring_formatting(screen, (5, 4))
205+
u' ']
206+
self.assert_paint_ignoring_formatting(screen, (3, 4))
207+
self.repl.scroll_offset += len(screen) - self.repl.height
208+
self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4))
209+
self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4))
210+
211+
def test_rewind_inconsistent_history_more_lines_same_screen(self):
212+
self.repl.width = 60
213+
sys.a = 5
214+
self.enter("import sys")
215+
self.enter("for i in range(sys.a): print(sys.a)")
216+
self.enter()
217+
self.enter("1 + 1")
218+
self.enter("2 + 2")
219+
screen = [u">>> import sys",
220+
u">>> for i in range(sys.a): print(sys.a)",
221+
u'... ',
222+
u'5',
223+
u'5',
224+
u'5',
225+
u'5',
226+
u'5',
227+
u'>>> 1 + 1',
228+
u'2',
229+
u'>>> 2 + 2',
230+
u'4',
231+
u'>>> ']
232+
self.assert_paint_ignoring_formatting(screen, (12, 4))
233+
self.repl.scroll_offset += len(screen) - self.repl.height
234+
self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
235+
sys.a = 6
236+
self.undo()
237+
screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
238+
u'6',
239+
u'>>> 1 + 1', # everything will jump down a line - that's perfectly reasonable
240+
u'2',
241+
u'>>> ',
242+
u' ']
243+
self.assert_paint_ignoring_formatting(screen, (4, 4))
244+
self.repl.scroll_offset += len(screen) - self.repl.height
245+
self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4))
246+
247+
def test_rewind_inconsistent_history_more_lines_lower_screen(self):
248+
self.repl.width = 60
249+
sys.a = 5
250+
self.enter("import sys")
251+
self.enter("for i in range(sys.a): print(sys.a)")
252+
self.enter()
253+
self.enter("1 + 1")
254+
self.enter("2 + 2")
255+
screen = [u">>> import sys",
256+
u">>> for i in range(sys.a): print(sys.a)",
257+
u'... ',
258+
u'5',
259+
u'5',
260+
u'5',
261+
u'5',
262+
u'5',
263+
u'>>> 1 + 1',
264+
u'2',
265+
u'>>> 2 + 2',
266+
u'4',
267+
u'>>> ']
268+
self.assert_paint_ignoring_formatting(screen, (12, 4))
269+
self.repl.scroll_offset += len(screen) - self.repl.height
270+
self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
271+
sys.a = 8
272+
self.undo()
273+
screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
274+
u'8',
275+
u'8',
276+
u'8',
277+
u'>>> 1 + 1',
278+
u'2',
279+
u'>>> ']
280+
self.assert_paint_ignoring_formatting(screen)
281+
self.repl.scroll_offset += len(screen) - self.repl.height
282+
self.assert_paint_ignoring_formatting(screen[-5:])
283+
284+
def test_rewind_inconsistent_history_more_lines_raise_screen(self):
285+
self.repl.width = 60
286+
sys.a = 5
287+
self.enter("import sys")
288+
self.enter("for i in range(sys.a): print(sys.a)")
289+
self.enter()
290+
self.enter("1 + 1")
291+
self.enter("2 + 2")
292+
screen = [u">>> import sys",
293+
u">>> for i in range(sys.a): print(sys.a)",
294+
u'... ',
295+
u'5',
296+
u'5',
297+
u'5',
298+
u'5',
299+
u'5',
300+
u'>>> 1 + 1',
301+
u'2',
302+
u'>>> 2 + 2',
303+
u'4',
304+
u'>>> ']
305+
self.assert_paint_ignoring_formatting(screen, (12, 4))
306+
self.repl.scroll_offset += len(screen) - self.repl.height
307+
self.assert_paint_ignoring_formatting(screen[8:], (4, 4))
308+
sys.a = 1
309+
self.undo()
310+
screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width],
311+
u'1',
312+
u'>>> 1 + 1',
313+
u'2',
314+
u'>>> ',
315+
u' ']
316+
self.assert_paint_ignoring_formatting(screen)
317+
self.repl.scroll_offset += len(screen) - self.repl.height
318+
self.assert_paint_ignoring_formatting(screen[1:-1])
319+
320+
def test_rewind_history_not_quite_inconsistent(self):
321+
self.repl.width = 50
322+
sys.a = 5
323+
self.enter("for i in range(__import__('sys').a): print(i)")
324+
self.enter()
325+
self.enter("1 + 1")
326+
self.enter("2 + 2")
327+
screen = [u">>> for i in range(__import__('sys').a): print(i)",
328+
u'... ',
329+
u'0',
330+
u'1',
331+
u'2',
332+
u'3',
333+
u'4',
334+
u'>>> 1 + 1',
335+
u'2',
336+
u'>>> 2 + 2',
337+
u'4',
338+
u'>>> ']
339+
self.assert_paint_ignoring_formatting(screen, (11, 4))
340+
self.repl.scroll_offset += len(screen) - self.repl.height
341+
self.assert_paint_ignoring_formatting(screen[7:], (4, 4))
342+
sys.a = 6
343+
self.undo()
344+
screen = [u'5',
345+
u'>>> 1 + 1', # everything will jump down a line - that's perfectly reasonable
346+
u'2',
347+
u'>>> ',]
348+
self.assert_paint_ignoring_formatting(screen, (3, 4))
349+
350+
def test_rewind_barely_consistent(self):
351+
self.enter("1 + 1")
352+
self.enter("2 + 2")
353+
self.enter("3 + 3")
354+
screen = [u">>> 1 + 1",
355+
u'2',
356+
u'>>> 2 + 2',
357+
u'4',
358+
u'>>> 3 + 3',
359+
u'6',
360+
u'>>> ']
361+
self.assert_paint_ignoring_formatting(screen, (6, 4))
362+
self.repl.scroll_offset += len(screen) - self.repl.height
363+
self.assert_paint_ignoring_formatting(screen[2:], (4, 4))
364+
self.repl.display_lines[2] = self.repl.display_lines[2] * 2
365+
self.undo()
366+
screen = [u'>>> 2 + 2',
367+
u'4',
368+
u'>>> ']
369+
self.assert_paint_ignoring_formatting(screen, (2, 4))
370+
207371

208372
def test_clear_screen(self):
209373
self.enter("1 + 1")
@@ -258,3 +422,27 @@ def test_clear_screen_while_banner_visible(self):
258422
u'', u'', u'',
259423
u'STATUS_BAR ']
260424
self.assert_paint_ignoring_formatting(screen, (3, 4))
425+
426+
def test_cursor_stays_at_bottom_of_screen(self):
427+
"""infobox showing up during intermediate render was causing this to fail, #371"""
428+
self.repl.width = 50
429+
self.repl.current_line = "__import__('random').__name__"
430+
with output_to_repl(self.repl):
431+
self.repl.on_enter()
432+
screen = [u">>> __import__('random').__name__",
433+
u"'random'"]
434+
self.assert_paint_ignoring_formatting(screen)
435+
436+
with output_to_repl(self.repl):
437+
self.repl.process_event(self.refresh_requests.pop())
438+
screen = [u">>> __import__('random').__name__",
439+
u"'random'",
440+
u""]
441+
self.assert_paint_ignoring_formatting(screen)
442+
443+
with output_to_repl(self.repl):
444+
self.repl.process_event(self.refresh_requests.pop())
445+
screen = [u">>> __import__('random').__name__",
446+
u"'random'",
447+
u">>> "]
448+
self.assert_paint_ignoring_formatting(screen, (2, 4))

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def initialize_options(self):
151151

152152
if sys.version_info[:2] >= (2, 6):
153153
# curtsies only supports 2.6 and onwards
154-
extras_require['curtsies'] = ['curtsies >=0.1.7, <0.2.0', 'greenlet']
154+
extras_require['curtsies'] = ['curtsies >=0.1.11, <0.2.0', 'greenlet']
155155
extras_require['watch'] = ['watchdog']
156156
packages.append("bpython.curtsiesfrontend")
157157
entry_points['console_scripts'].append(

0 commit comments

Comments
 (0)