1
# Copyright (C) 2005-2011 Canonical Ltd
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.
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.
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
17
"""Tests for the bzrlib ui
22
from StringIO import StringIO
24
from testtools.matchers import *
32
from bzrlib.tests import (
35
from bzrlib.ui import text as _mod_ui_text
36
from bzrlib.tests.testui import (
37
ProgressRecordingUIFactory,
41
class TTYStringIO(StringIO):
42
"""A helper class which makes a StringIO look like a terminal"""
48
class NonTTYStringIO(StringIO):
49
"""Helper that implements isatty() but returns False"""
55
class TestUIConfiguration(tests.TestCaseWithTransport):
57
def test_output_encoding_configuration(self):
58
enc = fixtures.generate_unicode_encodings().next()
59
config.GlobalConfig().set_user_option('output_encoding',
61
ui = tests.TestUIFactory(stdin=None,
62
stdout=tests.StringIOWrapper(),
63
stderr=tests.StringIOWrapper())
64
output = ui.make_output_stream()
65
self.assertEquals(output.encoding, enc)
68
class TestTextUIFactory(tests.TestCase):
70
def make_test_ui_factory(self, stdin_contents):
71
ui = tests.TestUIFactory(stdin=stdin_contents,
72
stdout=tests.StringIOWrapper(),
73
stderr=tests.StringIOWrapper())
76
def test_text_factory_confirm(self):
77
# turns into reading a regular boolean
78
ui = self.make_test_ui_factory('n\n')
79
self.assertEquals(ui.confirm_action(u'Should %(thing)s pass?',
80
'bzrlib.tests.test_ui.confirmation',
84
def test_text_factory_ascii_password(self):
85
ui = self.make_test_ui_factory('secret\n')
86
pb = ui.nested_progress_bar()
88
self.assertEqual('secret',
89
self.apply_redirected(ui.stdin, ui.stdout,
92
# ': ' is appended to prompt
93
self.assertEqual(': ', ui.stderr.getvalue())
94
self.assertEqual('', ui.stdout.readline())
95
# stdin should be empty
96
self.assertEqual('', ui.stdin.readline())
100
def test_text_factory_utf8_password(self):
101
"""Test an utf8 password.
103
We can't predict what encoding users will have for stdin, so we force
104
it to utf8 to test that we transport the password correctly.
106
ui = self.make_test_ui_factory(u'baz\u1234'.encode('utf8'))
107
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
108
pb = ui.nested_progress_bar()
110
password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
112
u'Hello \u1234 %(user)s',
114
# We use StringIO objects, we need to decode them
115
self.assertEqual(u'baz\u1234', password.decode('utf8'))
116
self.assertEqual(u'Hello \u1234 some\u1234: ',
117
ui.stderr.getvalue().decode('utf8'))
118
# stdin and stdout should be empty
119
self.assertEqual('', ui.stdin.readline())
120
self.assertEqual('', ui.stdout.readline())
124
def test_text_ui_get_boolean(self):
125
stdin = tests.StringIOWrapper("y\n" # True
129
"yes with garbage\nY\n" # True
130
"not an answer\nno\n" # False
131
"I'm sure!\nyes\n" # True
134
stdout = tests.StringIOWrapper()
135
stderr = tests.StringIOWrapper()
136
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
137
self.assertEqual(True, factory.get_boolean(u""))
138
self.assertEqual(False, factory.get_boolean(u""))
139
self.assertEqual(True, factory.get_boolean(u""))
140
self.assertEqual(False, factory.get_boolean(u""))
141
self.assertEqual(True, factory.get_boolean(u""))
142
self.assertEqual(False, factory.get_boolean(u""))
143
self.assertEqual(True, factory.get_boolean(u""))
144
self.assertEqual(False, factory.get_boolean(u""))
145
self.assertEqual("foo\n", factory.stdin.read())
146
# stdin should be empty
147
self.assertEqual('', factory.stdin.readline())
148
# return false on EOF
149
self.assertEqual(False, factory.get_boolean(u""))
151
def test_text_ui_choose_bad_parameters(self):
152
stdin = tests.StringIOWrapper()
153
stdout = tests.StringIOWrapper()
154
stderr = tests.StringIOWrapper()
155
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
156
# invalid default index
157
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
159
self.assertRaises(ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
160
# duplicated shortcut
161
self.assertRaises(ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
163
def test_text_ui_choose_prompt(self):
164
stdin = tests.StringIOWrapper()
165
stdout = tests.StringIOWrapper()
166
stderr = tests.StringIOWrapper()
167
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
168
# choices with explicit shortcuts
169
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
170
self.assertEqual("prompt ([y]es, [N]o, more [i]nfo): \n", factory.stderr.getvalue())
171
# automatic shortcuts
172
factory.stderr.truncate(0)
173
factory.choose(u"prompt", u"yes\nNo\nmore info")
174
self.assertEqual("prompt ([y]es, [N]o, [m]ore info): \n", factory.stderr.getvalue())
176
def test_text_ui_choose_return_values(self):
177
choose = lambda: factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
178
stdin = tests.StringIOWrapper("y\n" # 0
182
"b\na\nd \n" # bad shortcuts, all ignored
183
"yes with garbage\nY\n" # 0
184
"not an answer\nno\n" # 1
185
"info\nmore info\n" # 3
188
stdout = tests.StringIOWrapper()
189
stderr = tests.StringIOWrapper()
190
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
191
self.assertEqual(0, choose())
192
self.assertEqual(1, choose())
193
self.assertEqual(3, choose())
194
self.assertEqual(1, choose())
195
self.assertEqual(0, choose())
196
self.assertEqual(1, choose())
197
self.assertEqual(3, choose())
198
self.assertEqual(2, choose())
199
self.assertEqual("foo\n", factory.stdin.read())
200
# stdin should be empty
201
self.assertEqual('', factory.stdin.readline())
203
self.assertEqual(None, choose())
205
def test_text_ui_choose_no_default(self):
206
stdin = tests.StringIOWrapper(" \n" # no default, invalid!
209
stdout = tests.StringIOWrapper()
210
stderr = tests.StringIOWrapper()
211
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
212
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
213
self.assertEqual("foo\n", factory.stdin.read())
215
def test_text_ui_get_integer(self):
216
stdin = tests.StringIOWrapper(
219
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
220
stdout = tests.StringIOWrapper()
221
stderr = tests.StringIOWrapper()
222
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
223
self.assertEqual(1, factory.get_integer(u""))
224
self.assertEqual(-2, factory.get_integer(u""))
225
self.assertEqual(42, factory.get_integer(u""))
227
def test_text_factory_prompt(self):
228
# see <https://launchpad.net/bugs/365891>
229
StringIO = tests.StringIOWrapper
230
factory = _mod_ui_text.TextUIFactory(StringIO(), StringIO(), StringIO())
231
factory.prompt(u'foo %2e')
232
self.assertEqual('', factory.stdout.getvalue())
233
self.assertEqual('foo %2e', factory.stderr.getvalue())
235
def test_text_factory_prompts_and_clears(self):
236
# a get_boolean call should clear the pb before prompting
238
self.overrideEnv('TERM', 'xterm')
239
factory = _mod_ui_text.TextUIFactory(
240
stdin=tests.StringIOWrapper("yada\ny\n"),
241
stdout=out, stderr=out)
242
factory._avail_width = lambda: 79
243
pb = factory.nested_progress_bar()
245
pb.show_spinner = False
246
pb.show_count = False
247
pb.update("foo", 0, 1)
248
self.assertEqual(True,
249
self.apply_redirected(None, factory.stdout,
252
u"what do you want"))
253
output = out.getvalue()
254
self.assertContainsRe(output,
256
self.assertContainsString(output,
257
r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
258
# stdin should have been totally consumed
259
self.assertEqual('', factory.stdin.readline())
261
def test_text_tick_after_update(self):
262
ui_factory = _mod_ui_text.TextUIFactory(stdout=tests.StringIOWrapper(),
263
stderr=tests.StringIOWrapper())
264
pb = ui_factory.nested_progress_bar()
266
pb.update('task', 0, 3)
267
# Reset the clock, so that it actually tries to repaint itself
268
ui_factory._progress_view._last_repaint = time.time() - 1.0
273
def test_text_ui_getusername(self):
274
factory = _mod_ui_text.TextUIFactory(None, None, None)
275
factory.stdin = tests.StringIOWrapper("someuser\n\n")
276
factory.stdout = tests.StringIOWrapper()
277
factory.stderr = tests.StringIOWrapper()
278
factory.stdout.encoding = "utf8"
279
# there is no output from the base factory
280
self.assertEqual("someuser",
281
factory.get_username(u'Hello %(host)s', host='some'))
282
self.assertEquals("Hello some: ", factory.stderr.getvalue())
283
self.assertEquals('', factory.stdout.getvalue())
284
self.assertEqual("", factory.get_username(u"Gebruiker"))
285
# stdin should be empty
286
self.assertEqual('', factory.stdin.readline())
288
def test_text_ui_getusername_utf8(self):
289
ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
290
stdout=tests.StringIOWrapper(),
291
stderr=tests.StringIOWrapper())
292
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
293
pb = ui.nested_progress_bar()
295
# there is no output from the base factory
296
username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
297
ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
298
self.assertEquals(u"someuser\u1234", username.decode('utf8'))
299
self.assertEquals(u"Hello\u1234 some\u1234: ",
300
ui.stderr.getvalue().decode("utf8"))
301
self.assertEquals('', ui.stdout.getvalue())
305
def test_quietness(self):
306
self.overrideEnv('BZR_PROGRESS_BAR', 'text')
307
ui_factory = _mod_ui_text.TextUIFactory(None,
310
self.assertIsInstance(ui_factory._progress_view,
311
_mod_ui_text.TextProgressView)
312
ui_factory.be_quiet(True)
313
self.assertIsInstance(ui_factory._progress_view,
314
_mod_ui_text.NullProgressView)
316
def test_text_ui_show_user_warning(self):
317
from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2a
318
from bzrlib.repofmt.knitpack_repo import RepositoryFormatKnitPack5
321
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
322
remote_fmt = remote.RemoteRepositoryFormat()
323
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
324
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
325
to_format=remote_fmt)
326
self.assertEquals('', out.getvalue())
327
self.assertEquals("Doing on-the-fly conversion from RepositoryFormat2a() to "
328
"RemoteRepositoryFormat(_network_name='Bazaar RepositoryFormatKnitPack5 "
329
"(bzr 1.6)\\n').\nThis may take some time. Upgrade the repositories to "
330
"the same format for better performance.\n",
332
# and now with it suppressed please
335
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
336
ui.suppressed_warnings.add('cross_format_fetch')
337
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
338
to_format=remote_fmt)
339
self.assertEquals('', out.getvalue())
340
self.assertEquals('', err.getvalue())
343
class TestTextUIOutputStream(tests.TestCase):
344
"""Tests for output stream that synchronizes with progress bar."""
346
def test_output_clears_terminal(self):
347
stdout = tests.StringIOWrapper()
348
stderr = tests.StringIOWrapper()
351
uif = _mod_ui_text.TextUIFactory(None, stdout, stderr)
352
uif.clear_term = lambda: clear_calls.append('clear')
354
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout)
355
stream.write("Hello world!\n")
356
stream.write("there's more...\n")
357
stream.writelines(["1\n", "2\n", "3\n"])
359
self.assertEqual(stdout.getvalue(),
363
self.assertEqual(['clear', 'clear', 'clear'],
369
class UITests(tests.TestCase):
371
def test_progress_construction(self):
372
"""TextUIFactory constructs the right progress view.
374
FileStringIO = tests.StringIOWrapper
375
for (file_class, term, pb, expected_pb_class) in (
376
# on an xterm, either use them or not as the user requests,
377
# otherwise default on
378
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
379
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
380
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
381
# on a dumb terminal, again if there's explicit configuration do
382
# it, otherwise default off
383
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
384
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
385
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
386
# on a non-tty terminal, it's null regardless of $TERM
387
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
388
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
389
# however, it can still be forced on
390
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
392
self.overrideEnv('TERM', term)
393
self.overrideEnv('BZR_PROGRESS_BAR', pb)
394
stdin = file_class('')
395
stderr = file_class()
396
stdout = file_class()
397
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
398
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
399
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
400
self.assertIsInstance(uif.make_progress_view(),
402
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
404
def test_text_ui_non_terminal(self):
405
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
406
stdin = NonTTYStringIO('')
407
stderr = NonTTYStringIO()
408
stdout = NonTTYStringIO()
409
for term_type in ['dumb', None, 'xterm']:
410
self.overrideEnv('TERM', term_type)
411
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
412
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
413
'TERM=%r' % (term_type,))
416
class SilentUITests(tests.TestCase):
418
def test_silent_factory_get_password(self):
419
# A silent factory that can't do user interaction can't get a
420
# password. Possibly it should raise a more specific error but it
422
ui = _mod_ui.SilentUIFactory()
423
stdout = tests.StringIOWrapper()
426
self.apply_redirected,
427
None, stdout, stdout, ui.get_password)
428
# and it didn't write anything out either
429
self.assertEqual('', stdout.getvalue())
431
def test_silent_ui_getbool(self):
432
factory = _mod_ui.SilentUIFactory()
433
stdout = tests.StringIOWrapper()
436
self.apply_redirected,
437
None, stdout, stdout, factory.get_boolean, u"foo")
440
class TestUIFactoryTests(tests.TestCase):
442
def test_test_ui_factory_progress(self):
443
# there's no output; we just want to make sure this doesn't crash -
444
# see https://bugs.launchpad.net/bzr/+bug/408201
445
ui = tests.TestUIFactory()
446
pb = ui.nested_progress_bar()
452
class CannedInputUIFactoryTests(tests.TestCase):
454
def test_canned_input_get_input(self):
455
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
456
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
457
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
458
self.assertEqual('password',
459
uif.get_password(u'Password for %(host)s',
461
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
464
class TestBoolFromString(tests.TestCase):
466
def assertIsTrue(self, s, accepted_values=None):
467
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
468
self.assertEquals(True, res)
470
def assertIsFalse(self, s, accepted_values=None):
471
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
472
self.assertEquals(False, res)
474
def assertIsNone(self, s, accepted_values=None):
475
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
476
self.assertIs(None, res)
478
def test_know_valid_values(self):
479
self.assertIsTrue('true')
480
self.assertIsFalse('false')
481
self.assertIsTrue('1')
482
self.assertIsFalse('0')
483
self.assertIsTrue('on')
484
self.assertIsFalse('off')
485
self.assertIsTrue('yes')
486
self.assertIsFalse('no')
487
self.assertIsTrue('y')
488
self.assertIsFalse('n')
489
# Also try some case variations
490
self.assertIsTrue('True')
491
self.assertIsFalse('False')
492
self.assertIsTrue('On')
493
self.assertIsFalse('Off')
494
self.assertIsTrue('ON')
495
self.assertIsFalse('OFF')
496
self.assertIsTrue('oN')
497
self.assertIsFalse('oFf')
499
def test_invalid_values(self):
500
self.assertIsNone(None)
501
self.assertIsNone('doubt')
502
self.assertIsNone('frue')
503
self.assertIsNone('talse')
504
self.assertIsNone('42')
506
def test_provided_values(self):
507
av = dict(y=True, n=False, yes=True, no=False)
508
self.assertIsTrue('y', av)
509
self.assertIsTrue('Y', av)
510
self.assertIsTrue('Yes', av)
511
self.assertIsFalse('n', av)
512
self.assertIsFalse('N', av)
513
self.assertIsFalse('No', av)
514
self.assertIsNone('1', av)
515
self.assertIsNone('0', av)
516
self.assertIsNone('on', av)
517
self.assertIsNone('off', av)
520
class TestConfirmationUserInterfacePolicy(tests.TestCase):
522
def test_confirm_action_default(self):
523
base_ui = _mod_ui.NoninteractiveUIFactory()
524
for answer in [True, False]:
526
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
527
.confirm_action("Do something?",
528
"bzrlib.tests.do_something", {}),
531
def test_confirm_action_specific(self):
532
base_ui = _mod_ui.NoninteractiveUIFactory()
533
for default_answer in [True, False]:
534
for specific_answer in [True, False]:
535
for conf_id in ['given_id', 'other_id']:
536
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
537
base_ui, default_answer, dict(given_id=specific_answer))
538
result = wrapper.confirm_action("Do something?", conf_id, {})
539
if conf_id == 'given_id':
540
self.assertEquals(result, specific_answer)
542
self.assertEquals(result, default_answer)
545
base_ui = _mod_ui.NoninteractiveUIFactory()
546
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
547
base_ui, True, dict(a=2))
548
self.assertThat(repr(wrapper),
549
Equals("ConfirmationUserInterfacePolicy("
550
"NoninteractiveUIFactory(), True, {'a': 2})"))
553
class TestProgressRecordingUI(tests.TestCase):
554
"""Test test-oriented UIFactory that records progress updates"""
556
def test_nested_ignore_depth_beyond_one(self):
557
# we only want to capture the first level out progress, not
558
# want sub-components might do. So we have nested bars ignored.
559
factory = ProgressRecordingUIFactory()
560
pb1 = factory.nested_progress_bar()
561
pb1.update('foo', 0, 1)
562
pb2 = factory.nested_progress_bar()
563
pb2.update('foo', 0, 1)
566
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)