Files
tuir/tests/test_objects.py
2015-12-03 20:55:37 -08:00

443 lines
12 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import time
import curses
import pytest
import requests
from rtv.objects import Color, Controller, Navigator, curses_session
try:
from unittest import mock
except ImportError:
import mock
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen(terminal, stdscr, ascii):
terminal.ascii = ascii
# Ensure the thread is properly started/stopped
with terminal.loader(delay=0, message=u'Hello', trail=u'...'):
assert terminal.loader._animator.is_alive()
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert terminal.loader.exception is None
assert stdscr.subwin.ncols == 10
assert stdscr.subwin.nlines == 3
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_exception_unhandled(terminal, stdscr, ascii):
terminal.ascii = ascii
# Raising an exception should clean up the loader properly
with pytest.raises(Exception):
with terminal.loader(delay=0):
assert terminal.loader._animator.is_alive()
raise Exception()
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_exception_handled(terminal, stdscr, ascii):
terminal.ascii = ascii
# Raising a handled exception should get stored on the loaders
with terminal.loader(delay=0):
assert terminal.loader._animator.is_alive()
raise requests.ConnectionError()
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert isinstance(terminal.loader.exception, requests.ConnectionError)
error_message = 'ConnectionError'.encode('ascii' if ascii else 'utf-8')
stdscr.subwin.addstr.assert_called_with(1, 1, error_message)
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_exception_not_caught(terminal, stdscr, ascii):
terminal.ascii = ascii
with pytest.raises(KeyboardInterrupt):
with terminal.loader(delay=0, catch_exception=False):
assert terminal.loader._animator.is_alive()
raise KeyboardInterrupt()
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert terminal.loader.exception is None
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_keyboard_interrupt(terminal, stdscr, ascii):
terminal.ascii = ascii
# Raising a KeyboardInterrupt should be also be stored
with terminal.loader(delay=0):
assert terminal.loader._animator.is_alive()
raise KeyboardInterrupt()
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert isinstance(terminal.loader.exception, KeyboardInterrupt)
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_escape(terminal, stdscr, ascii):
terminal.ascii = ascii
stdscr.getch.return_value = terminal.ESCAPE
# Pressing escape should trigger an interrupt during the delay section
with mock.patch('os.kill') as kill:
with terminal.loader():
time.sleep(0.1)
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert kill.called
# As will as during the animation section
with mock.patch('os.kill') as kill:
with terminal.loader(delay=0):
time.sleep(0.1)
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert kill.called
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_initial_delay(terminal, stdscr, ascii):
terminal.ascii = ascii
# If we don't reach the initial delay nothing should be drawn
with terminal.loader(delay=0.1):
time.sleep(0.05)
assert not stdscr.subwin.addstr.called
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_nested(terminal, ascii):
terminal.ascii = ascii
with terminal.loader(message='Outer'):
with terminal.loader(message='Inner'):
raise requests.ConnectionError()
assert False # Should never be reached
assert isinstance(terminal.loader.exception, requests.ConnectionError)
assert terminal.loader.depth == 0
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_nested_complex(terminal, stdscr, ascii):
terminal.ascii = ascii
with terminal.loader(message='Outer') as outer_loader:
assert outer_loader.depth == 1
with terminal.loader(message='Inner') as inner_loader:
assert inner_loader.depth == 2
assert inner_loader._args[2] == 'Outer'
with terminal.loader():
assert terminal.loader.depth == 2
raise requests.ConnectionError()
assert False # Should never be reached
assert isinstance(terminal.loader.exception, requests.ConnectionError)
assert terminal.loader.depth == 0
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
error_message = 'ConnectionError'.encode('ascii' if ascii else 'utf-8')
stdscr.subwin.addstr.assert_called_once_with(1, 1, error_message)
def test_objects_color(stdscr):
colors = ['RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE']
# Check that all colors start with the default value
for color in colors:
assert getattr(Color, color) == curses.A_NORMAL
Color.init()
assert curses.use_default_colors.called
# Check that all colors are populated
for color in colors:
assert getattr(Color, color) == 23
def test_objects_curses_session(stdscr):
# Normal setup and cleanup
with curses_session():
pass
assert curses.initscr.called
assert curses.endwin.called
curses.initscr.reset_mock()
curses.endwin.reset_mock()
# Ensure cleanup runs if an error occurs
with pytest.raises(KeyboardInterrupt):
with curses_session():
raise KeyboardInterrupt()
assert curses.initscr.called
assert curses.endwin.called
curses.initscr.reset_mock()
curses.endwin.reset_mock()
# But cleanup shouldn't run if stdscr was never instantiated
curses.initscr.side_effect = KeyboardInterrupt
with pytest.raises(KeyboardInterrupt):
with curses_session():
pass
assert curses.initscr.called
assert not curses.endwin.called
curses.initscr.reset_mock()
curses.endwin.reset_mock()
def test_objects_controller():
class ControllerA(Controller):
character_map = {}
class ControllerB(ControllerA):
character_map = {}
class ControllerC(ControllerA):
character_map = {}
@ControllerA.register('1')
def call_page(_):
return 'a1'
@ControllerA.register('2')
def call_page(_):
return 'a2'
@ControllerB.register('1')
def call_page(_):
return 'b1'
@ControllerC.register('2')
def call_page(_):
return 'c2'
controller_a = ControllerA(None)
controller_b = ControllerB(None)
controller_c = ControllerC(None)
assert controller_a.trigger('1') == 'a1'
assert controller_a.trigger('2') == 'a2'
assert controller_a.trigger('3') is None
assert controller_b.trigger('1') == 'b1'
assert controller_b.trigger('2') == 'a2'
assert controller_b.trigger('3') is None
assert controller_c.trigger('1') == 'a1'
assert controller_c.trigger('2') == 'c2'
assert controller_c.trigger('3') is None
def test_objects_navigator_properties():
def valid_page_cb(_):
return
nav = Navigator(valid_page_cb)
assert nav.step == 1
assert nav.position == (0, 0, False)
assert nav.absolute_index == 0
nav = Navigator(valid_page_cb, 5, 2, True)
assert nav.step == -1
assert nav.position == (5, 2, True)
assert nav.absolute_index == 3
def test_objects_navigator_move():
def valid_page_cb(index):
if index < 0 or index > 3:
raise IndexError()
nav = Navigator(valid_page_cb)
# Try to scroll up past the first item
valid, redraw = nav.move(-1, 2)
assert not valid
assert not redraw
# Scroll down
valid, redraw = nav.move(1, 3)
assert nav.page_index == 0
assert nav.cursor_index == 1
assert valid
assert not redraw
# Scroll down, reach last item on the page and flip the screen
valid, redraw = nav.move(1, 3)
assert nav.page_index == 2
assert nav.cursor_index == 0
assert nav.inverted
assert valid
assert redraw
# Keep scrolling
valid, redraw = nav.move(1, 3)
assert nav.page_index == 3
assert nav.cursor_index == 0
assert nav.inverted
assert valid
assert redraw
# Reach the end of the page and stop
valid, redraw = nav.move(1, 1)
assert nav.page_index == 3
assert nav.cursor_index == 0
assert nav.inverted
assert not valid
assert not redraw
# Last item was large and takes up the whole screen, scroll back up and
# flip the screen again
valid, redraw = nav.move(-1, 1)
assert nav.page_index == 2
assert nav.cursor_index == 0
assert not nav.inverted
assert valid
assert redraw
def test_objects_navigator_move_new_submission():
def valid_page_cb(index):
if index != -1:
raise IndexError()
nav = Navigator(valid_page_cb, page_index=-1)
# Can't move up
valid, redraw = nav.move(-1, 1)
assert nav.page_index == -1
assert nav.cursor_index == 0
assert not nav.inverted
assert not valid
assert not redraw
# Can't move down
valid, redraw = nav.move(1, 1)
assert nav.page_index == -1
assert nav.cursor_index == 0
assert not nav.inverted
assert not valid
assert not redraw
def test_objects_navigator_move_submission():
def valid_page_cb(index):
if index < -1 or index > 4:
raise IndexError()
nav = Navigator(valid_page_cb, page_index=-1)
# Can't move up
valid, redraw = nav.move(-1, 2)
assert nav.page_index == -1
assert nav.cursor_index == 0
assert not nav.inverted
assert not valid
assert not redraw
# Moving down jumps to the first comment
valid, redraw = nav.move(1, 2)
assert nav.page_index == 0
assert nav.cursor_index == 0
assert not nav.inverted
assert valid
assert redraw
# Moving down again inverts the screen
valid, redraw = nav.move(1, 2)
assert nav.page_index == 1
assert nav.cursor_index == 0
assert nav.inverted
assert valid
assert redraw
# Move up to the first comment
valid, redraw = nav.move(-1, 2)
assert nav.page_index == 0
assert nav.cursor_index == 0
assert not nav.inverted
assert valid
assert redraw
# Move up to the submission
valid, redraw = nav.move(-1, 2)
assert nav.page_index == -1
assert nav.cursor_index == 0
assert not nav.inverted
assert valid
assert redraw
@pytest.mark.xfail(reason="Paging is still broken in several edge-cases")
def test_objects_navigator_move_page():
def valid_page_cb(index):
if index < 0 or index > 7:
raise IndexError()
nav = Navigator(valid_page_cb, cursor_index=2)
# Can't move up
valid, redraw = nav.move_page(-1, 5)
assert nav.page_index == 0
assert nav.cursor_index == 0
assert not nav.inverted
assert not valid
assert not redraw
# Page down
valid, redraw = nav.move_page(1, 5)
assert nav.page_index == 4
assert nav.cursor_index == 0
assert nav.inverted
assert valid
assert redraw
# Page up
valid, redraw = nav.move_page(-1, 3)
assert nav.page_index == 2
assert nav.cursor_index == 0
assert not nav.inverted
assert valid
assert redraw
def test_objects_navigator_flip():
def valid_page_cb(index):
if index < 0 or index > 10:
raise IndexError()
nav = Navigator(valid_page_cb)
nav.flip(5)
assert nav.page_index == 5
assert nav.cursor_index == 5
assert nav.inverted
nav.flip(3)
assert nav.page_index == 2
assert nav.cursor_index == 3
assert not nav.inverted