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.GlobalStack().set('output_encoding', enc)
60
ui = tests.TestUIFactory(stdin=None,
61
stdout=tests.StringIOWrapper(),
62
stderr=tests.StringIOWrapper())
63
output = ui.make_output_stream()
64
self.assertEquals(output.encoding, enc)
67
class TestTextUIFactory(tests.TestCase):
69
def make_test_ui_factory(self, stdin_contents):
70
ui = tests.TestUIFactory(stdin=stdin_contents,
71
stdout=tests.StringIOWrapper(),
72
stderr=tests.StringIOWrapper())
75
def test_text_factory_confirm(self):
76
# turns into reading a regular boolean
77
ui = self.make_test_ui_factory('n\n')
78
self.assertEquals(ui.confirm_action(u'Should %(thing)s pass?',
79
'bzrlib.tests.test_ui.confirmation',
83
def test_text_factory_ascii_password(self):
84
ui = self.make_test_ui_factory('secret\n')
85
pb = ui.nested_progress_bar()
87
self.assertEqual('secret',
88
self.apply_redirected(ui.stdin, ui.stdout,
91
# ': ' is appended to prompt
92
self.assertEqual(': ', ui.stderr.getvalue())
93
self.assertEqual('', ui.stdout.readline())
94
# stdin should be empty
95
self.assertEqual('', ui.stdin.readline())
99
def test_text_factory_utf8_password(self):
100
"""Test an utf8 password.
102
We can't predict what encoding users will have for stdin, so we force
103
it to utf8 to test that we transport the password correctly.
105
ui = self.make_test_ui_factory(u'baz\u1234'.encode('utf8'))
106
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
107
pb = ui.nested_progress_bar()
109
password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
111
u'Hello \u1234 %(user)s',
113
# We use StringIO objects, we need to decode them
114
self.assertEqual(u'baz\u1234', password.decode('utf8'))
115
self.assertEqual(u'Hello \u1234 some\u1234: ',
116
ui.stderr.getvalue().decode('utf8'))
117
# stdin and stdout should be empty
118
self.assertEqual('', ui.stdin.readline())
119
self.assertEqual('', ui.stdout.readline())
123
def test_text_ui_get_boolean(self):
124
stdin = tests.StringIOWrapper("y\n" # True
128
"yes with garbage\nY\n" # True
129
"not an answer\nno\n" # False
130
"I'm sure!\nyes\n" # True
133
stdout = tests.StringIOWrapper()
134
stderr = tests.StringIOWrapper()
135
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
136
self.assertEqual(True, factory.get_boolean(u""))
137
self.assertEqual(False, factory.get_boolean(u""))
138
self.assertEqual(True, factory.get_boolean(u""))
139
self.assertEqual(False, factory.get_boolean(u""))
140
self.assertEqual(True, factory.get_boolean(u""))
141
self.assertEqual(False, factory.get_boolean(u""))
142
self.assertEqual(True, factory.get_boolean(u""))
143
self.assertEqual(False, factory.get_boolean(u""))
144
self.assertEqual("foo\n", factory.stdin.read())
145
# stdin should be empty
146
self.assertEqual('', factory.stdin.readline())
147
# return false on EOF
148
self.assertEqual(False, factory.get_boolean(u""))
150
def test_text_ui_choose_bad_parameters(self):
151
stdin = tests.StringIOWrapper()
152
stdout = tests.StringIOWrapper()
153
stderr = tests.StringIOWrapper()
154
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
155
# invalid default index
156
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
158
self.assertRaises(ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
159
# duplicated shortcut
160
self.assertRaises(ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
162
def test_text_ui_choose_prompt(self):
163
stdin = tests.StringIOWrapper()
164
stdout = tests.StringIOWrapper()
165
stderr = tests.StringIOWrapper()
166
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
167
# choices with explicit shortcuts
168
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
169
self.assertEqual("prompt ([y]es, [N]o, more [i]nfo): \n", factory.stderr.getvalue())
170
# automatic shortcuts
171
factory.stderr.truncate(0)
172
factory.choose(u"prompt", u"yes\nNo\nmore info")
173
self.assertEqual("prompt ([y]es, [N]o, [m]ore info): \n", factory.stderr.getvalue())
175
def test_text_ui_choose_return_values(self):
176
choose = lambda: factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
177
stdin = tests.StringIOWrapper("y\n" # 0
181
"b\na\nd \n" # bad shortcuts, all ignored
182
"yes with garbage\nY\n" # 0
183
"not an answer\nno\n" # 1
184
"info\nmore info\n" # 3
187
stdout = tests.StringIOWrapper()
188
stderr = tests.StringIOWrapper()
189
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
190
self.assertEqual(0, choose())
191
self.assertEqual(1, choose())
192
self.assertEqual(3, choose())
193
self.assertEqual(1, choose())
194
self.assertEqual(0, choose())
195
self.assertEqual(1, choose())
196
self.assertEqual(3, choose())
197
self.assertEqual(2, choose())
198
self.assertEqual("foo\n", factory.stdin.read())
199
# stdin should be empty
200
self.assertEqual('', factory.stdin.readline())
202
self.assertEqual(None, choose())
204
def test_text_ui_choose_no_default(self):
205
stdin = tests.StringIOWrapper(" \n" # no default, invalid!
208
stdout = tests.StringIOWrapper()
209
stderr = tests.StringIOWrapper()
210
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
211
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
212
self.assertEqual("foo\n", factory.stdin.read())
214
def test_text_ui_get_integer(self):
215
stdin = tests.StringIOWrapper(
218
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
219
stdout = tests.StringIOWrapper()
220
stderr = tests.StringIOWrapper()
221
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
222
self.assertEqual(1, factory.get_integer(u""))
223
self.assertEqual(-2, factory.get_integer(u""))
224
self.assertEqual(42, factory.get_integer(u""))
226
def test_text_factory_prompt(self):
227
# see <https://launchpad.net/bugs/365891>
228
StringIO = tests.StringIOWrapper
229
factory = _mod_ui_text.TextUIFactory(StringIO(), StringIO(), StringIO())
230
factory.prompt(u'foo %2e')
231
self.assertEqual('', factory.stdout.getvalue())
232
self.assertEqual('foo %2e', factory.stderr.getvalue())
234
def test_text_factory_prompts_and_clears(self):
235
# a get_boolean call should clear the pb before prompting
237
self.overrideEnv('TERM', 'xterm')
238
factory = _mod_ui_text.TextUIFactory(
239
stdin=tests.StringIOWrapper("yada\ny\n"),
240
stdout=out, stderr=out)
241
factory._avail_width = lambda: 79
242
pb = factory.nested_progress_bar()
244
pb.show_spinner = False
245
pb.show_count = False
246
pb.update("foo", 0, 1)
247
self.assertEqual(True,
248
self.apply_redirected(None, factory.stdout,
251
u"what do you want"))
252
output = out.getvalue()
253
self.assertContainsRe(output,
255
self.assertContainsString(output,
256
r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
257
# stdin should have been totally consumed
258
self.assertEqual('', factory.stdin.readline())
260
def test_text_tick_after_update(self):
261
ui_factory = _mod_ui_text.TextUIFactory(stdout=tests.StringIOWrapper(),
262
stderr=tests.StringIOWrapper())
263
pb = ui_factory.nested_progress_bar()
265
pb.update('task', 0, 3)
266
# Reset the clock, so that it actually tries to repaint itself
267
ui_factory._progress_view._last_repaint = time.time() - 1.0
272
def test_text_ui_getusername(self):
273
factory = _mod_ui_text.TextUIFactory(None, None, None)
274
factory.stdin = tests.StringIOWrapper("someuser\n\n")
275
factory.stdout = tests.StringIOWrapper()
276
factory.stderr = tests.StringIOWrapper()
277
factory.stdout.encoding = "utf8"
278
# there is no output from the base factory
279
self.assertEqual("someuser",
280
factory.get_username(u'Hello %(host)s', host='some'))
281
self.assertEquals("Hello some: ", factory.stderr.getvalue())
282
self.assertEquals('', factory.stdout.getvalue())
283
self.assertEqual("", factory.get_username(u"Gebruiker"))
284
# stdin should be empty
285
self.assertEqual('', factory.stdin.readline())
287
def test_text_ui_getusername_utf8(self):
288
ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
289
stdout=tests.StringIOWrapper(),
290
stderr=tests.StringIOWrapper())
291
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
292
pb = ui.nested_progress_bar()
294
# there is no output from the base factory
295
username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
296
ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
297
self.assertEquals(u"someuser\u1234", username.decode('utf8'))
298
self.assertEquals(u"Hello\u1234 some\u1234: ",
299
ui.stderr.getvalue().decode("utf8"))
300
self.assertEquals('', ui.stdout.getvalue())
304
def test_quietness(self):
305
self.overrideEnv('BZR_PROGRESS_BAR', 'text')
306
ui_factory = _mod_ui_text.TextUIFactory(None,
309
self.assertIsInstance(ui_factory._progress_view,
310
_mod_ui_text.TextProgressView)
311
ui_factory.be_quiet(True)
312
self.assertIsInstance(ui_factory._progress_view,
313
_mod_ui_text.NullProgressView)
315
def test_text_ui_show_user_warning(self):
316
from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2a
317
from bzrlib.repofmt.knitpack_repo import RepositoryFormatKnitPack5
320
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
321
remote_fmt = remote.RemoteRepositoryFormat()
322
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
323
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
324
to_format=remote_fmt)
325
self.assertEquals('', out.getvalue())
326
self.assertEquals("Doing on-the-fly conversion from RepositoryFormat2a() to "
327
"RemoteRepositoryFormat(_network_name='Bazaar RepositoryFormatKnitPack5 "
328
"(bzr 1.6)\\n').\nThis may take some time. Upgrade the repositories to "
329
"the same format for better performance.\n",
331
# and now with it suppressed please
334
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
335
ui.suppressed_warnings.add('cross_format_fetch')
336
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
337
to_format=remote_fmt)
338
self.assertEquals('', out.getvalue())
339
self.assertEquals('', err.getvalue())
342
class TestTextUIOutputStream(tests.TestCase):
343
"""Tests for output stream that synchronizes with progress bar."""
345
def test_output_clears_terminal(self):
346
stdout = tests.StringIOWrapper()
347
stderr = tests.StringIOWrapper()
350
uif = _mod_ui_text.TextUIFactory(None, stdout, stderr)
351
uif.clear_term = lambda: clear_calls.append('clear')
353
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout)
354
stream.write("Hello world!\n")
355
stream.write("there's more...\n")
356
stream.writelines(["1\n", "2\n", "3\n"])
358
self.assertEqual(stdout.getvalue(),
362
self.assertEqual(['clear', 'clear', 'clear'],
368
class UITests(tests.TestCase):
370
def test_progress_construction(self):
371
"""TextUIFactory constructs the right progress view.
373
FileStringIO = tests.StringIOWrapper
374
for (file_class, term, pb, expected_pb_class) in (
375
# on an xterm, either use them or not as the user requests,
376
# otherwise default on
377
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
378
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
379
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
380
# on a dumb terminal, again if there's explicit configuration do
381
# it, otherwise default off
382
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
383
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
384
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
385
# on a non-tty terminal, it's null regardless of $TERM
386
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
387
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
388
# however, it can still be forced on
389
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
391
self.overrideEnv('TERM', term)
392
self.overrideEnv('BZR_PROGRESS_BAR', pb)
393
stdin = file_class('')
394
stderr = file_class()
395
stdout = file_class()
396
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
397
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
398
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
399
self.assertIsInstance(uif.make_progress_view(),
401
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
403
def test_text_ui_non_terminal(self):
404
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
405
stdin = NonTTYStringIO('')
406
stderr = NonTTYStringIO()
407
stdout = NonTTYStringIO()
408
for term_type in ['dumb', None, 'xterm']:
409
self.overrideEnv('TERM', term_type)
410
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
411
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
412
'TERM=%r' % (term_type,))
415
class SilentUITests(tests.TestCase):
417
def test_silent_factory_get_password(self):
418
# A silent factory that can't do user interaction can't get a
419
# password. Possibly it should raise a more specific error but it
421
ui = _mod_ui.SilentUIFactory()
422
stdout = tests.StringIOWrapper()
425
self.apply_redirected,
426
None, stdout, stdout, ui.get_password)
427
# and it didn't write anything out either
428
self.assertEqual('', stdout.getvalue())
430
def test_silent_ui_getbool(self):
431
factory = _mod_ui.SilentUIFactory()
432
stdout = tests.StringIOWrapper()
435
self.apply_redirected,
436
None, stdout, stdout, factory.get_boolean, u"foo")
439
class TestUIFactoryTests(tests.TestCase):
441
def test_test_ui_factory_progress(self):
442
# there's no output; we just want to make sure this doesn't crash -
443
# see https://bugs.launchpad.net/bzr/+bug/408201
444
ui = tests.TestUIFactory()
445
pb = ui.nested_progress_bar()
451
class CannedInputUIFactoryTests(tests.TestCase):
453
def test_canned_input_get_input(self):
454
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
455
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
456
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
457
self.assertEqual('password',
458
uif.get_password(u'Password for %(host)s',
460
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
463
class TestBoolFromString(tests.TestCase):
465
def assertIsTrue(self, s, accepted_values=None):
466
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
467
self.assertEquals(True, res)
469
def assertIsFalse(self, s, accepted_values=None):
470
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
471
self.assertEquals(False, res)
473
def assertIsNone(self, s, accepted_values=None):
474
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
475
self.assertIs(None, res)
477
def test_know_valid_values(self):
478
self.assertIsTrue('true')
479
self.assertIsFalse('false')
480
self.assertIsTrue('1')
481
self.assertIsFalse('0')
482
self.assertIsTrue('on')
483
self.assertIsFalse('off')
484
self.assertIsTrue('yes')
485
self.assertIsFalse('no')
486
self.assertIsTrue('y')
487
self.assertIsFalse('n')
488
# Also try some case variations
489
self.assertIsTrue('True')
490
self.assertIsFalse('False')
491
self.assertIsTrue('On')
492
self.assertIsFalse('Off')
493
self.assertIsTrue('ON')
494
self.assertIsFalse('OFF')
495
self.assertIsTrue('oN')
496
self.assertIsFalse('oFf')
498
def test_invalid_values(self):
499
self.assertIsNone(None)
500
self.assertIsNone('doubt')
501
self.assertIsNone('frue')
502
self.assertIsNone('talse')
503
self.assertIsNone('42')
505
def test_provided_values(self):
506
av = dict(y=True, n=False, yes=True, no=False)
507
self.assertIsTrue('y', av)
508
self.assertIsTrue('Y', av)
509
self.assertIsTrue('Yes', av)
510
self.assertIsFalse('n', av)
511
self.assertIsFalse('N', av)
512
self.assertIsFalse('No', av)
513
self.assertIsNone('1', av)
514
self.assertIsNone('0', av)
515
self.assertIsNone('on', av)
516
self.assertIsNone('off', av)
519
class TestConfirmationUserInterfacePolicy(tests.TestCase):
521
def test_confirm_action_default(self):
522
base_ui = _mod_ui.NoninteractiveUIFactory()
523
for answer in [True, False]:
525
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
526
.confirm_action("Do something?",
527
"bzrlib.tests.do_something", {}),
530
def test_confirm_action_specific(self):
531
base_ui = _mod_ui.NoninteractiveUIFactory()
532
for default_answer in [True, False]:
533
for specific_answer in [True, False]:
534
for conf_id in ['given_id', 'other_id']:
535
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
536
base_ui, default_answer, dict(given_id=specific_answer))
537
result = wrapper.confirm_action("Do something?", conf_id, {})
538
if conf_id == 'given_id':
539
self.assertEquals(result, specific_answer)
541
self.assertEquals(result, default_answer)
544
base_ui = _mod_ui.NoninteractiveUIFactory()
545
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
546
base_ui, True, dict(a=2))
547
self.assertThat(repr(wrapper),
548
Equals("ConfirmationUserInterfacePolicy("
549
"NoninteractiveUIFactory(), True, {'a': 2})"))
552
class TestProgressRecordingUI(tests.TestCase):
553
"""Test test-oriented UIFactory that records progress updates"""
555
def test_nested_ignore_depth_beyond_one(self):
556
# we only want to capture the first level out progress, not
557
# want sub-components might do. So we have nested bars ignored.
558
factory = ProgressRecordingUIFactory()
559
pb1 = factory.nested_progress_bar()
560
pb1.update('foo', 0, 1)
561
pb2 = factory.nested_progress_bar()
562
pb2.update('foo', 0, 1)
565
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)