1
# Copyright (C) 2005-2010 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
24
from StringIO import StringIO
34
from bzrlib.symbol_versioning import (
37
from bzrlib.tests import (
41
from bzrlib.ui import text as _mod_ui_text
44
class TestUIConfiguration(tests.TestCaseWithTransport):
46
def test_output_encoding_configuration(self):
47
enc = fixtures.generate_unicode_encodings().next()
48
config.GlobalConfig().set_user_option('output_encoding',
50
ui = tests.TestUIFactory(stdin=None,
51
stdout=tests.StringIOWrapper(),
52
stderr=tests.StringIOWrapper())
53
os = ui.make_output_stream()
54
self.assertEquals(os.encoding, enc)
57
class TestTextUIFactory(tests.TestCase):
59
def test_text_factory_ascii_password(self):
60
ui = tests.TestUIFactory(stdin='secret\n',
61
stdout=tests.StringIOWrapper(),
62
stderr=tests.StringIOWrapper())
63
pb = ui.nested_progress_bar()
65
self.assertEqual('secret',
66
self.apply_redirected(ui.stdin, ui.stdout,
69
# ': ' is appended to prompt
70
self.assertEqual(': ', ui.stderr.getvalue())
71
self.assertEqual('', ui.stdout.readline())
72
# stdin should be empty
73
self.assertEqual('', ui.stdin.readline())
77
def test_text_factory_utf8_password(self):
78
"""Test an utf8 password.
80
We can't predict what encoding users will have for stdin, so we force
81
it to utf8 to test that we transport the password correctly.
83
ui = tests.TestUIFactory(stdin=u'baz\u1234'.encode('utf8'),
84
stdout=tests.StringIOWrapper(),
85
stderr=tests.StringIOWrapper())
86
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
87
pb = ui.nested_progress_bar()
89
password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
91
u'Hello \u1234 %(user)s',
93
# We use StringIO objects, we need to decode them
94
self.assertEqual(u'baz\u1234', password.decode('utf8'))
95
self.assertEqual(u'Hello \u1234 some\u1234: ',
96
ui.stderr.getvalue().decode('utf8'))
97
# stdin and stdout should be empty
98
self.assertEqual('', ui.stdin.readline())
99
self.assertEqual('', ui.stdout.readline())
103
def test_text_ui_get_boolean(self):
104
stdin = tests.StringIOWrapper("y\n" # True
106
"yes with garbage\nY\n" # True
107
"not an answer\nno\n" # False
108
"I'm sure!\nyes\n" # True
111
stdout = tests.StringIOWrapper()
112
stderr = tests.StringIOWrapper()
113
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
114
self.assertEqual(True, factory.get_boolean(""))
115
self.assertEqual(False, factory.get_boolean(""))
116
self.assertEqual(True, factory.get_boolean(""))
117
self.assertEqual(False, factory.get_boolean(""))
118
self.assertEqual(True, factory.get_boolean(""))
119
self.assertEqual(False, factory.get_boolean(""))
120
self.assertEqual("foo\n", factory.stdin.read())
121
# stdin should be empty
122
self.assertEqual('', factory.stdin.readline())
124
def test_text_ui_get_integer(self):
125
stdin = tests.StringIOWrapper(
128
"hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
129
stdout = tests.StringIOWrapper()
130
stderr = tests.StringIOWrapper()
131
factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
132
self.assertEqual(1, factory.get_integer(""))
133
self.assertEqual(-2, factory.get_integer(""))
134
self.assertEqual(42, factory.get_integer(""))
136
def test_text_factory_prompt(self):
137
# see <https://launchpad.net/bugs/365891>
138
StringIO = tests.StringIOWrapper
139
factory = _mod_ui_text.TextUIFactory(StringIO(), StringIO(), StringIO())
140
factory.prompt('foo %2e')
141
self.assertEqual('', factory.stdout.getvalue())
142
self.assertEqual('foo %2e', factory.stderr.getvalue())
144
def test_text_factory_prompts_and_clears(self):
145
# a get_boolean call should clear the pb before prompting
146
out = test_progress._TTYStringIO()
147
os.environ['TERM'] = 'xterm'
148
factory = _mod_ui_text.TextUIFactory(
149
stdin=tests.StringIOWrapper("yada\ny\n"),
150
stdout=out, stderr=out)
151
factory._avail_width = lambda: 79
152
pb = factory.nested_progress_bar()
154
pb.show_spinner = False
155
pb.show_count = False
156
pb.update("foo", 0, 1)
157
self.assertEqual(True,
158
self.apply_redirected(None, factory.stdout,
162
output = out.getvalue()
163
self.assertContainsRe(output,
165
self.assertContainsRe(output,
166
r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")
167
# stdin should have been totally consumed
168
self.assertEqual('', factory.stdin.readline())
170
def test_text_tick_after_update(self):
171
ui_factory = _mod_ui_text.TextUIFactory(stdout=tests.StringIOWrapper(),
172
stderr=tests.StringIOWrapper())
173
pb = ui_factory.nested_progress_bar()
175
pb.update('task', 0, 3)
176
# Reset the clock, so that it actually tries to repaint itself
177
ui_factory._progress_view._last_repaint = time.time() - 1.0
182
def test_text_ui_getusername(self):
183
factory = _mod_ui_text.TextUIFactory(None, None, None)
184
factory.stdin = tests.StringIOWrapper("someuser\n\n")
185
factory.stdout = tests.StringIOWrapper()
186
factory.stderr = tests.StringIOWrapper()
187
factory.stdout.encoding = "utf8"
188
# there is no output from the base factory
189
self.assertEqual("someuser",
190
factory.get_username('Hello %(host)s', host='some'))
191
self.assertEquals("Hello some: ", factory.stderr.getvalue())
192
self.assertEquals('', factory.stdout.getvalue())
193
self.assertEqual("", factory.get_username("Gebruiker"))
194
# stdin should be empty
195
self.assertEqual('', factory.stdin.readline())
197
def test_text_ui_getusername_utf8(self):
198
ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
199
stdout=tests.StringIOWrapper(),
200
stderr=tests.StringIOWrapper())
201
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
202
pb = ui.nested_progress_bar()
204
# there is no output from the base factory
205
username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
206
ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
207
self.assertEquals(u"someuser\u1234", username.decode('utf8'))
208
self.assertEquals(u"Hello\u1234 some\u1234: ",
209
ui.stderr.getvalue().decode("utf8"))
210
self.assertEquals('', ui.stdout.getvalue())
214
def test_quietness(self):
215
os.environ['BZR_PROGRESS_BAR'] = 'text'
216
ui_factory = _mod_ui_text.TextUIFactory(None,
217
test_progress._TTYStringIO(),
218
test_progress._TTYStringIO())
219
self.assertIsInstance(ui_factory._progress_view,
220
_mod_ui_text.TextProgressView)
221
ui_factory.be_quiet(True)
222
self.assertIsInstance(ui_factory._progress_view,
223
_mod_ui_text.NullProgressView)
225
def test_text_ui_show_user_warning(self):
226
from bzrlib.repofmt.groupcompress_repo import RepositoryFormat2a
227
from bzrlib.repofmt.pack_repo import RepositoryFormatKnitPack5
230
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
231
remote_fmt = remote.RemoteRepositoryFormat()
232
remote_fmt._network_name = RepositoryFormatKnitPack5().network_name()
233
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
234
to_format=remote_fmt)
235
self.assertEquals('', out.getvalue())
236
self.assertEquals("Doing on-the-fly conversion from RepositoryFormat2a() to "
237
"RemoteRepositoryFormat(_network_name='Bazaar RepositoryFormatKnitPack5 "
238
"(bzr 1.6)\\n').\nThis may take some time. Upgrade the repositories to "
239
"the same format for better performance.\n",
241
# and now with it suppressed please
244
ui = tests.TextUIFactory(stdin=None, stdout=out, stderr=err)
245
ui.suppressed_warnings.add('cross_format_fetch')
246
ui.show_user_warning('cross_format_fetch', from_format=RepositoryFormat2a(),
247
to_format=remote_fmt)
248
self.assertEquals('', out.getvalue())
249
self.assertEquals('', err.getvalue())
252
class TestTextUIOutputStream(tests.TestCase):
253
"""Tests for output stream that synchronizes with progress bar."""
255
def test_output_clears_terminal(self):
256
stdout = tests.StringIOWrapper()
257
stderr = tests.StringIOWrapper()
260
uif = _mod_ui_text.TextUIFactory(None, stdout, stderr)
261
uif.clear_term = lambda: clear_calls.append('clear')
263
stream = _mod_ui_text.TextUIOutputStream(uif, uif.stdout)
264
stream.write("Hello world!\n")
265
stream.write("there's more...\n")
266
stream.writelines(["1\n", "2\n", "3\n"])
268
self.assertEqual(stdout.getvalue(),
272
self.assertEqual(['clear', 'clear', 'clear'],
278
class UITests(tests.TestCase):
280
def test_progress_construction(self):
281
"""TextUIFactory constructs the right progress view.
283
TTYStringIO = test_progress._TTYStringIO
284
FileStringIO = tests.StringIOWrapper
285
for (file_class, term, pb, expected_pb_class) in (
286
# on an xterm, either use them or not as the user requests,
287
# otherwise default on
288
(TTYStringIO, 'xterm', 'none', _mod_ui_text.NullProgressView),
289
(TTYStringIO, 'xterm', 'text', _mod_ui_text.TextProgressView),
290
(TTYStringIO, 'xterm', None, _mod_ui_text.TextProgressView),
291
# on a dumb terminal, again if there's explicit configuration do
292
# it, otherwise default off
293
(TTYStringIO, 'dumb', 'none', _mod_ui_text.NullProgressView),
294
(TTYStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
295
(TTYStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
296
# on a non-tty terminal, it's null regardless of $TERM
297
(FileStringIO, 'xterm', None, _mod_ui_text.NullProgressView),
298
(FileStringIO, 'dumb', None, _mod_ui_text.NullProgressView),
299
# however, it can still be forced on
300
(FileStringIO, 'dumb', 'text', _mod_ui_text.TextProgressView),
302
os.environ['TERM'] = term
304
if 'BZR_PROGRESS_BAR' in os.environ:
305
del os.environ['BZR_PROGRESS_BAR']
307
os.environ['BZR_PROGRESS_BAR'] = pb
308
stdin = file_class('')
309
stderr = file_class()
310
stdout = file_class()
311
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
312
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
313
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
314
self.assertIsInstance(uif.make_progress_view(),
316
"TERM=%s BZR_PROGRESS_BAR=%s uif=%r" % (term, pb, uif,))
318
def test_text_ui_non_terminal(self):
319
"""Even on non-ttys, make_ui_for_terminal gives a text ui."""
320
stdin = test_progress._NonTTYStringIO('')
321
stderr = test_progress._NonTTYStringIO()
322
stdout = test_progress._NonTTYStringIO()
323
for term_type in ['dumb', None, 'xterm']:
324
if term_type is None:
325
del os.environ['TERM']
327
os.environ['TERM'] = term_type
328
uif = _mod_ui.make_ui_for_terminal(stdin, stdout, stderr)
329
self.assertIsInstance(uif, _mod_ui_text.TextUIFactory,
330
'TERM=%r' % (term_type,))
333
class SilentUITests(tests.TestCase):
335
def test_silent_factory_get_password(self):
336
# A silent factory that can't do user interaction can't get a
337
# password. Possibly it should raise a more specific error but it
339
ui = _mod_ui.SilentUIFactory()
340
stdout = tests.StringIOWrapper()
343
self.apply_redirected,
344
None, stdout, stdout, ui.get_password)
345
# and it didn't write anything out either
346
self.assertEqual('', stdout.getvalue())
348
def test_silent_ui_getbool(self):
349
factory = _mod_ui.SilentUIFactory()
350
stdout = tests.StringIOWrapper()
353
self.apply_redirected,
354
None, stdout, stdout, factory.get_boolean, "foo")
357
class TestUIFactoryTests(tests.TestCase):
359
def test_test_ui_factory_progress(self):
360
# there's no output; we just want to make sure this doesn't crash -
361
# see https://bugs.launchpad.net/bzr/+bug/408201
362
ui = tests.TestUIFactory()
363
pb = ui.nested_progress_bar()
369
class CannedInputUIFactoryTests(tests.TestCase):
371
def test_canned_input_get_input(self):
372
uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
373
self.assertEqual(True, uif.get_boolean('Extra cheese?'))
374
self.assertEqual('mbp', uif.get_username('Enter your user name'))
375
self.assertEqual('password',
376
uif.get_password('Password for %(host)s',
378
self.assertEqual(42, uif.get_integer('And all that jazz ?'))
381
class TestBoolFromString(tests.TestCase):
383
def assertIsTrue(self, s, accepted_values=None):
384
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
385
self.assertEquals(True, res)
387
def assertIsFalse(self, s, accepted_values=None):
388
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
389
self.assertEquals(False, res)
391
def assertIsNone(self, s, accepted_values=None):
392
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
393
self.assertIs(None, res)
395
def test_know_valid_values(self):
396
self.assertIsTrue('true')
397
self.assertIsFalse('false')
398
self.assertIsTrue('1')
399
self.assertIsFalse('0')
400
self.assertIsTrue('on')
401
self.assertIsFalse('off')
402
self.assertIsTrue('yes')
403
self.assertIsFalse('no')
404
self.assertIsTrue('y')
405
self.assertIsFalse('n')
406
# Also try some case variations
407
self.assertIsTrue('True')
408
self.assertIsFalse('False')
409
self.assertIsTrue('On')
410
self.assertIsFalse('Off')
411
self.assertIsTrue('ON')
412
self.assertIsFalse('OFF')
413
self.assertIsTrue('oN')
414
self.assertIsFalse('oFf')
416
def test_invalid_values(self):
417
self.assertIsNone(None)
418
self.assertIsNone('doubt')
419
self.assertIsNone('frue')
420
self.assertIsNone('talse')
421
self.assertIsNone('42')
423
def test_provided_values(self):
424
av = dict(y=True, n=False, yes=True, no=False)
425
self.assertIsTrue('y', av)
426
self.assertIsTrue('Y', av)
427
self.assertIsTrue('Yes', av)
428
self.assertIsFalse('n', av)
429
self.assertIsFalse('N', av)
430
self.assertIsFalse('No', av)
431
self.assertIsNone('1', av)
432
self.assertIsNone('0', av)
433
self.assertIsNone('on', av)
434
self.assertIsNone('off', av)