summaryrefslogtreecommitdiff
path: root/blessed/tests/test_formatters.py
blob: 31841402d0f6f0a95557e4cd405f8c3de8a6e25e (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
# -*- coding: utf-8 -*-
"""Tests string formatting functions."""
import curses
import mock


def test_parameterizing_string_args_unspecified(monkeypatch):
    """Test default args of formatters.ParameterizingString."""
    from blessings.formatters import ParameterizingString, FormattingString
    # first argument to tparm() is the sequence name, returned as-is;
    # subsequent arguments are usually Integers.
    tparm = lambda *args: u'~'.join(
        arg.decode('latin1') if not num else '%s' % (arg,)
        for num, arg in enumerate(args)).encode('latin1')

    monkeypatch.setattr(curses, 'tparm', tparm)

    # given,
    pstr = ParameterizingString(u'')

    # excersize __new__
    assert str(pstr) == u''
    assert pstr._normal == u''
    assert pstr._name == u'<not specified>'

    # excersize __call__
    zero = pstr(0)
    assert type(zero) is FormattingString
    assert zero == u'~0'
    assert zero('text') == u'~0text'

    # excersize __call__ with multiple args
    onetwo = pstr(1, 2)
    assert type(onetwo) is FormattingString
    assert onetwo == u'~1~2'
    assert onetwo('text') == u'~1~2text'


def test_parameterizing_string_args(monkeypatch):
    """Test basic formatters.ParameterizingString."""
    from blessings.formatters import ParameterizingString, FormattingString

    # first argument to tparm() is the sequence name, returned as-is;
    # subsequent arguments are usually Integers.
    tparm = lambda *args: u'~'.join(
        arg.decode('latin1') if not num else '%s' % (arg,)
        for num, arg in enumerate(args)).encode('latin1')

    monkeypatch.setattr(curses, 'tparm', tparm)

    # given,
    pstr = ParameterizingString(u'cap', u'norm', u'seq-name')

    # excersize __new__
    assert str(pstr) == u'cap'
    assert pstr._normal == u'norm'
    assert pstr._name == u'seq-name'

    # excersize __call__
    zero = pstr(0)
    assert type(zero) is FormattingString
    assert zero == u'cap~0'
    assert zero('text') == u'cap~0textnorm'

    # excersize __call__ with multiple args
    onetwo = pstr(1, 2)
    assert type(onetwo) is FormattingString
    assert onetwo == u'cap~1~2'
    assert onetwo('text') == u'cap~1~2textnorm'


def test_parameterizing_string_type_error(monkeypatch):
    """Test formatters.ParameterizingString raising TypeError"""
    from blessings.formatters import ParameterizingString

    def tparm_raises_TypeError(*args):
        raise TypeError('custom_err')

    monkeypatch.setattr(curses, 'tparm', tparm_raises_TypeError)

    # given,
    pstr = ParameterizingString(u'cap', u'norm', u'cap-name')

    # ensure TypeError when given a string raises custom exception
    try:
        pstr('XYZ')
        assert False, "previous call should have raised TypeError"
    except TypeError as err:
        assert (err.args[0] == (  # py3x
            "A native or nonexistent capability template, "
            "'cap-name' received invalid argument ('XYZ',): "
            "custom_err. You probably misspelled a "
            "formatting call like `bright_red'") or
            err.args[0] == (
                "A native or nonexistent capability template, "
                "u'cap-name' received invalid argument ('XYZ',): "
                "custom_err. You probably misspelled a "
                "formatting call like `bright_red'"))

    # ensure TypeError when given an integer raises its natural exception
    try:
        pstr(0)
        assert False, "previous call should have raised TypeError"
    except TypeError as err:
        assert err.args[0] == "custom_err"


def test_formattingstring(monkeypatch):
    """Test formatters.FormattingString"""
    from blessings.formatters import FormattingString

    # given, with arg
    pstr = FormattingString(u'attr', u'norm')

    # excersize __call__,
    assert pstr._normal == u'norm'
    assert str(pstr) == u'attr'
    assert pstr('text') == u'attrtextnorm'

    # given, without arg
    pstr = FormattingString(u'', u'norm')
    assert pstr('text') == u'text'


def test_nullcallablestring(monkeypatch):
    """Test formatters.NullCallableString"""
    from blessings.formatters import (NullCallableString)

    # given, with arg
    pstr = NullCallableString()

    # excersize __call__,
    assert str(pstr) == u''
    assert pstr('text') == u'text'
    assert pstr('text', 1) == u''
    assert pstr('text', 'moretext') == u''
    assert pstr(99, 1) == u''
    assert pstr() == u''
    assert pstr(0) == u''


def test_split_compound():
    """Test formatters.split_compound."""
    from blessings.formatters import split_compound

    assert split_compound(u'') == [u'']
    assert split_compound(u'a_b_c') == [u'a', u'b', u'c']
    assert split_compound(u'a_on_b_c') == [u'a', u'on_b', u'c']
    assert split_compound(u'a_bright_b_c') == [u'a', u'bright_b', u'c']
    assert split_compound(u'a_on_bright_b_c') == [u'a', u'on_bright_b', u'c']


def test_resolve_capability(monkeypatch):
    """Test formatters.resolve_capability and term sugaring """
    from blessings.formatters import resolve_capability

    # given, always returns a b'seq'
    tigetstr = lambda attr: ('seq-%s' % (attr,)).encode('latin1')
    monkeypatch.setattr(curses, 'tigetstr', tigetstr)
    term = mock.Mock()
    term._sugar = dict(mnemonic='xyz')

    # excersize
    assert resolve_capability(term, 'mnemonic') == u'seq-xyz'
    assert resolve_capability(term, 'natural') == u'seq-natural'

    # given, where tigetstr returns None
    tigetstr_none = lambda attr: None
    monkeypatch.setattr(curses, 'tigetstr', tigetstr_none)

    # excersize,
    assert resolve_capability(term, 'natural') == u''

    # given, where does_styling is False
    def raises_exception(*args):
        assert False, "Should not be called"
    term.does_styling = False
    monkeypatch.setattr(curses, 'tigetstr', raises_exception)

    # excersize,
    assert resolve_capability(term, 'natural') == u''


def test_resolve_color(monkeypatch):
    """Test formatters.resolve_color."""
    from blessings.formatters import (resolve_color,
                                      FormattingString,
                                      NullCallableString)

    color_cap = lambda digit: 'seq-%s' % (digit,)
    monkeypatch.setattr(curses, 'COLOR_RED', 1984)

    # given, terminal with color capabilities
    term = mock.Mock()
    term._background_color = color_cap
    term._foreground_color = color_cap
    term.number_of_colors = -1
    term.normal = 'seq-normal'

    # excersize,
    red = resolve_color(term, 'red')
    assert type(red) == FormattingString
    assert red == u'seq-1984'
    assert red('text') == u'seq-1984textseq-normal'

    # excersize bold, +8
    bright_red = resolve_color(term, 'bright_red')
    assert type(bright_red) == FormattingString
    assert bright_red == u'seq-1992'
    assert bright_red('text') == u'seq-1992textseq-normal'

    # given, terminal without color
    term.number_of_colors = 0

    # excersize,
    red = resolve_color(term, 'red')
    assert type(red) == NullCallableString
    assert red == u''
    assert red('text') == u'text'

    # excesize bold,
    bright_red = resolve_color(term, 'bright_red')
    assert type(bright_red) == NullCallableString
    assert bright_red == u''
    assert bright_red('text') == u'text'


def test_resolve_attribute_as_color(monkeypatch):
    """ Test simple resolve_attribte() given color name. """
    import blessings
    from blessings.formatters import resolve_attribute

    resolve_color = lambda term, digit: 'seq-%s' % (digit,)
    COLORS = set(['COLORX', 'COLORY'])
    COMPOUNDABLES = set(['JOINT', 'COMPOUND'])
    monkeypatch.setattr(blessings.formatters, 'resolve_color', resolve_color)
    monkeypatch.setattr(blessings.formatters, 'COLORS', COLORS)
    monkeypatch.setattr(blessings.formatters, 'COMPOUNDABLES', COMPOUNDABLES)
    term = mock.Mock()
    assert resolve_attribute(term, 'COLORX') == u'seq-COLORX'


def test_resolve_attribute_as_compoundable(monkeypatch):
    """ Test simple resolve_attribte() given a compoundable. """
    import blessings
    from blessings.formatters import resolve_attribute, FormattingString

    resolve_cap = lambda term, digit: 'seq-%s' % (digit,)
    COMPOUNDABLES = set(['JOINT', 'COMPOUND'])
    monkeypatch.setattr(blessings.formatters,
                        'resolve_capability',
                        resolve_cap)
    monkeypatch.setattr(blessings.formatters, 'COMPOUNDABLES', COMPOUNDABLES)
    term = mock.Mock()
    term.normal = 'seq-normal'

    compound = resolve_attribute(term, 'JOINT')
    assert type(compound) is FormattingString
    assert str(compound) == u'seq-JOINT'
    assert compound('text') == u'seq-JOINTtextseq-normal'


def test_resolve_attribute_non_compoundables(monkeypatch):
    """ Test recursive compounding of resolve_attribute(). """
    import blessings
    from blessings.formatters import resolve_attribute, ParameterizingString
    uncompoundables = lambda attr: ['split', 'compound']
    resolve_cap = lambda term, digit: 'seq-%s' % (digit,)
    monkeypatch.setattr(blessings.formatters,
                        'split_compound',
                        uncompoundables)
    monkeypatch.setattr(blessings.formatters,
                        'resolve_capability',
                        resolve_cap)
    tparm = lambda *args: u'~'.join(
        arg.decode('latin1') if not num else '%s' % (arg,)
        for num, arg in enumerate(args)).encode('latin1')
    monkeypatch.setattr(curses, 'tparm', tparm)

    term = mock.Mock()
    term.normal = 'seq-normal'

    # given
    pstr = resolve_attribute(term, 'not-a-compoundable')
    assert type(pstr) == ParameterizingString
    assert str(pstr) == u'seq-not-a-compoundable'
    # this is like calling term.move_x(3)
    assert pstr(3) == u'seq-not-a-compoundable~3'
    # this is like calling term.move_x(3)('text')
    assert pstr(3)('text') == u'seq-not-a-compoundable~3textseq-normal'


def test_resolve_attribute_recursive_compoundables(monkeypatch):
    """ Test recursive compounding of resolve_attribute(). """
    import blessings
    from blessings.formatters import resolve_attribute, FormattingString

    # patch,
    resolve_cap = lambda term, digit: 'seq-%s' % (digit,)
    monkeypatch.setattr(blessings.formatters,
                        'resolve_capability',
                        resolve_cap)
    tparm = lambda *args: u'~'.join(
        arg.decode('latin1') if not num else '%s' % (arg,)
        for num, arg in enumerate(args)).encode('latin1')
    monkeypatch.setattr(curses, 'tparm', tparm)
    monkeypatch.setattr(curses, 'COLOR_RED', 6502)
    monkeypatch.setattr(curses, 'COLOR_BLUE', 6800)

    color_cap = lambda digit: 'seq-%s' % (digit,)
    term = mock.Mock()
    term._background_color = color_cap
    term._foreground_color = color_cap
    term.normal = 'seq-normal'

    # given,
    pstr = resolve_attribute(term, 'bright_blue_on_red')

    # excersize,
    assert type(pstr) == FormattingString
    assert str(pstr) == 'seq-6808seq-6502'
    assert pstr('text') == 'seq-6808seq-6502textseq-normal'


def test_pickled_parameterizing_string(monkeypatch):
    """Test pickle-ability of a formatters.ParameterizingString."""
    from blessings.formatters import ParameterizingString, FormattingString

    # simply send()/recv() over multiprocessing Pipe, a simple
    # pickle.loads(dumps(...)) did not reproduce this issue,
    from multiprocessing import Pipe
    import pickle

    # first argument to tparm() is the sequence name, returned as-is;
    # subsequent arguments are usually Integers.
    tparm = lambda *args: u'~'.join(
        arg.decode('latin1') if not num else '%s' % (arg,)
        for num, arg in enumerate(args)).encode('latin1')

    monkeypatch.setattr(curses, 'tparm', tparm)

    # given,
    pstr = ParameterizingString(u'seqname', u'norm', u'cap-name')

    # multiprocessing Pipe implicitly pickles.
    r, w = Pipe()

    # excersize picklability of ParameterizingString
    for proto_num in range(pickle.HIGHEST_PROTOCOL):
        assert pstr == pickle.loads(pickle.dumps(pstr, protocol=proto_num))
    w.send(pstr)
    r.recv() == pstr

    # excersize picklability of FormattingString
    # -- the return value of calling ParameterizingString
    zero = pstr(0)
    for proto_num in range(pickle.HIGHEST_PROTOCOL):
        assert zero == pickle.loads(pickle.dumps(zero, protocol=proto_num))
    w.send(zero)
    r.recv() == zero


def test_tparm_returns_null(monkeypatch):
    """ Test 'tparm() returned NULL' is caught (win32 PDCurses systems). """
    # on win32, any calls to tparm raises curses.error with message,
    # "tparm() returned NULL", function PyCurses_tparm of _cursesmodule.c
    from blessings.formatters import ParameterizingString, NullCallableString

    def tparm(*args):
        raise curses.error("tparm() returned NULL")

    monkeypatch.setattr(curses, 'tparm', tparm)

    term = mock.Mock()
    term.normal = 'seq-normal'

    pstr = ParameterizingString(u'cap', u'norm', u'seq-name')

    value = pstr(u'x')
    assert type(value) is NullCallableString


def test_tparm_other_exception(monkeypatch):
    """ Test 'tparm() returned NULL' is caught (win32 PDCurses systems). """
    # on win32, any calls to tparm raises curses.error with message,
    # "tparm() returned NULL", function PyCurses_tparm of _cursesmodule.c
    from blessings.formatters import ParameterizingString, NullCallableString

    def tparm(*args):
        raise curses.error("unexpected error in tparm()")

    monkeypatch.setattr(curses, 'tparm', tparm)

    term = mock.Mock()
    term.normal = 'seq-normal'

    pstr = ParameterizingString(u'cap', u'norm', u'seq-name')

    try:
        pstr(u'x')
        assert False, "previous call should have raised curses.error"
    except curses.error:
        pass