1
# Copyright (C) 2005-2012, 2016 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 breezy ui."""
21
from testtools.matchers import *
35
from ..ui import text as _mod_ui_text
37
ProgressRecordingUIFactory,
41
class TestUIConfiguration(tests.TestCaseInTempDir):
43
def test_output_encoding_configuration(self):
44
enc = next(fixtures.generate_unicode_encodings())
45
config.GlobalStack().set('output_encoding', enc)
46
IO = ui_testing.BytesIOWithEncoding
47
ui = _mod_ui.make_ui_for_terminal(IO(), IO(), IO())
48
output = ui.make_output_stream()
49
self.assertEqual(output.encoding, enc)
52
class TestTextUIFactory(tests.TestCase):
54
def test_text_factory_confirm(self):
55
# turns into reading a regular boolean
56
with ui_testing.TestUIFactory('n\n') as ui:
60
u'Should %(thing)s pass?',
61
'breezy.tests.test_ui.confirmation',
64
def test_text_factory_ascii_password(self):
65
ui = ui_testing.TestUIFactory('secret\n')
66
with ui.nested_progress_bar():
67
self.assertEqual('secret',
68
self.apply_redirected(ui.stdin, ui.stdout,
71
# ': ' is appended to prompt
72
self.assertEqual(': ', ui.stderr.getvalue())
73
self.assertEqual('', ui.stdout.readline())
74
# stdin should be empty
75
self.assertEqual('', ui.stdin.readline())
77
def test_text_factory_unicode_password(self):
78
"""Test a unicode password."""
79
ui = ui_testing.TextUIFactory(u'baz\u1234')
80
password = ui.get_password(
81
u'Hello \u1234 %(user)s', user=u'some\u1234')
82
self.assertEqual(u'baz\u1234', password)
83
self.assertEqual(u'Hello \u1234 some\u1234: ', ui.stderr.getvalue())
84
# stdin and stdout should be empty
85
self.assertEqual('', ui.stdin.readline())
86
self.assertEqual('', ui.stdout.getvalue())
88
def test_text_ui_get_boolean(self):
94
"yes with garbage\nY\n" # True
95
"not an answer\nno\n" # False
96
"I'm sure!\nyes\n" # True
99
with ui_testing.TextUIFactory(stdin_text) as factory:
100
self.assertEqual(True, factory.get_boolean(u""))
101
self.assertEqual(False, factory.get_boolean(u""))
102
self.assertEqual(True, factory.get_boolean(u""))
103
self.assertEqual(False, factory.get_boolean(u""))
104
self.assertEqual(True, factory.get_boolean(u""))
105
self.assertEqual(False, factory.get_boolean(u""))
106
self.assertEqual(True, factory.get_boolean(u""))
107
self.assertEqual(False, factory.get_boolean(u""))
108
self.assertEqual("foo\n", factory.stdin.read())
109
# stdin should be empty
110
self.assertEqual('', factory.stdin.readline())
111
# return false on EOF
112
self.assertEqual(False, factory.get_boolean(u""))
114
def test_text_ui_choose_bad_parameters(self):
115
with ui_testing.TextUIFactory(u"") as factory:
116
# invalid default index
117
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
120
ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
121
# duplicated shortcut
123
ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
125
def test_text_ui_choose_prompt_explicit(self):
126
# choices with explicit shortcuts
127
with ui_testing.TextUIFactory(u"") as factory:
128
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
130
"prompt ([y]es, [N]o, more [i]nfo): \n",
131
factory.stderr.getvalue())
133
def test_text_ui_choose_prompt_automatic(self):
134
# automatic shortcuts
135
with ui_testing.TextUIFactory(u"") as factory:
136
factory.choose(u"prompt", u"yes\nNo\nmore info")
138
"prompt ([y]es, [N]o, [m]ore info): \n",
139
factory.stderr.getvalue())
141
def test_text_ui_choose_return_values(self):
143
return factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
149
"b\na\nd \n" # bad shortcuts, all ignored
150
"yes with garbage\nY\n" # 0
151
"not an answer\nno\n" # 1
152
"info\nmore info\n" # 3
155
with ui_testing.TextUIFactory(stdin_text) as factory:
156
self.assertEqual(0, choose())
157
self.assertEqual(1, choose())
158
self.assertEqual(3, choose())
159
self.assertEqual(1, choose())
160
self.assertEqual(0, choose())
161
self.assertEqual(1, choose())
162
self.assertEqual(3, choose())
163
self.assertEqual(2, choose())
164
self.assertEqual("foo\n", factory.stdin.read())
165
# stdin should be empty
166
self.assertEqual('', factory.stdin.readline())
168
self.assertEqual(None, choose())
170
def test_text_ui_choose_no_default(self):
172
" \n" # no default, invalid!
175
with ui_testing.TextUIFactory(stdin_text) as factory:
176
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
177
self.assertEqual("foo\n", factory.stdin.read())
179
def test_text_ui_get_integer(self):
183
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
184
with ui_testing.TextUIFactory(stdin_text) as factory:
185
self.assertEqual(1, factory.get_integer(u""))
186
self.assertEqual(-2, factory.get_integer(u""))
187
self.assertEqual(42, factory.get_integer(u""))
189
def test_text_factory_prompt(self):
190
# see <https://launchpad.net/bugs/365891>
191
with ui_testing.TextUIFactory() as factory:
192
factory.prompt(u'foo %2e')
193
self.assertEqual('', factory.stdout.getvalue())
194
self.assertEqual('foo %2e', factory.stderr.getvalue())
196
def test_text_factory_prompts_and_clears(self):
197
# a get_boolean call should clear the pb before prompting
198
out = ui_testing.StringIOAsTTY()
199
self.overrideEnv('TERM', 'xterm')
200
factory = ui_testing.TextUIFactory("yada\ny\n", stdout=out, stderr=out)
202
pb = factory.nested_progress_bar()
203
pb._avail_width = lambda: 79
205
pb.show_spinner = False
206
pb.show_count = False
207
pb.update("foo", 0, 1)
210
self.apply_redirected(
211
None, factory.stdout, factory.stdout, factory.get_boolean,
212
u"what do you want"))
213
output = out.getvalue()
214
self.assertContainsRe(output,
216
self.assertContainsString(
218
r"what do you want? ([y]es, [n]o): what do you want? "
220
# stdin should have been totally consumed
221
self.assertEqual('', factory.stdin.readline())
223
def test_text_tick_after_update(self):
224
ui_factory = ui_testing.TextUIFactory()
225
with ui_factory.nested_progress_bar() as pb:
226
pb.update('task', 0, 3)
227
# Reset the clock, so that it actually tries to repaint itself
228
ui_factory._progress_view._last_repaint = time.time() - 1.0
231
def test_text_ui_getusername(self):
232
ui = ui_testing.TextUIFactory('someuser\n\n')
233
self.assertEqual('someuser',
234
ui.get_username(u'Hello %(host)s', host='some'))
235
self.assertEqual('Hello some: ', ui.stderr.getvalue())
236
self.assertEqual('', ui.stdout.getvalue())
237
self.assertEqual('', ui.get_username(u"Gebruiker"))
238
# stdin should be empty
239
self.assertEqual('', ui.stdin.readline())
241
def test_text_ui_getusername_unicode(self):
242
ui = ui_testing.TextUIFactory(u'someuser\u1234')
243
username = ui.get_username(u'Hello %(host)s', host=u'some\u1234')
244
self.assertEqual(u"someuser\u1234", username)
245
self.assertEqual(u"Hello some\u1234: ", ui.stderr.getvalue())
246
self.assertEqual('', ui.stdout.getvalue())
248
def test_quietness(self):
249
self.overrideEnv('BRZ_PROGRESS_BAR', 'text')
250
ui_factory = ui_testing.TextUIFactory(
251
stderr=ui_testing.StringIOAsTTY())
253
self.assertIsInstance(ui_factory._progress_view,
254
_mod_ui_text.TextProgressView)
255
ui_factory.be_quiet(True)
256
self.assertIsInstance(ui_factory._progress_view,
257
_mod_ui_text.NullProgressView)
259
def test_text_ui_show_user_warning(self):
260
from ..bzr.groupcompress_repo import RepositoryFormat2a
261
from ..bzr.knitpack_repo import RepositoryFormatKnitPack5
262
ui = ui_testing.TextUIFactory()
263
remote_fmt = remote.RemoteRepositoryFormat()
264
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
265
ui.show_user_warning(
266
'cross_format_fetch', from_format=RepositoryFormat2a(),
267
to_format=remote_fmt)
268
self.assertEqual('', ui.stdout.getvalue())
269
self.assertContainsRe(
270
ui.stderr.getvalue(),
271
"^Doing on-the-fly conversion from RepositoryFormat2a\\(\\) to "
272
"RemoteRepositoryFormat\\(_network_name="
273
"b?'Bazaar RepositoryFormatKnitPack5 \\(bzr 1.6\\)\\\\n'\\)\\.\n"
274
"This may take some time. Upgrade the repositories to "
275
"the same format for better performance\\.\n$")
276
# and now with it suppressed please
277
ui = ui_testing.TextUIFactory()
278
ui.suppressed_warnings.add('cross_format_fetch')
279
ui.show_user_warning(
280
'cross_format_fetch', from_format=RepositoryFormat2a(),
281
to_format=remote_fmt)
282
self.assertEqual('', ui.stdout.getvalue())
283
self.assertEqual('', ui.stderr.getvalue())
286
class TestTextUIOutputStream(tests.TestCase):
287
"""Tests for output stream that synchronizes with progress bar."""
289
def test_output_clears_terminal(self):
292
uif = ui_testing.TextUIFactory()
293
uif.clear_term = lambda: clear_calls.append('clear')
295
stream = _mod_ui_text.TextUIOutputStream(
296
uif, uif.stdout, 'utf-8', 'strict')
297
stream.write(u"Hello world!\n")
298
stream.write(u"there's more...\n")
299
stream.writelines([u"1\n", u"2\n", u"3\n"])
301
self.assertEqual(uif.stdout.getvalue(),
305
self.assertEqual(['clear', 'clear', 'clear'],
311
class UITests(tests.TestCase):
313
def test_progress_construction(self):
314
"""TextUIFactory constructs the right progress view.
316
FileStringIO = ui_testing.StringIOWithEncoding
317
TTYStringIO = ui_testing.StringIOAsTTY
318
for (file_class, term, pb, expected_pb_class) in (
319
# on an xterm, either use them or not as the user requests,
320
# otherwise default on
321
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
322
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
323
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
324
# on a dumb terminal, again if there's explicit configuration
325
# do it, otherwise default off
326
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
327
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
328
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
329
# on a non-tty terminal, it's null regardless of $TERM
330
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
331
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
332
# however, it can still be forced on
333
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
335
self.overrideEnv('TERM', term)
336
self.overrideEnv('BRZ_PROGRESS_BAR', pb)
337
stdin = file_class(u'')
338
stderr = file_class()
339
stdout = file_class()
340
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
341
self.assertIsInstance(
342
uif, _mod_ui_text.TextUIFactory,
343
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
344
self.assertIsInstance(
345
uif.make_progress_view(),
347
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
349
def test_text_ui_non_terminal(self):
350
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
351
stdin = stderr = stdout = ui_testing.StringIOWithEncoding()
352
for term_type in ['dumb', None, 'xterm']:
353
self.overrideEnv('TERM', term_type)
354
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
355
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
356
'TERM=%r' % (term_type,))
359
class SilentUITests(tests.TestCase):
361
def test_silent_factory_get_password(self):
362
# A silent factory that can't do user interaction can't get a
363
# password. Possibly it should raise a more specific error but it
365
ui = _mod_ui.SilentUIFactory()
366
stdout = ui_testing.StringIOWithEncoding()
369
self.apply_redirected,
370
None, stdout, stdout, ui.get_password)
371
# and it didn't write anything out either
372
self.assertEqual('', stdout.getvalue())
374
def test_silent_ui_getbool(self):
375
factory = _mod_ui.SilentUIFactory()
376
stdout = ui_testing.StringIOWithEncoding()
379
self.apply_redirected,
380
None, stdout, stdout, factory.get_boolean, u"foo")
383
class TestUIFactoryTests(tests.TestCase):
385
def test_test_ui_factory_progress(self):
386
# there's no output; we just want to make sure this doesn't crash -
387
# see https://bugs.launchpad.net/bzr/+bug/408201
388
ui = ui_testing.TestUIFactory()
389
with ui.nested_progress_bar() as pb:
394
class CannedInputUIFactoryTests(tests.TestCase):
396
def test_canned_input_get_input(self):
397
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
398
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
399
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
400
self.assertEqual('password',
401
uif.get_password(u'Password for %(host)s',
403
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
406
class TestBoolFromString(tests.TestCase):
408
def assertIsTrue(self, s, accepted_values=None):
409
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
410
self.assertEqual(True, res)
412
def assertIsFalse(self, s, accepted_values=None):
413
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
414
self.assertEqual(False, res)
416
def assertIsNone(self, s, accepted_values=None):
417
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
418
self.assertIs(None, res)
420
def test_know_valid_values(self):
421
self.assertIsTrue('true')
422
self.assertIsFalse('false')
423
self.assertIsTrue('1')
424
self.assertIsFalse('0')
425
self.assertIsTrue('on')
426
self.assertIsFalse('off')
427
self.assertIsTrue('yes')
428
self.assertIsFalse('no')
429
self.assertIsTrue('y')
430
self.assertIsFalse('n')
431
# Also try some case variations
432
self.assertIsTrue('True')
433
self.assertIsFalse('False')
434
self.assertIsTrue('On')
435
self.assertIsFalse('Off')
436
self.assertIsTrue('ON')
437
self.assertIsFalse('OFF')
438
self.assertIsTrue('oN')
439
self.assertIsFalse('oFf')
441
def test_invalid_values(self):
442
self.assertIsNone(None)
443
self.assertIsNone('doubt')
444
self.assertIsNone('frue')
445
self.assertIsNone('talse')
446
self.assertIsNone('42')
448
def test_provided_values(self):
449
av = dict(y=True, n=False, yes=True, no=False)
450
self.assertIsTrue('y', av)
451
self.assertIsTrue('Y', av)
452
self.assertIsTrue('Yes', av)
453
self.assertIsFalse('n', av)
454
self.assertIsFalse('N', av)
455
self.assertIsFalse('No', av)
456
self.assertIsNone('1', av)
457
self.assertIsNone('0', av)
458
self.assertIsNone('on', av)
459
self.assertIsNone('off', av)
462
class TestConfirmationUserInterfacePolicy(tests.TestCase):
464
def test_confirm_action_default(self):
465
base_ui = _mod_ui.NoninteractiveUIFactory()
466
for answer in [True, False]:
468
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
469
.confirm_action("Do something?",
470
"breezy.tests.do_something", {}),
473
def test_confirm_action_specific(self):
474
base_ui = _mod_ui.NoninteractiveUIFactory()
475
for default_answer in [True, False]:
476
for specific_answer in [True, False]:
477
for conf_id in ['given_id', 'other_id']:
478
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
479
base_ui, default_answer,
480
dict(given_id=specific_answer))
481
result = wrapper.confirm_action(
482
"Do something?", conf_id, {})
483
if conf_id == 'given_id':
484
self.assertEqual(result, specific_answer)
486
self.assertEqual(result, default_answer)
489
base_ui = _mod_ui.NoninteractiveUIFactory()
490
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
491
base_ui, True, dict(a=2))
492
self.assertThat(repr(wrapper),
493
Equals("ConfirmationUserInterfacePolicy("
494
"NoninteractiveUIFactory(), True, {'a': 2})"))
497
class TestProgressRecordingUI(tests.TestCase):
498
"""Test test-oriented UIFactory that records progress updates"""
500
def test_nested_ignore_depth_beyond_one(self):
501
# we only want to capture the first level out progress, not
502
# want sub-components might do. So we have nested bars ignored.
503
factory = ProgressRecordingUIFactory()
504
pb1 = factory.nested_progress_bar()
505
pb1.update('foo', 0, 1)
506
pb2 = factory.nested_progress_bar()
507
pb2.update('foo', 0, 1)
510
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)