diff options
Diffstat (limited to 'blessings/tests/test_core.py')
-rw-r--r-- | blessings/tests/test_core.py | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/blessings/tests/test_core.py b/blessings/tests/test_core.py new file mode 100644 index 0000000..ce1e838 --- /dev/null +++ b/blessings/tests/test_core.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- +"Core blessings Terminal() tests." + +# std +try: + from StringIO import StringIO +except ImportError: + from io import StringIO +import collections +import warnings +import platform +import locale +import sys +import imp +import os + +# local +from .accessories import ( + as_subprocess, + TestTerminal, + unicode_cap, + all_terms +) + +# 3rd party +import mock +import pytest + + +def test_export_only_Terminal(): + "Ensure only Terminal instance is exported for import * statements." + import blessings + assert blessings.__all__ == ('Terminal',) + + +def test_null_location(all_terms): + "Make sure ``location()`` with no args just does position restoration." + @as_subprocess + def child(kind): + t = TestTerminal(stream=StringIO(), force_styling=True) + with t.location(): + pass + expected_output = u''.join( + (unicode_cap('sc'), unicode_cap('rc'))) + assert (t.stream.getvalue() == expected_output) + + child(all_terms) + + +def test_flipped_location_move(all_terms): + "``location()`` and ``move()`` receive counter-example arguments." + @as_subprocess + def child(kind): + buf = StringIO() + t = TestTerminal(stream=buf, force_styling=True) + y, x = 10, 20 + with t.location(y, x): + xy_val = t.move(x, y) + yx_val = buf.getvalue()[len(t.sc):] + assert xy_val == yx_val + + child(all_terms) + + +def test_yield_keypad(): + "Ensure ``keypad()`` writes keyboard_xmit and keyboard_local." + @as_subprocess + def child(kind): + # given, + t = TestTerminal(stream=StringIO(), force_styling=True) + expected_output = u''.join((t.smkx, t.rmkx)) + + # exercise, + with t.keypad(): + pass + + # verify. + assert (t.stream.getvalue() == expected_output) + + child(kind='xterm') + + +def test_null_fileno(): + "Make sure ``Terminal`` works when ``fileno`` is ``None``." + @as_subprocess + def child(): + # This simulates piping output to another program. + out = StringIO() + out.fileno = None + t = TestTerminal(stream=out) + assert (t.save == u'') + + child() + + +def test_number_of_colors_without_tty(): + "``number_of_colors`` should return 0 when there's no tty." + @as_subprocess + def child_256_nostyle(): + t = TestTerminal(stream=StringIO()) + assert (t.number_of_colors == 0) + + @as_subprocess + def child_256_forcestyle(): + t = TestTerminal(stream=StringIO(), force_styling=True) + assert (t.number_of_colors == 256) + + @as_subprocess + def child_8_forcestyle(): + t = TestTerminal(kind='ansi', stream=StringIO(), + force_styling=True) + assert (t.number_of_colors == 8) + + @as_subprocess + def child_0_forcestyle(): + t = TestTerminal(kind='vt220', stream=StringIO(), + force_styling=True) + assert (t.number_of_colors == 0) + + child_0_forcestyle() + child_8_forcestyle() + child_256_forcestyle() + child_256_nostyle() + + +def test_number_of_colors_with_tty(): + "test ``number_of_colors`` 0, 8, and 256." + @as_subprocess + def child_256(): + t = TestTerminal() + assert (t.number_of_colors == 256) + + @as_subprocess + def child_8(): + t = TestTerminal(kind='ansi') + assert (t.number_of_colors == 8) + + @as_subprocess + def child_0(): + t = TestTerminal(kind='vt220') + assert (t.number_of_colors == 0) + + child_0() + child_8() + child_256() + + +def test_init_descriptor_always_initted(all_terms): + "Test height and width with non-tty Terminals." + @as_subprocess + def child(kind): + t = TestTerminal(kind=kind, stream=StringIO()) + assert t._init_descriptor == sys.__stdout__.fileno() + assert (isinstance(t.height, int)) + assert (isinstance(t.width, int)) + assert t.height == t._height_and_width()[0] + assert t.width == t._height_and_width()[1] + + child(all_terms) + + +def test_force_styling_none(all_terms): + "If ``force_styling=None`` is used, don't ever do styling." + @as_subprocess + def child(kind): + t = TestTerminal(kind=kind, force_styling=None) + assert (t.save == '') + assert (t.color(9) == '') + assert (t.bold('oi') == 'oi') + + child(all_terms) + + +def test_setupterm_singleton_issue33(): + "A warning is emitted if a new terminal ``kind`` is used per process." + @as_subprocess + def child(): + warnings.filterwarnings("error", category=UserWarning) + + # instantiate first terminal, of type xterm-256color + term = TestTerminal(force_styling=True) + + try: + # a second instantiation raises UserWarning + term = TestTerminal(kind="vt220", force_styling=True) + except UserWarning: + err = sys.exc_info()[1] + assert (err.args[0].startswith( + 'A terminal of kind "vt220" has been requested') + ), err.args[0] + assert ('a terminal of kind "xterm-256color" will ' + 'continue to be returned' in err.args[0]), err.args[0] + else: + # unless term is not a tty and setupterm() is not called + assert not term.is_a_tty or False, 'Should have thrown exception' + warnings.resetwarnings() + + child() + + +def test_setupterm_invalid_issue39(): + "A warning is emitted if TERM is invalid." + # https://bugzilla.mozilla.org/show_bug.cgi?id=878089 + + # if TERM is unset, defaults to 'unknown', which should + # fail to lookup and emit a warning, only. + @as_subprocess + def child(): + warnings.filterwarnings("error", category=UserWarning) + + try: + term = TestTerminal(kind='unknown', force_styling=True) + except UserWarning: + err = sys.exc_info()[1] + assert err.args[0] == ( + "Failed to setupterm(kind='unknown'): " + "setupterm: could not find terminal") + else: + assert not term.is_a_tty and not term.does_styling, ( + 'Should have thrown exception') + warnings.resetwarnings() + + child() + + +def test_setupterm_invalid_has_no_styling(): + "An unknown TERM type does not perform styling." + # https://bugzilla.mozilla.org/show_bug.cgi?id=878089 + + # if TERM is unset, defaults to 'unknown', which should + # fail to lookup and emit a warning, only. + @as_subprocess + def child(): + warnings.filterwarnings("ignore", category=UserWarning) + + term = TestTerminal(kind='unknown', force_styling=True) + assert term.kind is None + assert term.does_styling is False + assert term.number_of_colors == 0 + warnings.resetwarnings() + + child() + + +@pytest.mark.skipif(platform.python_implementation() == 'PyPy', + reason='PyPy freezes') +def test_missing_ordereddict_uses_module(monkeypatch): + "ordereddict module is imported when without collections.OrderedDict." + import blessings.keyboard + + if hasattr(collections, 'OrderedDict'): + monkeypatch.delattr('collections.OrderedDict') + + try: + imp.reload(blessings.keyboard) + except ImportError as err: + assert err.args[0] in ("No module named ordereddict", # py2 + "No module named 'ordereddict'") # py3 + sys.modules['ordereddict'] = mock.Mock() + sys.modules['ordereddict'].OrderedDict = -1 + imp.reload(blessings.keyboard) + assert blessings.keyboard.OrderedDict == -1 + del sys.modules['ordereddict'] + monkeypatch.undo() + imp.reload(blessings.keyboard) + else: + assert platform.python_version_tuple() < ('2', '7') # reached by py2.6 + + +@pytest.mark.skipif(platform.python_implementation() == 'PyPy', + reason='PyPy freezes') +def test_python3_2_raises_exception(monkeypatch): + "Test python version 3.0 through 3.2 raises an exception." + import blessings + + monkeypatch.setattr('platform.python_version_tuple', + lambda: ('3', '2', '2')) + + try: + imp.reload(blessings) + except ImportError as err: + assert err.args[0] == ( + 'Blessings needs Python 3.2.3 or greater for Python 3 ' + 'support due to http://bugs.python.org/issue10570.') + monkeypatch.undo() + imp.reload(blessings) + else: + assert False, 'Exception should have been raised' + + +def test_IOUnsupportedOperation_dummy(monkeypatch): + "Ensure dummy exception is used when io is without UnsupportedOperation." + import blessings.terminal + import io + if hasattr(io, 'UnsupportedOperation'): + monkeypatch.delattr('io.UnsupportedOperation') + + imp.reload(blessings.terminal) + assert blessings.terminal.IOUnsupportedOperation.__doc__.startswith( + "A dummy exception to take the place of") + monkeypatch.undo() + imp.reload(blessings.terminal) + + +def test_without_dunder(): + "Ensure dunder does not remain in module (py2x InterruptedError test." + import blessings.terminal + assert '_' not in dir(blessings.terminal) + + +def test_IOUnsupportedOperation(): + "Ensure stream that throws IOUnsupportedOperation results in non-tty." + @as_subprocess + def child(): + import blessings.terminal + + def side_effect(): + raise blessings.terminal.IOUnsupportedOperation + + mock_stream = mock.Mock() + mock_stream.fileno = side_effect + + term = TestTerminal(stream=mock_stream) + assert term.stream == mock_stream + assert term.does_styling is False + assert term.is_a_tty is False + assert term.number_of_colors is 0 + + child() + + +def test_winsize_IOError_returns_environ(): + """When _winsize raises IOError, defaults from os.environ given.""" + @as_subprocess + def child(): + def side_effect(fd): + raise IOError + + term = TestTerminal() + term._winsize = side_effect + os.environ['COLUMNS'] = '1984' + os.environ['LINES'] = '1888' + assert term._height_and_width() == (1888, 1984, None, None) + + child() + + +def test_yield_fullscreen(all_terms): + "Ensure ``fullscreen()`` writes enter_fullscreen and exit_fullscreen." + @as_subprocess + def child(kind): + t = TestTerminal(stream=StringIO(), force_styling=True) + t.enter_fullscreen = u'BEGIN' + t.exit_fullscreen = u'END' + with t.fullscreen(): + pass + expected_output = u''.join((t.enter_fullscreen, t.exit_fullscreen)) + assert (t.stream.getvalue() == expected_output) + + child(all_terms) + + +def test_yield_hidden_cursor(all_terms): + "Ensure ``hidden_cursor()`` writes hide_cursor and normal_cursor." + @as_subprocess + def child(kind): + t = TestTerminal(stream=StringIO(), force_styling=True) + t.hide_cursor = u'BEGIN' + t.normal_cursor = u'END' + with t.hidden_cursor(): + pass + expected_output = u''.join((t.hide_cursor, t.normal_cursor)) + assert (t.stream.getvalue() == expected_output) + + child(all_terms) + + +def test_no_preferredencoding_fallback_ascii(): + "Ensure empty preferredencoding value defaults to ascii." + @as_subprocess + def child(): + with mock.patch('locale.getpreferredencoding') as get_enc: + get_enc.return_value = u'' + t = TestTerminal() + assert t._encoding == 'ascii' + + child() + + +def test_unknown_preferredencoding_warned_and_fallback_ascii(): + "Ensure a locale without a codecs incrementaldecoder emits a warning." + @as_subprocess + def child(): + with mock.patch('locale.getpreferredencoding') as get_enc: + with warnings.catch_warnings(record=True) as warned: + get_enc.return_value = '---unknown--encoding---' + t = TestTerminal() + assert t._encoding == 'ascii' + assert len(warned) == 1 + assert issubclass(warned[-1].category, UserWarning) + assert "fallback to ASCII" in str(warned[-1].message) + + child() + + +def test_win32_missing_tty_modules(monkeypatch): + "Ensure dummy exception is used when io is without UnsupportedOperation." + @as_subprocess + def child(): + OLD_STYLE = False + try: + original_import = getattr(__builtins__, '__import__') + OLD_STYLE = True + except AttributeError: + original_import = __builtins__['__import__'] + + tty_modules = ('termios', 'fcntl', 'tty') + + def __import__(name, *args, **kwargs): + if name in tty_modules: + raise ImportError + return original_import(name, *args, **kwargs) + + for module in tty_modules: + sys.modules.pop(module, None) + + warnings.filterwarnings("error", category=UserWarning) + try: + if OLD_STYLE: + __builtins__.__import__ = __import__ + else: + __builtins__['__import__'] = __import__ + try: + import blessings.terminal + imp.reload(blessings.terminal) + except UserWarning: + err = sys.exc_info()[1] + assert err.args[0] == blessings.terminal.msg_nosupport + + warnings.filterwarnings("ignore", category=UserWarning) + import blessings.terminal + imp.reload(blessings.terminal) + assert blessings.terminal.HAS_TTY is False + term = blessings.terminal.Terminal('ansi') + assert term.height == 24 + assert term.width == 80 + + finally: + if OLD_STYLE: + setattr(__builtins__, '__import__', original_import) + else: + __builtins__['__import__'] = original_import + warnings.resetwarnings() + import blessings.terminal + imp.reload(blessings.terminal) + + child() |