Merge lp:~gary/launchpad/sqlprofiler into lp:launchpad
- sqlprofiler
- Merge into devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Gary Poster | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 13808 | ||||||||
Proposed branch: | lp:~gary/launchpad/sqlprofiler | ||||||||
Merge into: | lp:launchpad | ||||||||
Diff against target: |
432 lines (+151/-73) 4 files modified
lib/canonical/launchpad/webapp/adapter.py (+30/-9) lib/lp/services/profile/profile.py (+13/-4) lib/lp/services/stacktrace.py (+56/-42) lib/lp/services/tests/test_stacktrace.py (+52/-18) |
||||||||
To merge this branch: | bzr merge lp:~gary/launchpad/sqlprofiler | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+72765@code.launchpad.net |
Commit message
[bug=833972,834466] [r=benji][bug=833972] Fix ++profile++sqltrace for homepage, others
Description of the change
This branch fixes a problem observed in the QA for bug 832215: ++profile++sqltrace would cause Apache to fall over if used on some pages, such as the Launchpad home page. The problem was that strings need to be rendered immediately. This branch does that, as well as adding a quick hack to see a traceback if something like this happens again when trying to see a ++profile++ page.
lint is happy.
Tests are there for all of the core stacktrace changes. I did not add tests for the emergency hatch in ++profile++ traceback rendering, which I added for diagnosis of this problem and left in because it was convenient.
QA: go to https:/
Thank you
Gary Poster (gary) wrote : | # |
Change made, thanks.
comment: because non-iterating warnings are caught earlier in the pipeline of functions now, the rendering code should not actually encounter this.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/webapp/adapter.py' | |||
2 | --- lib/canonical/launchpad/webapp/adapter.py 2011-08-22 19:06:39 +0000 | |||
3 | +++ lib/canonical/launchpad/webapp/adapter.py 2011-08-26 13:16:48 +0000 | |||
4 | @@ -661,6 +661,15 @@ | |||
5 | 661 | threading.currentThread().lp_last_sql_statement = statement | 661 | threading.currentThread().lp_last_sql_statement = statement |
6 | 662 | request_starttime = getattr(_local, 'request_start_time', None) | 662 | request_starttime = getattr(_local, 'request_start_time', None) |
7 | 663 | if request_starttime is None: | 663 | if request_starttime is None: |
8 | 664 | if (sql_trace is not None or | ||
9 | 665 | self._debug_sql or | ||
10 | 666 | self._debug_sql_extra): | ||
11 | 667 | # Stash some information for logging at the end of the | ||
12 | 668 | # request. | ||
13 | 669 | connection._lp_statement_info = ( | ||
14 | 670 | time(), | ||
15 | 671 | 'SQL-%s' % connection._database.name, | ||
16 | 672 | statement_to_log) | ||
17 | 664 | return | 673 | return |
18 | 665 | action = get_request_timeline(get_current_browser_request()).start( | 674 | action = get_request_timeline(get_current_browser_request()).start( |
19 | 666 | 'SQL-%s' % connection._database.name, statement_to_log) | 675 | 'SQL-%s' % connection._database.name, statement_to_log) |
20 | @@ -673,15 +682,27 @@ | |||
21 | 673 | # action may be None if the tracer was installed after the | 682 | # action may be None if the tracer was installed after the |
22 | 674 | # statement was submitted. | 683 | # statement was submitted. |
23 | 675 | action.finish() | 684 | action.finish() |
33 | 676 | # Do data reporting for the [start|stop]_sql_traceback_logging | 685 | # Do data reporting for the [start|stop]_sql_traceback_logging |
34 | 677 | # feature, as exposed by ++profile++sqltrace, and/or for stderr, | 686 | # feature, as exposed by ++profile++sqltrace, and/or for stderr, |
35 | 678 | # as exposed by LP_DEBUG_SQL_EXTRA and LP_DEBUG_SQL. | 687 | # as exposed by LP_DEBUG_SQL_EXTRA and LP_DEBUG_SQL. |
36 | 679 | sql_trace = getattr(_local, 'sql_trace', None) | 688 | sql_trace = getattr(_local, 'sql_trace', None) |
37 | 680 | if sql_trace and sql_trace[-1]['sql'] is None: | 689 | if sql_trace is not None or self._debug_sql or self._debug_sql_extra: |
38 | 681 | sql_trace[-1]['sql'] = action.logTuple() | 690 | data = None |
39 | 682 | if self._debug_sql or self._debug_sql_extra: | 691 | if action is not None: |
40 | 683 | sys.stderr.write('%d-%d@%s %s\n' % action.logTuple()) | 692 | data = action.logTuple() |
41 | 684 | sys.stderr.write("-" * 70 + "\n") | 693 | else: |
42 | 694 | info = getattr(connection, '_lp_statement_info', None) | ||
43 | 695 | if info is not None: | ||
44 | 696 | start, dbname, statement = info | ||
45 | 697 | # Times are in milliseconds, to mirror actions. | ||
46 | 698 | duration = int((time() - start) * 1000) | ||
47 | 699 | data = (0, duration, dbname, statement) | ||
48 | 700 | if data is not None: | ||
49 | 701 | if sql_trace and sql_trace[-1]['sql'] is None: | ||
50 | 702 | sql_trace[-1]['sql'] = data | ||
51 | 703 | if self._debug_sql or self._debug_sql_extra: | ||
52 | 704 | sys.stderr.write('%d-%d@%s %s\n' % data) | ||
53 | 705 | sys.stderr.write("-" * 70 + "\n") | ||
54 | 685 | 706 | ||
55 | 686 | def connection_raw_execute_error(self, connection, raw_cursor, | 707 | def connection_raw_execute_error(self, connection, raw_cursor, |
56 | 687 | statement, params, error): | 708 | statement, params, error): |
57 | 688 | 709 | ||
58 | === modified file 'lib/lp/services/profile/profile.py' | |||
59 | --- lib/lp/services/profile/profile.py 2011-08-22 19:06:39 +0000 | |||
60 | +++ lib/lp/services/profile/profile.py 2011-08-26 13:16:48 +0000 | |||
61 | @@ -16,12 +16,13 @@ | |||
62 | 16 | import heapq | 16 | import heapq |
63 | 17 | import os | 17 | import os |
64 | 18 | import pstats | 18 | import pstats |
65 | 19 | import StringIO | ||
66 | 20 | import sys | ||
67 | 19 | import threading | 21 | import threading |
68 | 20 | import StringIO | ||
69 | 21 | 22 | ||
70 | 22 | from bzrlib import lsprof | 23 | from bzrlib import lsprof |
71 | 23 | import oops_datedir_repo.serializer_rfc822 | 24 | import oops_datedir_repo.serializer_rfc822 |
73 | 24 | from zope.pagetemplate.pagetemplatefile import PageTemplateFile | 25 | from z3c.pt.pagetemplate import PageTemplateFile |
74 | 25 | from zope.app.publication.interfaces import IEndRequestEvent | 26 | from zope.app.publication.interfaces import IEndRequestEvent |
75 | 26 | from zope.component import ( | 27 | from zope.component import ( |
76 | 27 | adapter, | 28 | adapter, |
77 | @@ -29,6 +30,7 @@ | |||
78 | 29 | ) | 30 | ) |
79 | 30 | from zope.contenttype.parse import parse | 31 | from zope.contenttype.parse import parse |
80 | 31 | from zope.error.interfaces import IErrorReportingUtility | 32 | from zope.error.interfaces import IErrorReportingUtility |
81 | 33 | from zope.exceptions.exceptionformatter import format_exception | ||
82 | 32 | from zope.traversing.namespace import view | 34 | from zope.traversing.namespace import view |
83 | 33 | 35 | ||
84 | 34 | from canonical.config import config | 36 | from canonical.config import config |
85 | @@ -344,7 +346,7 @@ | |||
86 | 344 | del prof_stats | 346 | del prof_stats |
87 | 345 | trace = None | 347 | trace = None |
88 | 346 | if 'sqltrace' in actions: | 348 | if 'sqltrace' in actions: |
90 | 347 | trace = da.stop_sql_traceback_logging() | 349 | trace = da.stop_sql_traceback_logging() or () |
91 | 348 | # The trace is a list of dicts, each with the keys "sql" and | 350 | # The trace is a list of dicts, each with the keys "sql" and |
92 | 349 | # "stack". "sql" is a tuple of start time, stop time, database | 351 | # "stack". "sql" is a tuple of start time, stop time, database |
93 | 350 | # name (with a "SQL-" prefix), and sql statement. "stack" is a | 352 | # name (with a "SQL-" prefix), and sql statement. "stack" is a |
94 | @@ -451,7 +453,14 @@ | |||
95 | 451 | if actions and is_html: | 453 | if actions and is_html: |
96 | 452 | # Hack the new HTML in at the end of the page. | 454 | # Hack the new HTML in at the end of the page. |
97 | 453 | encoding = content_type_params.get('charset', 'utf-8') | 455 | encoding = content_type_params.get('charset', 'utf-8') |
99 | 454 | added_html = template(**template_context).encode(encoding) | 456 | try: |
100 | 457 | added_html = template(**template_context).encode(encoding) | ||
101 | 458 | except (SystemExit, KeyboardInterrupt): | ||
102 | 459 | raise | ||
103 | 460 | except: | ||
104 | 461 | error = ''.join(format_exception(*sys.exc_info(), as_html=True)) | ||
105 | 462 | added_html = ( | ||
106 | 463 | '<div class="profiling_info">' + error + '</div>') | ||
107 | 455 | existing_html = request.response.consumeBody() | 464 | existing_html = request.response.consumeBody() |
108 | 456 | e_start, e_close_body, e_end = existing_html.rpartition( | 465 | e_start, e_close_body, e_end = existing_html.rpartition( |
109 | 457 | '</body>') | 466 | '</body>') |
110 | 458 | 467 | ||
111 | === modified file 'lib/lp/services/stacktrace.py' | |||
112 | --- lib/lp/services/stacktrace.py 2011-08-22 19:06:39 +0000 | |||
113 | +++ lib/lp/services/stacktrace.py 2011-08-26 13:16:48 +0000 | |||
114 | @@ -20,6 +20,18 @@ | |||
115 | 20 | import traceback | 20 | import traceback |
116 | 21 | 21 | ||
117 | 22 | DEBUG_EXCEPTION_FORMATTER = False | 22 | DEBUG_EXCEPTION_FORMATTER = False |
118 | 23 | EXPLOSIVE_ERRORS = (SystemExit, MemoryError, KeyboardInterrupt) | ||
119 | 24 | |||
120 | 25 | |||
121 | 26 | def _try_except(callable, *args, **kwargs): | ||
122 | 27 | try: | ||
123 | 28 | return callable(*args, **kwargs) | ||
124 | 29 | except EXPLOSIVE_ERRORS: | ||
125 | 30 | raise | ||
126 | 31 | except: | ||
127 | 32 | if DEBUG_EXCEPTION_FORMATTER: | ||
128 | 33 | traceback.print_exc() | ||
129 | 34 | # return None | ||
130 | 23 | 35 | ||
131 | 24 | 36 | ||
132 | 25 | def _get_frame(f): | 37 | def _get_frame(f): |
133 | @@ -85,7 +97,7 @@ | |||
134 | 85 | item.append(supp['extra']) # We do not include a prefix. | 97 | item.append(supp['extra']) # We do not include a prefix. |
135 | 86 | if info: | 98 | if info: |
136 | 87 | item.append(_fmt(info)) | 99 | item.append(_fmt(info)) |
138 | 88 | except (SystemExit, KeyboardInterrupt): | 100 | except EXPLOSIVE_ERRORS: |
139 | 89 | raise | 101 | raise |
140 | 90 | except: | 102 | except: |
141 | 91 | # The values above may not stringify properly, or who knows what | 103 | # The values above may not stringify properly, or who knows what |
142 | @@ -119,19 +131,11 @@ | |||
143 | 119 | 131 | ||
144 | 120 | def _get_frame_data(f, lineno): | 132 | def _get_frame_data(f, lineno): |
145 | 121 | "Given a frame and a lineno, return data for each item of extract_*." | 133 | "Given a frame and a lineno, return data for each item of extract_*." |
146 | 122 | co = f.f_code | ||
147 | 123 | filename = co.co_filename | ||
148 | 124 | name = co.co_name | ||
149 | 125 | linecache.checkcache(filename) | ||
150 | 126 | line = linecache.getline(filename, lineno, f.f_globals) | ||
151 | 127 | if line: | ||
152 | 128 | line = line.strip() | ||
153 | 129 | else: | ||
154 | 130 | line = None | ||
155 | 131 | # Adapted from zope.exceptions. | 134 | # Adapted from zope.exceptions. |
156 | 135 | try_except = _try_except # This is a micro-optimization. | ||
157 | 132 | modname = f.f_globals.get('__name__') | 136 | modname = f.f_globals.get('__name__') |
158 | 133 | # Output a traceback supplement, if any. | 137 | # Output a traceback supplement, if any. |
160 | 134 | supplement = info = None | 138 | supplement_dict = info = None |
161 | 135 | if '__traceback_supplement__' in f.f_locals: | 139 | if '__traceback_supplement__' in f.f_locals: |
162 | 136 | # Use the supplement defined in the function. | 140 | # Use the supplement defined in the function. |
163 | 137 | tbs = f.f_locals['__traceback_supplement__'] | 141 | tbs = f.f_locals['__traceback_supplement__'] |
164 | @@ -143,42 +147,50 @@ | |||
165 | 143 | if tbs is not None: | 147 | if tbs is not None: |
166 | 144 | factory = tbs[0] | 148 | factory = tbs[0] |
167 | 145 | args = tbs[1:] | 149 | args = tbs[1:] |
177 | 146 | try: | 150 | supplement = try_except(factory, *args) |
178 | 147 | supplement = factory(*args) | 151 | if supplement is not None: |
170 | 148 | except (SystemExit, KeyboardInterrupt): | ||
171 | 149 | raise | ||
172 | 150 | except: | ||
173 | 151 | if DEBUG_EXCEPTION_FORMATTER: | ||
174 | 152 | traceback.print_exc() | ||
175 | 153 | # else just swallow the exception. | ||
176 | 154 | else: | ||
179 | 155 | # It might be nice if supplements could be dicts, for simplicity. | 152 | # It might be nice if supplements could be dicts, for simplicity. |
180 | 156 | # Historically, though, they are objects. | 153 | # Historically, though, they are objects. |
184 | 157 | # We will turn the supplement into a dict, so that we have | 154 | # We will turn the supplement into a dict of strings, so that we |
185 | 158 | # "getInfo" pre-processed and so that we are not holding on to | 155 | # have "getInfo" pre-processed and so that we are not holding on |
186 | 159 | # anything from the frame. | 156 | # to anything from the frame. |
187 | 160 | extra = None | 157 | extra = None |
188 | 161 | getInfo = getattr(supplement, 'getInfo', None) | 158 | getInfo = getattr(supplement, 'getInfo', None) |
189 | 162 | if getInfo is not None: | 159 | if getInfo is not None: |
206 | 163 | try: | 160 | extra = try_except(getInfo) |
207 | 164 | extra = getInfo() | 161 | extra = try_except(str, extra) if extra is not None else None |
208 | 165 | except (SystemExit, KeyboardInterrupt): | 162 | warnings = [] |
209 | 166 | raise | 163 | # The outer try-except is for the iteration. |
210 | 167 | except: | 164 | try: |
211 | 168 | if DEBUG_EXCEPTION_FORMATTER: | 165 | for warning in getattr(supplement, 'warnings', ()): |
212 | 169 | traceback.print_exc() | 166 | if warning is not None: |
213 | 170 | # else just swallow the exception. | 167 | warning = try_except(str, warning) |
214 | 171 | supplement = dict( | 168 | if warning is not None: |
215 | 172 | # The "_url" suffix is historical. | 169 | warnings.append(warning) |
216 | 173 | source_url=getattr(supplement, 'source_url', None), | 170 | except EXPLOSIVE_ERRORS: |
217 | 174 | line=getattr(supplement, 'line', None), | 171 | raise |
218 | 175 | column=getattr(supplement, 'column', None), | 172 | except: |
219 | 176 | expression=getattr(supplement, 'expression', None), | 173 | if DEBUG_EXCEPTION_FORMATTER: |
220 | 177 | warnings=getattr(supplement, 'warnings', ()), | 174 | traceback.print_exc() |
221 | 178 | extra=extra) | 175 | supplement_dict = dict(warnings=warnings, extra=extra) |
222 | 176 | for key in ('source_url', 'line', 'column', 'expression'): | ||
223 | 177 | value = getattr(supplement, key, None) | ||
224 | 178 | if value is not None: | ||
225 | 179 | value = try_except(str, value) | ||
226 | 180 | supplement_dict[key] = value | ||
227 | 179 | info = f.f_locals.get('__traceback_info__', None) | 181 | info = f.f_locals.get('__traceback_info__', None) |
228 | 182 | info = try_except(str, info) if info is not None else None | ||
229 | 180 | # End part adapted from zope.exceptions. | 183 | # End part adapted from zope.exceptions. |
231 | 181 | return (filename, lineno, name, line, modname, supplement, info) | 184 | co = f.f_code |
232 | 185 | filename = co.co_filename | ||
233 | 186 | name = co.co_name | ||
234 | 187 | linecache.checkcache(filename) | ||
235 | 188 | line = linecache.getline(filename, lineno, f.f_globals) | ||
236 | 189 | if line: | ||
237 | 190 | line = line.strip() | ||
238 | 191 | else: | ||
239 | 192 | line = None | ||
240 | 193 | return (filename, lineno, name, line, modname, supplement_dict, info) | ||
241 | 182 | 194 | ||
242 | 183 | 195 | ||
243 | 184 | def extract_stack(f=None, limit=None): | 196 | def extract_stack(f=None, limit=None): |
244 | @@ -194,8 +206,9 @@ | |||
245 | 194 | limit = _get_limit(limit) | 206 | limit = _get_limit(limit) |
246 | 195 | list = [] | 207 | list = [] |
247 | 196 | n = 0 | 208 | n = 0 |
248 | 209 | get_frame_data = _get_frame_data # This is a micro-optimization. | ||
249 | 197 | while f is not None and (limit is None or n < limit): | 210 | while f is not None and (limit is None or n < limit): |
251 | 198 | list.append(_get_frame_data(f, f.f_lineno)) | 211 | list.append(get_frame_data(f, f.f_lineno)) |
252 | 199 | f = f.f_back | 212 | f = f.f_back |
253 | 200 | n = n + 1 | 213 | n = n + 1 |
254 | 201 | list.reverse() | 214 | list.reverse() |
255 | @@ -220,8 +233,9 @@ | |||
256 | 220 | limit = _get_limit(limit) | 233 | limit = _get_limit(limit) |
257 | 221 | list = [] | 234 | list = [] |
258 | 222 | n = 0 | 235 | n = 0 |
259 | 236 | get_frame_data = _get_frame_data # This is a micro-optimization. | ||
260 | 223 | while tb is not None and (limit is None or n < limit): | 237 | while tb is not None and (limit is None or n < limit): |
262 | 224 | list.append(_get_frame_data(tb.tb_frame, tb.tb_lineno)) | 238 | list.append(get_frame_data(tb.tb_frame, tb.tb_lineno)) |
263 | 225 | tb = tb.tb_next | 239 | tb = tb.tb_next |
264 | 226 | n = n + 1 | 240 | n = n + 1 |
265 | 227 | return list | 241 | return list |
266 | 228 | 242 | ||
267 | === modified file 'lib/lp/services/tests/test_stacktrace.py' | |||
268 | --- lib/lp/services/tests/test_stacktrace.py 2011-08-22 19:06:39 +0000 | |||
269 | +++ lib/lp/services/tests/test_stacktrace.py 2011-08-26 13:16:48 +0000 | |||
270 | @@ -16,6 +16,8 @@ | |||
271 | 16 | # the tests to pass. | 16 | # the tests to pass. |
272 | 17 | MY_LINE_NUMBER = 17 | 17 | MY_LINE_NUMBER = 17 |
273 | 18 | 18 | ||
274 | 19 | MY_FILE_NAME = __file__[:__file__.rindex('.py')] + '.py' | ||
275 | 20 | |||
276 | 19 | 21 | ||
277 | 20 | class Supplement: | 22 | class Supplement: |
278 | 21 | def __init__(self, kwargs): | 23 | def __init__(self, kwargs): |
279 | @@ -35,6 +37,12 @@ | |||
280 | 35 | return sys._getframe() | 37 | return sys._getframe() |
281 | 36 | 38 | ||
282 | 37 | 39 | ||
283 | 40 | class BadString: | ||
284 | 41 | |||
285 | 42 | def __str__(self): | ||
286 | 43 | raise ValueError() | ||
287 | 44 | |||
288 | 45 | |||
289 | 38 | class TestStacktrace(TestCase): | 46 | class TestStacktrace(TestCase): |
290 | 39 | 47 | ||
291 | 40 | layer = BaseLayer | 48 | layer = BaseLayer |
292 | @@ -71,7 +79,7 @@ | |||
293 | 71 | def test_get_frame_data_standard(self): | 79 | def test_get_frame_data_standard(self): |
294 | 72 | filename, lineno, name, line, modname, supplement, info = ( | 80 | filename, lineno, name, line, modname, supplement, info = ( |
295 | 73 | stacktrace._get_frame_data(get_frame(), MY_LINE_NUMBER)) | 81 | stacktrace._get_frame_data(get_frame(), MY_LINE_NUMBER)) |
297 | 74 | self.assertEqual(__file__, filename) | 82 | self.assertEqual(MY_FILE_NAME, filename) |
298 | 75 | self.assertEqual(MY_LINE_NUMBER, lineno) | 83 | self.assertEqual(MY_LINE_NUMBER, lineno) |
299 | 76 | self.assertEqual('get_frame', name) | 84 | self.assertEqual('get_frame', name) |
300 | 77 | self.assertStartsWith(line, 'MY_LINE_NUMBER = ') | 85 | self.assertStartsWith(line, 'MY_LINE_NUMBER = ') |
301 | @@ -91,7 +99,7 @@ | |||
302 | 91 | get_frame(supplement={}), MY_LINE_NUMBER)) | 99 | get_frame(supplement={}), MY_LINE_NUMBER)) |
303 | 92 | self.assertEqual( | 100 | self.assertEqual( |
304 | 93 | dict(source_url=None, line=None, column=None, expression=None, | 101 | dict(source_url=None, line=None, column=None, expression=None, |
306 | 94 | warnings=(), extra=None), | 102 | warnings=[], extra=None), |
307 | 95 | supplement) | 103 | supplement) |
308 | 96 | 104 | ||
309 | 97 | def test_get_frame_data_supplement_and_info(self): | 105 | def test_get_frame_data_supplement_and_info(self): |
310 | @@ -101,7 +109,7 @@ | |||
311 | 101 | MY_LINE_NUMBER)) | 109 | MY_LINE_NUMBER)) |
312 | 102 | self.assertEqual( | 110 | self.assertEqual( |
313 | 103 | dict(source_url=None, line=None, column=None, expression=None, | 111 | dict(source_url=None, line=None, column=None, expression=None, |
315 | 104 | warnings=(), extra=None), | 112 | warnings=[], extra=None), |
316 | 105 | supplement) | 113 | supplement) |
317 | 106 | self.assertEqual('foo bar', info) | 114 | self.assertEqual('foo bar', info) |
318 | 107 | 115 | ||
319 | @@ -119,9 +127,9 @@ | |||
320 | 119 | )), | 127 | )), |
321 | 120 | MY_LINE_NUMBER)) | 128 | MY_LINE_NUMBER)) |
322 | 121 | self.assertEqual( | 129 | self.assertEqual( |
324 | 122 | dict(source_url='/foo/bar.pt', line=42, column=84, | 130 | dict(source_url='/foo/bar.pt', line='42', column='84', |
325 | 123 | expression='tal:define="foo view/foo"', | 131 | expression='tal:define="foo view/foo"', |
327 | 124 | warnings=('watch out', 'pass auf'), | 132 | warnings=['watch out', 'pass auf'], |
328 | 125 | extra='read all about it'), | 133 | extra='read all about it'), |
329 | 126 | supplement) | 134 | supplement) |
330 | 127 | 135 | ||
331 | @@ -134,7 +142,7 @@ | |||
332 | 134 | MY_LINE_NUMBER)) | 142 | MY_LINE_NUMBER)) |
333 | 135 | self.assertEqual( | 143 | self.assertEqual( |
334 | 136 | dict(source_url=None, line=None, column=None, expression=None, | 144 | dict(source_url=None, line=None, column=None, expression=None, |
336 | 137 | warnings=(), extra=None), | 145 | warnings=[], extra=None), |
337 | 138 | supplement) | 146 | supplement) |
338 | 139 | 147 | ||
339 | 140 | def test_get_frame_data_supplement_bad_getInfo_with_traceback(self): | 148 | def test_get_frame_data_supplement_bad_getInfo_with_traceback(self): |
340 | @@ -153,16 +161,47 @@ | |||
341 | 153 | stacktrace.DEBUG_EXCEPTION_FORMATTER = False | 161 | stacktrace.DEBUG_EXCEPTION_FORMATTER = False |
342 | 154 | self.assertEqual( | 162 | self.assertEqual( |
343 | 155 | dict(source_url=None, line=None, column=None, expression=None, | 163 | dict(source_url=None, line=None, column=None, expression=None, |
345 | 156 | warnings=(), extra=None), | 164 | warnings=[], extra=None), |
346 | 157 | supplement) | 165 | supplement) |
347 | 158 | self.assertIn('boo_hiss', stderr.getvalue()) | 166 | self.assertIn('boo_hiss', stderr.getvalue()) |
348 | 159 | 167 | ||
349 | 168 | def test_get_frame_data_broken_str(self): | ||
350 | 169 | bad = BadString() | ||
351 | 170 | filename, lineno, name, line, modname, supplement, info = ( | ||
352 | 171 | stacktrace._get_frame_data( | ||
353 | 172 | get_frame( | ||
354 | 173 | supplement=dict( | ||
355 | 174 | source_url=bad, | ||
356 | 175 | line=bad, | ||
357 | 176 | column=bad, | ||
358 | 177 | expression=bad, | ||
359 | 178 | warnings=('watch out', bad), | ||
360 | 179 | getInfo=lambda: bad | ||
361 | 180 | ), | ||
362 | 181 | info=bad), | ||
363 | 182 | MY_LINE_NUMBER)) | ||
364 | 183 | self.assertEqual( | ||
365 | 184 | dict(source_url=None, line=None, column=None, | ||
366 | 185 | expression=None, warnings=['watch out'], extra=None), | ||
367 | 186 | supplement) | ||
368 | 187 | self.assertIs(None, info) | ||
369 | 188 | |||
370 | 189 | def test_get_frame_data_broken_warnings(self): | ||
371 | 190 | filename, lineno, name, line, modname, supplement, info = ( | ||
372 | 191 | stacktrace._get_frame_data( | ||
373 | 192 | get_frame( | ||
374 | 193 | supplement=dict( | ||
375 | 194 | warnings=object() | ||
376 | 195 | )), | ||
377 | 196 | MY_LINE_NUMBER)) | ||
378 | 197 | self.assertEqual([], supplement['warnings']) | ||
379 | 198 | |||
380 | 160 | def test_extract_stack(self): | 199 | def test_extract_stack(self): |
381 | 161 | extracted = stacktrace.extract_stack(get_frame()) | 200 | extracted = stacktrace.extract_stack(get_frame()) |
382 | 162 | self.assertTrue(len(extracted) > 1) | 201 | self.assertTrue(len(extracted) > 1) |
383 | 163 | filename, lineno, name, line, modname, supplement, info = ( | 202 | filename, lineno, name, line, modname, supplement, info = ( |
384 | 164 | extracted[-1]) | 203 | extracted[-1]) |
386 | 165 | self.assertEqual(__file__, filename) | 204 | self.assertEqual(MY_FILE_NAME, filename) |
387 | 166 | self.assertIsInstance(lineno, int) | 205 | self.assertIsInstance(lineno, int) |
388 | 167 | self.assertEqual('get_frame', name) | 206 | self.assertEqual('get_frame', name) |
389 | 168 | self.assertEqual('return sys._getframe()', line) | 207 | self.assertEqual('return sys._getframe()', line) |
390 | @@ -179,7 +218,7 @@ | |||
391 | 179 | self.assertEqual(1, len(extracted)) | 218 | self.assertEqual(1, len(extracted)) |
392 | 180 | filename, lineno, name, line, modname, supplement, info = ( | 219 | filename, lineno, name, line, modname, supplement, info = ( |
393 | 181 | extracted[0]) | 220 | extracted[0]) |
395 | 182 | self.assertEqual(__file__, filename) | 221 | self.assertEqual(MY_FILE_NAME, filename) |
396 | 183 | self.assertIsInstance(lineno, int) | 222 | self.assertIsInstance(lineno, int) |
397 | 184 | self.assertEqual('test_extract_tb', name) | 223 | self.assertEqual('test_extract_tb', name) |
398 | 185 | self.assertEqual('raise ValueError()', line) | 224 | self.assertEqual('raise ValueError()', line) |
399 | @@ -195,7 +234,7 @@ | |||
400 | 195 | self.assertEndsWith(line, '\n') | 234 | self.assertEndsWith(line, '\n') |
401 | 196 | line = formatted[-1].split('\n') | 235 | line = formatted[-1].split('\n') |
402 | 197 | self.assertStartsWith( | 236 | self.assertStartsWith( |
404 | 198 | line[0], ' File "' + __file__ + '", line ') | 237 | line[0], ' File "' + MY_FILE_NAME + '", line ') |
405 | 199 | self.assertEndsWith(line[0], ', in get_frame') | 238 | self.assertEndsWith(line[0], ', in get_frame') |
406 | 200 | self.assertEqual(' return sys._getframe()', line[1]) | 239 | self.assertEqual(' return sys._getframe()', line[1]) |
407 | 201 | 240 | ||
408 | @@ -218,7 +257,7 @@ | |||
409 | 218 | self.assertEndsWith(line, '\n') | 257 | self.assertEndsWith(line, '\n') |
410 | 219 | line = formatted[-1].split('\n') | 258 | line = formatted[-1].split('\n') |
411 | 220 | self.assertStartsWith( | 259 | self.assertStartsWith( |
413 | 221 | line[0], ' File "' + __file__ + '", line ') | 260 | line[0], ' File "' + MY_FILE_NAME + '", line ') |
414 | 222 | self.assertEndsWith(line[0], ', in get_frame') | 261 | self.assertEndsWith(line[0], ', in get_frame') |
415 | 223 | self.assertEqual(' return sys._getframe()', line[1]) | 262 | self.assertEqual(' return sys._getframe()', line[1]) |
416 | 224 | self.assertEqual(' - /foo/bar.pt', line[2]) | 263 | self.assertEqual(' - /foo/bar.pt', line[2]) |
417 | @@ -234,13 +273,8 @@ | |||
418 | 234 | self.assertEqual(' - I am the Walrus', line[8]) | 273 | self.assertEqual(' - I am the Walrus', line[8]) |
419 | 235 | 274 | ||
420 | 236 | def test_format_list_extra_errors(self): | 275 | def test_format_list_extra_errors(self): |
428 | 237 | extracted = stacktrace.extract_stack( | 276 | extracted = stacktrace.extract_stack(get_frame(supplement=dict())) |
429 | 238 | get_frame( | 277 | extracted[-1][-2]['warnings'] = object() # This should never happen. |
423 | 239 | supplement=dict( | ||
424 | 240 | # This will cause an error because it is not iterable. | ||
425 | 241 | warnings=object() | ||
426 | 242 | )) | ||
427 | 243 | ) | ||
430 | 244 | stderr = sys.stderr = StringIO.StringIO() | 278 | stderr = sys.stderr = StringIO.StringIO() |
431 | 245 | self.assertFalse(stacktrace.DEBUG_EXCEPTION_FORMATTER) | 279 | self.assertFalse(stacktrace.DEBUG_EXCEPTION_FORMATTER) |
432 | 246 | stacktrace.DEBUG_EXCEPTION_FORMATTER = True | 280 | stacktrace.DEBUG_EXCEPTION_FORMATTER = True |
This branch looks good. Here are a couple of little things:
I would add MemoryError to the (SystemExit, KeyboardInterrupt)
exceptions. Since that tuple is repeated several times you might want
to define a "constant" for it as well.
I don't understand the comment near the end of the diff: "This should
never happen."