Merge lp:~gz/brz/py3_crash_debug_trace 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/py3_crash_debug_trace
Merge into: lp:brz
Diff against target: 568 lines (+115/-110)
7 files modified
breezy/crash.py (+1/-1)
breezy/plugin.py (+4/-2)
breezy/tests/blackbox/test_exceptions.py (+1/-2)
breezy/tests/test_crash.py (+15/-24)
breezy/tests/test_debug.py (+5/-4)
breezy/tests/test_trace.py (+73/-67)
breezy/trace.py (+16/-10)
To merge this branch: bzr merge lp:~gz/brz/py3_crash_debug_trace
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+325445@code.launchpad.net

Commit message

Make tests pass on Python 3 for crash, debug, and trace modules

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote :

Running landing tests failed
http://10.242.247.184:8080/job/brz-dev/114/

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

Running landing tests failed
http://10.242.247.184:8080/job/brz-dev/115/

Revision history for this message
Martin Packman (gz) wrote :

Intermittent failure.

ERROR: breezy.tests.test_test_server.TestTCPServerInAThread.test_handle_request_closes_if_it_doesnt_process(TestingTCPServer)

socket.error: [Errno 104] Connection reset by peer

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/crash.py'
2--- breezy/crash.py 2017-05-22 00:56:52 +0000
3+++ breezy/crash.py 2017-06-10 22:21:19 +0000
4@@ -226,7 +226,7 @@
5
6 def _attach_log_tail(pr):
7 try:
8- bzr_log = open(trace._get_bzr_log_filename(), 'rt')
9+ bzr_log = open(trace._get_brz_log_filename(), 'rt')
10 except (IOError, OSError) as e:
11 pr['BzrLogTail'] = repr(e)
12 return
13
14=== modified file 'breezy/plugin.py'
15--- breezy/plugin.py 2017-06-10 14:01:07 +0000
16+++ breezy/plugin.py 2017-06-10 22:21:19 +0000
17@@ -441,11 +441,13 @@
18 return result
19
20
21-def format_concise_plugin_list():
22+def format_concise_plugin_list(state=None):
23 """Return a string holding a concise list of plugins and their version.
24 """
25+ if state is None:
26+ state = breezy.global_state
27 items = []
28- for name, a_plugin in sorted(plugins().items()):
29+ for name, a_plugin in sorted(state.plugins.items()):
30 items.append("%s[%s]" %
31 (name, a_plugin.__version__))
32 return ', '.join(items)
33
34=== modified file 'breezy/tests/blackbox/test_exceptions.py'
35--- breezy/tests/blackbox/test_exceptions.py 2017-06-10 00:52:37 +0000
36+++ breezy/tests/blackbox/test_exceptions.py 2017-06-10 22:21:19 +0000
37@@ -40,8 +40,7 @@
38 universal_newlines=True,
39 retcode=errors.EXIT_INTERNAL_ERROR)
40 self.assertEqual(4, errors.EXIT_INTERNAL_ERROR)
41- self.assertContainsRe(err,
42- r'exceptions\.AssertionError: always fails\n')
43+ self.assertContainsRe(err, r'\nAssertionError: always fails\n')
44 self.assertContainsRe(err, r'Bazaar has encountered an internal error')
45
46 def test_undecodable_argv(self):
47
48=== modified file 'breezy/tests/test_crash.py'
49--- breezy/tests/test_crash.py 2017-05-22 00:56:52 +0000
50+++ breezy/tests/test_crash.py 2017-06-10 22:21:19 +0000
51@@ -19,6 +19,7 @@
52 import os
53 import sys
54
55+import breezy
56 from .. import (
57 config,
58 crash,
59@@ -27,7 +28,7 @@
60 tests,
61 )
62 from ..sixish import (
63- BytesIO,
64+ StringIO,
65 )
66 from . import features
67
68@@ -43,29 +44,23 @@
69 self.assertEqual(crash_dir, config.crash_dir())
70
71 self.overrideAttr(
72- plugin,
73+ breezy.global_state,
74 'plugin_warnings',
75 {'example': ['Failed to load plugin foo']})
76
77- stderr = BytesIO()
78+ stderr = StringIO()
79
80 try:
81 raise AssertionError("my error")
82 except AssertionError as e:
83- pass
84-
85- crash_filename = crash.report_bug_to_apport(sys.exc_info(),
86- stderr)
87+ crash_filename = crash.report_bug_to_apport(sys.exc_info(), stderr)
88
89 # message explaining the crash
90 self.assertContainsRe(stderr.getvalue(),
91 " apport-bug %s" % crash_filename)
92
93- crash_file = open(crash_filename)
94- try:
95+ with open(crash_filename) as crash_file:
96 report = crash_file.read()
97- finally:
98- crash_file.close()
99
100 self.assertContainsRe(report,
101 '(?m)^BzrVersion:') # should be in the traceback
102@@ -83,31 +78,27 @@
103
104 class TestNonApportReporting(tests.TestCase):
105 """Reporting of crash-type bugs without apport.
106-
107+
108 This should work in all environments.
109 """
110
111 def setup_fake_plugins(self):
112- def fake_plugins():
113- fake = plugin.PlugIn('fake_plugin', plugin)
114- fake.version_info = lambda: (1, 2, 3)
115- return {"fake_plugin": fake}
116- self.overrideAttr(plugin, 'plugins', fake_plugins)
117+ fake = plugin.PlugIn('fake_plugin', plugin)
118+ fake.version_info = lambda: (1, 2, 3)
119+ fake_plugins = {"fake_plugin": fake}
120+ self.overrideAttr(breezy.global_state, 'plugins', fake_plugins)
121
122 def test_report_bug_legacy(self):
123 self.setup_fake_plugins()
124- err_file = BytesIO()
125+ err_file = StringIO()
126 try:
127 raise AssertionError("my error")
128 except AssertionError as e:
129- pass
130- crash.report_bug_legacy(sys.exc_info(), err_file)
131+ crash.report_bug_legacy(sys.exc_info(), err_file)
132 report = err_file.getvalue()
133 for needle in [
134- "brz: ERROR: exceptions.AssertionError: my error",
135+ "brz: ERROR: AssertionError: my error",
136 r"Traceback \(most recent call last\):",
137 r"plugins: fake_plugin\[1\.2\.3\]",
138 ]:
139- self.assertContainsRe(
140- report,
141- needle)
142+ self.assertContainsRe(report, needle)
143
144=== modified file 'breezy/tests/test_debug.py'
145--- breezy/tests/test_debug.py 2017-05-22 00:56:52 +0000
146+++ breezy/tests/test_debug.py 2017-06-10 22:21:19 +0000
147@@ -27,17 +27,18 @@
148 class TestDebugFlags(tests.TestCaseInTempDir):
149
150 def test_set_no_debug_flags_from_config(self):
151- self.assertDebugFlags([], '')
152+ self.assertDebugFlags([], b'')
153
154 def test_set_single_debug_flags_from_config(self):
155- self.assertDebugFlags(['hpss'], 'debug_flags = hpss\n')
156+ self.assertDebugFlags(['hpss'], b'debug_flags = hpss\n')
157
158 def test_set_multiple_debug_flags_from_config(self):
159- self.assertDebugFlags(['hpss', 'error'], 'debug_flags = hpss, error\n')
160+ self.assertDebugFlags(
161+ ['hpss', 'error'], b'debug_flags = hpss, error\n')
162
163 def assertDebugFlags(self, expected_flags, conf_bytes):
164 conf = config.GlobalStack()
165- conf.store._load_from_string('[DEFAULT]\n' + conf_bytes)
166+ conf.store._load_from_string(b'[DEFAULT]\n' + conf_bytes)
167 conf.store.save()
168 self.overrideAttr(debug, 'debug_flags', set())
169 debug.set_debug_flags_from_config()
170
171=== modified file 'breezy/tests/test_trace.py'
172--- breezy/tests/test_trace.py 2017-05-22 00:56:52 +0000
173+++ breezy/tests/test_trace.py 2017-06-10 22:21:19 +0000
174@@ -31,7 +31,8 @@
175 trace,
176 )
177 from ..sixish import (
178- BytesIO,
179+ PY3,
180+ StringIO,
181 )
182 from . import features, TestCaseInTempDir, TestCase
183 from ..trace import (
184@@ -46,7 +47,7 @@
185
186 def _format_exception():
187 """Format an exception as it would normally be displayed to the user"""
188- buf = BytesIO()
189+ buf = StringIO()
190 report_exception(sys.exc_info(), buf)
191 return buf.getvalue()
192
193@@ -61,10 +62,9 @@
194 try:
195 raise NotImplementedError("time travel")
196 except NotImplementedError:
197- pass
198- err = _format_exception()
199- self.assertEqualDiff(err.splitlines()[0],
200- 'brz: ERROR: exceptions.NotImplementedError: time travel')
201+ err = _format_exception()
202+ self.assertContainsRe(err,
203+ '^brz: ERROR: NotImplementedError: time travel')
204 self.assertContainsRe(err,
205 'Bazaar has encountered an internal error.')
206
207@@ -73,17 +73,14 @@
208 raise KeyboardInterrupt()
209 except KeyboardInterrupt:
210 # XXX: Some risk that a *real* keyboard interrupt won't be seen
211- pass
212- msg = _format_exception()
213- self.assertTrue(len(msg) > 0)
214- self.assertEqualDiff(msg, 'brz: interrupted\n')
215+ msg = _format_exception()
216+ self.assertEqual(msg, 'brz: interrupted\n')
217
218 def test_format_memory_error(self):
219 try:
220 raise MemoryError()
221 except MemoryError:
222- pass
223- msg = _format_exception()
224+ msg = _format_exception()
225 self.assertEqual(msg,
226 "brz: out of memory\nUse -Dmem_dump to dump memory to a file.\n")
227
228@@ -93,8 +90,7 @@
229 try:
230 raise MemoryError()
231 except MemoryError:
232- pass
233- msg = _format_exception()
234+ msg = _format_exception()
235 self.assertStartsWith(msg,
236 "brz: out of memory\nMemory dumped to ")
237
238@@ -103,22 +99,21 @@
239 os.rmdir('nosuchfile22222')
240 except OSError as e:
241 e_str = str(e)
242- msg = _format_exception()
243+ msg = _format_exception()
244 # Linux seems to give "No such file" but Windows gives "The system
245 # cannot find the file specified".
246 self.assertEqual('brz: ERROR: %s\n' % (e_str,), msg)
247
248 def test_format_io_error(self):
249 try:
250- file('nosuchfile22222')
251+ open('nosuchfile22222')
252 except IOError:
253- pass
254- msg = _format_exception()
255+ msg = _format_exception()
256 # Even though Windows and Linux differ for 'os.rmdir', they both give
257 # 'No such file' for open()
258 # However it now gets translated so we can not test for a specific message
259 self.assertContainsRe(msg,
260- r'^brz: ERROR: \[Errno .*\] .*nosuchfile')
261+ '^brz: ERROR: \\[Errno .*\\] .*nosuchfile')
262
263 def test_format_pywintypes_error(self):
264 self.requireFeature(features.pywintypes)
265@@ -126,63 +121,62 @@
266 try:
267 win32file.RemoveDirectory('nosuchfile22222')
268 except pywintypes.error:
269- pass
270- msg = _format_exception()
271+ msg = _format_exception()
272 # GZ 2010-05-03: Formatting for pywintypes.error is basic, a 3-tuple
273 # with errno, function name, and locale error message
274 self.assertContainsRe(msg,
275- r"^brz: ERROR: \(2, 'RemoveDirectory[AW]?', .*\)")
276-
277+ "^brz: ERROR: \\(2, 'RemoveDirectory[AW]?', .*\\)")
278+
279 def test_format_sockets_error(self):
280 try:
281 import socket
282 sock = socket.socket()
283- sock.send("This should fail.")
284+ sock.send(b"This should fail.")
285 except socket.error:
286- pass
287- msg = _format_exception()
288-
289+ msg = _format_exception()
290+
291 self.assertNotContainsRe(msg,
292- r"Traceback (most recent call last):")
293+ "Traceback \\(most recent call last\\):")
294
295 def test_format_unicode_error(self):
296 try:
297 raise errors.BzrCommandError(u'argument foo\xb5 does not exist')
298 except errors.BzrCommandError:
299- pass
300- msg = _format_exception()
301+ msg = _format_exception()
302+ if PY3:
303+ expected = 'brz: ERROR: argument foo\xb5 does not exist\n'
304+ else:
305+ # GZ 2017-06-10: Pretty bogus, should encode per the output stream
306+ expected = 'brz: ERROR: argument foo\xc2\xb5 does not exist\n'
307+ self.assertEqual(msg, expected)
308
309 def test_format_exception(self):
310 """Short formatting of brz exceptions"""
311 try:
312 raise errors.NotBranchError('wibble')
313 except errors.NotBranchError:
314- pass
315- msg = _format_exception()
316- self.assertTrue(len(msg) > 0)
317- self.assertEqualDiff(msg, 'brz: ERROR: Not a branch: \"wibble\".\n')
318+ msg = _format_exception()
319+ self.assertEqual(msg, 'brz: ERROR: Not a branch: \"wibble\".\n')
320
321 def test_report_external_import_error(self):
322 """Short friendly message for missing system modules."""
323 try:
324 import ImaginaryModule
325 except ImportError as e:
326- pass
327+ msg = _format_exception()
328 else:
329 self.fail("somehow succeeded in importing %r" % ImaginaryModule)
330- msg = _format_exception()
331- self.assertEqual(msg,
332- 'brz: ERROR: No module named ImaginaryModule\n'
333- 'You may need to install this Python library separately.\n')
334+ self.assertContainsRe(msg,
335+ "^brz: ERROR: No module named '?ImaginaryModule'?\n"
336+ "You may need to install this Python library separately.\n$")
337
338 def test_report_import_syntax_error(self):
339 try:
340 raise ImportError("syntax error")
341 except ImportError as e:
342- pass
343- msg = _format_exception()
344+ msg = _format_exception()
345 self.assertContainsRe(msg,
346- r'Bazaar has encountered an internal error')
347+ 'Bazaar has encountered an internal error')
348
349 def test_trace_unicode(self):
350 """Write Unicode to trace log"""
351@@ -212,17 +206,18 @@
352 else:
353 self.fail("expected error not raised")
354
355- def assertLogStartsWith(self, log, string):
356- """Like assertStartsWith, but skips the log timestamp."""
357+ def assertLogContainsLine(self, log, string):
358+ """Assert log contains a line including log timestamp."""
359+ # Does not check absolute position in log as there may be kipple.
360 self.assertContainsRe(log,
361- '^\\d+\\.\\d+ ' + re.escape(string))
362+ '(?m)^\\d+\\.\\d+ ' + re.escape(string))
363
364 def test_mutter_callsite_1(self):
365 """mutter_callsite can capture 1 level of stack frame."""
366 mutter_callsite(1, "foo %s", "a string")
367 log = self.get_log()
368 # begin with the message
369- self.assertLogStartsWith(log, 'foo a string\nCalled from:\n')
370+ self.assertLogContainsLine(log, 'foo a string\nCalled from:\n')
371 # should show two frame: this frame and the one above
372 self.assertContainsRe(log,
373 'test_trace\\.py", line \\d+, in test_mutter_callsite_1\n')
374@@ -234,7 +229,7 @@
375 mutter_callsite(2, "foo %s", "a string")
376 log = self.get_log()
377 # begin with the message
378- self.assertLogStartsWith(log, 'foo a string\nCalled from:\n')
379+ self.assertLogContainsLine(log, 'foo a string\nCalled from:\n')
380 # should show two frame: this frame and the one above
381 self.assertContainsRe(log,
382 'test_trace.py", line \d+, in test_mutter_callsite_2\n')
383@@ -247,15 +242,15 @@
384 # This test checks that mutter doesn't fail; the current behaviour
385 # is that it doesn't fail *and writes non-utf8*.
386 mutter(u'Writing a greek mu (\xb5) works in a unicode string')
387- mutter('But fails in an ascii string \xb5')
388- mutter('and in an ascii argument: %s', '\xb5')
389+ mutter(b'But fails in an ascii string \xb5')
390+ mutter(b'and in an ascii argument: %s', b'\xb5')
391 log = self.get_log()
392 self.assertContainsRe(log, 'Writing a greek mu')
393 self.assertContainsRe(log, "But fails in an ascii string")
394 # However, the log content object does unicode replacement on reading
395 # to let it get unicode back where good data has been written. So we
396 # have to do a replaceent here as well.
397- self.assertContainsRe(log, "ascii argument: \xb5".decode('utf8',
398+ self.assertContainsRe(log, b"ascii argument: \xb5".decode('utf8',
399 'replace'))
400
401 def test_show_error(self):
402@@ -303,10 +298,11 @@
403 # as there's a timestamp at the front.
404 tmp1.seek(0)
405 self.assertContainsRe(tmp1.read(),
406- r"\d+\.\d+ comment to file1\n\d+\.\d+ again to file1\n")
407+ b"\\d+\\.\\d+ comment to file1\n"
408+ b"\\d+\\.\\d+ again to file1\n")
409 tmp2.seek(0)
410 self.assertContainsRe(tmp2.read(),
411- r"\d+\.\d+ comment to file2\n")
412+ b"\\d+\\.\\d+ comment to file2\n")
413 finally:
414 tmp1.close()
415 tmp2.close()
416@@ -315,7 +311,7 @@
417 # If _open_brz_log cannot open the file, then we should write the
418 # warning to stderr. Since this is normally happening before logging is
419 # set up.
420- self.overrideAttr(sys, 'stderr', BytesIO())
421+ self.overrideAttr(sys, 'stderr', StringIO())
422 # Set the log file to something that cannot exist
423 self.overrideEnv('BRZ_LOG', '/no-such-dir/brz.log')
424 self.overrideAttr(trace, '_brz_log_filename')
425@@ -378,33 +374,43 @@
426 self.assertEqual(u" DEBUG \xa7\n", self.get_log())
427
428 def test_log_utf8_msg(self):
429- logging.getLogger("brz").debug("\xc2\xa7")
430+ logging.getLogger("brz").debug(b"\xc2\xa7")
431 self.assertEqual(u" DEBUG \xa7\n", self.get_log())
432
433 def test_log_utf8_arg(self):
434- logging.getLogger("brz").debug("%s", "\xc2\xa7")
435- self.assertEqual(u" DEBUG \xa7\n", self.get_log())
436+ logging.getLogger("brz").debug(b"%s", b"\xc2\xa7")
437+ if PY3:
438+ expected = u" DEBUG b'\\xc2\\xa7'\n"
439+ else:
440+ expected = u" DEBUG \xa7\n"
441+ self.assertEqual(expected, self.get_log())
442
443 def test_log_bytes_msg(self):
444- logging.getLogger("brz").debug("\xa7")
445+ logging.getLogger("brz").debug(b"\xa7")
446 log = self.get_log()
447 self.assertContainsString(log, "UnicodeDecodeError: ")
448- self.assertContainsString(log,
449- "Logging record unformattable: '\\xa7' % ()\n")
450+ self.assertContainsRe(log,
451+ "Logging record unformattable: b?'\\\\xa7' % \\(\\)\n")
452
453 def test_log_bytes_arg(self):
454- logging.getLogger("brz").debug("%s", "\xa7")
455+ logging.getLogger("brz").debug(b"%s", b"\xa7")
456 log = self.get_log()
457- self.assertContainsString(log, "UnicodeDecodeError: ")
458- self.assertContainsString(log,
459- "Logging record unformattable: '%s' % ('\\xa7',)\n")
460+ if PY3:
461+ self.assertEqual(u" DEBUG b'\\xa7'\n", self.get_log())
462+ else:
463+ self.assertContainsString(log, "UnicodeDecodeError: ")
464+ self.assertContainsRe(log,
465+ "Logging record unformattable: ?'%s' % \\(b?'\\\\xa7',\\)\n")
466
467 def test_log_mixed_strings(self):
468- logging.getLogger("brz").debug(u"%s", "\xa7")
469+ logging.getLogger("brz").debug(u"%s", b"\xa7")
470 log = self.get_log()
471- self.assertContainsString(log, "UnicodeDecodeError: ")
472- self.assertContainsString(log,
473- "Logging record unformattable: u'%s' % ('\\xa7',)\n")
474+ if PY3:
475+ self.assertEqual(u" DEBUG b'\\xa7'\n", self.get_log())
476+ else:
477+ self.assertContainsString(log, "UnicodeDecodeError: ")
478+ self.assertContainsRe(log,
479+ "Logging record unformattable: u'%s' % \\('\\\\xa7',\\)\n")
480
481 def test_log_repr_broken(self):
482 class BadRepr(object):
483
484=== modified file 'breezy/trace.py'
485--- breezy/trace.py 2017-05-22 00:56:52 +0000
486+++ breezy/trace.py 2017-06-10 22:21:19 +0000
487@@ -82,6 +82,8 @@
488
489 from .sixish import (
490 BytesIO,
491+ PY3,
492+ StringIO,
493 text_type,
494 )
495
496@@ -173,7 +175,7 @@
497 :param fmt: The format string to pass to mutter.
498 :param args: A list of substitution variables.
499 """
500- outf = BytesIO()
501+ outf = StringIO()
502 if stacklevel is None:
503 limit = None
504 else:
505@@ -252,9 +254,9 @@
506 brz_log_file = _open_or_create_log_file(_brz_log_filename)
507 brz_log_file.write(b'\n')
508 if brz_log_file.tell() <= 2:
509- brz_log_file.write("this is a debug log for diagnosing/reporting problems in brz\n")
510- brz_log_file.write("you can delete or truncate this file, or include sections in\n")
511- brz_log_file.write("bug reports to https://bugs.launchpad.net/brz/+filebug\n\n")
512+ brz_log_file.write(b"this is a debug log for diagnosing/reporting problems in brz\n")
513+ brz_log_file.write(b"you can delete or truncate this file, or include sections in\n")
514+ brz_log_file.write(b"bug reports to https://bugs.launchpad.net/brz/+filebug\n\n")
515
516 return brz_log_file
517
518@@ -469,7 +471,7 @@
519 """
520 class_name = eclass.__name__
521 module_name = eclass.__module__
522- if module_name in ("exceptions", "__main__") or (
523+ if module_name in ("builtins", "exceptions", "__main__") or (
524 unqualified_breezy_errors and module_name == "breezy.errors"):
525 return class_name
526 return "%s.%s" % (module_name, class_name)
527@@ -521,8 +523,8 @@
528
529 def print_exception(exc_info, err_file):
530 exc_type, exc_object, exc_tb = exc_info
531- err_file.write("brz: ERROR: %s.%s: %s\n" % (
532- exc_type.__module__, exc_type.__name__, exc_object))
533+ err_file.write("brz: ERROR: %s: %s\n" % (
534+ _qualified_exception_name(exc_type), exc_object))
535 err_file.write('\n')
536 traceback.print_exception(exc_type, exc_object, exc_tb, file=err_file)
537
538@@ -539,7 +541,7 @@
539 """
540 err_file.write("brz: ERROR: %s\n" % (exc_info[1],))
541 if advice:
542- err_file.write("%s\n" % (advice,))
543+ err_file.write("%s\n" % advice)
544
545
546 def report_bug(exc_info, err_file):
547@@ -597,6 +599,10 @@
548
549 def emit(self, record):
550 try:
551+ if not isinstance(record.msg, text_type):
552+ msg = record.msg.decode("utf-8")
553+ if PY3:
554+ record.msg = msg
555 line = self.format(record)
556 if not isinstance(line, text_type):
557 line = line.decode("utf-8")
558@@ -606,8 +612,8 @@
559 # Try saving the details that would have been logged in some form
560 msg = args = "<Unformattable>"
561 try:
562- msg = repr(record.msg).encode("ascii")
563- args = repr(record.args).encode("ascii")
564+ msg = repr(record.msg).encode("ascii", "backslashescape")
565+ args = repr(record.args).encode("ascii", "backslashescape")
566 except Exception:
567 pass
568 # Using mutter() bypasses the logging module and writes directly

Subscribers

People subscribed via source and target branches