def testWrappingInvalidArguments(self): with self.assertRaisesRegexp(ValueError, "Invalid type of input screen_output"): debugger_cli_common.wrap_rich_text_lines("foo", 12) with self.assertRaisesRegexp(ValueError, "Invalid type of input cols"): debugger_cli_common.wrap_rich_text_lines( debugger_cli_common.RichTextLines(["foo", "bar"]), "12")
def testWrappingInvalidArguments(self): with self.assertRaisesRegexp(ValueError, "Invalid type of input screen_output"): debugger_cli_common.wrap_rich_text_lines("foo", 12) with self.assertRaisesRegexp(ValueError, "Invalid type of input cols"): debugger_cli_common.wrap_rich_text_lines( debugger_cli_common.RichTextLines(["foo", "bar"]), "12")
def _display_candidates(self, candidates): """Show candidates (e.g., tab-completion candidates) on multiple lines. Args: candidates: (list of str) candidates. """ if self._curr_unwrapped_output: # Force refresh screen output. self._scroll_output(self._SCROLL_REFRESH) if not candidates: return candidates_prefix = "Candidates: " candidates_line = candidates_prefix + " ".join(candidates) candidates_output = debugger_cli_common.RichTextLines( candidates_line, font_attr_segs={0: [(len(candidates_prefix), len(candidates_line), "yellow")]} ) candidates_output, _ = debugger_cli_common.wrap_rich_text_lines(candidates_output, self._max_x - 2) # Calculate how many lines the candidate text should occupy. Limit it to # a maximum value. candidates_num_rows = min(len(candidates_output.lines), self._candidates_max_lines) self._candidates_top_row = self._candidates_bottom_row - candidates_num_rows + 1 # Render the candidate text on screen. pad, _, _ = self._display_lines(candidates_output, 0) self._screen_scroll_output_pad( pad, 0, 0, self._candidates_top_row, 0, self._candidates_top_row + candidates_num_rows - 1, self._max_x - 1 )
def testWrappingWithAttrCutoff(self): out = debugger_cli_common.wrap_rich_text_lines(self._orig_screen_output, 11) # Check wrapped text. self.assertEqual(5, len(out.lines)) self.assertEqual("Folk song:", out.lines[0]) self.assertEqual("Roses are r", out.lines[1]) self.assertEqual("ed", out.lines[2]) self.assertEqual("Violets are", out.lines[3]) self.assertEqual(" blue", out.lines[4]) # Check wrapped font_attr_segs. self.assertFalse(0 in out.font_attr_segs) self.assertEqual([(0, 5, "red"), (6, 9, "gray"), (10, 11, "red")], out.font_attr_segs[1]) self.assertEqual([(0, 1, "red"), (1, 2, "crimson")], out.font_attr_segs[2]) self.assertEqual([(0, 7, "blue"), (8, 11, "gray")], out.font_attr_segs[3]) self.assertEqual([(1, 3, "blue"), (3, 5, "indigo")], out.font_attr_segs[4]) # Check annotations. self.assertFalse(0 in out.annotations) self.assertEqual("longer wavelength", out.annotations[1]) self.assertFalse(2 in out.annotations) self.assertEqual("shorter wavelength", out.annotations[3]) self.assertFalse(4 in out.annotations)
def _screen_display_output(self, output): """Actually render text output on the screen. Wraps the lines according to screen width. Pad lines below according to screen height so that the user can scroll the output to a state where the last non-empty line is on the top of the screen. Then renders the lines on the screen. Args: output: (RichTextLines) text lines to display on the screen. These lines may have widths exceeding the screen width. This method will take care of the wrapping. """ # Wrap the output lines according to screen width. self._curr_wrapped_output = debugger_cli_common.wrap_rich_text_lines( output, self._max_x - 1) # Append lines to curr_wrapped_output so that the user can scroll to a # state where the last text line is on the top of the output area. self._curr_wrapped_output.lines.extend([""] * (self._output_num_rows - 1)) # Limit number of lines displayed to avoid curses overflow problems. if self._curr_wrapped_output.num_lines() > self.max_output_lines: self._curr_wrapped_output = self._curr_wrapped_output.slice( 0, self.max_output_lines) self._curr_wrapped_output.lines.append("Output cut off at %d lines!" % self.max_output_lines) self._curr_wrapped_output.font_attr_segs[self.max_output_lines] = [ (0, len(output.lines[-1]), "magenta") ] (self._output_pad, self._output_pad_height, self._output_pad_width) = self._display_lines(self._curr_wrapped_output, self._output_num_rows)
def _display_main_menu(self, output): """Display main menu associated with screen output, if the menu exists. Args: output: (debugger_cli_common.RichTextLines) The RichTextLines output from the annotations field of which the menu will be extracted and used (if the menu exists). """ if debugger_cli_common.MAIN_MENU_KEY in output.annotations: self._main_menu = output.annotations[ debugger_cli_common.MAIN_MENU_KEY].format_as_single_line( prefix="| ", divider=" | ", enabled_item_attrs=["underline"]) self._main_menu_pad = self._screen_new_output_pad( 1, self._max_x - 1) # The unwrapped menu line may exceed screen width, in which case it needs # to be cut off. wrapped_menu, _ = debugger_cli_common.wrap_rich_text_lines( self._main_menu, self._max_x - 2) self._screen_add_line_to_output_pad( self._main_menu_pad, 0, wrapped_menu.lines[0], color_segments=(wrapped_menu.font_attr_segs[0] if 0 in wrapped_menu.font_attr_segs else None)) else: self._main_menu = None self._main_menu_pad = None
def testWrappingWithAttrCutoff(self): out, new_line_indices = debugger_cli_common.wrap_rich_text_lines( self._orig_screen_output, 11) # Add non-row-index field to out. out.annotations["metadata"] = "foo" # Check wrapped text. self.assertEqual(5, len(out.lines)) self.assertEqual("Folk song:", out.lines[0]) self.assertEqual("Roses are r", out.lines[1]) self.assertEqual("ed", out.lines[2]) self.assertEqual("Violets are", out.lines[3]) self.assertEqual(" blue", out.lines[4]) # Check wrapped font_attr_segs. self.assertFalse(0 in out.font_attr_segs) self.assertEqual([(0, 5, "red"), (6, 9, "gray"), (10, 11, "red")], out.font_attr_segs[1]) self.assertEqual([(0, 1, "red"), (1, 2, "crimson")], out.font_attr_segs[2]) self.assertEqual([(0, 7, "blue"), (8, 11, "gray")], out.font_attr_segs[3]) self.assertEqual([(1, 3, "blue"), (3, 5, "indigo")], out.font_attr_segs[4]) # Check annotations. self.assertFalse(0 in out.annotations) self.assertEqual("longer wavelength", out.annotations[1]) self.assertFalse(2 in out.annotations) self.assertEqual("shorter wavelength", out.annotations[3]) self.assertFalse(4 in out.annotations) # Chec that the non-row-index field is present in output. self.assertEqual("foo", out.annotations["metadata"]) self.assertEqual(new_line_indices, [0, 1, 3])
def testWrappingWithAttrCutoff(self): out, new_line_indices = debugger_cli_common.wrap_rich_text_lines( self._orig_screen_output, 11) # Add non-row-index field to out. out.annotations["metadata"] = "foo" # Check wrapped text. self.assertEqual(5, len(out.lines)) self.assertEqual("Folk song:", out.lines[0]) self.assertEqual("Roses are r", out.lines[1]) self.assertEqual("ed", out.lines[2]) self.assertEqual("Violets are", out.lines[3]) self.assertEqual(" blue", out.lines[4]) # Check wrapped font_attr_segs. self.assertFalse(0 in out.font_attr_segs) self.assertEqual([(0, 5, "red"), (6, 9, "gray"), (10, 11, "red")], out.font_attr_segs[1]) self.assertEqual([(0, 1, "red"), (1, 2, "crimson")], out.font_attr_segs[2]) self.assertEqual([(0, 7, "blue"), (8, 11, "gray")], out.font_attr_segs[3]) self.assertEqual([(1, 3, "blue"), (3, 5, "indigo")], out.font_attr_segs[4]) # Check annotations. self.assertFalse(0 in out.annotations) self.assertEqual("longer wavelength", out.annotations[1]) self.assertFalse(2 in out.annotations) self.assertEqual("shorter wavelength", out.annotations[3]) self.assertFalse(4 in out.annotations) # Chec that the non-row-index field is present in output. self.assertEqual("foo", out.annotations["metadata"]) self.assertEqual(new_line_indices, [0, 1, 3])
def _display_main_menu(self, output): """Display main menu associated with screen output, if the menu exists. Args: output: (debugger_cli_common.RichTextLines) The RichTextLines output from the annotations field of which the menu will be extracted and used (if the menu exists). """ if debugger_cli_common.MAIN_MENU_KEY in output.annotations: self._main_menu = output.annotations[ debugger_cli_common.MAIN_MENU_KEY].format_as_single_line( prefix="| ", divider=" | ", enabled_item_attrs=["underline"]) self._main_menu_pad = self._screen_new_output_pad(1, self._max_x - 2) # The unwrapped menu line may exceed screen width, in which case it needs # to be cut off. wrapped_menu, _ = debugger_cli_common.wrap_rich_text_lines( self._main_menu, self._max_x - 3) self._screen_add_line_to_output_pad( self._main_menu_pad, 0, wrapped_menu.lines[0], color_segments=(wrapped_menu.font_attr_segs[0] if 0 in wrapped_menu.font_attr_segs else None)) else: self._main_menu = None self._main_menu_pad = None
def testWrappingWithAttrCutoff(self): out = debugger_cli_common.wrap_rich_text_lines(self._orig_screen_output, 11) # Check wrapped text. self.assertEqual(5, len(out.lines)) self.assertEqual("Folk song:", out.lines[0]) self.assertEqual("Roses are r", out.lines[1]) self.assertEqual("ed", out.lines[2]) self.assertEqual("Violets are", out.lines[3]) self.assertEqual(" blue", out.lines[4]) # Check wrapped font_attr_segs. self.assertFalse(0 in out.font_attr_segs) self.assertEqual([(0, 5, "red"), (6, 9, "gray"), (10, 11, "red")], out.font_attr_segs[1]) self.assertEqual([(0, 1, "red"), (1, 2, "crimson")], out.font_attr_segs[2]) self.assertEqual([(0, 7, "blue"), (8, 11, "gray")], out.font_attr_segs[3]) self.assertEqual([(1, 3, "blue"), (3, 5, "indigo")], out.font_attr_segs[4]) # Check annotations. self.assertFalse(0 in out.annotations) self.assertEqual("longer wavelength", out.annotations[1]) self.assertFalse(2 in out.annotations) self.assertEqual("shorter wavelength", out.annotations[3]) self.assertFalse(4 in out.annotations)
def testNoActualWrapping(self): # Large column limit should lead to no actual wrapping. out = debugger_cli_common.wrap_rich_text_lines(self._orig_screen_output, 100) self.assertEqual(self._orig_screen_output.lines, out.lines) self.assertEqual(self._orig_screen_output.font_attr_segs, out.font_attr_segs) self.assertEqual(self._orig_screen_output.annotations, out.annotations)
def testNoActualWrapping(self): # Large column limit should lead to no actual wrapping. out = debugger_cli_common.wrap_rich_text_lines(self._orig_screen_output, 100) self.assertEqual(self._orig_screen_output.lines, out.lines) self.assertEqual(self._orig_screen_output.font_attr_segs, out.font_attr_segs) self.assertEqual(self._orig_screen_output.annotations, out.annotations)
def testWrappingWithMultipleAttrCutoff(self): self._orig_screen_output = debugger_cli_common.RichTextLines( ["Folk song:", "Roses are red", "Violets are blue"], font_attr_segs={ 1: [(0, 12, "red")], 2: [(1, 16, "blue")] }, annotations={ 1: "longer wavelength", 2: "shorter wavelength" }) out, new_line_indices = debugger_cli_common.wrap_rich_text_lines( self._orig_screen_output, 5) # Check wrapped text. self.assertEqual(9, len(out.lines)) self.assertEqual("Folk ", out.lines[0]) self.assertEqual("song:", out.lines[1]) self.assertEqual("Roses", out.lines[2]) self.assertEqual(" are ", out.lines[3]) self.assertEqual("red", out.lines[4]) self.assertEqual("Viole", out.lines[5]) self.assertEqual("ts ar", out.lines[6]) self.assertEqual("e blu", out.lines[7]) self.assertEqual("e", out.lines[8]) # Check wrapped font_attr_segs. self.assertFalse(0 in out.font_attr_segs) self.assertFalse(1 in out.font_attr_segs) self.assertEqual([(0, 5, "red")], out.font_attr_segs[2]) self.assertEqual([(0, 5, "red")], out.font_attr_segs[3]) self.assertEqual([(0, 2, "red")], out.font_attr_segs[4]) self.assertEqual([(1, 5, "blue")], out.font_attr_segs[5]) self.assertEqual([(0, 5, "blue")], out.font_attr_segs[6]) self.assertEqual([(0, 5, "blue")], out.font_attr_segs[7]) self.assertEqual([(0, 1, "blue")], out.font_attr_segs[8]) # Check annotations. self.assertFalse(0 in out.annotations) self.assertFalse(1 in out.annotations) self.assertEqual("longer wavelength", out.annotations[2]) self.assertFalse(3 in out.annotations) self.assertFalse(4 in out.annotations) self.assertEqual("shorter wavelength", out.annotations[5]) self.assertFalse(6 in out.annotations) self.assertFalse(7 in out.annotations) self.assertFalse(8 in out.annotations) self.assertEqual(new_line_indices, [0, 2, 5])
def _display_output(self, output, is_refresh=False, highlight_regex=None): """Display text output in a scrollable text pad. Args: output: A RichTextLines object that is the screen output text. is_refresh: (bool) Is this a refreshing display with existing output. highlight_regex: (str) Optional string representing the regex used to search and highlight in the current screen output. """ if highlight_regex: output = debugger_cli_common.regex_find( output, highlight_regex, font_attr=self._search_highlight_font_attr) else: self._curr_unwrapped_output = output self._curr_wrapped_output = debugger_cli_common.wrap_rich_text_lines( output, self._max_x - 1) # Limit number of lines displayed to avoid curses overflow problems. if self._curr_wrapped_output.num_lines() > self.max_output_lines: self._curr_wrapped_output = self._curr_wrapped_output.slice( 0, self.max_output_lines) self._curr_wrapped_output.lines.append( "Output cut off at %d lines!" % self.max_output_lines) self._curr_wrapped_output.font_attr_segs[self.max_output_lines] = [ (0, len(output.lines[-1]), "magenta") ] (self._output_pad, self._output_pad_height, self._output_pad_width) = self._display_lines( self._curr_wrapped_output, self._output_num_rows) # Size of view port on screen, which is always smaller or equal to the # screen size. self._output_pad_screen_height = self._output_num_rows - 1 self._output_pad_screen_width = self._max_x - 1 self._output_pad_screen_location = self.rectangle( top=self._output_top_row, left=0, bottom=self._output_top_row + self._output_num_rows, right=self._output_pad_screen_width) if is_refresh: self._scroll_output(self._SCROLL_REFRESH) else: self._output_pad_row = 0 self._scroll_output(self._SCROLL_HOME)
def _screen_display_output(self, output): """Actually render text output on the screen. Wraps the lines according to screen width. Pad lines below according to screen height so that the user can scroll the output to a state where the last non-empty line is on the top of the screen. Then renders the lines on the screen. Args: output: (RichTextLines) text lines to display on the screen. These lines may have widths exceeding the screen width. This method will take care of the wrapping. Returns: (List of int) A list of line indices, in the wrapped output, where there are regex matches. """ # Wrap the output lines according to screen width. self._curr_wrapped_output, wrapped_line_indices = ( debugger_cli_common.wrap_rich_text_lines(output, self._max_x - 1)) # Append lines to curr_wrapped_output so that the user can scroll to a # state where the last text line is on the top of the output area. self._curr_wrapped_output.lines.extend([""] * (self._output_num_rows - 1)) # Limit number of lines displayed to avoid curses overflow problems. if self._curr_wrapped_output.num_lines() > self.max_output_lines: self._curr_wrapped_output = self._curr_wrapped_output.slice( 0, self.max_output_lines) self._curr_wrapped_output.lines.append( "Output cut off at %d lines!" % self.max_output_lines) self._curr_wrapped_output.font_attr_segs[self.max_output_lines] = [ (0, len(output.lines[-1]), "magenta") ] self._display_main_menu(self._curr_wrapped_output) (self._output_pad, self._output_pad_height, self._output_pad_width) = self._display_lines( self._curr_wrapped_output, self._output_num_rows) # The indices of lines with regex matches (if any) need to be mapped to # indices of wrapped lines. return [ wrapped_line_indices[line] for line in self._unwrapped_regex_match_lines ]
def testWrappingWithMultipleAttrCutoff(self): self._orig_screen_output = debugger_cli_common.RichTextLines( ["Folk song:", "Roses are red", "Violets are blue"], font_attr_segs={ 1: [(0, 12, "red")], 2: [(1, 16, "blue")] }, annotations={ 1: "longer wavelength", 2: "shorter wavelength" }) out = debugger_cli_common.wrap_rich_text_lines( self._orig_screen_output, 5) # Check wrapped text. self.assertEqual(9, len(out.lines)) self.assertEqual("Folk ", out.lines[0]) self.assertEqual("song:", out.lines[1]) self.assertEqual("Roses", out.lines[2]) self.assertEqual(" are ", out.lines[3]) self.assertEqual("red", out.lines[4]) self.assertEqual("Viole", out.lines[5]) self.assertEqual("ts ar", out.lines[6]) self.assertEqual("e blu", out.lines[7]) self.assertEqual("e", out.lines[8]) # Check wrapped font_attr_segs. self.assertFalse(0 in out.font_attr_segs) self.assertFalse(1 in out.font_attr_segs) self.assertEqual([(0, 5, "red")], out.font_attr_segs[2]) self.assertEqual([(0, 5, "red")], out.font_attr_segs[3]) self.assertEqual([(0, 2, "red")], out.font_attr_segs[4]) self.assertEqual([(1, 5, "blue")], out.font_attr_segs[5]) self.assertEqual([(0, 5, "blue")], out.font_attr_segs[6]) self.assertEqual([(0, 5, "blue")], out.font_attr_segs[7]) self.assertEqual([(0, 1, "blue")], out.font_attr_segs[8]) # Check annotations. self.assertFalse(0 in out.annotations) self.assertFalse(1 in out.annotations) self.assertEqual("longer wavelength", out.annotations[2]) self.assertFalse(3 in out.annotations) self.assertFalse(4 in out.annotations) self.assertEqual("shorter wavelength", out.annotations[5]) self.assertFalse(6 in out.annotations) self.assertFalse(7 in out.annotations) self.assertFalse(8 in out.annotations)
def _display_output(self, output, is_refresh=False, highlight_regex=None): """Display text output in a scrollable text pad. Args: output: A RichTextLines object that is the screen output text. is_refresh: (bool) Is this a refreshing display with existing output. highlight_regex: (str) Optional string representing the regex used to search and highlight in the current screen output. """ if highlight_regex: output = debugger_cli_common.regex_find( output, highlight_regex, font_attr=self._search_highlight_font_attr) else: self._curr_unwrapped_output = output self._curr_wrapped_output = debugger_cli_common.wrap_rich_text_lines( output, self._max_x - 1) # Limit number of lines displayed to avoid curses overflow problems. if self._curr_wrapped_output.num_lines() > self.max_output_lines: self._curr_wrapped_output = self._curr_wrapped_output.slice( 0, self.max_output_lines) self._curr_wrapped_output.lines.append("Output cut off at %d lines!" % self.max_output_lines) self._curr_wrapped_output.font_attr_segs[self.max_output_lines] = [ (0, len(output.lines[-1]), "magenta") ] (self._output_pad, self._output_pad_height, self._output_pad_width) = self._display_lines(self._curr_wrapped_output, self._output_num_rows) # Size of view port on screen, which is always smaller or equal to the # screen size. self._output_pad_screen_height = self._output_num_rows - 1 self._output_pad_screen_width = self._max_x - 1 self._output_pad_screen_location = self.rectangle( top=self._output_top_row, left=0, bottom=self._output_top_row + self._output_num_rows, right=self._output_pad_screen_width) if is_refresh: self._scroll_output(self._SCROLL_REFRESH) else: self._output_pad_row = 0 self._scroll_output(self._SCROLL_HOME)
def _display_candidates(self, candidates): """Show candidates (e.g., tab-completion candidates) on multiple lines. Args: candidates: (list of str) candidates. """ if self._curr_unwrapped_output: # Force refresh screen output. self._scroll_output(self._SCROLL_REFRESH) if not candidates: return candidates_prefix = "Candidates: " candidates_line = candidates_prefix + " ".join(candidates) candidates_output = debugger_cli_common.RichTextLines( candidates_line, font_attr_segs={ 0: [(len(candidates_prefix), len(candidates_line), "yellow")] }) candidates_output, _ = debugger_cli_common.wrap_rich_text_lines( candidates_output, self._max_x - 2) # Calculate how many lines the candidate text should occupy. Limit it to # a maximum value. candidates_num_rows = min(len(candidates_output.lines), self._candidates_max_lines) self._candidates_top_row = (self._candidates_bottom_row - candidates_num_rows + 1) # Render the candidate text on screen. pad, _, _ = self._display_lines(candidates_output, 0) self._screen_scroll_output_pad( pad, 0, 0, self._candidates_top_row, 0, self._candidates_top_row + candidates_num_rows - 1, self._max_x - 1)
def _display_output(self, output, is_refresh=False, highlight_regex=None): """Display text output in a scrollable text pad. Args: output: A RichTextLines object that is the screen output text. is_refresh: (bool) Is this a refreshing display with existing output. highlight_regex: (str) Optional string representing the regex used to search and highlight in the current screen output. """ if highlight_regex: output = debugger_cli_common.regex_find( output, highlight_regex, font_attr=self._search_highlight_font_attr) else: self._curr_unwrapped_output = output self._curr_wrapped_output = debugger_cli_common.wrap_rich_text_lines( output, self._max_x - 1) (self._output_pad, self._output_pad_height, self._output_pad_width) = self._display_lines(self._curr_wrapped_output, self._output_num_rows) # Size of view port on screen, which is always smaller or equal to the # screen size. self._output_pad_screen_height = self._output_num_rows - 1 self._output_pad_screen_width = self._max_x - 1 self._output_pad_screen_location = self.rectangle( top=self._output_top_row, left=0, bottom=self._output_top_row + self._output_num_rows, right=self._output_pad_screen_width) if is_refresh: self._scroll_output(self._SCROLL_REFRESH) else: self._output_pad_row = 0 self._scroll_output(self._SCROLL_HOME)
def _display_lines(self, output): """Display RichTextLines object on screen. Args: output: A RichTextLines object. Raises: ValueError: If input argument "output" is invalid. """ if not isinstance(output, debugger_cli_common.RichTextLines): raise ValueError( "Output is required to be an instance of RichTextLines, but is not." ) self._curr_unwrapped_output = output # TODO(cais): Cut off output with too many lines to prevent overflow issues # in curses. cols = self._max_x self._curr_wrapped_output = debugger_cli_common.wrap_rich_text_lines( output, cols - 1) self._screen_refresh() # Minimum number of rows that the output area has to have: Screen height # space above the output region, the height of the command textbox and # the single scroll information row. min_rows = (self._max_y - self._output_top_row - self._command_textbox_height - 1) rows = max(min_rows, len(self._curr_wrapped_output.lines)) # Size of the output pad, which may exceed screen size and require # scrolling. self._output_pad_height = rows self._output_pad_width = cols # Size of view port on screen, which is always smaller or equal to the # screen size. self._output_pad_scr_height = min_rows - 1 self._output_pad_scr_width = cols # Create new output pad. self._screen_new_output_pad(rows, cols) for i in xrange(len(self._curr_wrapped_output.lines)): if i in self._curr_wrapped_output.font_attr_segs: self._screen_add_line_to_output_pad( i, self._curr_wrapped_output.lines[i], color_segments=self._curr_wrapped_output.font_attr_segs[i]) else: self._screen_add_line_to_output_pad( i, self._curr_wrapped_output.lines[i]) # 1st row of the output pad to be displayed: Scroll to top first. self._output_pad_row = 0 # The location of the rectangular viewport on the screen. self._output_pad_scr_loc = [ self._output_top_row, 0, self._output_top_row + min_rows, cols ] self._scroll_output("home")
def testWrappingEmptyInput(self): out, new_line_indices = debugger_cli_common.wrap_rich_text_lines( debugger_cli_common.RichTextLines([]), 10) self.assertEqual([], out.lines) self.assertEqual([], new_line_indices)
def testWrappingEmptyInput(self): out, new_line_indices = debugger_cli_common.wrap_rich_text_lines( debugger_cli_common.RichTextLines([]), 10) self.assertEqual([], out.lines) self.assertEqual([], new_line_indices)