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 *
33
from ..ui import text as _mod_ui_text
35
ProgressRecordingUIFactory,
39
class TestUIConfiguration(tests.TestCaseInTempDir):
41
def test_output_encoding_configuration(self):
42
enc = next(fixtures.generate_unicode_encodings())
43
config.GlobalStack().set('output_encoding', enc)
44
IO = ui_testing.BytesIOWithEncoding
45
ui = _mod_ui.make_ui_for_terminal(IO(), IO(), IO())
46
output = ui.make_output_stream()
47
self.assertEqual(output.encoding, enc)
50
class TestTextUIFactory(tests.TestCase):
52
def test_text_factory_confirm(self):
53
# turns into reading a regular boolean
54
ui = ui_testing.TestUIFactory('n\n')
55
self.assertEqual(ui.confirm_action(u'Should %(thing)s pass?',
56
'breezy.tests.test_ui.confirmation',
60
def test_text_factory_ascii_password(self):
61
ui = ui_testing.TestUIFactory('secret\n')
62
pb = ui.nested_progress_bar()
64
self.assertEqual('secret',
65
self.apply_redirected(ui.stdin, ui.stdout,
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())
76
def test_text_factory_unicode_password(self):
77
"""Test a unicode password."""
78
ui = ui_testing.TextUIFactory(u'baz\u1234')
79
password = ui.get_password(u'Hello \u1234 %(user)s', user=u'some\u1234')
80
self.assertEqual(u'baz\u1234', password)
81
self.assertEqual(u'Hello \u1234 some\u1234: ', ui.stderr.getvalue())
82
# stdin and stdout should be empty
83
self.assertEqual('', ui.stdin.readline())
84
self.assertEqual('', ui.stdout.getvalue())
86
def test_text_ui_get_boolean(self):
92
"yes with garbage\nY\n" # True
93
"not an answer\nno\n" # False
94
"I'm sure!\nyes\n" # True
97
factory = ui_testing.TextUIFactory(stdin_text)
98
self.assertEqual(True, factory.get_boolean(u""))
99
self.assertEqual(False, factory.get_boolean(u""))
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("foo\n", factory.stdin.read())
107
# stdin should be empty
108
self.assertEqual('', factory.stdin.readline())
109
# return false on EOF
110
self.assertEqual(False, factory.get_boolean(u""))
112
def test_text_ui_choose_bad_parameters(self):
113
factory = ui_testing.TextUIFactory(u"")
114
# invalid default index
115
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
117
self.assertRaises(ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
118
# duplicated shortcut
119
self.assertRaises(ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
121
def test_text_ui_choose_prompt_explicit(self):
122
# choices with explicit shortcuts
123
factory = ui_testing.TextUIFactory(u"")
124
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
125
self.assertEqual("prompt ([y]es, [N]o, more [i]nfo): \n", factory.stderr.getvalue())
127
def test_text_ui_choose_prompt_automatic(self):
128
# automatic shortcuts
129
factory = ui_testing.TextUIFactory(u"")
130
factory.choose(u"prompt", u"yes\nNo\nmore info")
131
self.assertEqual("prompt ([y]es, [N]o, [m]ore info): \n", factory.stderr.getvalue())
133
def test_text_ui_choose_return_values(self):
134
choose = lambda: factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
140
"b\na\nd \n" # bad shortcuts, all ignored
141
"yes with garbage\nY\n" # 0
142
"not an answer\nno\n" # 1
143
"info\nmore info\n" # 3
146
factory = ui_testing.TextUIFactory(stdin_text)
147
self.assertEqual(0, choose())
148
self.assertEqual(1, choose())
149
self.assertEqual(3, choose())
150
self.assertEqual(1, choose())
151
self.assertEqual(0, choose())
152
self.assertEqual(1, choose())
153
self.assertEqual(3, choose())
154
self.assertEqual(2, choose())
155
self.assertEqual("foo\n", factory.stdin.read())
156
# stdin should be empty
157
self.assertEqual('', factory.stdin.readline())
159
self.assertEqual(None, choose())
161
def test_text_ui_choose_no_default(self):
163
" \n" # no default, invalid!
166
factory = ui_testing.TextUIFactory(stdin_text)
167
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
168
self.assertEqual("foo\n", factory.stdin.read())
170
def test_text_ui_get_integer(self):
174
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
175
factory = ui_testing.TextUIFactory(stdin_text)
176
self.assertEqual(1, factory.get_integer(u""))
177
self.assertEqual(-2, factory.get_integer(u""))
178
self.assertEqual(42, factory.get_integer(u""))
180
def test_text_factory_prompt(self):
181
# see <https://launchpad.net/bugs/365891>
182
factory = ui_testing.TextUIFactory()
183
factory.prompt(u'foo %2e')
184
self.assertEqual('', factory.stdout.getvalue())
185
self.assertEqual('foo %2e', factory.stderr.getvalue())
187
def test_text_factory_prompts_and_clears(self):
188
# a get_boolean call should clear the pb before prompting
189
out = ui_testing.StringIOAsTTY()
190
self.overrideEnv('TERM', 'xterm')
191
factory = ui_testing.TextUIFactory("yada\ny\n", stdout=out, stderr=out)
192
pb = factory.nested_progress_bar()
193
pb._avail_width = lambda: 79
195
pb.show_spinner = False
196
pb.show_count = False
197
pb.update("foo", 0, 1)
198
self.assertEqual(True,
199
self.apply_redirected(None, factory.stdout,
202
u"what do you want"))
203
output = out.getvalue()
204
self.assertContainsRe(output,
206
self.assertContainsString(output,
207
r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
208
# stdin should have been totally consumed
209
self.assertEqual('', factory.stdin.readline())
211
def test_text_tick_after_update(self):
212
ui_factory = ui_testing.TextUIFactory()
213
pb = ui_factory.nested_progress_bar()
215
pb.update('task', 0, 3)
216
# Reset the clock, so that it actually tries to repaint itself
217
ui_factory._progress_view._last_repaint = time.time() - 1.0
222
def test_text_ui_getusername(self):
223
ui = ui_testing.TextUIFactory('someuser\n\n')
224
self.assertEqual('someuser',
225
ui.get_username(u'Hello %(host)s', host='some'))
226
self.assertEqual('Hello some: ', ui.stderr.getvalue())
227
self.assertEqual('', ui.stdout.getvalue())
228
self.assertEqual('', ui.get_username(u"Gebruiker"))
229
# stdin should be empty
230
self.assertEqual('', ui.stdin.readline())
232
def test_text_ui_getusername_unicode(self):
233
ui = ui_testing.TextUIFactory(u'someuser\u1234')
234
username = ui.get_username(u'Hello %(host)s', host=u'some\u1234')
235
self.assertEqual(u"someuser\u1234", username)
236
self.assertEqual(u"Hello some\u1234: ", ui.stderr.getvalue())
237
self.assertEqual('', ui.stdout.getvalue())
239
def test_quietness(self):
240
self.overrideEnv('BRZ_PROGRESS_BAR', 'text')
241
ui_factory = ui_testing.TextUIFactory(
242
stderr=ui_testing.StringIOAsTTY())
243
self.assertIsInstance(ui_factory._progress_view,
244
_mod_ui_text.TextProgressView)
245
ui_factory.be_quiet(True)
246
self.assertIsInstance(ui_factory._progress_view,
247
_mod_ui_text.NullProgressView)
249
def test_text_ui_show_user_warning(self):
250
from ..repofmt.groupcompress_repo import RepositoryFormat2a
251
from ..repofmt.knitpack_repo import RepositoryFormatKnitPack5
252
ui = ui_testing.TextUIFactory()
253
remote_fmt = remote.RemoteRepositoryFormat()
254
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
255
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
256
to_format=remote_fmt)
257
self.assertEqual('', ui.stdout.getvalue())
258
self.assertEqual("Doing on-the-fly conversion from RepositoryFormat2a() to "
259
"RemoteRepositoryFormat(_network_name='Bazaar RepositoryFormatKnitPack5 "
260
"(bzr 1.6)\\n').\nThis may take some time. Upgrade the repositories to "
261
"the same format for better performance.\n",
262
ui.stderr.getvalue())
263
# and now with it suppressed please
264
ui = ui_testing.TextUIFactory()
265
ui.suppressed_warnings.add('cross_format_fetch')
266
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
267
to_format=remote_fmt)
268
self.assertEqual('', ui.stdout.getvalue())
269
self.assertEqual('', ui.stderr.getvalue())
272
class TestTextUIOutputStream(tests.TestCase):
273
"""Tests for output stream that synchronizes with progress bar."""
275
def test_output_clears_terminal(self):
278
uif = ui_testing.TextUIFactory()
279
uif.clear_term = lambda: clear_calls.append('clear')
281
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout, 'utf-8', 'strict')
282
stream.write(u"Hello world!\n")
283
stream.write(u"there's more...\n")
284
stream.writelines([u"1\n", u"2\n", u"3\n"])
286
self.assertEqual(uif.stdout.getvalue(),
290
self.assertEqual(['clear', 'clear', 'clear'],
296
class UITests(tests.TestCase):
298
def test_progress_construction(self):
299
"""TextUIFactory constructs the right progress view.
301
FileStringIO = ui_testing.StringIOWithEncoding
302
TTYStringIO = ui_testing.StringIOAsTTY
303
for (file_class, term, pb, expected_pb_class) in (
304
# on an xterm, either use them or not as the user requests,
305
# otherwise default on
306
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
307
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
308
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
309
# on a dumb terminal, again if there's explicit configuration do
310
# it, otherwise default off
311
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
312
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
313
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
314
# on a non-tty terminal, it's null regardless of $TERM
315
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
316
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
317
# however, it can still be forced on
318
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
320
self.overrideEnv('TERM', term)
321
self.overrideEnv('BRZ_PROGRESS_BAR', pb)
322
stdin = file_class(u'')
323
stderr = file_class()
324
stdout = file_class()
325
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
326
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
327
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
328
self.assertIsInstance(uif.make_progress_view(),
330
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
332
def test_text_ui_non_terminal(self):
333
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
334
stdin = stderr = stdout = ui_testing.StringIOWithEncoding()
335
for term_type in ['dumb', None, 'xterm']:
336
self.overrideEnv('TERM', term_type)
337
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
338
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
339
'TERM=%r' % (term_type,))
342
class SilentUITests(tests.TestCase):
344
def test_silent_factory_get_password(self):
345
# A silent factory that can't do user interaction can't get a
346
# password. Possibly it should raise a more specific error but it
348
ui = _mod_ui.SilentUIFactory()
349
stdout = ui_testing.StringIOWithEncoding()
352
self.apply_redirected,
353
None, stdout, stdout, ui.get_password)
354
# and it didn't write anything out either
355
self.assertEqual('', stdout.getvalue())
357
def test_silent_ui_getbool(self):
358
factory = _mod_ui.SilentUIFactory()
359
stdout = ui_testing.StringIOWithEncoding()
362
self.apply_redirected,
363
None, stdout, stdout, factory.get_boolean, u"foo")
366
class TestUIFactoryTests(tests.TestCase):
368
def test_test_ui_factory_progress(self):
369
# there's no output; we just want to make sure this doesn't crash -
370
# see https://bugs.launchpad.net/bzr/+bug/408201
371
ui = ui_testing.TestUIFactory()
372
pb = ui.nested_progress_bar()
378
class CannedInputUIFactoryTests(tests.TestCase):
380
def test_canned_input_get_input(self):
381
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
382
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
383
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
384
self.assertEqual('password',
385
uif.get_password(u'Password for %(host)s',
387
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
390
class TestBoolFromString(tests.TestCase):
392
def assertIsTrue(self, s, accepted_values=None):
393
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
394
self.assertEqual(True, res)
396
def assertIsFalse(self, s, accepted_values=None):
397
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
398
self.assertEqual(False, res)
400
def assertIsNone(self, s, accepted_values=None):
401
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
402
self.assertIs(None, res)
404
def test_know_valid_values(self):
405
self.assertIsTrue('true')
406
self.assertIsFalse('false')
407
self.assertIsTrue('1')
408
self.assertIsFalse('0')
409
self.assertIsTrue('on')
410
self.assertIsFalse('off')
411
self.assertIsTrue('yes')
412
self.assertIsFalse('no')
413
self.assertIsTrue('y')
414
self.assertIsFalse('n')
415
# Also try some case variations
416
self.assertIsTrue('True')
417
self.assertIsFalse('False')
418
self.assertIsTrue('On')
419
self.assertIsFalse('Off')
420
self.assertIsTrue('ON')
421
self.assertIsFalse('OFF')
422
self.assertIsTrue('oN')
423
self.assertIsFalse('oFf')
425
def test_invalid_values(self):
426
self.assertIsNone(None)
427
self.assertIsNone('doubt')
428
self.assertIsNone('frue')
429
self.assertIsNone('talse')
430
self.assertIsNone('42')
432
def test_provided_values(self):
433
av = dict(y=True, n=False, yes=True, no=False)
434
self.assertIsTrue('y', av)
435
self.assertIsTrue('Y', av)
436
self.assertIsTrue('Yes', av)
437
self.assertIsFalse('n', av)
438
self.assertIsFalse('N', av)
439
self.assertIsFalse('No', av)
440
self.assertIsNone('1', av)
441
self.assertIsNone('0', av)
442
self.assertIsNone('on', av)
443
self.assertIsNone('off', av)
446
class TestConfirmationUserInterfacePolicy(tests.TestCase):
448
def test_confirm_action_default(self):
449
base_ui = _mod_ui.NoninteractiveUIFactory()
450
for answer in [True, False]:
452
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
453
.confirm_action("Do something?",
454
"breezy.tests.do_something", {}),
457
def test_confirm_action_specific(self):
458
base_ui = _mod_ui.NoninteractiveUIFactory()
459
for default_answer in [True, False]:
460
for specific_answer in [True, False]:
461
for conf_id in ['given_id', 'other_id']:
462
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
463
base_ui, default_answer, dict(given_id=specific_answer))
464
result = wrapper.confirm_action("Do something?", conf_id, {})
465
if conf_id == 'given_id':
466
self.assertEqual(result, specific_answer)
468
self.assertEqual(result, default_answer)
471
base_ui = _mod_ui.NoninteractiveUIFactory()
472
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
473
base_ui, True, dict(a=2))
474
self.assertThat(repr(wrapper),
475
Equals("ConfirmationUserInterfacePolicy("
476
"NoninteractiveUIFactory(), True, {'a': 2})"))
479
class TestProgressRecordingUI(tests.TestCase):
480
"""Test test-oriented UIFactory that records progress updates"""
482
def test_nested_ignore_depth_beyond_one(self):
483
# we only want to capture the first level out progress, not
484
# want sub-components might do. So we have nested bars ignored.
485
factory = ProgressRecordingUIFactory()
486
pb1 = factory.nested_progress_bar()
487
pb1.update('foo', 0, 1)
488
pb2 = factory.nested_progress_bar()
489
pb2.update('foo', 0, 1)
492
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)