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 (
36
from bzrlib.ui import text as _mod_ui_text
37
from bzrlib.tests.testui import (
38
ProgressRecordingUIFactory,
42
class TestUIConfiguration(tests.TestCaseWithTransport):
44
def test_output_encoding_configuration(self):
45
enc = fixtures.generate_unicode_encodings().next()
46
config.GlobalConfig().set_user_option('output_encoding',
48
ui = tests.TestUIFactory(stdin=None,
49
stdout=tests.StringIOWrapper(),
50
stderr=tests.StringIOWrapper())
51
output = ui.make_output_stream()
52
self.assertEquals(output.encoding, enc)
55
class TestTextUIFactory(tests.TestCase):
57
def make_test_ui_factory(self, stdin_contents):
58
ui = tests.TestUIFactory(stdin=stdin_contents,
59
stdout=tests.StringIOWrapper(),
60
stderr=tests.StringIOWrapper())
63
def test_text_factory_confirm(self):
64
# turns into reading a regular boolean
65
ui = self.make_test_ui_factory('n\n')
66
self.assertEquals(ui.confirm_action(u'Should %(thing)s pass?',
67
'bzrlib.tests.test_ui.confirmation',
71
def test_text_factory_ascii_password(self):
72
ui = self.make_test_ui_factory('secret\n')
73
pb = ui.nested_progress_bar()
75
self.assertEqual('secret',
76
self.apply_redirected(ui.stdin, ui.stdout,
79
# ': ' is appended to prompt
80
self.assertEqual(': ', ui.stderr.getvalue())
81
self.assertEqual('', ui.stdout.readline())
82
# stdin should be empty
83
self.assertEqual('', ui.stdin.readline())
87
def test_text_factory_utf8_password(self):
88
"""Test an utf8 password.
90
We can't predict what encoding users will have for stdin, so we force
91
it to utf8 to test that we transport the password correctly.
93
ui = self.make_test_ui_factory(u'baz\u1234'.encode('utf8'))
94
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
95
pb = ui.nested_progress_bar()
97
password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
99
u'Hello \u1234 %(user)s',
101
# We use StringIO objects, we need to decode them
102
self.assertEqual(u'baz\u1234', password.decode('utf8'))
103
self.assertEqual(u'Hello \u1234 some\u1234: ',
104
ui.stderr.getvalue().decode('utf8'))
105
# stdin and stdout should be empty
106
self.assertEqual('', ui.stdin.readline())
107
self.assertEqual('', ui.stdout.readline())
111
def test_text_ui_get_boolean(self):
112
stdin = tests.StringIOWrapper("y\n" # True
116
"yes with garbage\nY\n" # True
117
"not an answer\nno\n" # False
118
"I'm sure!\nyes\n" # True
121
stdout = tests.StringIOWrapper()
122
stderr = tests.StringIOWrapper()
123
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
124
self.assertEqual(True, factory.get_boolean(u""))
125
self.assertEqual(False, factory.get_boolean(u""))
126
self.assertEqual(True, factory.get_boolean(u""))
127
self.assertEqual(False, factory.get_boolean(u""))
128
self.assertEqual(True, factory.get_boolean(u""))
129
self.assertEqual(False, factory.get_boolean(u""))
130
self.assertEqual(True, factory.get_boolean(u""))
131
self.assertEqual(False, factory.get_boolean(u""))
132
self.assertEqual("foo\n", factory.stdin.read())
133
# stdin should be empty
134
self.assertEqual('', factory.stdin.readline())
135
# return false on EOF
136
self.assertEqual(False, factory.get_boolean(u""))
138
def test_text_ui_choose_bad_parameters(self):
139
stdin = tests.StringIOWrapper()
140
stdout = tests.StringIOWrapper()
141
stderr = tests.StringIOWrapper()
142
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
143
# invalid default index
144
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
146
self.assertRaises(ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
147
# duplicated shortcut
148
self.assertRaises(ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
150
def test_text_ui_choose_prompt(self):
151
stdin = tests.StringIOWrapper()
152
stdout = tests.StringIOWrapper()
153
stderr = tests.StringIOWrapper()
154
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
155
# choices with explicit shortcuts
156
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
157
self.assertEqual("prompt ([y]es, [N]o, more [i]nfo): \n", factory.stderr.getvalue())
158
# automatic shortcuts
159
factory.stderr.truncate(0)
160
factory.choose(u"prompt", u"yes\nNo\nmore info")
161
self.assertEqual("prompt ([y]es, [N]o, [m]ore info): \n", factory.stderr.getvalue())
163
def test_text_ui_choose_return_values(self):
164
choose = lambda: factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
165
stdin = tests.StringIOWrapper("y\n" # 0
169
"b\na\nd \n" # bad shortcuts, all ignored
170
"yes with garbage\nY\n" # 0
171
"not an answer\nno\n" # 1
172
"info\nmore info\n" # 3
175
stdout = tests.StringIOWrapper()
176
stderr = tests.StringIOWrapper()
177
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
178
self.assertEqual(0, choose())
179
self.assertEqual(1, choose())
180
self.assertEqual(3, choose())
181
self.assertEqual(1, choose())
182
self.assertEqual(0, choose())
183
self.assertEqual(1, choose())
184
self.assertEqual(3, choose())
185
self.assertEqual(2, choose())
186
self.assertEqual("foo\n", factory.stdin.read())
187
# stdin should be empty
188
self.assertEqual('', factory.stdin.readline())
190
self.assertEqual(None, choose())
192
def test_text_ui_choose_no_default(self):
193
stdin = tests.StringIOWrapper(" \n" # no default, invalid!
196
stdout = tests.StringIOWrapper()
197
stderr = tests.StringIOWrapper()
198
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
199
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
200
self.assertEqual("foo\n", factory.stdin.read())
202
def test_text_ui_get_integer(self):
203
stdin = tests.StringIOWrapper(
206
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
207
stdout = tests.StringIOWrapper()
208
stderr = tests.StringIOWrapper()
209
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
210
self.assertEqual(1, factory.get_integer(u""))
211
self.assertEqual(-2, factory.get_integer(u""))
212
self.assertEqual(42, factory.get_integer(u""))
214
def test_text_factory_prompt(self):
215
# see <https://launchpad.net/bugs/365891>
216
StringIO = tests.StringIOWrapper
217
factory = _mod_ui_text.TextUIFactory(StringIO(), StringIO(), StringIO())
218
factory.prompt(u'foo %2e')
219
self.assertEqual('', factory.stdout.getvalue())
220
self.assertEqual('foo %2e', factory.stderr.getvalue())
222
def test_text_factory_prompts_and_clears(self):
223
# a get_boolean call should clear the pb before prompting
224
out = test_progress._TTYStringIO()
225
self.overrideEnv('TERM', 'xterm')
226
factory = _mod_ui_text.TextUIFactory(
227
stdin=tests.StringIOWrapper("yada\ny\n"),
228
stdout=out, stderr=out)
229
factory._avail_width = lambda: 79
230
pb = factory.nested_progress_bar()
232
pb.show_spinner = False
233
pb.show_count = False
234
pb.update("foo", 0, 1)
235
self.assertEqual(True,
236
self.apply_redirected(None, factory.stdout,
239
u"what do you want"))
240
output = out.getvalue()
241
self.assertContainsRe(output,
243
self.assertContainsString(output,
244
r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
245
# stdin should have been totally consumed
246
self.assertEqual('', factory.stdin.readline())
248
def test_text_tick_after_update(self):
249
ui_factory = _mod_ui_text.TextUIFactory(stdout=tests.StringIOWrapper(),
250
stderr=tests.StringIOWrapper())
251
pb = ui_factory.nested_progress_bar()
253
pb.update('task', 0, 3)
254
# Reset the clock, so that it actually tries to repaint itself
255
ui_factory._progress_view._last_repaint = time.time() - 1.0
260
def test_text_ui_getusername(self):
261
factory = _mod_ui_text.TextUIFactory(None, None, None)
262
factory.stdin = tests.StringIOWrapper("someuser\n\n")
263
factory.stdout = tests.StringIOWrapper()
264
factory.stderr = tests.StringIOWrapper()
265
factory.stdout.encoding = "utf8"
266
# there is no output from the base factory
267
self.assertEqual("someuser",
268
factory.get_username(u'Hello %(host)s', host='some'))
269
self.assertEquals("Hello some: ", factory.stderr.getvalue())
270
self.assertEquals('', factory.stdout.getvalue())
271
self.assertEqual("", factory.get_username(u"Gebruiker"))
272
# stdin should be empty
273
self.assertEqual('', factory.stdin.readline())
275
def test_text_ui_getusername_utf8(self):
276
ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
277
stdout=tests.StringIOWrapper(),
278
stderr=tests.StringIOWrapper())
279
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
280
pb = ui.nested_progress_bar()
282
# there is no output from the base factory
283
username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
284
ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
285
self.assertEquals(u"someuser\u1234", username.decode('utf8'))
286
self.assertEquals(u"Hello\u1234 some\u1234: ",
287
ui.stderr.getvalue().decode("utf8"))
288
self.assertEquals('', ui.stdout.getvalue())
292
def test_quietness(self):
293
self.overrideEnv('BZR_PROGRESS_BAR', 'text')
294
ui_factory = _mod_ui_text.TextUIFactory(None,
295
test_progress._TTYStringIO(),
296
test_progress._TTYStringIO())
297
self.assertIsInstance(ui_factory._progress_view,
298
_mod_ui_text.TextProgressView)
299
ui_factory.be_quiet(True)
300
self.assertIsInstance(ui_factory._progress_view,
301
_mod_ui_text.NullProgressView)
303
def test_text_ui_show_user_warning(self):
304
from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2a
305
from bzrlib.repofmt.knitpack_repo import RepositoryFormatKnitPack5
308
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
309
remote_fmt = remote.RemoteRepositoryFormat()
310
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
311
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
312
to_format=remote_fmt)
313
self.assertEquals('', out.getvalue())
314
self.assertEquals("Doing on-the-fly conversion from RepositoryFormat2a() to "
315
"RemoteRepositoryFormat(_network_name='Bazaar RepositoryFormatKnitPack5 "
316
"(bzr 1.6)\\n').\nThis may take some time. Upgrade the repositories to "
317
"the same format for better performance.\n",
319
# and now with it suppressed please
322
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
323
ui.suppressed_warnings.add('cross_format_fetch')
324
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
325
to_format=remote_fmt)
326
self.assertEquals('', out.getvalue())
327
self.assertEquals('', err.getvalue())
330
class TestTextUIOutputStream(tests.TestCase):
331
"""Tests for output stream that synchronizes with progress bar."""
333
def test_output_clears_terminal(self):
334
stdout = tests.StringIOWrapper()
335
stderr = tests.StringIOWrapper()
338
uif = _mod_ui_text.TextUIFactory(None, stdout, stderr)
339
uif.clear_term = lambda: clear_calls.append('clear')
341
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout)
342
stream.write("Hello world!\n")
343
stream.write("there's more...\n")
344
stream.writelines(["1\n", "2\n", "3\n"])
346
self.assertEqual(stdout.getvalue(),
350
self.assertEqual(['clear', 'clear', 'clear'],
356
class UITests(tests.TestCase):
358
def test_progress_construction(self):
359
"""TextUIFactory constructs the right progress view.
361
TTYStringIO = test_progress._TTYStringIO
362
FileStringIO = tests.StringIOWrapper
363
for (file_class, term, pb, expected_pb_class) in (
364
# on an xterm, either use them or not as the user requests,
365
# otherwise default on
366
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
367
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
368
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
369
# on a dumb terminal, again if there's explicit configuration do
370
# it, otherwise default off
371
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
372
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
373
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
374
# on a non-tty terminal, it's null regardless of $TERM
375
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
376
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
377
# however, it can still be forced on
378
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
380
self.overrideEnv('TERM', term)
381
self.overrideEnv('BZR_PROGRESS_BAR', pb)
382
stdin = file_class('')
383
stderr = file_class()
384
stdout = file_class()
385
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
386
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
387
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
388
self.assertIsInstance(uif.make_progress_view(),
390
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
392
def test_text_ui_non_terminal(self):
393
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
394
stdin = test_progress._NonTTYStringIO('')
395
stderr = test_progress._NonTTYStringIO()
396
stdout = test_progress._NonTTYStringIO()
397
for term_type in ['dumb', None, 'xterm']:
398
self.overrideEnv('TERM', term_type)
399
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
400
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
401
'TERM=%r' % (term_type,))
404
class SilentUITests(tests.TestCase):
406
def test_silent_factory_get_password(self):
407
# A silent factory that can't do user interaction can't get a
408
# password. Possibly it should raise a more specific error but it
410
ui = _mod_ui.SilentUIFactory()
411
stdout = tests.StringIOWrapper()
414
self.apply_redirected,
415
None, stdout, stdout, ui.get_password)
416
# and it didn't write anything out either
417
self.assertEqual('', stdout.getvalue())
419
def test_silent_ui_getbool(self):
420
factory = _mod_ui.SilentUIFactory()
421
stdout = tests.StringIOWrapper()
424
self.apply_redirected,
425
None, stdout, stdout, factory.get_boolean, u"foo")
428
class TestUIFactoryTests(tests.TestCase):
430
def test_test_ui_factory_progress(self):
431
# there's no output; we just want to make sure this doesn't crash -
432
# see https://bugs.launchpad.net/bzr/+bug/408201
433
ui = tests.TestUIFactory()
434
pb = ui.nested_progress_bar()
440
class CannedInputUIFactoryTests(tests.TestCase):
442
def test_canned_input_get_input(self):
443
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
444
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
445
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
446
self.assertEqual('password',
447
uif.get_password(u'Password for %(host)s',
449
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
452
class TestBoolFromString(tests.TestCase):
454
def assertIsTrue(self, s, accepted_values=None):
455
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
456
self.assertEquals(True, res)
458
def assertIsFalse(self, s, accepted_values=None):
459
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
460
self.assertEquals(False, res)
462
def assertIsNone(self, s, accepted_values=None):
463
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
464
self.assertIs(None, res)
466
def test_know_valid_values(self):
467
self.assertIsTrue('true')
468
self.assertIsFalse('false')
469
self.assertIsTrue('1')
470
self.assertIsFalse('0')
471
self.assertIsTrue('on')
472
self.assertIsFalse('off')
473
self.assertIsTrue('yes')
474
self.assertIsFalse('no')
475
self.assertIsTrue('y')
476
self.assertIsFalse('n')
477
# Also try some case variations
478
self.assertIsTrue('True')
479
self.assertIsFalse('False')
480
self.assertIsTrue('On')
481
self.assertIsFalse('Off')
482
self.assertIsTrue('ON')
483
self.assertIsFalse('OFF')
484
self.assertIsTrue('oN')
485
self.assertIsFalse('oFf')
487
def test_invalid_values(self):
488
self.assertIsNone(None)
489
self.assertIsNone('doubt')
490
self.assertIsNone('frue')
491
self.assertIsNone('talse')
492
self.assertIsNone('42')
494
def test_provided_values(self):
495
av = dict(y=True, n=False, yes=True, no=False)
496
self.assertIsTrue('y', av)
497
self.assertIsTrue('Y', av)
498
self.assertIsTrue('Yes', av)
499
self.assertIsFalse('n', av)
500
self.assertIsFalse('N', av)
501
self.assertIsFalse('No', av)
502
self.assertIsNone('1', av)
503
self.assertIsNone('0', av)
504
self.assertIsNone('on', av)
505
self.assertIsNone('off', av)
508
class TestConfirmationUserInterfacePolicy(tests.TestCase):
510
def test_confirm_action_default(self):
511
base_ui = _mod_ui.NoninteractiveUIFactory()
512
for answer in [True, False]:
514
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
515
.confirm_action("Do something?",
516
"bzrlib.tests.do_something", {}),
519
def test_confirm_action_specific(self):
520
base_ui = _mod_ui.NoninteractiveUIFactory()
521
for default_answer in [True, False]:
522
for specific_answer in [True, False]:
523
for conf_id in ['given_id', 'other_id']:
524
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
525
base_ui, default_answer, dict(given_id=specific_answer))
526
result = wrapper.confirm_action("Do something?", conf_id, {})
527
if conf_id == 'given_id':
528
self.assertEquals(result, specific_answer)
530
self.assertEquals(result, default_answer)
533
base_ui = _mod_ui.NoninteractiveUIFactory()
534
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
535
base_ui, True, dict(a=2))
536
self.assertThat(repr(wrapper),
537
Equals("ConfirmationUserInterfacePolicy("
538
"NoninteractiveUIFactory(), True, {'a': 2})"))
541
class TestProgressRecordingUI(tests.TestCase):
542
"""Test test-oriented UIFactory that records progress updates"""
544
def test_nested_ignore_depth_beyond_one(self):
545
# we only want to capture the first level out progress, not
546
# want sub-components might do. So we have nested bars ignored.
547
factory = ProgressRecordingUIFactory()
548
pb1 = factory.nested_progress_bar()
549
pb1.update('foo', 0, 1)
550
pb2 = factory.nested_progress_bar()
551
pb2.update('foo', 0, 1)
554
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)