Merge lp:~gz/brz/ui_enter into lp:brz

Proposed by Martin Packman
Status: Merged
Approved by: Martin Packman
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~gz/brz/ui_enter
Merge into: lp:brz
Diff against target: 428 lines (+139/-114)
7 files modified
breezy/tests/__init__.py (+6/-5)
breezy/tests/per_uifactory/__init__.py (+12/-12)
breezy/tests/test_ui.py (+92/-81)
breezy/tests/ui_testing.py (+5/-0)
breezy/ui/__init__.py (+1/-6)
breezy/ui/text.py (+22/-5)
brz (+1/-5)
To merge this branch: bzr merge lp:~gz/brz/ui_enter
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+342043@code.launchpad.net

Commit message

Further work on sane ui factory interface and fix choose in char mode

Description of the change

Supersedes previous proposal:

https://code.launchpad.net/~gz/brz/ui_config_after_init/+merge/327992

But does not include requirement to initialize() before using config, instead leave ugly hack in place there for now.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Thanks :)

review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Running landing tests failed
https://ci.breezy-vcs.org/job/brz-dev/76/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/tests/__init__.py'
2--- breezy/tests/__init__.py 2018-03-16 02:56:15 +0000
3+++ breezy/tests/__init__.py 2018-03-24 17:11:49 +0000
4@@ -1959,11 +1959,12 @@
5 os.chdir(working_dir)
6
7 try:
8- result = self.apply_redirected(
9- ui.ui_factory.stdin,
10- stdout, stderr,
11- _mod_commands.run_bzr_catch_user_errors,
12- args)
13+ with ui.ui_factory:
14+ result = self.apply_redirected(
15+ ui.ui_factory.stdin,
16+ stdout, stderr,
17+ _mod_commands.run_bzr_catch_user_errors,
18+ args)
19 finally:
20 logger.removeHandler(handler)
21 ui.ui_factory = old_ui_factory
22
23=== modified file 'breezy/tests/per_uifactory/__init__.py'
24--- breezy/tests/per_uifactory/__init__.py 2017-05-22 00:56:52 +0000
25+++ breezy/tests/per_uifactory/__init__.py 2018-03-24 17:11:49 +0000
26@@ -44,6 +44,7 @@
27 from ..ui_testing import (
28 StringIOWithEncoding,
29 StringIOAsTTY,
30+ TextUIFactory,
31 )
32
33
34@@ -124,11 +125,15 @@
35
36 def setUp(self):
37 super(TestTextUIFactory, self).setUp()
38- self.stdin = StringIOWithEncoding()
39- self.stdout = StringIOWithEncoding()
40- self.stderr = StringIOWithEncoding()
41- self.factory = ui.text.TextUIFactory(self.stdin, self.stdout,
42- self.stderr)
43+ self.factory = self._create_ui_factory()
44+ self.factory.__enter__()
45+ self.addCleanup(self.factory.__exit__, None, None, None)
46+ self.stdin = self.factory.stdin
47+ self.stdout = self.factory.stdout
48+ self.stderr = self.factory.stderr
49+
50+ def _create_ui_factory(self):
51+ return TextUIFactory(u'')
52
53 def _check_note(self, note_text):
54 self.assertEqual("%s\n" % note_text,
55@@ -172,16 +177,11 @@
56
57 class TestTTYTextUIFactory(TestTextUIFactory):
58
59- def setUp(self):
60- super(TestTTYTextUIFactory, self).setUp()
61-
62+ def _create_ui_factory(self):
63 # Remove 'TERM' == 'dumb' which causes us to *not* treat output as a
64 # real terminal, even though isatty returns True
65 self.overrideEnv('TERM', None)
66- self.stderr = StringIOAsTTY()
67- self.stdout = StringIOAsTTY()
68- self.factory = ui.text.TextUIFactory(self.stdin, self.stdout,
69- self.stderr)
70+ return TextUIFactory(u'', StringIOAsTTY(), StringIOAsTTY())
71
72 def _check_log_transport_activity_display(self):
73 self.assertEqual('', self.stdout.getvalue())
74
75=== modified file 'breezy/tests/test_ui.py'
76--- breezy/tests/test_ui.py 2018-02-24 15:50:23 +0000
77+++ breezy/tests/test_ui.py 2018-03-24 17:11:49 +0000
78@@ -53,11 +53,13 @@
79
80 def test_text_factory_confirm(self):
81 # turns into reading a regular boolean
82- ui = ui_testing.TestUIFactory('n\n')
83- self.assertEqual(ui.confirm_action(u'Should %(thing)s pass?',
84- 'breezy.tests.test_ui.confirmation',
85- {'thing': 'this'},),
86- False)
87+ with ui_testing.TestUIFactory('n\n') as ui:
88+ self.assertEqual(
89+ False,
90+ ui.confirm_action(
91+ u'Should %(thing)s pass?',
92+ 'breezy.tests.test_ui.confirmation',
93+ {'thing': 'this'}))
94
95 def test_text_factory_ascii_password(self):
96 ui = ui_testing.TestUIFactory('secret\n')
97@@ -93,44 +95,51 @@
98 "I'm sure!\nyes\n" # True
99 "NO\n" # False
100 "foo\n")
101- factory = ui_testing.TextUIFactory(stdin_text)
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(True, factory.get_boolean(u""))
109- self.assertEqual(False, factory.get_boolean(u""))
110- self.assertEqual("foo\n", factory.stdin.read())
111- # stdin should be empty
112- self.assertEqual('', factory.stdin.readline())
113- # return false on EOF
114- self.assertEqual(False, factory.get_boolean(u""))
115+ with ui_testing.TextUIFactory(stdin_text) as factory:
116+ self.assertEqual(True, factory.get_boolean(u""))
117+ self.assertEqual(False, factory.get_boolean(u""))
118+ self.assertEqual(True, factory.get_boolean(u""))
119+ self.assertEqual(False, factory.get_boolean(u""))
120+ self.assertEqual(True, factory.get_boolean(u""))
121+ self.assertEqual(False, factory.get_boolean(u""))
122+ self.assertEqual(True, factory.get_boolean(u""))
123+ self.assertEqual(False, factory.get_boolean(u""))
124+ self.assertEqual("foo\n", factory.stdin.read())
125+ # stdin should be empty
126+ self.assertEqual('', factory.stdin.readline())
127+ # return false on EOF
128+ self.assertEqual(False, factory.get_boolean(u""))
129
130 def test_text_ui_choose_bad_parameters(self):
131- factory = ui_testing.TextUIFactory(u"")
132- # invalid default index
133- self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
134- # duplicated choice
135- self.assertRaises(ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
136- # duplicated shortcut
137- self.assertRaises(ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
138+ with ui_testing.TextUIFactory(u"") as factory:
139+ # invalid default index
140+ self.assertRaises(ValueError, factory.choose, u"", u"&Yes\n&No", 3)
141+ # duplicated choice
142+ self.assertRaises(
143+ ValueError, factory.choose, u"", u"&choice\n&ChOiCe")
144+ # duplicated shortcut
145+ self.assertRaises(
146+ ValueError, factory.choose, u"", u"&choice1\nchoi&ce2")
147
148 def test_text_ui_choose_prompt_explicit(self):
149 # choices with explicit shortcuts
150- factory = ui_testing.TextUIFactory(u"")
151- factory.choose(u"prompt", u"&yes\n&No\nmore &info")
152- self.assertEqual("prompt ([y]es, [N]o, more [i]nfo): \n", factory.stderr.getvalue())
153+ with ui_testing.TextUIFactory(u"") as factory:
154+ factory.choose(u"prompt", u"&yes\n&No\nmore &info")
155+ self.assertEqual(
156+ "prompt ([y]es, [N]o, more [i]nfo): \n",
157+ factory.stderr.getvalue())
158
159 def test_text_ui_choose_prompt_automatic(self):
160 # automatic shortcuts
161- factory = ui_testing.TextUIFactory(u"")
162- factory.choose(u"prompt", u"yes\nNo\nmore info")
163- self.assertEqual("prompt ([y]es, [N]o, [m]ore info): \n", factory.stderr.getvalue())
164+ with ui_testing.TextUIFactory(u"") as factory:
165+ factory.choose(u"prompt", u"yes\nNo\nmore info")
166+ self.assertEqual(
167+ "prompt ([y]es, [N]o, [m]ore info): \n",
168+ factory.stderr.getvalue())
169
170 def test_text_ui_choose_return_values(self):
171- choose = lambda: factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
172+ def choose():
173+ return factory.choose(u"", u"&Yes\n&No\nMaybe\nmore &info", 3)
174 stdin_text = (
175 "y\n" # 0
176 "n\n" # 1
177@@ -142,70 +151,71 @@
178 "info\nmore info\n" # 3
179 "Maybe\n" # 2
180 "foo\n")
181- factory = ui_testing.TextUIFactory(stdin_text)
182- self.assertEqual(0, choose())
183- self.assertEqual(1, choose())
184- self.assertEqual(3, choose())
185- self.assertEqual(1, choose())
186- self.assertEqual(0, choose())
187- self.assertEqual(1, choose())
188- self.assertEqual(3, choose())
189- self.assertEqual(2, choose())
190- self.assertEqual("foo\n", factory.stdin.read())
191- # stdin should be empty
192- self.assertEqual('', factory.stdin.readline())
193- # return None on EOF
194- self.assertEqual(None, choose())
195+ with ui_testing.TextUIFactory(stdin_text) as factory:
196+ self.assertEqual(0, choose())
197+ self.assertEqual(1, choose())
198+ self.assertEqual(3, choose())
199+ self.assertEqual(1, choose())
200+ self.assertEqual(0, choose())
201+ self.assertEqual(1, choose())
202+ self.assertEqual(3, choose())
203+ self.assertEqual(2, choose())
204+ self.assertEqual("foo\n", factory.stdin.read())
205+ # stdin should be empty
206+ self.assertEqual('', factory.stdin.readline())
207+ # return None on EOF
208+ self.assertEqual(None, choose())
209
210 def test_text_ui_choose_no_default(self):
211 stdin_text = (
212 " \n" # no default, invalid!
213 " yes \n" # 0
214 "foo\n")
215- factory = ui_testing.TextUIFactory(stdin_text)
216- self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
217- self.assertEqual("foo\n", factory.stdin.read())
218+ with ui_testing.TextUIFactory(stdin_text) as factory:
219+ self.assertEqual(0, factory.choose(u"", u"&Yes\n&No"))
220+ self.assertEqual("foo\n", factory.stdin.read())
221
222 def test_text_ui_get_integer(self):
223 stdin_text = (
224 "1\n"
225 " -2 \n"
226 "hmmm\nwhat else ?\nCome on\nok 42\n4.24\n42\n")
227- factory = ui_testing.TextUIFactory(stdin_text)
228- self.assertEqual(1, factory.get_integer(u""))
229- self.assertEqual(-2, factory.get_integer(u""))
230- self.assertEqual(42, factory.get_integer(u""))
231+ with ui_testing.TextUIFactory(stdin_text) as factory:
232+ self.assertEqual(1, factory.get_integer(u""))
233+ self.assertEqual(-2, factory.get_integer(u""))
234+ self.assertEqual(42, factory.get_integer(u""))
235
236 def test_text_factory_prompt(self):
237 # see <https://launchpad.net/bugs/365891>
238- factory = ui_testing.TextUIFactory()
239- factory.prompt(u'foo %2e')
240- self.assertEqual('', factory.stdout.getvalue())
241- self.assertEqual('foo %2e', factory.stderr.getvalue())
242+ with ui_testing.TextUIFactory() as factory:
243+ factory.prompt(u'foo %2e')
244+ self.assertEqual('', factory.stdout.getvalue())
245+ self.assertEqual('foo %2e', factory.stderr.getvalue())
246
247 def test_text_factory_prompts_and_clears(self):
248 # a get_boolean call should clear the pb before prompting
249 out = ui_testing.StringIOAsTTY()
250 self.overrideEnv('TERM', 'xterm')
251 factory = ui_testing.TextUIFactory("yada\ny\n", stdout=out, stderr=out)
252- pb = factory.nested_progress_bar()
253- pb._avail_width = lambda: 79
254- pb.show_bar = False
255- pb.show_spinner = False
256- pb.show_count = False
257- pb.update("foo", 0, 1)
258- self.assertEqual(True,
259- self.apply_redirected(None, factory.stdout,
260- factory.stdout,
261- factory.get_boolean,
262- u"what do you want"))
263- output = out.getvalue()
264- self.assertContainsRe(output,
265- "| foo *\r\r *\r*")
266- self.assertContainsString(output,
267- r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
268- # stdin should have been totally consumed
269- self.assertEqual('', factory.stdin.readline())
270+ with factory:
271+ pb = factory.nested_progress_bar()
272+ pb._avail_width = lambda: 79
273+ pb.show_bar = False
274+ pb.show_spinner = False
275+ pb.show_count = False
276+ pb.update("foo", 0, 1)
277+ self.assertEqual(
278+ True,
279+ self.apply_redirected(
280+ None, factory.stdout, factory.stdout, factory.get_boolean,
281+ u"what do you want"))
282+ output = out.getvalue()
283+ self.assertContainsRe(output,
284+ "| foo *\r\r *\r*")
285+ self.assertContainsString(output,
286+ r"what do you want? ([y]es, [n]o): what do you want? ([y]es, [n]o): ")
287+ # stdin should have been totally consumed
288+ self.assertEqual('', factory.stdin.readline())
289
290 def test_text_tick_after_update(self):
291 ui_factory = ui_testing.TextUIFactory()
292@@ -236,11 +246,12 @@
293 self.overrideEnv('BRZ_PROGRESS_BAR', 'text')
294 ui_factory = ui_testing.TextUIFactory(
295 stderr=ui_testing.StringIOAsTTY())
296- self.assertIsInstance(ui_factory._progress_view,
297- _mod_ui_text.TextProgressView)
298- ui_factory.be_quiet(True)
299- self.assertIsInstance(ui_factory._progress_view,
300- _mod_ui_text.NullProgressView)
301+ with ui_factory:
302+ self.assertIsInstance(ui_factory._progress_view,
303+ _mod_ui_text.TextProgressView)
304+ ui_factory.be_quiet(True)
305+ self.assertIsInstance(ui_factory._progress_view,
306+ _mod_ui_text.NullProgressView)
307
308 def test_text_ui_show_user_warning(self):
309 from ..bzr.groupcompress_repo import RepositoryFormat2a
310
311=== modified file 'breezy/tests/ui_testing.py'
312--- breezy/tests/ui_testing.py 2017-06-14 22:19:52 +0000
313+++ breezy/tests/ui_testing.py 2018-03-24 17:11:49 +0000
314@@ -62,6 +62,11 @@
315 stderr = StringIOWithEncoding()
316 super(TextUIFactory, self).__init__(stdin, stdout, stderr)
317
318+ def _setup_streams(self):
319+ self.raw_stdin = self.stdin
320+ self.raw_stdout = self.stdout
321+ self.raw_stderr = self.stderr
322+
323
324 class TestUIFactory(TextUIFactory):
325 """A UI Factory for testing.
326
327=== modified file 'breezy/ui/__init__.py'
328--- breezy/ui/__init__.py 2017-05-22 00:56:52 +0000
329+++ breezy/ui/__init__.py 2018-03-24 17:11:49 +0000
330@@ -522,12 +522,7 @@
331 """
332 # this is now always TextUIFactory, which in turn decides whether it
333 # should display progress bars etc
334- from .text import TextUIFactory, _wrap_in_stream, _wrap_out_stream
335- # GZ 2017-05-21: May want to rewrap streams on Python 3 if encoding config
336- if not PY3:
337- stdin = _wrap_in_stream(stdin)
338- stdout = _wrap_out_stream(stdout)
339- stderr = _wrap_out_stream(stderr)
340+ from .text import TextUIFactory
341 return TextUIFactory(stdin, stdout, stderr)
342
343
344
345=== modified file 'breezy/ui/text.py'
346--- breezy/ui/text.py 2017-06-14 22:19:52 +0000
347+++ breezy/ui/text.py 2018-03-24 17:11:49 +0000
348@@ -65,13 +65,14 @@
349 variable is set to 'line-based', or if there is no controlling
350 terminal.
351 """
352- if os.environ.get('BRZ_TEXTUI_INPUT') != 'line-based' and \
353- self.ui.stdin == sys.stdin and self.ui.stdin.isatty():
354+ is_tty = self.ui.raw_stdin.isatty()
355+ if (os.environ.get('BRZ_TEXTUI_INPUT') != 'line-based' and
356+ self.ui.raw_stdin == sys.stdin and is_tty):
357 self.line_based = False
358 self.echo_back = True
359 else:
360 self.line_based = True
361- self.echo_back = not self.ui.stdin.isatty()
362+ self.echo_back = not is_tty
363
364 def _build_alternatives(self, msg, choices, default):
365 """Parse choices string.
366@@ -126,7 +127,9 @@
367 raise KeyboardInterrupt
368 if char == chr(4): # EOF (^d, C-d)
369 raise EOFError
370- return char.decode("ascii", "replace")
371+ if isinstance(char, bytes):
372+ return char.decode('ascii', 'replace')
373+ return char
374
375 def interact(self):
376 """Keep asking the user until a valid choice is made.
377@@ -173,8 +176,22 @@
378 self.stdin = stdin
379 self.stdout = stdout
380 self.stderr = stderr
381+ self._progress_view = NullProgressView()
382+
383+ def __enter__(self):
384+ # Choose default encoding and handle py2/3 differences
385+ self._setup_streams()
386 # paints progress, network activity, etc
387 self._progress_view = self.make_progress_view()
388+ return self
389+
390+ def _setup_streams(self):
391+ self.raw_stdin = _unwrap_stream(self.stdin)
392+ self.stdin = _wrap_in_stream(self.raw_stdin)
393+ self.raw_stdout = _unwrap_stream(self.stdout)
394+ self.stdout = _wrap_out_stream(self.raw_stdout)
395+ self.raw_stderr = _unwrap_stream(self.stderr)
396+ self.stderr = _wrap_out_stream(self.raw_stderr)
397
398 def choose(self, msg, choices, default=None):
399 """Prompt the user for a list of alternatives.
400@@ -642,7 +659,7 @@
401 def _unwrap_stream(stream):
402 inner = getattr(stream, "buffer", None)
403 if inner is None:
404- inner = getattr(stream, "stream", None)
405+ inner = getattr(stream, "stream", stream)
406 return inner
407
408
409
410=== modified file 'brz'
411--- brz 2017-09-06 04:15:55 +0000
412+++ brz 2018-03-24 17:11:49 +0000
413@@ -89,14 +89,10 @@
414
415
416 if __name__ == '__main__':
417- library_state = breezy.initialize()
418- library_state.__enter__()
419- try:
420+ with breezy.initialize():
421 exit_val = breezy.commands.main()
422 if profiling:
423 profile_imports.log_stack_info(sys.stderr)
424- finally:
425- library_state.__exit__(None, None, None)
426
427 # By this point we really have completed everything we want to do, and
428 # there's no point doing any additional cleanup. Abruptly exiting here

Subscribers

People subscribed via source and target branches