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
ui = ui_testing.TestUIFactory('n\n')
57
self.assertEqual(ui.confirm_action(u'Should %(thing)s pass?',
58
'breezy.tests.test_ui.confirmation',
62
def test_text_factory_ascii_password(self):
63
ui = ui_testing.TestUIFactory('secret\n')
64
pb = ui.nested_progress_bar()
66
self.assertEqual('secret',
67
self.apply_redirected(ui.stdin, ui.stdout,
70
# ': ' is appended to prompt
71
self.assertEqual(': ', ui.stderr.getvalue())
72
self.assertEqual('', ui.stdout.readline())
73
# stdin should be empty
74
self.assertEqual('', ui.stdin.readline())
78
def test_text_factory_unicode_password(self):
79
"""Test a unicode password."""
80
ui = ui_testing.TextUIFactory(u'baz\u1234')
81
password = ui.get_password(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
factory = ui_testing.TextUIFactory(stdin_text)
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
factory = ui_testing.TextUIFactory(u"")
116
# invalid default index
117
self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
119
self.assertRaises(ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
120
# duplicated shortcut
121
self.assertRaises(ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
123
def test_text_ui_choose_prompt_explicit(self):
124
# choices with explicit shortcuts
125
factory = ui_testing.TextUIFactory(u"")
126
factory.choose(u"prompt", u"&yes\n&No\nmore &info")
127
self.assertEqual("prompt ([y]es, [N]o, more [i]nfo): \n", factory.stderr.getvalue())
129
def test_text_ui_choose_prompt_automatic(self):
130
# automatic shortcuts
131
factory = ui_testing.TextUIFactory(u"")
132
factory.choose(u"prompt", u"yes\nNo\nmore info")
133
self.assertEqual("prompt ([y]es, [N]o, [m]ore info): \n", factory.stderr.getvalue())
135
def test_text_ui_choose_return_values(self):
136
choose = lambda: factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
142
"b\na\nd \n" # bad shortcuts, all ignored
143
"yes with garbage\nY\n" # 0
144
"not an answer\nno\n" # 1
145
"info\nmore info\n" # 3
148
factory = ui_testing.TextUIFactory(stdin_text)
149
self.assertEqual(0, choose())
150
self.assertEqual(1, choose())
151
self.assertEqual(3, choose())
152
self.assertEqual(1, choose())
153
self.assertEqual(0, choose())
154
self.assertEqual(1, choose())
155
self.assertEqual(3, choose())
156
self.assertEqual(2, choose())
157
self.assertEqual("foo\n", factory.stdin.read())
158
# stdin should be empty
159
self.assertEqual('', factory.stdin.readline())
161
self.assertEqual(None, choose())
163
def test_text_ui_choose_no_default(self):
165
" \n" # no default, invalid!
168
factory = ui_testing.TextUIFactory(stdin_text)
169
self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
170
self.assertEqual("foo\n", factory.stdin.read())
172
def test_text_ui_get_integer(self):
176
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
177
factory = ui_testing.TextUIFactory(stdin_text)
178
self.assertEqual(1, factory.get_integer(u""))
179
self.assertEqual(-2, factory.get_integer(u""))
180
self.assertEqual(42, factory.get_integer(u""))
182
def test_text_factory_prompt(self):
183
# see <https://launchpad.net/bugs/365891>
184
factory = ui_testing.TextUIFactory()
185
factory.prompt(u'foo %2e')
186
self.assertEqual('', factory.stdout.getvalue())
187
self.assertEqual('foo %2e', factory.stderr.getvalue())
189
def test_text_factory_prompts_and_clears(self):
190
# a get_boolean call should clear the pb before prompting
191
out = ui_testing.StringIOAsTTY()
192
self.overrideEnv('TERM', 'xterm')
193
factory = ui_testing.TextUIFactory("yada\ny\n", stdout=out, stderr=out)
194
pb = factory.nested_progress_bar()
195
pb._avail_width = lambda: 79
197
pb.show_spinner = False
198
pb.show_count = False
199
pb.update("foo", 0, 1)
200
self.assertEqual(True,
201
self.apply_redirected(None, factory.stdout,
204
u"what do you want"))
205
output = out.getvalue()
206
self.assertContainsRe(output,
208
self.assertContainsString(output,
209
r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
210
# stdin should have been totally consumed
211
self.assertEqual('', factory.stdin.readline())
213
def test_text_tick_after_update(self):
214
ui_factory = ui_testing.TextUIFactory()
215
pb = ui_factory.nested_progress_bar()
217
pb.update('task', 0, 3)
218
# Reset the clock, so that it actually tries to repaint itself
219
ui_factory._progress_view._last_repaint = time.time() - 1.0
224
def test_text_ui_getusername(self):
225
ui = ui_testing.TextUIFactory('someuser\n\n')
226
self.assertEqual('someuser',
227
ui.get_username(u'Hello %(host)s', host='some'))
228
self.assertEqual('Hello some: ', ui.stderr.getvalue())
229
self.assertEqual('', ui.stdout.getvalue())
230
self.assertEqual('', ui.get_username(u"Gebruiker"))
231
# stdin should be empty
232
self.assertEqual('', ui.stdin.readline())
234
def test_text_ui_getusername_unicode(self):
235
ui = ui_testing.TextUIFactory(u'someuser\u1234')
236
username = ui.get_username(u'Hello %(host)s', host=u'some\u1234')
237
self.assertEqual(u"someuser\u1234", username)
238
self.assertEqual(u"Hello some\u1234: ", ui.stderr.getvalue())
239
self.assertEqual('', ui.stdout.getvalue())
241
def test_quietness(self):
242
self.overrideEnv('BRZ_PROGRESS_BAR', 'text')
243
ui_factory = ui_testing.TextUIFactory(
244
stderr=ui_testing.StringIOAsTTY())
245
self.assertIsInstance(ui_factory._progress_view,
246
_mod_ui_text.TextProgressView)
247
ui_factory.be_quiet(True)
248
self.assertIsInstance(ui_factory._progress_view,
249
_mod_ui_text.NullProgressView)
251
def test_text_ui_show_user_warning(self):
252
from ..bzr.groupcompress_repo import RepositoryFormat2a
253
from ..bzr.knitpack_repo import RepositoryFormatKnitPack5
254
ui = ui_testing.TextUIFactory()
255
remote_fmt = remote.RemoteRepositoryFormat()
256
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
257
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
258
to_format=remote_fmt)
259
self.assertEqual('', ui.stdout.getvalue())
260
self.assertContainsRe(
261
ui.stderr.getvalue(),
262
"^Doing on-the-fly conversion from RepositoryFormat2a\(\) to "
263
"RemoteRepositoryFormat\(_network_name="
264
"b?'Bazaar RepositoryFormatKnitPack5 \(bzr 1.6\)\\\\n'\)\.\n"
265
"This may take some time. Upgrade the repositories to "
266
"the same format for better performance\.\n$")
267
# and now with it suppressed please
268
ui = ui_testing.TextUIFactory()
269
ui.suppressed_warnings.add('cross_format_fetch')
270
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
271
to_format=remote_fmt)
272
self.assertEqual('', ui.stdout.getvalue())
273
self.assertEqual('', ui.stderr.getvalue())
276
class TestTextUIOutputStream(tests.TestCase):
277
"""Tests for output stream that synchronizes with progress bar."""
279
def test_output_clears_terminal(self):
282
uif = ui_testing.TextUIFactory()
283
uif.clear_term = lambda: clear_calls.append('clear')
285
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout, 'utf-8', 'strict')
286
stream.write(u"Hello world!\n")
287
stream.write(u"there's more...\n")
288
stream.writelines([u"1\n", u"2\n", u"3\n"])
290
self.assertEqual(uif.stdout.getvalue(),
294
self.assertEqual(['clear', 'clear', 'clear'],
300
class UITests(tests.TestCase):
302
def test_progress_construction(self):
303
"""TextUIFactory constructs the right progress view.
305
FileStringIO = ui_testing.StringIOWithEncoding
306
TTYStringIO = ui_testing.StringIOAsTTY
307
for (file_class, term, pb, expected_pb_class) in (
308
# on an xterm, either use them or not as the user requests,
309
# otherwise default on
310
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
311
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
312
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
313
# on a dumb terminal, again if there's explicit configuration do
314
# it, otherwise default off
315
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
316
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
317
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
318
# on a non-tty terminal, it's null regardless of $TERM
319
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
320
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
321
# however, it can still be forced on
322
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
324
self.overrideEnv('TERM', term)
325
self.overrideEnv('BRZ_PROGRESS_BAR', pb)
326
stdin = file_class(u'')
327
stderr = file_class()
328
stdout = file_class()
329
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
330
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
331
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
332
self.assertIsInstance(uif.make_progress_view(),
334
"TERM=%s BRZ_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
336
def test_text_ui_non_terminal(self):
337
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
338
stdin = stderr = stdout = ui_testing.StringIOWithEncoding()
339
for term_type in ['dumb', None, 'xterm']:
340
self.overrideEnv('TERM', term_type)
341
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
342
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
343
'TERM=%r' % (term_type,))
346
class SilentUITests(tests.TestCase):
348
def test_silent_factory_get_password(self):
349
# A silent factory that can't do user interaction can't get a
350
# password. Possibly it should raise a more specific error but it
352
ui = _mod_ui.SilentUIFactory()
353
stdout = ui_testing.StringIOWithEncoding()
356
self.apply_redirected,
357
None, stdout, stdout, ui.get_password)
358
# and it didn't write anything out either
359
self.assertEqual('', stdout.getvalue())
361
def test_silent_ui_getbool(self):
362
factory = _mod_ui.SilentUIFactory()
363
stdout = ui_testing.StringIOWithEncoding()
366
self.apply_redirected,
367
None, stdout, stdout, factory.get_boolean, u"foo")
370
class TestUIFactoryTests(tests.TestCase):
372
def test_test_ui_factory_progress(self):
373
# there's no output; we just want to make sure this doesn't crash -
374
# see https://bugs.launchpad.net/bzr/+bug/408201
375
ui = ui_testing.TestUIFactory()
376
pb = ui.nested_progress_bar()
382
class CannedInputUIFactoryTests(tests.TestCase):
384
def test_canned_input_get_input(self):
385
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
386
self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
387
self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
388
self.assertEqual('password',
389
uif.get_password(u'Password for %(host)s',
391
self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
394
class TestBoolFromString(tests.TestCase):
396
def assertIsTrue(self, s, accepted_values=None):
397
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
398
self.assertEqual(True, res)
400
def assertIsFalse(self, s, accepted_values=None):
401
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
402
self.assertEqual(False, res)
404
def assertIsNone(self, s, accepted_values=None):
405
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
406
self.assertIs(None, res)
408
def test_know_valid_values(self):
409
self.assertIsTrue('true')
410
self.assertIsFalse('false')
411
self.assertIsTrue('1')
412
self.assertIsFalse('0')
413
self.assertIsTrue('on')
414
self.assertIsFalse('off')
415
self.assertIsTrue('yes')
416
self.assertIsFalse('no')
417
self.assertIsTrue('y')
418
self.assertIsFalse('n')
419
# Also try some case variations
420
self.assertIsTrue('True')
421
self.assertIsFalse('False')
422
self.assertIsTrue('On')
423
self.assertIsFalse('Off')
424
self.assertIsTrue('ON')
425
self.assertIsFalse('OFF')
426
self.assertIsTrue('oN')
427
self.assertIsFalse('oFf')
429
def test_invalid_values(self):
430
self.assertIsNone(None)
431
self.assertIsNone('doubt')
432
self.assertIsNone('frue')
433
self.assertIsNone('talse')
434
self.assertIsNone('42')
436
def test_provided_values(self):
437
av = dict(y=True, n=False, yes=True, no=False)
438
self.assertIsTrue('y', av)
439
self.assertIsTrue('Y', av)
440
self.assertIsTrue('Yes', av)
441
self.assertIsFalse('n', av)
442
self.assertIsFalse('N', av)
443
self.assertIsFalse('No', av)
444
self.assertIsNone('1', av)
445
self.assertIsNone('0', av)
446
self.assertIsNone('on', av)
447
self.assertIsNone('off', av)
450
class TestConfirmationUserInterfacePolicy(tests.TestCase):
452
def test_confirm_action_default(self):
453
base_ui = _mod_ui.NoninteractiveUIFactory()
454
for answer in [True, False]:
456
_mod_ui.ConfirmationUserInterfacePolicy(base_ui, answer, {})
457
.confirm_action("Do something?",
458
"breezy.tests.do_something", {}),
461
def test_confirm_action_specific(self):
462
base_ui = _mod_ui.NoninteractiveUIFactory()
463
for default_answer in [True, False]:
464
for specific_answer in [True, False]:
465
for conf_id in ['given_id', 'other_id']:
466
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
467
base_ui, default_answer, dict(given_id=specific_answer))
468
result = wrapper.confirm_action("Do something?", conf_id, {})
469
if conf_id == 'given_id':
470
self.assertEqual(result, specific_answer)
472
self.assertEqual(result, default_answer)
475
base_ui = _mod_ui.NoninteractiveUIFactory()
476
wrapper = _mod_ui.ConfirmationUserInterfacePolicy(
477
base_ui, True, dict(a=2))
478
self.assertThat(repr(wrapper),
479
Equals("ConfirmationUserInterfacePolicy("
480
"NoninteractiveUIFactory(), True, {'a': 2})"))
483
class TestProgressRecordingUI(tests.TestCase):
484
"""Test test-oriented UIFactory that records progress updates"""
486
def test_nested_ignore_depth_beyond_one(self):
487
# we only want to capture the first level out progress, not
488
# want sub-components might do. So we have nested bars ignored.
489
factory = ProgressRecordingUIFactory()
490
pb1 = factory.nested_progress_bar()
491
pb1.update('foo', 0, 1)
492
pb2 = factory.nested_progress_bar()
493
pb2.update('foo', 0, 1)
496
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)