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() as pb:
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(u'Hello \u1234 %(user)s', user=u'some\u1234')
81
self.assertEqual(u'baz\u1234', password)
82
self.assertEqual(u'Hello \u1234 some\u1234: ', ui.stderr.getvalue())
83
# stdin and stdout should be empty
84
self.assertEqual('', ui.stdin.readline())
85
self.assertEqual('', ui.stdout.getvalue())
87
def test_text_ui_get_boolean(self):
93
"yes with garbage\nY\n" # True
94
"not an answer\nno\n" # False
95
"I'm sure!\nyes\n" # True
98
with ui_testing.TextUIFactory(stdin_text) as factory:
99
self.assertEqual(True, factory.get_boolean(u""))
100
self.assertEqual(False, factory.get_boolean(u""))
101
self.assertEqual(True, factory.get_boolean(u""))
102
self.assertEqual(False, factory.get_boolean(u""))
103
self.assertEqual(True, factory.get_boolean(u""))
104
self.assertEqual(False, factory.get_boolean(u""))
105
self.assertEqual(True, factory.get_boolean(u""))
106
self.assertEqual(False, factory.get_boolean(u""))
107
self.assertEqual("foo\n", factory.stdin.read())
108
# stdin should be empty
109
self.assertEqual('', factory.stdin.readline())
110
# return false on EOF
111
self.assertEqual(False, factory.get_boolean(u""))
113
def test_text_ui_choose_bad_parameters(self):
114
with ui_testing.TextUIFactory(u"") as factory:
115
# invalid default index
116
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
119
ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
120
# duplicated shortcut
122
ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
124
def test_text_ui_choose_prompt_explicit(self):
125
# choices with explicit shortcuts
126
with ui_testing.TextUIFactory(u"") as factory:
127
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
129
"prompt ([y]es, [N]o, more [i]nfo): \n",
130
factory.stderr.getvalue())
132
def test_text_ui_choose_prompt_automatic(self):
133
# automatic shortcuts
134
with ui_testing.TextUIFactory(u"") as factory:
135
factory.choose(u"prompt", u"yes\nNo\nmore info")
137
"prompt ([y]es, [N]o, [m]ore info): \n",
138
factory.stderr.getvalue())
140
def test_text_ui_choose_return_values(self):
142
return factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
148
"b\na\nd \n" # bad shortcuts, all ignored
149
"yes with garbage\nY\n" # 0
150
"not an answer\nno\n" # 1
151
"info\nmore info\n" # 3
154
with ui_testing.TextUIFactory(stdin_text) as factory:
155
self.assertEqual(0, choose())
156
self.assertEqual(1, choose())
157
self.assertEqual(3, choose())
158
self.assertEqual(1, choose())
159
self.assertEqual(0, choose())
160
self.assertEqual(1, choose())
161
self.assertEqual(3, choose())
162
self.assertEqual(2, choose())
163
self.assertEqual("foo\n", factory.stdin.read())
164
# stdin should be empty
165
self.assertEqual('', factory.stdin.readline())
167
self.assertEqual(None, choose())
169
def test_text_ui_choose_no_default(self):
171
" \n" # no default, invalid!
174
with ui_testing.TextUIFactory(stdin_text) as factory:
175
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
176
self.assertEqual("foo\n", factory.stdin.read())
178
def test_text_ui_get_integer(self):
182
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
183
with ui_testing.TextUIFactory(stdin_text) as factory:
184
self.assertEqual(1, factory.get_integer(u""))
185
self.assertEqual(-2, factory.get_integer(u""))
186
self.assertEqual(42, factory.get_integer(u""))
188
def test_text_factory_prompt(self):
189
# see <https://launchpad.net/bugs/365891>
190
with ui_testing.TextUIFactory() as factory:
191
factory.prompt(u'foo %2e')
192
self.assertEqual('', factory.stdout.getvalue())
193
self.assertEqual('foo %2e', factory.stderr.getvalue())
195
def test_text_factory_prompts_and_clears(self):
196
# a get_boolean call should clear the pb before prompting
197
out = ui_testing.StringIOAsTTY()
198
self.overrideEnv('TERM', 'xterm')
199
factory = ui_testing.TextUIFactory("yada\ny\n", stdout=out, stderr=out)
201
pb = factory.nested_progress_bar()
202
pb._avail_width = lambda: 79
204
pb.show_spinner = False
205
pb.show_count = False
206
pb.update("foo", 0, 1)
209
self.apply_redirected(
210
None, factory.stdout, factory.stdout, factory.get_boolean,
211
u"what do you want"))
212
output = out.getvalue()
213
self.assertContainsRe(output,
215
self.assertContainsString(output,
216
r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
217
# stdin should have been totally consumed
218
self.assertEqual('', factory.stdin.readline())
220
def test_text_tick_after_update(self):
221
ui_factory = ui_testing.TextUIFactory()
222
with ui_factory.nested_progress_bar() as pb:
223
pb.update('task', 0, 3)
224
# Reset the clock, so that it actually tries to repaint itself
225
ui_factory._progress_view._last_repaint = time.time() - 1.0
228
def test_text_ui_getusername(self):
229
ui = ui_testing.TextUIFactory('someuser\n\n')
230
self.assertEqual('someuser',
231
ui.get_username(u'Hello %(host)s', host='some'))
232
self.assertEqual('Hello some: ', ui.stderr.getvalue())
233
self.assertEqual('', ui.stdout.getvalue())
234
self.assertEqual('', ui.get_username(u"Gebruiker"))
235
# stdin should be empty
236
self.assertEqual('', ui.stdin.readline())
238
def test_text_ui_getusername_unicode(self):
239
ui = ui_testing.TextUIFactory(u'someuser\u1234')
240
username = ui.get_username(u'Hello %(host)s', host=u'some\u1234')
241
self.assertEqual(u"someuser\u1234", username)
242
self.assertEqual(u"Hello some\u1234: ", ui.stderr.getvalue())
243
self.assertEqual('', ui.stdout.getvalue())
245
def test_quietness(self):
246
self.overrideEnv('BRZ_PROGRESS_BAR', 'text')
247
ui_factory = ui_testing.TextUIFactory(
248
stderr=ui_testing.StringIOAsTTY())
250
self.assertIsInstance(ui_factory._progress_view,
251
_mod_ui_text.TextProgressView)
252
ui_factory.be_quiet(True)
253
self.assertIsInstance(ui_factory._progress_view,
254
_mod_ui_text.NullProgressView)
256
def test_text_ui_show_user_warning(self):
257
from ..bzr.groupcompress_repo import RepositoryFormat2a
258
from ..bzr.knitpack_repo import RepositoryFormatKnitPack5
259
ui = ui_testing.TextUIFactory()
260
remote_fmt = remote.RemoteRepositoryFormat()
261
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
262
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
263
to_format=remote_fmt)
264
self.assertEqual('', ui.stdout.getvalue())
265
self.assertContainsRe(
266
ui.stderr.getvalue(),
267
"^Doing on-the-fly conversion from RepositoryFormat2a\\(\\) to "
268
"RemoteRepositoryFormat\\(_network_name="
269
"b?'Bazaar RepositoryFormatKnitPack5 \\(bzr 1.6\\)\\\\n'\\)\\.\n"
270
"This may take some time. Upgrade the repositories to "
271
"the same format for better performance\\.\n$")
272
# and now with it suppressed please
273
ui = ui_testing.TextUIFactory()
274
ui.suppressed_warnings.add('cross_format_fetch')
275
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
276
to_format=remote_fmt)
277
self.assertEqual('', ui.stdout.getvalue())
278
self.assertEqual('', ui.stderr.getvalue())
281
class TestTextUIOutputStream(tests.TestCase):
282
"""Tests for output stream that synchronizes with progress bar."""
284
def test_output_clears_terminal(self):
287
uif = ui_testing.TextUIFactory()
288
uif.clear_term = lambda: clear_calls.append('clear')
290
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout, 'utf-8', 'strict')
291
stream.write(u"Hello world!\n")
292
stream.write(u"there's more...\n")
293
stream.writelines([u"1\n", u"2\n", u"3\n"])
295
self.assertEqual(uif.stdout.getvalue(),
299
self.assertEqual(['clear', 'clear', 'clear'],
305
class UITests(tests.TestCase):
307
def test_progress_construction(self):
308
"""TextUIFactory constructs the right progress view.
310
FileStringIO = ui_testing.StringIOWithEncoding
311
TTYStringIO = ui_testing.StringIOAsTTY
312
for (file_class, term, pb, expected_pb_class) in (
313
# on an xterm, either use them or not as the user requests,
314
# otherwise default on
315
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
316
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
317
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
318
# on a dumb terminal, again if there's explicit configuration do
319
# it, otherwise default off
320
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
321
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
322
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
323
# on a non-tty terminal, it's null regardless of $TERM
324
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
325
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
326
# however, it can still be forced on
327
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
329
self.overrideEnv('TERM', term)
330
self.overrideEnv('BRZ_PROGRESS_BAR', pb)
331
stdin = file_class(u'')
332
stderr = file_class()
333
stdout = file_class()
334
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
335
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
336
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
337
self.assertIsInstance(uif.make_progress_view(),
339
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
341
def test_text_ui_non_terminal(self):
342
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
343
stdin = stderr = stdout = ui_testing.StringIOWithEncoding()
344
for term_type in ['dumb', None, 'xterm']:
345
self.overrideEnv('TERM', term_type)
346
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
347
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
348
'TERM=%r' % (term_type,))
351
class SilentUITests(tests.TestCase):
353
def test_silent_factory_get_password(self):
354
# A silent factory that can't do user interaction can't get a
355
# password. Possibly it should raise a more specific error but it
357
ui = _mod_ui.SilentUIFactory()
358
stdout = ui_testing.StringIOWithEncoding()
361
self.apply_redirected,
362
None, stdout, stdout, ui.get_password)
363
# and it didn't write anything out either
364
self.assertEqual('', stdout.getvalue())
366
def test_silent_ui_getbool(self):
367
factory = _mod_ui.SilentUIFactory()
368
stdout = ui_testing.StringIOWithEncoding()
371
self.apply_redirected,
372
None, stdout, stdout, factory.get_boolean, u"foo")
375
class TestUIFactoryTests(tests.TestCase):
377
def test_test_ui_factory_progress(self):
378
# there's no output; we just want to make sure this doesn't crash -
379
# see https://bugs.launchpad.net/bzr/+bug/408201
380
ui = ui_testing.TestUIFactory()
381
with ui.nested_progress_bar() as pb:
386
class CannedInputUIFactoryTests(tests.TestCase):
388
def test_canned_input_get_input(self):
389
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
390
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
391
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
392
self.assertEqual('password',
393
uif.get_password(u'Password for %(host)s',
395
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
398
class TestBoolFromString(tests.TestCase):
400
def assertIsTrue(self, s, accepted_values=None):
401
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
402
self.assertEqual(True, res)
404
def assertIsFalse(self, s, accepted_values=None):
405
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
406
self.assertEqual(False, res)
408
def assertIsNone(self, s, accepted_values=None):
409
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
410
self.assertIs(None, res)
412
def test_know_valid_values(self):
413
self.assertIsTrue('true')
414
self.assertIsFalse('false')
415
self.assertIsTrue('1')
416
self.assertIsFalse('0')
417
self.assertIsTrue('on')
418
self.assertIsFalse('off')
419
self.assertIsTrue('yes')
420
self.assertIsFalse('no')
421
self.assertIsTrue('y')
422
self.assertIsFalse('n')
423
# Also try some case variations
424
self.assertIsTrue('True')
425
self.assertIsFalse('False')
426
self.assertIsTrue('On')
427
self.assertIsFalse('Off')
428
self.assertIsTrue('ON')
429
self.assertIsFalse('OFF')
430
self.assertIsTrue('oN')
431
self.assertIsFalse('oFf')
433
def test_invalid_values(self):
434
self.assertIsNone(None)
435
self.assertIsNone('doubt')
436
self.assertIsNone('frue')
437
self.assertIsNone('talse')
438
self.assertIsNone('42')
440
def test_provided_values(self):
441
av = dict(y=True, n=False, yes=True, no=False)
442
self.assertIsTrue('y', av)
443
self.assertIsTrue('Y', av)
444
self.assertIsTrue('Yes', av)
445
self.assertIsFalse('n', av)
446
self.assertIsFalse('N', av)
447
self.assertIsFalse('No', av)
448
self.assertIsNone('1', av)
449
self.assertIsNone('0', av)
450
self.assertIsNone('on', av)
451
self.assertIsNone('off', av)
454
class TestConfirmationUserInterfacePolicy(tests.TestCase):
456
def test_confirm_action_default(self):
457
base_ui = _mod_ui.NoninteractiveUIFactory()
458
for answer in [True, False]:
460
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
461
.confirm_action("Do something?",
462
"breezy.tests.do_something", {}),
465
def test_confirm_action_specific(self):
466
base_ui = _mod_ui.NoninteractiveUIFactory()
467
for default_answer in [True, False]:
468
for specific_answer in [True, False]:
469
for conf_id in ['given_id', 'other_id']:
470
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
471
base_ui, default_answer, dict(given_id=specific_answer))
472
result = wrapper.confirm_action("Do something?", conf_id, {})
473
if conf_id == 'given_id':
474
self.assertEqual(result, specific_answer)
476
self.assertEqual(result, default_answer)
479
base_ui = _mod_ui.NoninteractiveUIFactory()
480
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
481
base_ui, True, dict(a=2))
482
self.assertThat(repr(wrapper),
483
Equals("ConfirmationUserInterfacePolicy("
484
"NoninteractiveUIFactory(), True, {'a': 2})"))
487
class TestProgressRecordingUI(tests.TestCase):
488
"""Test test-oriented UIFactory that records progress updates"""
490
def test_nested_ignore_depth_beyond_one(self):
491
# we only want to capture the first level out progress, not
492
# want sub-components might do. So we have nested bars ignored.
493
factory = ProgressRecordingUIFactory()
494
pb1 = factory.nested_progress_bar()
495
pb1.update('foo', 0, 1)
496
pb2 = factory.nested_progress_bar()
497
pb2.update('foo', 0, 1)
500
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)