~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_ui.py

  • Committer: Ian Clatworthy
  • Date: 2009-12-03 23:21:16 UTC
  • mfrom: (4852.4.1 RCStoVCS)
  • mto: This revision was merged to the branch mainline in revision 4860.
  • Revision ID: ian.clatworthy@canonical.com-20091203232116-f8igfvc6muqrn4yx
Revision Control -> Version Control in docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2008, 2009 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for the bzrlib ui
 
18
"""
 
19
 
 
20
import os
 
21
from StringIO import StringIO
 
22
import re
 
23
import sys
 
24
import time
 
25
 
 
26
from bzrlib import (
 
27
    errors,
 
28
    tests,
 
29
    ui as _mod_ui,
 
30
    )
 
31
from bzrlib.symbol_versioning import (
 
32
    deprecated_in,
 
33
    )
 
34
from bzrlib.tests import (
 
35
    TestCase,
 
36
    TestUIFactory,
 
37
    StringIOWrapper,
 
38
    )
 
39
from bzrlib.tests.test_progress import (
 
40
    _NonTTYStringIO,
 
41
    _TTYStringIO,
 
42
    )
 
43
from bzrlib.ui import (
 
44
    CannedInputUIFactory,
 
45
    SilentUIFactory,
 
46
    UIFactory,
 
47
    make_ui_for_terminal,
 
48
    )
 
49
from bzrlib.ui.text import (
 
50
    NullProgressView,
 
51
    TextProgressView,
 
52
    TextUIFactory,
 
53
    )
 
54
 
 
55
 
 
56
class TestTextUIFactory(tests.TestCase):
 
57
 
 
58
    def test_text_factory_ascii_password(self):
 
59
        ui = tests.TestUIFactory(stdin='secret\n',
 
60
                                 stdout=tests.StringIOWrapper(),
 
61
                                 stderr=tests.StringIOWrapper())
 
62
        pb = ui.nested_progress_bar()
 
63
        try:
 
64
            self.assertEqual('secret',
 
65
                             self.apply_redirected(ui.stdin, ui.stdout,
 
66
                                                   ui.stderr,
 
67
                                                   ui.get_password))
 
68
            # ': ' is appended to prompt
 
69
            self.assertEqual(': ', ui.stderr.getvalue())
 
70
            self.assertEqual('', ui.stdout.readline())
 
71
            # stdin should be empty
 
72
            self.assertEqual('', ui.stdin.readline())
 
73
        finally:
 
74
            pb.finished()
 
75
 
 
76
    def test_text_factory_utf8_password(self):
 
77
        """Test an utf8 password.
 
78
 
 
79
        We can't predict what encoding users will have for stdin, so we force
 
80
        it to utf8 to test that we transport the password correctly.
 
