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\nyes with garbage\n"
178
"yes\nn\nnot an answer\n"
183
factory.stdout = StringIO()
184
factory.stderr = StringIO()
185
# there is no output from the base factory
186
self.assertEqual(True, factory.get_boolean(""))
187
self.assertEqual(True, factory.get_boolean(""))
188
self.assertEqual(False, factory.get_boolean(""))
189
self.assertEqual(False, factory.get_boolean(""))
190
self.assertEqual(False, factory.get_boolean(""))
191
self.assertEqual(True, factory.get_boolean(""))
192
self.assertEqual("foo\n", factory.stdin.read())
193
# stdin should be empty
194
self.assertEqual('', factory.stdin.readline())
196
def test_silent_ui_getbool(self):
197
factory = _mod_ui.SilentUIFactory()
198
self.assert_get_bool_acceptance_of_user_input(factory)
200
def test_silent_factory_prompts_silently(self):
201
factory = _mod_ui.SilentUIFactory()
203
factory.stdin = StringIO("y\n")
204
self.assertEqual(True,
205
self.apply_redirected(None, stdout, stdout,
206
factory.get_boolean, "foo"))
207
self.assertEqual("", stdout.getvalue())
208
# stdin should be empty
209
self.assertEqual('', factory.stdin.readline())
211
def test_text_ui_getbool(self):
212
factory = TextUIFactory(None, None, None)
213
self.assert_get_bool_acceptance_of_user_input(factory)
215
def test_text_factory_prompt(self):
216
# see <https://launchpad.net/bugs/365891>
217
factory = TextUIFactory(None, StringIO(), StringIO(), StringIO())
218
factory.prompt('foo %2e')
219
self.assertEqual('', factory.stdout.getvalue())
220
self.assertEqual('foo %2e', factory.stderr.getvalue())
222
def test_text_factory_prompts_and_clears(self):
223
# a get_boolean call should clear the pb before prompting
225
factory = TextUIFactory(stdin=StringIO("yada\ny\n"),
226
stdout=out, stderr=out)
227
pb = factory.nested_progress_bar()
229
pb.show_spinner = False
230
pb.show_count = False
231
pb.update("foo", 0, 1)
232
self.assertEqual(True,
233
self.apply_redirected(None, factory.stdout,
237
output = out.getvalue()
238
self.assertContainsRe(factory.stdout.getvalue(),
240
self.assertContainsRe(factory.stdout.getvalue(),
241
r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")
242
# stdin should have been totally consumed
243
self.assertEqual('', factory.stdin.readline())
245
def test_text_tick_after_update(self):
246
ui_factory = TextUIFactory(stdout=StringIO(), stderr=StringIO())
247
pb = ui_factory.nested_progress_bar()
249
pb.update('task', 0, 3)
250
# Reset the clock, so that it actually tries to repaint itself
251
ui_factory._progress_view._last_repaint = time.time() - 1.0
256
def test_silent_ui_getusername(self):
257
factory = _mod_ui.SilentUIFactory()
258
factory.stdin = StringIO("someuser\n\n")
259
factory.stdout = StringIO()
260
factory.stderr = StringIO()
261
self.assertEquals(None,
262
factory.get_username(u'Hello\u1234 %(host)s', host=u'some\u1234'))
263
self.assertEquals("", factory.stdout.getvalue())
264
self.assertEquals("", factory.stderr.getvalue())
265
self.assertEquals("someuser\n\n", factory.stdin.getvalue())
267
def test_text_ui_getusername(self):
268
factory = TextUIFactory(None, None, None)
269
factory.stdin = StringIO("someuser\n\n")
270
factory.stdout = StringIO()
271
factory.stderr = StringIO()
272
factory.stdout.encoding = "utf8"
273
# there is no output from the base factory
274
self.assertEqual("someuser",
275
factory.get_username('Hello %(host)s', host='some'))
276
self.assertEquals("Hello some: ", factory.stderr.getvalue())
277
self.assertEquals('', factory.stdout.getvalue())
278
self.assertEqual("", factory.get_username("Gebruiker"))
279
# stdin should be empty
280
self.assertEqual('', factory.stdin.readline())
282
def test_text_ui_getusername_utf8(self):
283
ui = tests.TestUIFactory(stdin=u'someuser\u1234'.encode('utf8'),
284
stdout=tests.StringIOWrapper(),
285
stderr=tests.StringIOWrapper())
286
ui.stderr.encoding = ui.stdout.encoding = ui.stdin.encoding = "utf8"
287
pb = ui.nested_progress_bar()
289
# there is no output from the base factory
290
username = self.apply_redirected(ui.stdin, ui.stdout, ui.stderr,
291
ui.get_username, u'Hello\u1234 %(host)s', host=u'some\u1234')
292
self.assertEquals(u"someuser\u1234", username.decode('utf8'))
293
self.assertEquals(u"Hello\u1234 some\u1234: ",
294
ui.stderr.getvalue().decode("utf8"))
295
self.assertEquals('', ui.stdout.getvalue())
300
class TestTextProgressView(tests.TestCase):
301
"""Tests for text display of progress bars.
303
# XXX: These might be a bit easier to write if the rendering and
304
# state-maintaining parts of TextProgressView were more separate, and if
305
# the progress task called back directly to its own view not to the ui
306
# factory. -- mbp 20090312
308
def _make_factory(self):
310
uif = TextUIFactory(stderr=out)
311
uif._progress_view._width = 80
314
def test_render_progress_easy(self):
315
"""Just one task and one quarter done"""
316
out, uif = self._make_factory()
317
task = uif.nested_progress_bar()
318
task.update('reticulating splines', 5, 20)
320
'\r[####/ ] reticulating splines 5/20 \r'
323
def test_render_progress_nested(self):
324
"""Tasks proportionally contribute to overall progress"""
325
out, uif = self._make_factory()
326
task = uif.nested_progress_bar()
327
task.update('reticulating splines', 0, 2)
328
task2 = uif.nested_progress_bar()
329
task2.update('stage2', 1, 2)
330
# so we're in the first half of the main task, and half way through
333
r'[####\ ] reticulating splines:stage2 1/2'
334
, uif._progress_view._render_line())
335
# if the nested task is complete, then we're all the way through the
336
# first half of the overall work
337
task2.update('stage2', 2, 2)
339
r'[#########| ] reticulating splines:stage2 2/2'
340
, uif._progress_view._render_line())
342
def test_render_progress_sub_nested(self):
343
"""Intermediate tasks don't mess up calculation."""
344
out, uif = self._make_factory()
345
task_a = uif.nested_progress_bar()
346
task_a.update('a', 0, 2)
347
task_b = uif.nested_progress_bar()
349
task_c = uif.nested_progress_bar()
350
task_c.update('c', 1, 2)
351
# the top-level task is in its first half; the middle one has no
352
# progress indication, just a label; and the bottom one is half done,
353
# so the overall fraction is 1/4
355
r'[####| ] a:b:c 1/2'
356
, uif._progress_view._render_line())