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 | ||||||||
Related bugs: |
|
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:/
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
The Breezy Bot (the-breezy-bot) wrote : | # |
Running landing tests failed
https:/
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 |
Thanks :)