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