81
        """
 
82
        ui = tests.TestUIFactory(stdin=u'baz\u1234'.encode('utf8'),
 
83
                                 stdout=tests.StringIOWrapper(),
 
84
                                 stderr=tests.StringIOWrapper())
 
85
        ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
 
86
        pb = ui.nested_progress_bar()
 
87
        try:
 
88
            password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
 
89
                                             ui.get_password,
 
90
                                             u'Hello \u1234 %(user)s',
 
91
                                             user=u'some\u1234')
 
92
            # We use StringIO objects, we need to decode them
 
93
            self.assertEqual(u'baz\u1234', password.decode('utf8'))
 
94
            self.assertEqual(u'Hello \u1234 some\u1234: ',
 
95
                             ui.stderr.getvalue().decode('utf8'))
 
96
            # stdin and stdout should be empty
 
97
            self.assertEqual('', ui.stdin.readline())
 
98
            self.assertEqual('', ui.stdout.readline())
 
99
        finally:
 
100
            pb.finished()
 
101
 
 
102
    def test_progress_note(self):
 
103
        stderr = StringIO()
 
104
        stdout = StringIO()
 
105
        ui_factory = TextUIFactory(stdin=StringIO(''),
 
106
            stderr=stderr,
 
107
            stdout=stdout)
 
108
        pb = ui_factory.nested_progress_bar()
 
109
        try:
 
110
            result = self.applyDeprecated(deprecated_in((2, 1, 0)),
 
111
                pb.note,
 
112
                't')
 
113
            self.assertEqual(None, result)
 
114
            self.assertEqual("t\n", stdout.getvalue())
 
115
            # Since there was no update() call, there should be no clear() call
 
116
            self.failIf(re.search(r'^\r {10,}\r$',
 
117
                                  stderr.getvalue()) is not None,
 
118
                        'We cleared the stderr without anything to put there')
 
119
        finally:
 
120
            pb.finished()
 
121
 
 
122
    def test_progress_note_clears(self):
 
123
        stderr = _TTYStringIO()
 
124
        stdout = _TTYStringIO()
 
125
        # so that we get a TextProgressBar
 
126
        os.environ['TERM'] = 'xterm'
 
127
        ui_factory = TextUIFactory(
 
128
            stdin=StringIO(''),
 
129
            stdout=stdout, stderr=stderr)
 
130
        self.assertIsInstance(ui_factory._progress_view,
 
131
            TextProgressView)
 
132
        pb = ui_factory.nested_progress_bar()
 
133
        try:
 
134
            # Create a progress update that isn't throttled
 
135
            pb.update('x', 1, 1)
 
136
            result = self.applyDeprecated(deprecated_in((2, 1, 0)),
 
137
                pb.note, 't')
 
138
            self.assertEqual(None, result)
 
139
            self.assertEqual("t\n", stdout.getvalue())
 
140
            # the exact contents will depend on the terminal width and we don't
 
141
            # care about that right now - but you're probably running it on at
 
142
            # least a 10-character wide terminal :)
 
143
            self.assertContainsRe(stderr.getvalue(), r'\r {10,}\r$')
 
144
        finally:
 
145
            pb.finished()
 
146
 
 
147
    def test_progress_nested(self):
 
148
        # test factory based nested and popping.
 
149
        ui = TextUIFactory(None, None, None)
 
150
        pb1 = ui.nested_progress_bar()
 
151
        pb2 = ui.nested_progress_bar()
 
152
        # You do get a warning if the outermost progress bar wasn't finished
 
153
        # first - it's not clear if this is really useful or if it should just
 
154
        # become orphaned -- mbp 20090120
 
155
        warnings, _ = self.callCatchWarnings(pb1.finished)
 
156
        if len(warnings) != 1:
 
157
            self.fail("unexpected warnings: %r" % (warnings,))
 
158
        pb2.finished()
 
159
        pb1.finished()
 
160
 
 
161
    def test_text_ui_get_boolean(self):
 
162
        stdin = StringIO("y\n" # True
 
163
                         "n\n" # False
 
164
                         "yes with garbage\nY\n" # True
 
165
                         "not an answer\nno\n" # False
 
166
                         "I'm sure!\nyes\n" # True
 
167
                         "NO\n" # False
 
168
                         "foo\n")
 
169
        stdout = StringIO()
 
170
        stderr = StringIO()
 
171
        factory = TextUIFactory(stdin, stdout, stderr)
 
172
        self.assertEqual(True, factory.get_boolean(""))
 
173
        self.assertEqual(False, factory.get_boolean(""))
 
174
        self.assertEqual(True, factory.get_boolean(""))
 
175
        self.assertEqual(False, factory.get_boolean(""))
 
176
        self.assertEqual(True, factory.get_boolean(""))
 
177
        self.assertEqual(False, factory.get_boolean(""))
 
178
        self.assertEqual("foo\n", factory.stdin.read())
 
179
        # stdin should be empty
 
180
        self.assertEqual('', factory.stdin.readline())
 
181
 
 
182
    def test_text_factory_prompt(self):
 
183
        # see <https://launchpad.net/bugs/365891>
 
184
        factory = TextUIFactory(StringIO(), StringIO(), StringIO())
 
185
        factory.prompt('foo %2e')
 
186
        self.assertEqual('', factory.stdout.getvalue())
 
187
        self.assertEqual('foo %2e', factory.stderr.getvalue())
 
188
 
 
189
    def test_text_factory_prompts_and_clears(self):
 
190
        # a get_boolean call should clear the pb before prompting
 
191
        out = _TTYStringIO()
 
192
        os.environ['TERM'] = 'xterm'
 
193
        factory = TextUIFactory(stdin=StringIO("yada\ny\n"), stdout=out, stderr=out)
 
194
        pb = factory.nested_progress_bar()
 
195
        pb.show_bar = False
 
196
        pb.show_spinner = False
 
197
        pb.show_count = False
 
198
        pb.update("foo", 0, 1)
 
199
        self.assertEqual(True,
 
200
                         self.apply_redirected(None, factory.stdout,
 
201
                                               factory.stdout,
 
202
                                               factory.get_boolean,
 
203
                                               "what do you want"))
 
204
        output = out.getvalue()
 
205
        self.assertContainsRe(factory.stdout.getvalue(),
 
206
            "foo *\r\r  *\r*")
 
207
        self.assertContainsRe(factory.stdout.getvalue(),
 
208
            r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")
 
209
        # stdin should have been totally consumed
 
210
        self.assertEqual('', factory.stdin.readline())
 
211
 
 
212
    def test_text_tick_after_update(self):
 
213
        ui_factory = TextUIFactory(stdout=StringIO(), stderr=StringIO())
 
214
        pb = ui_factory.nested_progress_bar()
 
215
        try:
 
216
            pb.update('task', 0, 3)
 
217
            # Reset the clock, so that it actually tries to repaint itself
 
218
            ui_factory._progress_view._last_repaint = time.time() - 1.0
 
219
            pb.tick()
 
220
        finally:
 
221
            pb.finished()
 
222
 
 
223
    def test_text_ui_getusername(self):
 
224
        factory = TextUIFactory(None, None, None)
 
225
        factory.stdin = StringIO("someuser\n\n")
 
226
        factory.stdout = StringIO()
 
227
        factory.stderr = StringIO()
 
228
        factory.stdout.encoding = "utf8"
 
229
        # there is no output from the base factory
 
230
        self.assertEqual("someuser",
 
231
                         factory.get_username('Hello %(host)s', host='some'))
 
232
        self.assertEquals("Hello some: ", factory.stderr.getvalue())
 
233
        self.assertEquals('', factory.stdout.getvalue())
 
234
        self.assertEqual("", factory.get_username("Gebruiker"))
 
235
        # stdin should be empty
 
236
        self.assertEqual('', factory.stdin.readline())
 
237
 
 
238
    def test_text_ui_getusername_utf8(self):
 
239
        ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
 
240
                                 stdout=tests.StringIOWrapper(),
 
241
                                 stderr=tests.StringIOWrapper())
 
242
        ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
 
243
        pb = ui.nested_progress_bar()
 
244
        try:
 
245
            # there is no output from the base factory
 
246
            username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
 
247
                ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
 
248
            self.assertEquals(u"someuser\u1234", username.decode('utf8'))
 
249
            self.assertEquals(u"Hello\u1234 some\u1234: ",
 
250
                              ui.stderr.getvalue().decode("utf8"))
 
251
            self.assertEquals('', ui.stdout.getvalue())
 
252
        finally:
 
253
            pb.finished()
 
254
 
 
255
 
 
256
class UITests(tests.TestCase):
 
257
 
 
258
    def test_progress_construction(self):
 
259
        """TextUIFactory constructs the right progress view.
 
