1
# Copyright (C) 2005, 2008, 2009 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
21
from StringIO import StringIO
31
from bzrlib.symbol_versioning import (
34
from bzrlib.tests.test_progress import _TTYStringIO
35
from bzrlib.ui.text import (
42
class UITests(tests.TestCase):
44
def test_silent_factory(self):
45
ui = _mod_ui.SilentUIFactory()
47
self.assertEqual(None,
48
self.apply_redirected(None, stdout, stdout,
50
self.assertEqual('', stdout.getvalue())
51
self.assertEqual(None,
52
self.apply_redirected(None, stdout, stdout,
54
u'Hello\u1234 %(user)s',
56
self.assertEqual('', stdout.getvalue())
58
def test_text_factory_ascii_password(self):
59
ui = tests.TestUIFactory(stdin='secret\n',
60
stdout=tests.StringIOWrapper(),
61
stderr=tests.StringIOWrapper())
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_utf8_password(self):
77
"""Test an utf8 password.
79
We can't predict what encoding users will have for stdin, so we force
80
it to utf8 to test that we transport the password correctly.
82
ui = tests.TestUIFactory(stdin=u'baz\u1234'.encode('utf8'),
83
stdout=tests.StringIOWrapper(),
84
stderr=tests.StringIOWrapper())
85
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = 'utf8'
86
pb = ui.nested_progress_bar()
88
password = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
90
u'Hello \u1234 %(user)s',
92
# We use StringIO objects, we need to decode them
93
self.assertEqual(u'baz\u1234', password.decode('utf8'))
94
self.assertEqual(u'Hello \u1234 some\u1234: ',
95
ui.stderr.getvalue().decode('utf8'))
96
# stdin and stdout should be empty
97
self.assertEqual('', ui.stdin.readline())
98
self.assertEqual('', ui.stdout.readline())
102
def test_progress_construction(self):
103
"""TextUIFactory constructs the right progress view.
105
os.environ['BZR_PROGRESS_BAR'] = 'none'
106
self.assertIsInstance(TextUIFactory()._progress_view,
109
os.environ['BZR_PROGRESS_BAR'] = 'text'
110
self.assertIsInstance(TextUIFactory()._progress_view,
113
os.environ['BZR_PROGRESS_BAR'] = 'text'
114
self.assertIsInstance(TextUIFactory()._progress_view,
117
del os.environ['BZR_PROGRESS_BAR']
118
self.assertIsInstance(TextUIFactory()._progress_view,
121
def test_progress_note(self):
124
ui_factory = TextUIFactory(stdin=StringIO(''),
127
pb = ui_factory.nested_progress_bar()
129
result = pb.note('t')
130
self.assertEqual(None, result)
131
self.assertEqual("t\n", stdout.getvalue())
132
# Since there was no update() call, there should be no clear() call
133
self.failIf(re.search(r'^\r {10,}\r$',
134
stderr.getvalue()) is not None,
135
'We cleared the stderr without anything to put there')
139
def test_progress_note_clears(self):
142
# The PQM redirects the output to a file, so it
143
# defaults to creating a Dots progress bar. we
144
# need to force it to believe we are a TTY
145
ui_factory = TextUIFactory(
147
stdout=stdout, stderr=stderr)
148
pb = ui_factory.nested_progress_bar()
150
# Create a progress update that isn't throttled
152
result = pb.note('t')
153
self.assertEqual(None, result)
154
self.assertEqual("t\n", stdout.getvalue())
155
# the exact contents will depend on the terminal width and we don't
156
# care about that right now - but you're probably running it on at
157
# least a 10-character wide terminal :)
158
self.assertContainsRe(stderr.getvalue(), r'\r {10,}\r$')
162
def test_progress_nested(self):
163
# test factory based nested and popping.
164
ui = TextUIFactory(None, None, None)
165
pb1 = ui.nested_progress_bar()
166
pb2 = ui.nested_progress_bar()
167
# You do get a warning if the outermost progress bar wasn't finished
168
# first - it's not clear if this is really useful or if it should just
169
# become orphaned -- mbp 20090120
170
warnings, _ = self.callCatchWarnings(pb1.finished)
171
if len(warnings) != 1:
172
self.fail("unexpected warnings: %r" % (warnings,))
176
def assert_get_bool_acceptance_of_user_input(self, factory):
177
factory.stdin = StringIO("y\n" # True
179
"yes with garbage\nY\n" # True
180
"not an answer\nno\n" # False
181
"I'm sure!\nyes\n" # True
184
factory.stdout = StringIO()
185
factory.stderr = StringIO()
186
# there is no output from the base factory
187
self.assertEqual(True, factory.get_boolean(""))
188
self.assertEqual(False, factory.get_boolean(""))
189
self.assertEqual(True, factory.get_boolean(""))
190
self.assertEqual(False, factory.get_boolean(""))
191
self.assertEqual(True, factory.get_boolean(""))
192
self.assertEqual(False, factory.get_boolean(""))
193
self.assertEqual("foo\n", factory.stdin.read())
194
# stdin should be empty
195
self.assertEqual('', factory.stdin.readline())
197
def test_silent_ui_getbool(self):
198
factory = _mod_ui.SilentUIFactory()
199
self.assert_get_bool_acceptance_of_user_input(factory)
201
def test_silent_factory_prompts_silently(self):
202
factory = _mod_ui.SilentUIFactory()
204
factory.stdin = StringIO("y\n")
205
self.assertEqual(True,
206
self.apply_redirected(None, stdout, stdout,
207
factory.get_boolean, "foo"))
208
self.assertEqual("", stdout.getvalue())
209
# stdin should be empty
210
self.assertEqual('', factory.stdin.readline())
212
def test_text_ui_getbool(self):
213
factory = TextUIFactory(None, None, None)
214
self.assert_get_bool_acceptance_of_user_input(factory)
216
def test_text_factory_prompt(self):
217
# see <https://launchpad.net/bugs/365891>
218
factory = TextUIFactory(None, StringIO(), StringIO(), StringIO())
219
factory.prompt('foo %2e')
220
self.assertEqual('', factory.stdout.getvalue())
221
self.assertEqual('foo %2e', factory.stderr.getvalue())
223
def test_text_factory_prompts_and_clears(self):
224
# a get_boolean call should clear the pb before prompting
226
factory = TextUIFactory(stdin=StringIO("yada\ny\n"),
227
stdout=out, stderr=out)
228
pb = factory.nested_progress_bar()
230
pb.show_spinner = False
231
pb.show_count = False
232
pb.update("foo", 0, 1)
233
self.assertEqual(True,
234
self.apply_redirected(None, factory.stdout,
238
output = out.getvalue()
239
self.assertContainsRe(factory.stdout.getvalue(),
241
self.assertContainsRe(factory.stdout.getvalue(),
242
r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")
243
# stdin should have been totally consumed
244
self.assertEqual('', factory.stdin.readline())
246
def test_text_tick_after_update(self):
247
ui_factory = TextUIFactory(stdout=StringIO(), stderr=StringIO())
248
pb = ui_factory.nested_progress_bar()
250
pb.update('task', 0, 3)
251
# Reset the clock, so that it actually tries to repaint itself
252
ui_factory._progress_view._last_repaint = time.time() - 1.0
257
def test_silent_ui_getusername(self):
258
factory = _mod_ui.SilentUIFactory()
259
factory.stdin = StringIO("someuser\n\n")
260
factory.stdout = StringIO()
261
factory.stderr = StringIO()
262
self.assertEquals(None,
263
factory.get_username(u'Hello\u1234 %(host)s', host=u'some\u1234'))
264
self.assertEquals("", factory.stdout.getvalue())
265
self.assertEquals("", factory.stderr.getvalue())
266
self.assertEquals("someuser\n\n", factory.stdin.getvalue())
268
def test_text_ui_getusername(self):
269
factory = TextUIFactory(None, None, None)
270
factory.stdin = StringIO("someuser\n\n")
271
factory.stdout = StringIO()
272
factory.stderr = StringIO()
273
factory.stdout.encoding = "utf8"
274
# there is no output from the base factory
275
self.assertEqual("someuser",
276
factory.get_username('Hello %(host)s', host='some'))
277
self.assertEquals("Hello some: ", factory.stderr.getvalue())
278
self.assertEquals('', factory.stdout.getvalue())
279
self.assertEqual("", factory.get_username("Gebruiker"))
280
# stdin should be empty
281
self.assertEqual('', factory.stdin.readline())
283
def test_text_ui_getusername_utf8(self):
284
ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
285
stdout=tests.StringIOWrapper(),
286
stderr=tests.StringIOWrapper())
287
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
288
pb = ui.nested_progress_bar()
290
# there is no output from the base factory
291
username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
292
ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
293
self.assertEquals(u"someuser\u1234", username.decode('utf8'))
294
self.assertEquals(u"Hello\u1234 some\u1234: ",
295
ui.stderr.getvalue().decode("utf8"))
296
self.assertEquals('', ui.stdout.getvalue())
301
class TestTextProgressView(tests.TestCase):
302
"""Tests for text display of progress bars.
304
# XXX: These might be a bit easier to write if the rendering and
305
# state-maintaining parts of TextProgressView were more separate, and if
306
# the progress task called back directly to its own view not to the ui
307
# factory. -- mbp 20090312
309
def _make_factory(self):
311
uif = TextUIFactory(stderr=out)
312
uif._progress_view._width = 80
315
def test_render_progress_easy(self):
316
"""Just one task and one quarter done"""
317
out, uif = self._make_factory()
318
task = uif.nested_progress_bar()
319
task.update('reticulating splines', 5, 20)
321
'\r[####/ ] reticulating splines 5/20 \r'
324
def test_render_progress_nested(self):
325
"""Tasks proportionally contribute to overall progress"""
326
out, uif = self._make_factory()
327
task = uif.nested_progress_bar()
328
task.update('reticulating splines', 0, 2)
329
task2 = uif.nested_progress_bar()
330
task2.update('stage2', 1, 2)
331
# so we're in the first half of the main task, and half way through
334
r'[####\ ] reticulating splines:stage2 1/2'
335
, uif._progress_view._render_line())
336
# if the nested task is complete, then we're all the way through the
337
# first half of the overall work
338
task2.update('stage2', 2, 2)
340
r'[#########| ] reticulating splines:stage2 2/2'
341
, uif._progress_view._render_line())
343
def test_render_progress_sub_nested(self):
344
"""Intermediate tasks don't mess up calculation."""
345
out, uif = self._make_factory()
346
task_a = uif.nested_progress_bar()
347
task_a.update('a', 0, 2)
348
task_b = uif.nested_progress_bar()
350
task_c = uif.nested_progress_bar()
351
task_c.update('c', 1, 2)
352
# the top-level task is in its first half; the middle one has no
353
# progress indication, just a label; and the bottom one is half done,
354
# so the overall fraction is 1/4
356
r'[####| ] a:b:c 1/2'
357
, uif._progress_view._render_line())
360
class TestBoolFromString(tests.TestCase):
362
def assertIsTrue(self, s, accepted_values=None):
363
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
364
self.assertEquals(True, res)
366
def assertIsFalse(self, s, accepted_values=None):
367
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
368
self.assertEquals(False, res)
370
def assertIsNone(self, s, accepted_values=None):
371
res = _mod_ui.bool_from_string(s, accepted_values=accepted_values)
372
self.assertIs(None, res)
374
def test_know_valid_values(self):
375
self.assertIsTrue('true')
376
self.assertIsFalse('false')
377
self.assertIsTrue('1')
378
self.assertIsFalse('0')
379
self.assertIsTrue('on')
380
self.assertIsFalse('off')
381
self.assertIsTrue('yes')
382
self.assertIsFalse('no')
383
self.assertIsTrue('y')
384
self.assertIsFalse('n')
385
# Also try some case variations
386
self.assertIsTrue('True')
387
self.assertIsFalse('False')
388
self.assertIsTrue('On')
389
self.assertIsFalse('Off')
390
self.assertIsTrue('ON')
391
self.assertIsFalse('OFF')
392
self.assertIsTrue('oN')
393
self.assertIsFalse('oFf')
395
def test_invalid_values(self):
396
self.assertIsNone(None)
397
self.assertIsNone('doubt')
398
self.assertIsNone('frue')
399
self.assertIsNone('talse')
400
self.assertIsNone('42')
402
def test_provided_values(self):
403
av = dict(y=True, n=False, yes=True, no=False)
404
self.assertIsTrue('y', av)
405
self.assertIsTrue('Y', av)
406
self.assertIsTrue('Yes', av)
407
self.assertIsFalse('n', av)
408
self.assertIsFalse('N', av)
409
self.assertIsFalse('No', av)
410
self.assertIsNone('1', av)
411
self.assertIsNone('0', av)
412
self.assertIsNone('on', av)
413
self.assertIsNone('off', av)