def test__render_line_bg_colors(self): cell_width = 8 screen_line = { 0: anim.CharacterCell('A', 'black', 'red'), 1: anim.CharacterCell('A', 'black', 'red'), 3: anim.CharacterCell('A', 'black', 'red'), 4: anim.CharacterCell('A', 'black', 'blue'), 6: anim.CharacterCell('A', 'black', 'blue'), 7: anim.CharacterCell('A', 'black', 'blue'), 8: anim.CharacterCell('A', 'black', 'green'), 9: anim.CharacterCell('A', 'black', 'red'), 10: anim.CharacterCell('A', 'black', 'red'), 99: anim.CharacterCell('A', 'black', 'black'), } rectangles = anim._render_line_bg_colors(screen_line=screen_line, height=0, line_height=1, cell_width=cell_width, background_color='black') rect_0, rect_3, rect_4, rect_6, rect_8, rect_9 = sorted( rectangles, key=lambda r: r.attribs['x']) self.assertEqual(rect_0.attribs['x'], 0) self.assertEqual(rect_0.attribs['width'], 16) self.assertEqual(rect_0.attribs['fill'], 'red') self.assertEqual(rect_3.attribs['x'], 24) self.assertEqual(rect_3.attribs['width'], 8) self.assertEqual(rect_4.attribs['x'], 32) self.assertEqual(rect_6.attribs['x'], 48) self.assertEqual(rect_6.attribs['width'], 16) self.assertEqual(rect_6.attribs['fill'], 'blue') self.assertEqual(rect_8.attribs['x'], 64) self.assertEqual(rect_8.attribs['fill'], 'green') self.assertEqual(rect_9.attribs['x'], 72)
def test_screen_events_empty_groups(self): """screen_events should never return an empty group of events""" records = [ AsciiCastV2Header(version=2, width=80, height=24, theme=THEME), AsciiCastV2Event(0, 'o', 'i', None), AsciiCastV2Event(1, 'o', '', None), AsciiCastV2Event(2, 'o', '', None), AsciiCastV2Event(3, 'o', '', None), ] expected_events = [ term.Configuration(80, 24), [ term.DisplayLine(0, { 0: anim.CharacterCell('i'), 1: CURSOR_CHAR }, 0), ], [ term.DisplayLine(0, { 0: anim.CharacterCell('i'), 1: CURSOR_CHAR }, 0, 4000), ] ] events = term.screen_events(records, 1, None, last_frame_dur=1000) list_events = [next(events)] list_events.extend([le for le in events]) z = itertools.zip_longest(expected_events, list_events) for count, (expected_item, item) in enumerate(z): with self.subTest(case='No empty group - item #{}'.format(count)):
def test_screen_events_simple_events(self): records = [AsciiCastV2Header(version=2, width=80, height=24, theme=THEME)] + \ [AsciiCastV2Event(time=i, event_type='o', event_data='{}\r\n'.format(i), duration=None) for i in range(0, 2)] events = term.screen_events(records, 1, None, 42) list_events = [next(events)] list_events.extend([list(le) for le in events]) expected_events = [ term.Configuration(80, 24), [ term.DisplayLine(0, {0: anim.CharacterCell('0')}, 0), term.DisplayLine(1, {0: CURSOR_CHAR}, 0), ], [ term.DisplayLine(1, {0: CURSOR_CHAR}, 0, 1000), term.DisplayLine(1, {0: anim.CharacterCell('1')}, 1000), term.DisplayLine(2, {0: CURSOR_CHAR}, 1000), ], [ term.DisplayLine(0, {0: anim.CharacterCell('0')}, 0, 1042), term.DisplayLine(1, {0: anim.CharacterCell('1')}, 1000, 42), term.DisplayLine(2, {0: CURSOR_CHAR}, 1000, 42), ], ] z = itertools.zip_longest(expected_events, list_events) for count, (expected_item, item) in enumerate(z): with self.subTest(case='Simple events - item #{}'.format(count)):
def line(i): chars = [] for c in 'line{}'.format(i): chars.append( anim.CharacterCell(c, '#123456', '#789012', False, False, False, False)) return dict(enumerate(chars))
def test__render_characters(self): screen_line = { 0: anim.CharacterCell('A', 'red', 'white'), 1: anim.CharacterCell('B', 'blue', 'white'), 2: anim.CharacterCell('C', 'blue', 'white'), 7: anim.CharacterCell('D', 'green', 'white'), 8: anim.CharacterCell('E', 'green', 'white'), 9: anim.CharacterCell('F', 'green', 'white'), 10: anim.CharacterCell('G', 'green', 'white'), 11: anim.CharacterCell('H', 'red', 'white'), 20: anim.CharacterCell(' ', 'black', 'black') } with self.subTest(case='Content'):
def test_from_pyte(self): pyte_chars = [ # Simple mapping pyte.screens.Char('A', 'red', 'blue'), # Reverse colors pyte.screens.Char('B', 'red', 'blue', reverse=True), # Bold for foreground -> bright colors pyte.screens.Char('C', 'red', 'blue', bold=True), # Bold and reverse pyte.screens.Char('D', 'red', 'blue', bold=True, reverse=True), # Defaults pyte.screens.Char('E', 'default', 'default'), # Hexadecimal pyte.screens.Char('F', '008700', 'ABCDEF'), # Bright and bold pyte.screens.Char('G', 'brightgreen', 'ABCDEF', bold=True), ] char_cells = [ anim.CharacterCell('A', 'color1', 'color4', False), anim.CharacterCell('B', 'color4', 'color1', False), anim.CharacterCell('C', 'color9', 'color4', True), anim.CharacterCell('D', 'color4', 'color9', True), anim.CharacterCell('E', 'foreground', 'background', False), anim.CharacterCell('F', '#008700', '#ABCDEF', False), anim.CharacterCell('G', 'color10', '#ABCDEF', True), ] for pyte_char, cell_char in zip(pyte_chars, char_cells): with self.subTest(case=pyte_char): self.assertEqual(anim.CharacterCell.from_pyte(pyte_char), cell_char)
def test_timed_frames_simple_events(self): records = [AsciiCastV2Header(version=2, width=80, height=24, theme=THEME)] + \ [AsciiCastV2Event(time=i, event_type='o', event_data='{}\r\n'.format(i), duration=None) for i in range(0, 2)] geometry, frames = term.timed_frames(records, 1, None, 42) self.assertEqual(geometry, (80, 24)) expected_frames = [ term.TimedFrame(0, 1000, { 0: { 0: anim.CharacterCell('0') }, 1: { 0: CURSOR_CHAR }, }), term.TimedFrame( 1000, 42, { 0: { 0: anim.CharacterCell('0') }, 1: { 0: anim.CharacterCell('1') }, 2: { 0: CURSOR_CHAR }, }) ] z = itertools.zip_longest(expected_frames, frames) for (expected_frame, frame) in z: self.assertEqual(expected_frame.time, frame.time) self.assertEqual(expected_frame.duration, frame.duration) for row in frame.buffer: if row in expected_frame.buffer: self.assertEqual(expected_frame.buffer[row], frame.buffer[row]) else: self.assertEqual({}, frame.buffer[row])
def test_from_pyte(self): pyte_chars = [ # Simple mapping pyte.screens.Char('A', 'red', 'blue'), # Reverse colors pyte.screens.Char('B', 'red', 'blue', reverse=True), # Bold for foreground -> bright colors pyte.screens.Char('C', 'red', 'blue', bold=True), # Bold and reverse pyte.screens.Char('D', 'red', 'blue', bold=True, reverse=True), # Bold with no matching bright color pyte.screens.Char('E', 'blue', 'blue', bold=True), # Defaults pyte.screens.Char('F', 'default', 'default'), # Hexadecimal pyte.screens.Char('G', '008700', 'ABCDEF'), # Bright and bold pyte.screens.Char('H', 'brightgreen', 'ABCDEF', bold=True), # Bright but not in the palette --> fallback to the non bright color # for compatibility with an 8-color palette (issue #1) pyte.screens.Char('I', 'brown', 'ABCDEF', bold=True), ] char_cells = [ anim.CharacterCell('A', 'color1', 'color4'), anim.CharacterCell('B', 'color4', 'color1'), anim.CharacterCell('C', 'color9', 'color4'), anim.CharacterCell('D', 'color4', 'color9'), anim.CharacterCell('E', 'color12', 'color4'), anim.CharacterCell('F', 'foreground', 'background'), anim.CharacterCell('G', '#008700', '#ABCDEF'), anim.CharacterCell('H', 'color10', '#ABCDEF'), anim.CharacterCell('I', 'color3', '#ABCDEF'), ] palette = { 'foreground': 'foreground', 'background': 'background', 1: 'color1', 3: 'color3', 4: 'color4', 9: 'color9', 10: 'color10', 12: 'color12', } for pyte_char, cell_char in zip(pyte_chars, char_cells): with self.subTest(case=pyte_char): self.assertEqual(
def test__render_line_bg_colors_xml(self): cell_width = 8 screen_line = { 0: anim.CharacterCell('A', 'black', 'red'), 1: anim.CharacterCell('A', 'black', 'red'), 3: anim.CharacterCell('A', 'black', 'red'), 4: anim.CharacterCell('A', 'black', 'blue'), 6: anim.CharacterCell('A', 'black', 'blue'), 7: anim.CharacterCell('A', 'black', 'blue'), 8: anim.CharacterCell('A', 'black', 'green'), 9: anim.CharacterCell('A', 'black', 'red'), 10: anim.CharacterCell('A', 'black', 'red'), 11: anim.CharacterCell('A', 'black', '#123456'), } rectangles = anim._render_line_bg_colors(screen_line=screen_line, height=0, cell_height=1, cell_width=cell_width) def key(r): return r.attrib['x'] rect_0, rect_3, rect_4, rect_6, rect_8, rect_9, rect_11 = sorted( rectangles, key=key) self.assertEqual(rect_0.attrib['x'], '0') self.assertEqual(rect_0.attrib['width'], '16') self.assertEqual(rect_0.attrib['height'], '1') self.assertEqual(rect_0.attrib['class'], 'red') self.assertEqual(rect_3.attrib['x'], '24') self.assertEqual(rect_3.attrib['width'], '8') self.assertEqual(rect_4.attrib['x'], '32') self.assertEqual(rect_6.attrib['x'], '48') self.assertEqual(rect_6.attrib['width'], '16') self.assertEqual(rect_6.attrib['class'], 'blue') self.assertEqual(rect_8.attrib['x'], '64') self.assertEqual(rect_8.attrib['class'], 'green') self.assertEqual(rect_9.attrib['x'], '72') self.assertEqual(rect_11.attrib['fill'], '#123456')
def test__feed(self): redraw_buffers = [{ 0: { 0: anim.CharacterCell('1') }, 1: { 0: CURSOR_CHAR }, }, { 1: { 0: anim.CharacterCell('2') }, 2: { 0: CURSOR_CHAR }, }] all_expected_events = [[ term.DisplayLine(0, {0: anim.CharacterCell('1')}, 0), term.DisplayLine(1, {0: CURSOR_CHAR}, 0), ], [ term.DisplayLine(1, {0: CURSOR_CHAR}, 0, 1000), term.DisplayLine( 1, {0: anim.CharacterCell('2')}, 1000), term.DisplayLine(2, {0: CURSOR_CHAR}, 1000), ]] display_events = {} time = 0 z = itertools.zip_longest(redraw_buffers, all_expected_events) for buffer, expected_events in z: display_events, events = term._feed(buffer, display_events, time) with self.subTest(time=time): self.assertEqual(expected_events, events) time += 1000
def line(s): return dict(enumerate([anim.CharacterCell(c) for c in s]))
def test__render_characters(self): screen_line = { 0: anim.CharacterCell('A', 'red', 'white'), 1: anim.CharacterCell('B', 'blue', 'white'), 2: anim.CharacterCell('C', 'blue', 'white'), 7: anim.CharacterCell('D', '#00FF00', 'white'), 8: anim.CharacterCell('E', '#00FF00', 'white'), 9: anim.CharacterCell('F', '#00FF00', 'white'), 10: anim.CharacterCell('G', '#00FF00', 'white'), 20: anim.CharacterCell('H', 'black', 'black', bold=True), 30: anim.CharacterCell('I', 'black', 'black', italics=True), 40: anim.CharacterCell('J', 'black', 'black', underscore=True), 50: anim.CharacterCell('K', 'black', 'black', strikethrough=True), 60: anim.CharacterCell('L', 'black', 'black', underscore=True, strikethrough=True), } with self.subTest(case='Content'):
def line(i): chars = [ anim.CharacterCell(c, '#123456', '#789012') for c in 'line{}'.format(i) ] return dict(enumerate(chars))
class TestTerm(unittest.TestCase): def test__record(self): fd_in_read, fd_in_write = os.pipe() fd_out_read, fd_out_write = os.pipe() lines = 24 columns = 80 pid = os.fork() if pid == 0: # Child process for line in commands: os.write(fd_in_write, line.encode('utf-8')) time.sleep(0.060) os._exit(0) # Parent process with term.TerminalMode(fd_in_read): for _ in term._record(['sh'], columns, lines, fd_in_read, fd_out_write): pass os.waitpid(pid, 0) for fd in fd_in_read, fd_in_write, fd_out_read, fd_out_write: os.close(fd) def test_record(self): # Use pipes in lieu of stdin and stdout fd_in_read, fd_in_write = os.pipe() fd_out_read, fd_out_write = os.pipe() lines = 24 columns = 80 pid = os.fork() if pid == 0: # Child process for line in commands: os.write(fd_in_write, line.encode('utf-8')) time.sleep(0.060) os._exit(0) # Parent process with term.TerminalMode(fd_in_read): for _ in term.record(['sh'], columns, lines, fd_in_read, fd_out_write): pass os.waitpid(pid, 0) for fd in fd_in_read, fd_in_write, fd_out_read, fd_out_write: os.close(fd) def test__buffer_simple_events(self): escape_sequences = ['{}\r\n'.format(i) for i in range(5)] screen = pyte.Screen(80, 24) stream = pyte.Stream(screen) for count, escape_sequence in enumerate(escape_sequences): with self.subTest(case='Simple events (record #{})'.format(count)): stream.feed(escape_sequence) buffer = term._screen_buffer(screen) expected_buffer = {} for i in range(screen.lines): if i <= count: expected_buffer[i] = {0: anim.CharacterCell(str(i))} elif i == count + 1: expected_buffer[i] = {0: CURSOR_CHAR} else: expected_buffer[i] = {} self.assertEqual(expected_buffer, buffer)
import unittest from unittest.mock import MagicMock, patch import pyte import termtosvg.anim as anim from termtosvg import term from termtosvg.asciicast import AsciiCastV2Event, AsciiCastV2Header, AsciiCastV2Theme commands = [ 'echo $SHELL && sleep 0.1;\r\n', 'date && sleep 0.1;\r\n', 'uname && sleep 0.1;\r\n', 'w', 'h', 'o', 'a', 'm', 'i\r\n', 'exit;\r\n' ] THEME = AsciiCastV2Theme('#000000', '#FFFFFF', ':'.join(['#123456'] * 16)) CURSOR_CHAR = anim.CharacterCell(' ', 'background', 'foreground') unittest.TestCase.maxDiff = None class TestTerm(unittest.TestCase): def test__record(self): fd_in_read, fd_in_write = os.pipe() fd_out_read, fd_out_write = os.pipe() lines = 24 columns = 80 pid = os.fork() if pid == 0: # Child process