260
        """
 
261
        for (file_class, term, pb, expected_pb_class) in (
 
262
            # on an xterm, either use them or not as the user requests,
 
263
            # otherwise default on
 
264
            (_TTYStringIO, 'xterm', 'none', NullProgressView),
 
265
            (_TTYStringIO, 'xterm', 'text', TextProgressView),
 
266
            (_TTYStringIO, 'xterm', None, TextProgressView),
 
267
            # on a dumb terminal, again if there's explicit configuration do
 
268
            # it, otherwise default off
 
269
            (_TTYStringIO, 'dumb', 'none', NullProgressView),
 
270
            (_TTYStringIO, 'dumb', 'text', TextProgressView),
 
271
            (_TTYStringIO, 'dumb', None, NullProgressView),
 
272
            # on a non-tty terminal, it's null regardless of $TERM
 
273
            (StringIO, 'xterm', None, NullProgressView),
 
274
            (StringIO, 'dumb', None, NullProgressView),
 
275
            # however, it can still be forced on
 
276
            (StringIO, 'dumb', 'text', TextProgressView),
 
277
            ):
 
278
            os.environ['TERM'] = term
 
279
            if pb is None:
 
280
                if 'BZR_PROGRESS_BAR' in os.environ:
 
281
                    del os.environ['BZR_PROGRESS_BAR']
 
282
            else:
 
283
                os.environ['BZR_PROGRESS_BAR'] = pb
 
284
            stdin = file_class('')
 
285
            stderr = file_class()
 
286
            stdout = file_class()
 
287
            uif = make_ui_for_terminal(stdin, stdout, stderr)
 
288
            self.assertIsInstance(uif, TextUIFactory,
 
289
                "TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
 
290
            self.assertIsInstance(uif.make_progress_view(),
 
291
                expected_pb_class,
 
292
                "TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
 
293
 
 
294
    def test_text_ui_non_terminal(self):
 
295
        """Even on non-ttys, make_ui_for_terminal gives a text ui."""
 
296
        stdin = _NonTTYStringIO('')
 
297
        stderr = _NonTTYStringIO()
 
298
        stdout = _NonTTYStringIO()
 
299
        for term_type in ['dumb', None, 'xterm']:
 
300
            if term_type is None:
 
301
                del os.environ['TERM']
 
302
            else:
 
303
                os.environ['TERM'] = term_type
 
304
            uif = make_ui_for_terminal(stdin, stdout, stderr)
 
305
            self.assertIsInstance(uif, TextUIFactory,
 
306
                'TERM=%r' % (term_type,))
 
307
 
 
308
 
 
309
class SilentUITests(TestCase):
 
310
 
 
311
    def test_silent_factory_get_password(self):
 
312
        # A silent factory that can't do user interaction can't get a
 
313
        # password.  Possibly it should raise a more specific error but it
 
314
        # can't succeed.
 
315
        ui = SilentUIFactory()
 
316
        stdout = StringIO()
 
317
        self.assertRaises(
 
318
            NotImplementedError,
 
319
            self.apply_redirected,
 
320
            None, stdout, stdout, ui.get_password)
 
321
        # and it didn't write anything out either
 
322
        self.assertEqual('', stdout.getvalue())
 
323
 
 
324
    def test_silent_ui_getbool(self):
 
325
        factory = SilentUIFactory()
 
326
        stdout = StringIO()
 
327
        self.assertRaises(
 
328
            NotImplementedError,
 
329
            self.apply_redirected,
 
330
            None, stdout, stdout, factory.get_boolean, "foo")
 
331
 
 
332
 
 
333
class TestUIFactoryTests(TestCase):
 
334
 
 
335
    def test_test_ui_factory_progress(self):
 
336
        # there's no output; we just want to make sure this doesn't crash -
 
337
        # see https://bugs.edge.launchpad.net/bzr/+bug/408201
 
338
        ui = TestUIFactory()
 
339
        pb = ui.nested_progress_bar()
 
340
        pb.update('hello')
 
341
        pb.tick()
 
342
        pb.finished()
 
343
 
 
344
 
 
345
class CannedInputUIFactoryTests(TestCase):
 
346
    
 
347
    def test_canned_input_get_input(self):
 
348
        uif = CannedInputUIFactory([True, 'mbp', 'password'])
 
349
        self.assertEqual(uif.get_boolean('Extra cheese?'), True)
 
350
        self.assertEqual(uif.get_username('Enter your user name'), 'mbp')
 
351
        self.assertEqual(uif.get_password('Password for %(host)s', host='example.com'),
 
352
            'password')
 
353
 
 
354
 
 
355
class TestBoolFromString(tests.TestCase):
 
356
 
 
357
    def assertIsTrue(self, s, accepted_values=None):
 
358
        res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
 
359
        self.assertEquals(True, res)
 
360
 
 
361
    def assertIsFalse(self, s, accepted_values=None):
 
362
        res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
 
363
        self.assertEquals(False, res)
 
364
 
 
365
    def assertIsNone(self, s, accepted_values=None):
 
366
        res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
 
367
        self.assertIs(None, res)
 
368
 
 
369
    def test_know_valid_values(self):
 
370
        self.assertIsTrue('true')
 
371
        self.assertIsFalse('false')
 
372
        self.assertIsTrue('1')
 
373
        self.assertIsFalse('0')
 
374
        self.assertIsTrue('on')
 
375
        self.assertIsFalse('off')
 
376
        self.assertIsTrue('yes')
 
377
        self.assertIsFalse('no')
 
378
        self.assertIsTrue('y')
 
379
        self.assertIsFalse('n')
 
380
        # Also try some case variations
 
381
        self.assertIsTrue('True')
 
382
        self.assertIsFalse('False')
 
383
        self.assertIsTrue('On')
 
384
        self.assertIsFalse('Off')
 
385
        self.assertIsTrue('ON')
 
386
        self.assertIsFalse('OFF')
 
387
        self.assertIsTrue('oN')
 
388
        self.assertIsFalse('oFf')
 
389
 
 
390
    def test_invalid_values(self):
 
391
        self.assertIsNone(None)
 
392
        self.assertIsNone('doubt')
 
393
        self.assertIsNone('frue')
 
394
        self.assertIsNone('talse')
 
395
        self.assertIsNone('42')
 
396
 
 
397
    def test_provided_values(self):
 
398
        av = dict(y=True, n=False, yes=True, no=False)
 
399
        self.assertIsTrue('y', av)
 
400
        self.assertIsTrue('Y', av)
 
401
        self.assertIsTrue('Yes', av)
 
402
        self.assertIsFalse('n', av)
 
403
        self.assertIsFalse('N', av)
 
404
        self.assertIsFalse('No', av)
 
405
        self.assertIsNone('1', av)
 
406
        self.assertIsNone('0', av)
 
407
        self.assertIsNone('on', av)
 
408
        self.assertIsNone('off', av)