summaryrefslogtreecommitdiff
path: root/blessings/tests/test_core.py
blob: bc7f7f1ca7fc2b6665a988f7b1ead9a8ecc2375d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# -*- 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():
        kind = 'ansi'
        if platform.system().lower() == 'freebsd':
            # 'ansi' on freebsd returns 0 colors, we use the 'cons25' driver,
            # compatible with its kernel tty.c
            kind = 'cons25'
        t = TestTerminal(kind=kind, 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():
        kind = 'ansi'
        if platform.system().lower() == 'freebsd':
            # 'ansi' on freebsd returns 0 colors, we use the 'cons25' driver,
            # compatible with its kernel tty.c
            kind = 'cons25'
        t = TestTerminal(kind=kind)
        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 on *some* systems.
    # freebsd actually has a termcap entry for 'unknown'
    @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:
            if platform.system().lower() != 'freebsd':
                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='xxXunknownXxx', force_styling=True)
        assert term.kind is None
        assert not term.does_styling
        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 not term.does_styling
        assert not term.is_a_tty
        assert term.number_of_colors == 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 not blessings.terminal.HAS_TTY
            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()