diff --git a/benchmark.py b/benchmark.py index 75657db..b52787e 100644 --- a/benchmark.py +++ b/benchmark.py @@ -10,6 +10,15 @@ ..................... ls.input: Mean +- std dev: 644 ns +- 23 ns + $ BENCHMARK=tests/captured/ls.input GEOMETRY=1024x1024 python benchmark.py -o results.json + ..................... + ls.input: Mean +- std dev: 644 ns +- 23 ns + + Environment variables: + + BENCHMARK: the input file to feed pyte's Stream and render on the Screen + GEOMETRY: the dimensions of the screen with format "x" (default 24x80) + :copyright: (c) 2016-2021 by pyte authors and contributors, see AUTHORS for details. :license: LGPL, see LICENSE for more details. @@ -27,21 +36,54 @@ import pyte - -def make_benchmark(path, screen_cls): - with io.open(path, "rt", encoding="utf-8") as handle: +def setup(path, screen_cls, columns, lines): + with io.open(path, "rb") as handle: data = handle.read() - stream = pyte.Stream(screen_cls(80, 24)) + screen = screen_cls(columns, lines) + stream = pyte.ByteStream(screen) + + return data, screen, stream + +def make_stream_feed_benchmark(path, screen_cls, columns, lines): + data, _, stream = setup(path, screen_cls, columns, lines) return partial(stream.feed, data) +def make_screen_display_benchmark(path, screen_cls, columns, lines): + data, screen, stream = setup(path, screen_cls, columns, lines) + stream.feed(data) + return lambda: screen.display + +def make_screen_reset_benchmark(path, screen_cls, columns, lines): + data, screen, stream = setup(path, screen_cls, columns, lines) + stream.feed(data) + return screen.reset + +def make_screen_resize_half_benchmark(path, screen_cls, columns, lines): + data, screen, stream = setup(path, screen_cls, columns, lines) + stream.feed(data) + return partial(screen.resize, lines=lines//2, columns=columns//2) if __name__ == "__main__": benchmark = os.environ["BENCHMARK"] - sys.argv.extend(["--inherit-environ", "BENCHMARK"]) + lines, columns = map(int, os.environ.get("GEOMETRY", "24x80").split('x')) + sys.argv.extend(["--inherit-environ", "BENCHMARK,GEOMETRY"]) runner = Runner() + metadata = { + 'input_file': benchmark, + 'columns': columns, + 'lines': lines + } + + benchmark_name = os.path.basename(benchmark) for screen_cls in [pyte.Screen, pyte.DiffScreen, pyte.HistoryScreen]: - name = os.path.basename(benchmark) + "->" + screen_cls.__name__ - runner.bench_func(name, make_benchmark(benchmark, screen_cls)) + screen_cls_name = screen_cls.__name__ + for make_test in (make_stream_feed_benchmark, make_screen_display_benchmark, make_screen_reset_benchmark, make_screen_resize_half_benchmark): + scenario = make_test.__name__[5:-10] # remove make_ and _benchmark + + name = f"[{scenario} {lines}x{columns}] {benchmark_name}->{screen_cls_name}" + metadata.update({'scenario': scenario, 'screen_cls': screen_cls_name}) + runner.bench_func(name, make_test(benchmark, screen_cls, columns, lines), metadata=metadata) + diff --git a/full_benchmark.sh b/full_benchmark.sh new file mode 100755 index 0000000..02a88dd --- /dev/null +++ b/full_benchmark.sh @@ -0,0 +1,51 @@ +#!/usr/bin/bash + +if [ "$#" != "1" -a "$#" != "2" ]; then + echo "Usage benchmark.sh " + echo "Usage benchmark.sh tracemalloc" + exit 1 +fi + +if [ "$2" = "tracemalloc" ]; then + tracemalloc="--tracemalloc" +elif [ "$2" = "" ]; then + tracemalloc="" +else + echo "Usage benchmark.sh " + echo "Usage benchmark.sh tracemalloc" + exit 1 +fi + +outputfile=$1 + +if [ ! -f benchmark.py ]; then + echo "File benchmark.py missing. Are you in the home folder of pyte project?" + exit 1 +fi + +for inputfile in $(ls -1 tests/captured/*.input); do + export GEOMETRY=24x80 + echo "$inputfile - $GEOMETRY" + echo "======================" + BENCHMARK=$inputfile python benchmark.py $tracemalloc --append $outputfile + + export GEOMETRY=240x800 + echo "$inputfile - $GEOMETRY" + echo "======================" + BENCHMARK=$inputfile python benchmark.py $tracemalloc --append $outputfile + + export GEOMETRY=2400x8000 + echo "$inputfile - $GEOMETRY" + echo "======================" + BENCHMARK=$inputfile python benchmark.py $tracemalloc --append $outputfile + + export GEOMETRY=24x8000 + echo "$inputfile - $GEOMETRY" + echo "======================" + BENCHMARK=$inputfile python benchmark.py $tracemalloc --append $outputfile + + export GEOMETRY=2400x80 + echo "$inputfile - $GEOMETRY" + echo "======================" + BENCHMARK=$inputfile python benchmark.py $tracemalloc --append $outputfile +done diff --git a/pyte/screens.py b/pyte/screens.py index 5e7f759..796acec 100644 --- a/pyte/screens.py +++ b/pyte/screens.py @@ -71,6 +71,7 @@ class Char(namedtuple("Char", [ "strikethrough", "reverse", "blink", + "width", ])): """A single styled on-screen character. @@ -89,15 +90,16 @@ class Char(namedtuple("Char", [ during rendering. Defaults to ``False``. :param bool blink: flag for rendering the character blinked. Defaults to ``False``. + :param bool width: the width in terms of cells to display this char. """ __slots__ = () - def __new__(cls, data, fg="default", bg="default", bold=False, + def __new__(cls, data=" ", fg="default", bg="default", bold=False, italics=False, underscore=False, - strikethrough=False, reverse=False, blink=False): + strikethrough=False, reverse=False, blink=False, width=wcwidth(" ")): return super(Char, cls).__new__(cls, data, fg, bg, bold, italics, underscore, strikethrough, reverse, - blink) + blink, width) class Cursor: @@ -111,7 +113,7 @@ class Cursor: """ __slots__ = ("x", "y", "attrs", "hidden") - def __init__(self, x, y, attrs=Char(" ")): + def __init__(self, x, y, attrs=Char(" ", width=wcwidth(" "))): self.x = x self.y = y self.attrs = attrs @@ -211,7 +213,7 @@ class Screen: def default_char(self): """An empty character with default foreground and background colors.""" reverse = mo.DECSCNM in self.mode - return Char(data=" ", fg="default", bg="default", reverse=reverse) + return Char(data=" ", fg="default", bg="default", reverse=reverse, width=wcwidth(" ")) def __init__(self, columns, lines): self.savepoints = [] @@ -228,18 +230,48 @@ def __repr__(self): @property def display(self): """A :func:`list` of screen lines as unicode strings.""" - def render(line): + padding = self.default_char.data + + prev_y = -1 + output = [] + columns = self.columns + for y, line in sorted(self.buffer.items()): + empty_lines = y - (prev_y + 1) + if empty_lines: + output.extend([padding * columns] * empty_lines) + prev_y = y + is_wide_char = False - for x in range(self.columns): + prev_x = -1 + display_line = [] + for x, cell in sorted(line.items()): + if x >= columns: + break + + gap = x - (prev_x + 1) + if gap: + display_line.append(padding * gap) + + prev_x = x + if is_wide_char: # Skip stub is_wide_char = False continue - char = line[x].data - assert sum(map(wcwidth, char[1:])) == 0 - is_wide_char = wcwidth(char[0]) == 2 - yield char + char = cell.data + is_wide_char = cell.width == 2 + display_line.append(char) + + gap = columns - (prev_x + 1) + if gap: + display_line.append(padding * gap) + + output.append("".join(display_line)) + + empty_lines = self.lines - (prev_y + 1) + if empty_lines: + output.extend([padding * columns] * empty_lines) - return ["".join(render(self.buffer[y])) for y in range(self.lines)] + return output def reset(self): """Reset the terminal to its initial state. @@ -497,16 +529,18 @@ def draw(self, data): line = self.buffer[self.cursor.y] if char_width == 1: - line[self.cursor.x] = self.cursor.attrs._replace(data=char) + line[self.cursor.x] = self.cursor.attrs._replace(data=char, width=char_width) elif char_width == 2: # A two-cell character has a stub slot after it. - line[self.cursor.x] = self.cursor.attrs._replace(data=char) + line[self.cursor.x] = self.cursor.attrs._replace(data=char, width=char_width) if self.cursor.x + 1 < self.columns: line[self.cursor.x + 1] = self.cursor.attrs \ - ._replace(data="") + ._replace(data="", width=0) elif char_width == 0 and unicodedata.combining(char): # A zero-cell character is combined with the previous # character either on this or preceding line. + # Because char's width is zero, this will not change the width + # of the previous character. if self.cursor.x: last = line[self.cursor.x - 1] normalized = unicodedata.normalize("NFC", last.data + char) diff --git a/requirements_dev.txt b/requirements_dev.txt index c1ee84d..8c54e74 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ pytest -pyperf == 1.7.1 +pyperf >= 2.3.0 wcwidth wheel diff --git a/tests/helpers/asserts.py b/tests/helpers/asserts.py new file mode 100644 index 0000000..17aa295 --- /dev/null +++ b/tests/helpers/asserts.py @@ -0,0 +1,22 @@ +from wcwidth import wcwidth +def consistency_asserts(screen): + # Ensure that all the cells in the buffer, if they have + # a data of 2 or more code points, they all sum up 0 width + # In other words, the width of the cell is determinated by the + # width of the first code point. + for y in range(screen.lines): + for x in range(screen.columns): + data = screen.buffer[y][x].data + assert sum(map(wcwidth, data[1:])) == 0 + + + # Ensure consistency between the real width (computed here + # with wcwidth(...)) and the char.width attribute + for y in range(screen.lines): + for x in range(screen.columns): + char = screen.buffer[y][x] + if char.data: + assert wcwidth(char.data[0]) == char.width + else: + assert char.data == "" + assert char.width == 0 diff --git a/tests/test_history.py b/tests/test_history.py index 52084ec..288b940 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,8 +1,10 @@ -import os +import os, sys import pyte from pyte import control as ctrl, modes as mo +sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) +from asserts import consistency_asserts def chars(history_lines, columns): return ["".join(history_lines[y][x].data for x in range(columns)) @@ -96,6 +98,7 @@ def test_prev_page(): "39 ", " " ] + consistency_asserts(screen) assert chars(screen.history.top, screen.columns)[-4:] == [ "33 ", @@ -114,6 +117,7 @@ def test_prev_page(): "37 ", "38 " ] + consistency_asserts(screen) assert chars(screen.history.top, screen.columns)[-4:] == [ "31 ", @@ -138,6 +142,7 @@ def test_prev_page(): "35 ", "36 ", ] + consistency_asserts(screen) assert len(screen.history.bottom) == 4 assert chars(screen.history.bottom, screen.columns) == [ @@ -165,6 +170,7 @@ def test_prev_page(): "49 ", " " ] + consistency_asserts(screen) screen.prev_page() assert screen.history.position == 47 @@ -175,6 +181,7 @@ def test_prev_page(): "46 ", "47 " ] + consistency_asserts(screen) assert len(screen.history.bottom) == 3 assert chars(screen.history.bottom, screen.columns) == [ @@ -200,6 +207,7 @@ def test_prev_page(): "39 ", " " ] + consistency_asserts(screen) screen.prev_page() assert screen.history.position == 37 @@ -209,6 +217,7 @@ def test_prev_page(): "36 ", "37 " ] + consistency_asserts(screen) assert len(screen.history.bottom) == 3 assert chars(screen.history.bottom, screen.columns) == [ @@ -235,6 +244,7 @@ def test_prev_page(): "49 ", " " ] + consistency_asserts(screen) screen.cursor_to_line(screen.lines // 2) @@ -250,6 +260,7 @@ def test_prev_page(): "4 ", "5 " ] + consistency_asserts(screen) while screen.history.position < screen.history.size: screen.next_page() @@ -262,6 +273,7 @@ def test_prev_page(): "49 ", " " ] + consistency_asserts(screen) # e) same with cursor near the middle of the screen. screen = pyte.HistoryScreen(5, 5, history=50) @@ -282,6 +294,7 @@ def test_prev_page(): "49 ", " " ] + consistency_asserts(screen) screen.cursor_to_line(screen.lines // 2 - 2) @@ -297,6 +310,7 @@ def test_prev_page(): "4 ", "5 " ] + consistency_asserts(screen) while screen.history.position < screen.history.size: screen.next_page() @@ -310,6 +324,7 @@ def test_prev_page(): "49 ", " " ] + consistency_asserts(screen) def test_next_page(): @@ -332,6 +347,7 @@ def test_next_page(): "24 ", " " ] + consistency_asserts(screen) # a) page up -- page down. screen.prev_page() @@ -346,6 +362,7 @@ def test_next_page(): "24 ", " " ] + consistency_asserts(screen) # b) double page up -- page down. screen.prev_page() @@ -366,6 +383,7 @@ def test_next_page(): "21 ", "22 " ] + consistency_asserts(screen) # c) double page up -- double page down screen.prev_page() @@ -381,6 +399,7 @@ def test_next_page(): "21 ", "22 " ] + consistency_asserts(screen) def test_ensure_width(monkeypatch): @@ -402,6 +421,7 @@ def test_ensure_width(monkeypatch): "0024 ", " " ] + consistency_asserts(screen) # Shrinking the screen should truncate the displayed lines following lines. screen.resize(5, 3) @@ -416,6 +436,7 @@ def test_ensure_width(monkeypatch): "002", # 21 "002" # 22 ] + consistency_asserts(screen) def test_not_enough_lines(): @@ -436,6 +457,7 @@ def test_not_enough_lines(): "4 ", " " ] + consistency_asserts(screen) screen.prev_page() assert not screen.history.top @@ -448,6 +470,7 @@ def test_not_enough_lines(): "3 ", "4 ", ] + consistency_asserts(screen) screen.next_page() assert screen.history.top @@ -459,6 +482,7 @@ def test_not_enough_lines(): "4 ", " " ] + consistency_asserts(screen) def test_draw(monkeypatch): @@ -479,6 +503,7 @@ def test_draw(monkeypatch): "24 ", " " ] + consistency_asserts(screen) # a) doing a pageup and then a draw -- expecting the screen # to scroll to the bottom before drawing anything. @@ -494,6 +519,7 @@ def test_draw(monkeypatch): "24 ", "x " ] + consistency_asserts(screen) def test_cursor_is_hidden(monkeypatch): diff --git a/tests/test_input_output.py b/tests/test_input_output.py index 4d25c03..ab6535d 100644 --- a/tests/test_input_output.py +++ b/tests/test_input_output.py @@ -1,5 +1,5 @@ import json -import os.path +import os.path, sys import pytest @@ -8,6 +8,8 @@ captured_dir = os.path.join(os.path.dirname(__file__), "captured") +sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) +from asserts import consistency_asserts @pytest.mark.parametrize("name", [ "cat-gpl3", "find-etc", "htop", "ls", "mc", "top", "vi" @@ -23,3 +25,4 @@ def test_input_output(name): stream = pyte.ByteStream(screen) stream.feed(input) assert screen.display == output + consistency_asserts(screen) diff --git a/tests/test_screen.py b/tests/test_screen.py index b6ba90d..b4fe75a 100644 --- a/tests/test_screen.py +++ b/tests/test_screen.py @@ -1,4 +1,4 @@ -import copy +import copy, sys, os import pytest @@ -6,6 +6,8 @@ from pyte import modes as mo, control as ctrl, graphics as g from pyte.screens import Char +sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) +from asserts import consistency_asserts # Test helpers. @@ -230,12 +232,14 @@ def test_resize(): screen = update(pyte.Screen(2, 2), ["bo", "sh"], [None, None]) screen.resize(2, 3) assert screen.display == ["bo ", "sh "] + consistency_asserts(screen) # b) if the current display is wider than the requested size, # columns should be removed from the right... screen = update(pyte.Screen(2, 2), ["bo", "sh"], [None, None]) screen.resize(2, 1) assert screen.display == ["b", "s"] + consistency_asserts(screen) # c) if the current display is shorter than the requested # size, new rows should be added on the bottom. @@ -243,12 +247,14 @@ def test_resize(): screen.resize(3, 2) assert screen.display == ["bo", "sh", " "] + consistency_asserts(screen) # d) if the current display is taller than the requested # size, rows should be removed from the top. screen = update(pyte.Screen(2, 2), ["bo", "sh"], [None, None]) screen.resize(1, 2) assert screen.display == ["sh"] + consistency_asserts(screen) def test_resize_same(): @@ -312,6 +318,7 @@ def test_draw(): assert screen.display == ["abc", " ", " "] assert (screen.cursor.y, screen.cursor.x) == (0, 3) + consistency_asserts(screen) # ... one` more character -- now we got a linefeed! screen.draw("a") @@ -326,11 +333,13 @@ def test_draw(): assert screen.display == ["abc", " ", " "] assert (screen.cursor.y, screen.cursor.x) == (0, 3) + consistency_asserts(screen) # No linefeed is issued on the end of the line ... screen.draw("a") assert screen.display == ["aba", " ", " "] assert (screen.cursor.y, screen.cursor.x) == (0, 3) + consistency_asserts(screen) # ``IRM`` mode is on, expecting new characters to move the old ones # instead of replacing them. @@ -338,10 +347,12 @@ def test_draw(): screen.cursor_position() screen.draw("x") assert screen.display == ["xab", " ", " "] + consistency_asserts(screen) screen.cursor_position() screen.draw("y") assert screen.display == ["yxa", " ", " "] + consistency_asserts(screen) def test_draw_russian(): @@ -350,6 +361,7 @@ def test_draw_russian(): stream = pyte.Stream(screen) stream.feed("Нерусский текст") assert screen.display == ["Нерусский текст "] + consistency_asserts(screen) def test_draw_multiple_chars(): @@ -357,6 +369,7 @@ def test_draw_multiple_chars(): screen.draw("foobar") assert screen.cursor.x == 6 assert screen.display == ["foobar "] + consistency_asserts(screen) def test_draw_utf8(): @@ -365,6 +378,7 @@ def test_draw_utf8(): stream = pyte.ByteStream(screen) stream.feed(b"\xE2\x80\x9D") assert screen.display == ["”"] + consistency_asserts(screen) def test_draw_width2(): @@ -387,12 +401,14 @@ def test_draw_width2_irm(): screen.draw("コ") assert screen.display == ["コ"] assert tolist(screen) == [[Char("コ"), Char(" ")]] + consistency_asserts(screen) # Overwrite the stub part of a width 2 character. screen.set_mode(mo.IRM) screen.cursor_to_column(screen.columns) screen.draw("x") assert screen.display == [" x"] + consistency_asserts(screen) def test_draw_width0_combining(): @@ -401,17 +417,20 @@ def test_draw_width0_combining(): # a) no prev. character screen.draw("\N{COMBINING DIAERESIS}") assert screen.display == [" ", " "] + consistency_asserts(screen) screen.draw("bad") # b) prev. character is on the same line screen.draw("\N{COMBINING DIAERESIS}") assert screen.display == ["bad̈ ", " "] + consistency_asserts(screen) # c) prev. character is on the prev. line screen.draw("!") screen.draw("\N{COMBINING DIAERESIS}") assert screen.display == ["bad̈!̈", " "] + consistency_asserts(screen) def test_draw_width0_irm(): @@ -422,6 +441,7 @@ def test_draw_width0_irm(): screen.draw("\N{ZERO WIDTH SPACE}") screen.draw("\u0007") # DELETE. assert screen.display == [" " * screen.columns] + consistency_asserts(screen) def test_draw_width0_decawm_off(): @@ -446,6 +466,7 @@ def test_draw_cp437(): stream.feed("α ± ε".encode("cp437")) assert screen.display == ["α ± ε"] + consistency_asserts(screen) def test_draw_with_carriage_return(): @@ -465,12 +486,14 @@ def test_draw_with_carriage_return(): "pcrm sem ;ps aux|grep -P 'httpd|fcgi'|grep -v grep", "}'|xargs kill -9;/etc/init.d/httpd startssl " ] + consistency_asserts(screen) def test_display_wcwidth(): screen = pyte.Screen(10, 1) screen.draw("コンニチハ") assert screen.display == ["コンニチハ"] + consistency_asserts(screen) def test_carriage_return(): @@ -519,6 +542,7 @@ def test_index(): [screen.default_char, screen.default_char], [Char("o"), Char("h")], ] + consistency_asserts(screen) # ... and again ... screen.index() @@ -531,6 +555,7 @@ def test_index(): [screen.default_char, screen.default_char], [Char("o"), Char("h")], ] + consistency_asserts(screen) # ... and again ... screen.index() @@ -543,6 +568,7 @@ def test_index(): [screen.default_char, screen.default_char], [Char("o"), Char("h")], ] + consistency_asserts(screen) # look, nothing changes! screen.index() @@ -555,6 +581,7 @@ def test_index(): [screen.default_char, screen.default_char], [Char("o"), Char("h")], ] + consistency_asserts(screen) def test_reverse_index(): @@ -594,6 +621,7 @@ def test_reverse_index(): [Char("t", fg="red"), Char("h", fg="red")], [Char("o"), Char("h")], ] + consistency_asserts(screen) # ... and again ... screen.reverse_index() @@ -606,6 +634,7 @@ def test_reverse_index(): [Char("s"), Char("h")], [Char("o"), Char("h")], ] + consistency_asserts(screen) # ... and again ... screen.reverse_index() @@ -618,6 +647,7 @@ def test_reverse_index(): [screen.default_char, screen.default_char], [Char("o"), Char("h")], ] + consistency_asserts(screen) # look, nothing changes! screen.reverse_index() @@ -630,6 +660,7 @@ def test_reverse_index(): [screen.default_char, screen.default_char], [Char("o"), Char("h")], ] + consistency_asserts(screen) def test_linefeed(): @@ -810,6 +841,7 @@ def test_insert_lines(): [Char("s"), Char("a"), Char("m")], [Char("i", fg="red"), Char("s", fg="red"), Char(" ", fg="red")], ] + consistency_asserts(screen) screen = update(pyte.Screen(3, 3), ["sam", "is ", "foo"], colored=[1]) screen.insert_lines(2) @@ -821,6 +853,7 @@ def test_insert_lines(): [screen.default_char] * 3, [Char("s"), Char("a"), Char("m")] ] + consistency_asserts(screen) # b) with margins screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"], @@ -838,6 +871,7 @@ def test_insert_lines(): [Char("f", fg="red"), Char("o", fg="red"), Char("o", fg="red")], [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"], colored=[2, 3]) @@ -854,6 +888,7 @@ def test_insert_lines(): [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")], [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) screen.insert_lines(2) assert (screen.cursor.y, screen.cursor.x) == (1, 0) @@ -865,6 +900,7 @@ def test_insert_lines(): [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")], [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) # c) with margins -- trying to insert more than we have available screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"], @@ -882,6 +918,7 @@ def test_insert_lines(): [screen.default_char] * 3, [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) # d) with margins -- trying to insert outside scroll boundaries; # expecting nothing to change @@ -899,6 +936,7 @@ def test_insert_lines(): [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")], [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) def test_delete_lines(): @@ -913,6 +951,7 @@ def test_delete_lines(): [Char("f"), Char("o"), Char("o")], [screen.default_char] * 3, ] + consistency_asserts(screen) screen.delete_lines(0) @@ -923,6 +962,7 @@ def test_delete_lines(): [screen.default_char] * 3, [screen.default_char] * 3, ] + consistency_asserts(screen) # b) with margins screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"], @@ -940,6 +980,7 @@ def test_delete_lines(): [screen.default_char] * 3, [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) screen = update(pyte.Screen(3, 5), ["sam", "is ", "foo", "bar", "baz"], colored=[2, 3]) @@ -956,6 +997,7 @@ def test_delete_lines(): [screen.default_char] * 3, [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) # c) with margins -- trying to delete more than we have available screen = update(pyte.Screen(3, 5), @@ -978,6 +1020,7 @@ def test_delete_lines(): [screen.default_char] * 3, [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) # d) with margins -- trying to delete outside scroll boundaries; # expecting nothing to change @@ -996,6 +1039,7 @@ def test_delete_lines(): [Char("b", fg="red"), Char("a", fg="red"), Char("r", fg="red")], [Char("b"), Char("a"), Char("z")], ] + consistency_asserts(screen) def test_insert_characters(): @@ -1052,16 +1096,19 @@ def test_delete_characters(): Char("m", fg="red"), screen.default_char, screen.default_char ] + consistency_asserts(screen) screen.cursor.y, screen.cursor.x = 2, 2 screen.delete_characters() assert (screen.cursor.y, screen.cursor.x) == (2, 2) assert screen.display == ["m ", "is ", "fo "] + consistency_asserts(screen) screen.cursor.y, screen.cursor.x = 1, 1 screen.delete_characters(0) assert (screen.cursor.y, screen.cursor.x) == (1, 1) assert screen.display == ["m ", "i ", "fo "] + consistency_asserts(screen) # ! extreme cases. screen = update(pyte.Screen(5, 1), ["12345"], colored=[0]) @@ -1076,6 +1123,7 @@ def test_delete_characters(): screen.default_char, screen.default_char ] + consistency_asserts(screen) screen = update(pyte.Screen(5, 1), ["12345"], colored=[0]) screen.cursor.x = 2 @@ -1089,6 +1137,7 @@ def test_delete_characters(): screen.default_char, screen.default_char ] + consistency_asserts(screen) screen = update(pyte.Screen(5, 1), ["12345"], colored=[0]) screen.delete_characters(4) @@ -1101,6 +1150,7 @@ def test_delete_characters(): screen.default_char, screen.default_char ] + consistency_asserts(screen) def test_erase_character(): @@ -1114,16 +1164,19 @@ def test_erase_character(): screen.default_char, Char("m", fg="red") ] + consistency_asserts(screen) screen.cursor.y, screen.cursor.x = 2, 2 screen.erase_characters() assert (screen.cursor.y, screen.cursor.x) == (2, 2) assert screen.display == [" m", "is ", "fo "] + consistency_asserts(screen) screen.cursor.y, screen.cursor.x = 1, 1 screen.erase_characters(0) assert (screen.cursor.y, screen.cursor.x) == (1, 1) assert screen.display == [" m", "i ", "fo "] + consistency_asserts(screen) # ! extreme cases. screen = update(pyte.Screen(5, 1), ["12345"], colored=[0]) @@ -1138,6 +1191,7 @@ def test_erase_character(): screen.default_char, Char("5", "red") ] + consistency_asserts(screen) screen = update(pyte.Screen(5, 1), ["12345"], colored=[0]) screen.cursor.x = 2 @@ -1151,6 +1205,7 @@ def test_erase_character(): screen.default_char, screen.default_char ] + consistency_asserts(screen) screen = update(pyte.Screen(5, 1), ["12345"], colored=[0]) screen.erase_characters(4) @@ -1163,6 +1218,7 @@ def test_erase_character(): screen.default_char, Char("5", fg="red") ] + consistency_asserts(screen) def test_erase_in_line(): @@ -1189,6 +1245,7 @@ def test_erase_in_line(): screen.default_char, screen.default_char ] + consistency_asserts(screen) # b) erase from the beginning of the line to the cursor screen = update(screen, @@ -1211,6 +1268,7 @@ def test_erase_in_line(): Char(" ", fg="red"), Char("i", fg="red") ] + consistency_asserts(screen) # c) erase the entire line screen = update(screen, @@ -1227,6 +1285,7 @@ def test_erase_in_line(): "re yo", "u? "] assert tolist(screen)[0] == [screen.default_char] * 5 + consistency_asserts(screen) def test_erase_in_display(): @@ -1256,6 +1315,7 @@ def test_erase_in_display(): [screen.default_char] * 5, [screen.default_char] * 5 ] + consistency_asserts(screen) # b) erase from the beginning of the display to the cursor, # including it @@ -1281,6 +1341,7 @@ def test_erase_in_display(): Char(" ", fg="red"), Char("a", fg="red")], ] + consistency_asserts(screen) # c) erase the while display screen.erase_in_display(2) @@ -1291,6 +1352,7 @@ def test_erase_in_display(): " ", " "] assert tolist(screen) == [[screen.default_char] * 5] * 5 + consistency_asserts(screen) # d) erase with private mode screen = update(pyte.Screen(5, 5), @@ -1305,6 +1367,7 @@ def test_erase_in_display(): " ", " ", " "] + consistency_asserts(screen) # e) erase with extra args screen = update(pyte.Screen(5, 5), @@ -1320,6 +1383,7 @@ def test_erase_in_display(): " ", " ", " "] + consistency_asserts(screen) # f) erase with extra args and private screen = update(pyte.Screen(5, 5), @@ -1334,6 +1398,7 @@ def test_erase_in_display(): " ", " ", " "] + consistency_asserts(screen) def test_cursor_up(): @@ -1462,6 +1527,7 @@ def test_unicode(): stream.feed("тест".encode("utf-8")) assert screen.display == ["тест", " "] + consistency_asserts(screen) def test_alignment_display(): @@ -1477,6 +1543,7 @@ def test_alignment_display(): "b ", " ", " "] + consistency_asserts(screen) screen.alignment_display() @@ -1485,6 +1552,7 @@ def test_alignment_display(): "EEEEE", "EEEEE", "EEEEE"] + consistency_asserts(screen) def test_set_margins(): diff --git a/tests/test_stream.py b/tests/test_stream.py index 7a3ad92..0d05df7 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -1,10 +1,12 @@ -import io +import io, sys, os import pytest import pyte from pyte import charsets as cs, control as ctrl, escape as esc +sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) +from asserts import consistency_asserts class counter: def __init__(self): @@ -227,6 +229,7 @@ def test_define_charset(): stream = pyte.Stream(screen) stream.feed(ctrl.ESC + "(B") assert screen.display[0] == " " * 3 + consistency_asserts(screen) def test_non_utf8_shifts(): @@ -305,6 +308,7 @@ def test_byte_stream_define_charset_unknown(): stream.feed((ctrl.ESC + "(Z").encode()) assert screen.display[0] == " " * 3 assert screen.g0_charset == default_g0_charset + consistency_asserts(screen) @pytest.mark.parametrize("charset,mapping", cs.MAPS.items()) @@ -315,6 +319,7 @@ def test_byte_stream_define_charset(charset, mapping): stream.feed((ctrl.ESC + "(" + charset).encode()) assert screen.display[0] == " " * 3 assert screen.g0_charset == mapping + consistency_asserts(screen) def test_byte_stream_select_other_charset():