提交 be53ec07 编写于 作者: S Shanqing Cai 提交者: TensorFlower Gardener

tfdbg CLI: Add navigation bar to CursesUI

Hitchhiking change:
* Let "node_info/ni" menu shortcuts always use the "-t" option.
Change: 149999182
上级 ea963dd0
......@@ -168,6 +168,13 @@ py_library(
],
)
py_library(
name = "curses_widgets",
srcs = ["cli/curses_widgets.py"],
srcs_version = "PY2AND3",
deps = [":debugger_cli_common"],
)
py_library(
name = "curses_ui",
srcs = ["cli/curses_ui.py"],
......@@ -175,6 +182,7 @@ py_library(
deps = [
":base_ui",
":command_parser",
":curses_widgets",
":debugger_cli_common",
":tensor_format",
"@six_archive//:six",
......@@ -398,6 +406,17 @@ py_test(
],
)
py_test(
name = "curses_widgets_test",
size = "small",
srcs = ["cli/curses_widgets_test.py"],
srcs_version = "PY2AND3",
deps = [
":curses_widgets",
"//tensorflow/python:framework_test_lib",
],
)
py_test(
name = "curses_ui_test",
size = "small",
......
......@@ -1266,7 +1266,7 @@ class DebugAnalyzer(object):
lines.append(line)
font_attr_segs[len(lines) - 1] = [(
len(line) - len(non_ctrl), len(line),
debugger_cli_common.MenuItem(None, "ni -a -d %s" % non_ctrl))]
debugger_cli_common.MenuItem(None, "ni -a -d -t %s" % non_ctrl))]
if ctrls:
lines.append("")
......@@ -1276,7 +1276,7 @@ class DebugAnalyzer(object):
lines.append(line)
font_attr_segs[len(lines) - 1] = [(
len(line) - len(ctrl), len(line),
debugger_cli_common.MenuItem(None, "ni -a -d %s" % ctrl))]
debugger_cli_common.MenuItem(None, "ni -a -d -t %s" % ctrl))]
return debugger_cli_common.RichTextLines(
lines, font_attr_segs=font_attr_segs)
......
......@@ -1436,7 +1436,7 @@ class AnalyzerCLIControlDepTest(test_util.TensorFlowTestCase):
# Verify the menu items (command shortcuts) in the output.
check_menu_item(self, out, 10,
len(out.lines[10]) - len("control_deps/x/read"),
len(out.lines[10]), "ni -a -d control_deps/x/read")
len(out.lines[10]), "ni -a -d -t control_deps/x/read")
if out.lines[13].endswith("control_deps/ctrl_dep_y"):
y_line = 13
z_line = 14
......@@ -1445,10 +1445,12 @@ class AnalyzerCLIControlDepTest(test_util.TensorFlowTestCase):
z_line = 13
check_menu_item(self, out, y_line,
len(out.lines[y_line]) - len("control_deps/ctrl_dep_y"),
len(out.lines[y_line]), "ni -a -d control_deps/ctrl_dep_y")
len(out.lines[y_line]),
"ni -a -d -t control_deps/ctrl_dep_y")
check_menu_item(self, out, z_line,
len(out.lines[z_line]) - len("control_deps/ctrl_dep_z"),
len(out.lines[z_line]), "ni -a -d control_deps/ctrl_dep_z")
len(out.lines[z_line]),
"ni -a -d -t control_deps/ctrl_dep_z")
def testListInputsNonRecursiveNoControl(self):
"""List inputs non-recursively, without any control inputs."""
......
......@@ -27,6 +27,7 @@ from six.moves import xrange # pylint: disable=redefined-builtin
from tensorflow.python.debug.cli import base_ui
from tensorflow.python.debug.cli import command_parser
from tensorflow.python.debug.cli import curses_widgets
from tensorflow.python.debug.cli import debugger_cli_common
from tensorflow.python.debug.cli import tensor_format
......@@ -206,6 +207,9 @@ class CursesUI(base_ui.BaseUI):
REGEX_SEARCH_PREFIX = "/"
TENSOR_INDICES_NAVIGATION_PREFIX = "@"
_NAVIGATION_FORWARD_COMMAND = "next"
_NAVIGATION_BACK_COMMAND = "prev"
# Limit screen width to work around the limitation of the curses library that
# it may return invalid x coordinates for large values.
_SCREEN_WIDTH_LIMIT = 220
......@@ -273,6 +277,8 @@ class CursesUI(base_ui.BaseUI):
self._pending_command = ""
self._nav_history = curses_widgets.CursesNavigationHistory(10)
# State related to screen output.
self._output_pad = None
self._output_pad_row = 0
......@@ -305,11 +311,15 @@ class CursesUI(base_ui.BaseUI):
self._title_row = 0
# Row index of the Navigation Bar (i.e., the bar that contains forward and
# backward buttons and displays the current command line).
self._nav_bar_row = 1
# Top row index of the output pad.
# A "pad" is a curses object that holds lines of text and not limited to
# screen size. It can be rendered on the screen partially with scroll
# parameters specified.
self._output_top_row = 1
self._output_top_row = 2
# Number of rows that the output pad has.
self._output_num_rows = (
......@@ -542,6 +552,34 @@ class CursesUI(base_ui.BaseUI):
if self._max_x > self._SCREEN_WIDTH_LIMIT:
self._max_x = self._SCREEN_WIDTH_LIMIT
def _navigate_screen_output(self, command):
"""Navigate in screen output history.
Args:
command: (`str`) the navigation command, from
{self._NAVIGATION_FORWARD_COMMAND, self._NAVIGATION_BACK_COMMAND}.
"""
if command == self._NAVIGATION_FORWARD_COMMAND:
if self._nav_history.can_go_forward():
item = self._nav_history.go_forward()
scroll_position = item.scroll_position
else:
self._toast("At the LATEST in navigation history!",
color="red_on_white")
return
else:
if self._nav_history.can_go_back():
item = self._nav_history.go_back()
scroll_position = item.scroll_position
else:
self._toast("At the OLDEST in navigation history!",
color="red_on_white")
return
self._display_output(item.screen_output)
if scroll_position != 0:
self._scroll_output(_SCROLL_TO_LINE_INDEX, line_index=scroll_position)
def _dispatch_command(self, command):
"""Dispatch user command.
......@@ -560,6 +598,10 @@ class CursesUI(base_ui.BaseUI):
# Explicit user command-triggered exit: EXPLICIT_USER_EXIT as the exit
# token.
return debugger_cli_common.EXPLICIT_USER_EXIT
elif (command == self._NAVIGATION_FORWARD_COMMAND or
command == self._NAVIGATION_BACK_COMMAND):
self._navigate_screen_output(command)
return
if command:
self._command_history_store.add_command(command)
......@@ -589,7 +631,6 @@ class CursesUI(base_ui.BaseUI):
indices = command_parser.parse_indices(indices_str)
omitted, line_index, _, _ = tensor_format.locate_tensor_element(
self._curr_wrapped_output, indices)
if not omitted:
self._scroll_output(
_SCROLL_TO_LINE_INDEX, line_index=line_index)
......@@ -630,6 +671,8 @@ class CursesUI(base_ui.BaseUI):
if exit_token is not None:
return exit_token
self._nav_history.add_item(command, screen_output, 0)
self._display_output(screen_output)
if output_file_path:
try:
......@@ -761,6 +804,7 @@ class CursesUI(base_ui.BaseUI):
def _redraw_output(self):
if self._curr_unwrapped_output is not None:
self._display_nav_bar()
self._display_main_menu(self._curr_unwrapped_output)
self._display_output(self._curr_unwrapped_output, is_refresh=True)
......@@ -769,7 +813,11 @@ class CursesUI(base_ui.BaseUI):
if self._main_menu_pad:
output_top += 1
if mouse_y == self._output_top_row and self._main_menu_pad:
if mouse_y == self._nav_bar_row and self._nav_bar:
# Click was in the nav bar.
return _get_command_from_line_attr_segs(mouse_x,
self._nav_bar.font_attr_segs[0])
elif mouse_y == self._output_top_row and self._main_menu_pad:
# Click was in the menu bar.
return _get_command_from_line_attr_segs(mouse_x,
self._main_menu.font_attr_segs[0])
......@@ -891,6 +939,7 @@ class CursesUI(base_ui.BaseUI):
(0, len(output.lines[-1]), "magenta")
]
self._display_nav_bar()
self._display_main_menu(self._curr_wrapped_output)
(self._output_pad, self._output_pad_height,
......@@ -1012,6 +1061,18 @@ class CursesUI(base_ui.BaseUI):
return pad, rows, cols
def _display_nav_bar(self):
nav_bar_width = self._max_x - 2
self._nav_bar_pad = self._screen_new_output_pad(1, nav_bar_width)
self._nav_bar = self._nav_history.render(
nav_bar_width,
self._NAVIGATION_BACK_COMMAND,
self._NAVIGATION_FORWARD_COMMAND)
self._screen_add_line_to_output_pad(
self._nav_bar_pad, 0, self._nav_bar.lines[0][:nav_bar_width - 1],
color_segments=(self._nav_bar.font_attr_segs[0]
if 0 in self._nav_bar.font_attr_segs else None))
def _display_main_menu(self, output):
"""Display main menu associated with screen output, if the menu exists.
......@@ -1124,7 +1185,8 @@ class CursesUI(base_ui.BaseUI):
(scroll_pad, _, _) = self._display_lines(
self._scroll_bar.layout(), self._output_num_rows - 1)
scroll_pad.refresh(
0, 0, 2, self._max_x - 2, self._output_num_rows, self._max_x - 1)
0, 0, self._output_top_row + 1, self._max_x - 2,
self._output_num_rows + 1, self._max_x - 1)
def _scroll_output(self, direction, line_index=None):
"""Scroll the output pad.
......@@ -1183,6 +1245,8 @@ class CursesUI(base_ui.BaseUI):
else:
raise ValueError("Unsupported scroll mode: %s" % direction)
self._nav_history.update_scroll_position(self._output_pad_row)
# Actually scroll the output pad: refresh with new location.
output_pad_top = self._output_pad_screen_location.top
if self._main_menu_pad:
......@@ -1192,6 +1256,7 @@ class CursesUI(base_ui.BaseUI):
self._output_pad_screen_location.left,
self._output_pad_screen_location.bottom,
self._output_pad_screen_location.right)
self._screen_render_nav_bar()
self._screen_render_menu_pad()
self._scroll_info = self._compile_ui_status_summary()
......@@ -1200,6 +1265,12 @@ class CursesUI(base_ui.BaseUI):
self._scroll_info,
color=self._STATUS_BAR_COLOR_PAIR)
def _screen_render_nav_bar(self):
if self._nav_bar_pad:
self._nav_bar_pad.refresh(0, 0, self._nav_bar_row, 0,
self._output_pad_screen_location.top,
self._max_x)
def _screen_render_menu_pad(self):
if self._main_menu_pad:
self._main_menu_pad.refresh(0, 0, self._output_pad_screen_location.top, 0,
......
......@@ -207,6 +207,9 @@ class MockCursesUI(curses_ui.CursesUI):
self.main_menu_list.append(self._main_menu)
def _screen_render_nav_bar(self):
pass
def _screen_render_menu_pad(self):
pass
......@@ -1013,7 +1016,7 @@ class CursesTest(test_util.TensorFlowTestCase):
"""Test displaying tensor with indices."""
ui = MockCursesUI(
8, # Use a small screen height to cause scrolling.
9, # Use a small screen height to cause scrolling.
80,
command_sequence=[
string_to_codes("print_ones --size 5\n"),
......@@ -1158,19 +1161,19 @@ class CursesTest(test_util.TensorFlowTestCase):
self.assertEqual({
0: None,
-1: [1, 0]
-1: [0, 0]
}, ui.output_array_pointer_indices[0])
self.assertEqual({
0: [0, 0],
-1: [3, 0]
-1: [2, 0]
}, ui.output_array_pointer_indices[1])
self.assertEqual({
0: [1, 0],
-1: [4, 0]
-1: [3, 0]
}, ui.output_array_pointer_indices[2])
self.assertEqual({
0: [0, 0],
-1: [3, 0]
-1: [2, 0]
}, ui.output_array_pointer_indices[3])
def testScrollTensorByInvalidIndices(self):
......@@ -1349,7 +1352,7 @@ class CursesTest(test_util.TensorFlowTestCase):
command_sequence=[
string_to_codes("babble -n 10 -m\n"),
# A click on the enabled menu item.
[curses.KEY_MOUSE, 3, 1],
[curses.KEY_MOUSE, 3, 2],
self._EXIT
])
ui.register_command_handler("babble", self._babble, "")
......@@ -1389,6 +1392,128 @@ class CursesTest(test_util.TensorFlowTestCase):
self.assertEqual(1, len(ui.unwrapped_outputs))
self.assertEqual(["bar"] * 10, ui.unwrapped_outputs[0].lines)
def testNavigationUsingCommandLineWorks(self):
ui = MockCursesUI(
40,
80,
command_sequence=[
string_to_codes("babble -n 2\n"),
string_to_codes("babble -n 4\n"),
string_to_codes("prev\n"),
string_to_codes("next\n"),
self._EXIT
])
ui.register_command_handler("babble", self._babble, "")
ui.run_ui()
self.assertEqual(4, len(ui.unwrapped_outputs))
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[0].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[1].lines)
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[2].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[3].lines)
def testNavigationOverOldestLimitUsingCommandLineGivesCorrectWarning(self):
ui = MockCursesUI(
40,
80,
command_sequence=[
string_to_codes("babble -n 2\n"),
string_to_codes("babble -n 4\n"),
string_to_codes("prev\n"),
string_to_codes("prev\n"), # Navigate over oldest limit.
self._EXIT
])
ui.register_command_handler("babble", self._babble, "")
ui.run_ui()
self.assertEqual(3, len(ui.unwrapped_outputs))
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[0].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[1].lines)
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[2].lines)
self.assertEqual("At the OLDEST in navigation history!", ui.toasts[-2])
def testNavigationOverLatestLimitUsingCommandLineGivesCorrectWarning(self):
ui = MockCursesUI(
40,
80,
command_sequence=[
string_to_codes("babble -n 2\n"),
string_to_codes("babble -n 4\n"),
string_to_codes("prev\n"),
string_to_codes("next\n"),
string_to_codes("next\n"), # Navigate over latest limit.
self._EXIT
])
ui.register_command_handler("babble", self._babble, "")
ui.run_ui()
self.assertEqual(4, len(ui.unwrapped_outputs))
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[0].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[1].lines)
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[2].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[3].lines)
self.assertEqual("At the LATEST in navigation history!", ui.toasts[-2])
def testMouseClicksOnNavBarWorks(self):
ui = MockCursesUI(
40,
80,
command_sequence=[
string_to_codes("babble -n 2\n"),
string_to_codes("babble -n 4\n"),
# A click on the back (prev) button of the nav bar.
[curses.KEY_MOUSE, 3, 1],
# A click on the forward (prev) button of the nav bar.
[curses.KEY_MOUSE, 7, 1],
self._EXIT
])
ui.register_command_handler("babble", self._babble, "")
ui.run_ui()
self.assertEqual(4, len(ui.unwrapped_outputs))
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[0].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[1].lines)
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[2].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[3].lines)
def testMouseClicksOnNavBarAfterPreviousScrollingWorks(self):
ui = MockCursesUI(
40,
80,
command_sequence=[
string_to_codes("babble -n 2\n"),
[curses.KEY_NPAGE], # Scroll down one line.
string_to_codes("babble -n 4\n"),
# A click on the back (prev) button of the nav bar.
[curses.KEY_MOUSE, 3, 1],
# A click on the forward (prev) button of the nav bar.
[curses.KEY_MOUSE, 7, 1],
self._EXIT
])
ui.register_command_handler("babble", self._babble, "")
ui.run_ui()
self.assertEqual(6, len(ui.unwrapped_outputs))
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[0].lines)
# From manual scroll.
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[1].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[2].lines)
# From history navigation.
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[3].lines)
# From history navigation's auto-scroll to history scroll position.
self.assertEqual(["bar"] * 2, ui.unwrapped_outputs[4].lines)
self.assertEqual(["bar"] * 4, ui.unwrapped_outputs[5].lines)
self.assertEqual(6, len(ui.scroll_messages))
self.assertIn("Scroll (PgDn): 0.00%", ui.scroll_messages[0])
self.assertIn("Scroll (PgUp): 100.00%", ui.scroll_messages[1])
self.assertIn("Scroll (PgDn): 0.00%", ui.scroll_messages[2])
self.assertIn("Scroll (PgDn): 0.00%", ui.scroll_messages[3])
self.assertIn("Scroll (PgUp): 100.00%", ui.scroll_messages[4])
self.assertIn("Scroll (PgDn): 0.00%", ui.scroll_messages[5])
class ScrollBarTest(test_util.TensorFlowTestCase):
......
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Widgets for Curses-based CLI."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from tensorflow.python.debug.cli import debugger_cli_common
RL = debugger_cli_common.RichLine
class NavigationHistoryItem(object):
"""Individual item in navigation history."""
def __init__(self, command, screen_output, scroll_position):
"""Constructor of NavigationHistoryItem.
Args:
command: (`str`) the command line text.
screen_output: the screen output of the command.
scroll_position: (`int`) scroll position in the screen output.
"""
self.command = command
self.screen_output = screen_output
self.scroll_position = scroll_position
class CursesNavigationHistory(object):
"""Navigation history containing commands, outputs and scroll info."""
BACK_ARROW_TEXT = "<--"
FORWARD_ARROW_TEXT = "-->"
def __init__(self, capacity):
"""Constructor of CursesNavigationHistory.
Args:
capacity: (`int`) How many items this object can hold. Each item consists
of a command stirng, an output RichTextLines object and a scroll
position.
Raises:
ValueError: If capacity is not a positive number.
"""
if capacity <= 0:
raise ValueError("In valid capacity value: %d" % capacity)
self._capacity = capacity
self._items = []
self._pointer = -1
def add_item(self, command, screen_output, scroll_position):
"""Add an item to the navigation histoyr.
Args:
command: command line text.
screen_output: screen output produced for the command.
scroll_position: (`int`) scroll position in the screen output.
"""
if self._pointer + 1 < len(self._items):
self._items = self._items[:self._pointer + 1]
self._items.append(
NavigationHistoryItem(command, screen_output, scroll_position))
if len(self._items) > self._capacity:
self._items = self._items[-self._capacity:]
self._pointer = len(self._items) - 1
def update_scroll_position(self, new_scroll_position):
"""Update the scroll position of the currently-pointed-to history item.
Args:
new_scroll_position: (`int`) new scroll-position value.
Raises:
ValueError: If the history is empty.
"""
if not self._items:
raise ValueError("Empty navigation history")
self._items[self._pointer].scroll_position = new_scroll_position
def size(self):
return len(self._items)
def pointer(self):
return self._pointer
def go_back(self):
"""Go back one place in the history, if possible.
Decrease the pointer value by 1, if possible. Otherwise, the pointer value
will be unchanged.
Returns:
The updated pointer value.
Raises:
ValueError: If history is empty.
"""
if not self._items:
raise ValueError("Empty navigation history")
if self.can_go_back():
self._pointer -= 1
return self._items[self._pointer]
def go_forward(self):
"""Go forward one place in the history, if possible.
Increase the pointer value by 1, if possible. Otherwise, the pointer value
will be unchanged.
Returns:
The updated pointer value.
Raises:
ValueError: If history is empty.
"""
if not self._items:
raise ValueError("Empty navigation history")
if self.can_go_forward():
self._pointer += 1
return self._items[self._pointer]
def can_go_back(self):
"""Test whether client can go back one place.
Returns:
(`bool`) Whether going back one place is possible.
"""
return self._pointer >= 1
def can_go_forward(self):
"""Test whether client can go forward one place.
Returns:
(`bool`) Whether going back one place is possible.
"""
return self._pointer + 1 < len(self._items)
def render(self,
max_length,
backward_command,
forward_command,
latest_command_attribute="black_on_white",
old_command_attribute="magenta_on_white"):
"""Render the rich text content of the single-line navigation bar.
Args:
max_length: (`int`) Maximum length of the navigation bar, in characters.
backward_command: (`str`) command for going backward. Used to construct
the shortcut menu item.
forward_command: (`str`) command for going forward. Used to construct the
shortcut menu item.
latest_command_attribute: font attribute for lastest command.
old_command_attribute: font attribute for old (non-latest) command.
Returns:
(`debugger_cli_common.RichTextLines`) the navigation bar text with
attributes.
"""
output = RL("| ")
output += RL(
self.BACK_ARROW_TEXT,
(debugger_cli_common.MenuItem(None, backward_command)
if self.can_go_back() else None))
output += RL(" ")
output += RL(
self.FORWARD_ARROW_TEXT,
(debugger_cli_common.MenuItem(None, forward_command)
if self.can_go_forward() else None))
if self._items:
command_attribute = (latest_command_attribute
if (self._pointer == (len(self._items) - 1))
else old_command_attribute)
output += RL(" | ")
if self._pointer != len(self._items) - 1:
output += RL("(-%d) " % (len(self._items) - 1 - self._pointer),
command_attribute)
if len(output) < max_length:
maybe_truncated_command = self._items[self._pointer].command[
:(max_length - len(output))]
output += RL(maybe_truncated_command, command_attribute)
return debugger_cli_common.rich_text_lines_from_rich_line_list([output])
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Unit tests for curses-based CLI widgets."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from tensorflow.python.debug.cli import curses_widgets
from tensorflow.python.debug.cli import debugger_cli_common
from tensorflow.python.framework import test_util
from tensorflow.python.platform import googletest
RTL = debugger_cli_common.RichTextLines
CNH = curses_widgets.CursesNavigationHistory
class CNHTest(test_util.TensorFlowTestCase):
def testConstructorWorks(self):
CNH(10)
def testConstructorWithInvalidCapacityErrors(self):
with self.assertRaises(ValueError):
CNH(0)
with self.assertRaises(ValueError):
CNH(-1)
def testInitialStateIsCorrect(self):
nav_history = CNH(10)
self.assertEqual(0, nav_history.size())
self.assertFalse(nav_history.can_go_forward())
self.assertFalse(nav_history.can_go_back())
with self.assertRaisesRegexp(ValueError, "Empty navigation history"):
nav_history.go_back()
with self.assertRaisesRegexp(ValueError, "Empty navigation history"):
nav_history.go_forward()
with self.assertRaisesRegexp(ValueError, "Empty navigation history"):
nav_history.update_scroll_position(3)
def testAddOneItemWorks(self):
nav_history = CNH(10)
nav_history.add_item("foo", RTL(["bar"]), 0)
self.assertEqual(1, nav_history.size())
self.assertEqual(0, nav_history.pointer())
self.assertFalse(nav_history.can_go_forward())
self.assertFalse(nav_history.can_go_back())
output = nav_history.go_back()
self.assertEqual("foo", output.command)
self.assertEqual(["bar"], output.screen_output.lines)
self.assertEqual(0, output.scroll_position)
def testAddItemsBeyondCapacityWorks(self):
nav_history = CNH(2)
nav_history.add_item("foo", RTL(["foo_output"]), 0)
nav_history.add_item("bar", RTL(["bar_output"]), 0)
self.assertEqual(2, nav_history.size())
self.assertEqual(1, nav_history.pointer())
self.assertTrue(nav_history.can_go_back())
self.assertFalse(nav_history.can_go_forward())
nav_history.add_item("baz", RTL(["baz_output"]), 0)
self.assertEqual(2, nav_history.size())
self.assertEqual(1, nav_history.pointer())
self.assertTrue(nav_history.can_go_back())
self.assertFalse(nav_history.can_go_forward())
item = nav_history.go_back()
self.assertEqual("bar", item.command)
self.assertFalse(nav_history.can_go_back())
self.assertTrue(nav_history.can_go_forward())
item = nav_history.go_forward()
self.assertEqual("baz", item.command)
self.assertTrue(nav_history.can_go_back())
self.assertFalse(nav_history.can_go_forward())
def testAddItemFromNonLatestPointerPositionWorks(self):
nav_history = CNH(2)
nav_history.add_item("foo", RTL(["foo_output"]), 0)
nav_history.add_item("bar", RTL(["bar_output"]), 0)
nav_history.go_back()
nav_history.add_item("baz", RTL(["baz_output"]), 0)
self.assertEqual(2, nav_history.size())
self.assertEqual(1, nav_history.pointer())
self.assertTrue(nav_history.can_go_back())
self.assertFalse(nav_history.can_go_forward())
item = nav_history.go_back()
self.assertEqual("foo", item.command)
item = nav_history.go_forward()
self.assertEqual("baz", item.command)
def testUpdateScrollPositionOnLatestItemWorks(self):
nav_history = CNH(2)
nav_history.add_item("foo", RTL(["foo_out", "more_foo_out"]), 0)
nav_history.add_item("bar", RTL(["bar_out", "more_bar_out"]), 0)
nav_history.update_scroll_position(1)
nav_history.go_back()
item = nav_history.go_forward()
self.assertEqual("bar", item.command)
self.assertEqual(1, item.scroll_position)
def testUpdateScrollPositionOnOldItemWorks(self):
nav_history = CNH(2)
nav_history.add_item("foo", RTL(["foo_out", "more_foo_out"]), 0)
nav_history.add_item("bar", RTL(["bar_out", "more_bar_out"]), 0)
item = nav_history.go_back()
self.assertEqual("foo", item.command)
self.assertEqual(0, item.scroll_position)
nav_history.update_scroll_position(1)
nav_history.go_forward()
item = nav_history.go_back()
self.assertEqual("foo", item.command)
self.assertEqual(1, item.scroll_position)
item = nav_history.go_forward()
self.assertEqual("bar", item.command)
self.assertEqual(0, item.scroll_position)
def testRenderWithEmptyHistoryWorks(self):
nav_history = CNH(2)
output = nav_history.render(40, "prev", "next")
self.assertEqual(1, len(output.lines))
self.assertEqual(
"| " + CNH.BACK_ARROW_TEXT + " " + CNH.FORWARD_ARROW_TEXT,
output.lines[0])
self.assertEqual({}, output.font_attr_segs)
def testRenderLatestWithSufficientLengthWorks(self):
nav_history = CNH(2)
nav_history.add_item("foo", RTL(["foo_out", "more_foo_out"]), 0)
nav_history.add_item("bar", RTL(["bar_out", "more_bar_out"]), 0)
output = nav_history.render(
40,
"prev",
"next",
latest_command_attribute="green",
old_command_attribute="yellow")
self.assertEqual(1, len(output.lines))
self.assertEqual(
"| " + CNH.BACK_ARROW_TEXT + " " + CNH.FORWARD_ARROW_TEXT +
" | bar",
output.lines[0])
self.assertEqual(2, output.font_attr_segs[0][0][0])
self.assertEqual(5, output.font_attr_segs[0][0][1])
self.assertEqual("prev", output.font_attr_segs[0][0][2].content)
self.assertEqual(12, output.font_attr_segs[0][1][0])
self.assertEqual(15, output.font_attr_segs[0][1][1])
self.assertEqual("green", output.font_attr_segs[0][1][2])
def testRenderOldButNotOldestWithSufficientLengthWorks(self):
nav_history = CNH(3)
nav_history.add_item("foo", RTL(["foo_out", "more_foo_out"]), 0)
nav_history.add_item("bar", RTL(["bar_out", "more_bar_out"]), 0)
nav_history.add_item("baz", RTL(["baz_out", "more_baz_out"]), 0)
nav_history.go_back()
output = nav_history.render(
40,
"prev",
"next",
latest_command_attribute="green",
old_command_attribute="yellow")
self.assertEqual(1, len(output.lines))
self.assertEqual(
"| " + CNH.BACK_ARROW_TEXT + " " + CNH.FORWARD_ARROW_TEXT +
" | (-1) bar",
output.lines[0])
self.assertEqual(2, output.font_attr_segs[0][0][0])
self.assertEqual(5, output.font_attr_segs[0][0][1])
self.assertEqual("prev", output.font_attr_segs[0][0][2].content)
self.assertEqual(6, output.font_attr_segs[0][1][0])
self.assertEqual(9, output.font_attr_segs[0][1][1])
self.assertEqual("next", output.font_attr_segs[0][1][2].content)
self.assertEqual(12, output.font_attr_segs[0][2][0])
self.assertEqual(17, output.font_attr_segs[0][2][1])
self.assertEqual("yellow", output.font_attr_segs[0][2][2])
self.assertEqual(17, output.font_attr_segs[0][3][0])
self.assertEqual(20, output.font_attr_segs[0][3][1])
self.assertEqual("yellow", output.font_attr_segs[0][3][2])
def testRenderOldestWithSufficientLengthWorks(self):
nav_history = CNH(3)
nav_history.add_item("foo", RTL(["foo_out", "more_foo_out"]), 0)
nav_history.add_item("bar", RTL(["bar_out", "more_bar_out"]), 0)
nav_history.add_item("baz", RTL(["baz_out", "more_baz_out"]), 0)
nav_history.go_back()
nav_history.go_back()
output = nav_history.render(
40,
"prev",
"next",
latest_command_attribute="green",
old_command_attribute="yellow")
self.assertEqual(1, len(output.lines))
self.assertEqual(
"| " + CNH.BACK_ARROW_TEXT + " " + CNH.FORWARD_ARROW_TEXT +
" | (-2) foo",
output.lines[0])
self.assertEqual(6, output.font_attr_segs[0][0][0])
self.assertEqual(9, output.font_attr_segs[0][0][1])
self.assertEqual("next", output.font_attr_segs[0][0][2].content)
self.assertEqual(12, output.font_attr_segs[0][1][0])
self.assertEqual(17, output.font_attr_segs[0][1][1])
self.assertEqual("yellow", output.font_attr_segs[0][1][2])
self.assertEqual(17, output.font_attr_segs[0][2][0])
self.assertEqual(20, output.font_attr_segs[0][2][1])
self.assertEqual("yellow", output.font_attr_segs[0][2][2])
def testRenderWithInsufficientLengthWorks(self):
nav_history = CNH(2)
nav_history.add_item("long_command", RTL(["output"]), 0)
output = nav_history.render(
15,
"prev",
"next",
latest_command_attribute="green",
old_command_attribute="yellow")
self.assertEqual(1, len(output.lines))
self.assertEqual(
"| " + CNH.BACK_ARROW_TEXT + " " + CNH.FORWARD_ARROW_TEXT +
" | lon",
output.lines[0])
self.assertEqual(12, output.font_attr_segs[0][0][0])
self.assertEqual(15, output.font_attr_segs[0][0][1])
self.assertEqual("green", output.font_attr_segs[0][0][2])
if __name__ == "__main__":
googletest.main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册