Skip to content

Commit 7ec78ab

Browse files
committed
Add handling for internal repr failures.
1 parent 65cc354 commit 7ec78ab

3 files changed

Lines changed: 95 additions & 9 deletions

File tree

src/hunter.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@
99
import re
1010
import sys
1111
import tokenize
12-
import types
1312

1413
from functools import partial
1514
from itertools import chain
1615

1716
from colorama import AnsiToWin32
17+
from colorama import Back
1818
from colorama import Fore
1919
from colorama import Style
2020
from fields import Fields
2121

22-
23-
2422
__version__ = "0.2.1"
2523
__all__ = 'Q', 'When', 'And', 'Or', 'CodePrinter', 'Debugger', 'VarsPrinter', 'trace', 'stop'
2624

@@ -36,8 +34,11 @@
3634
'exception': '',
3735
'detail': '',
3836
'vars': '',
37+
'vars-name': '',
3938
'call': '',
4039
'line': '',
40+
'internal-failure': '',
41+
'internal-detail': '',
4142
}
4243
EVENT_COLORS = {
4344
'reset': Style.RESET_ALL,
@@ -49,7 +50,10 @@
4950
'return': Style.BRIGHT + Fore.GREEN,
5051
'exception': Style.BRIGHT + Fore.RED,
5152
'detail': Style.NORMAL,
52-
'vars': Fore.MAGENTA,
53+
'vars': Style.RESET_ALL + Fore.MAGENTA,
54+
'vars-name': Style.BRIGHT,
55+
'internal-failure': Back.RED + Style.BRIGHT + Fore.RED,
56+
'internal-detail': Fore.WHITE,
5357
}
5458
CODE_COLORS = {
5559
'call': Fore.RESET + Style.BRIGHT,
@@ -439,6 +443,12 @@ def stream(self, value):
439443
self.event_colors = NO_COLORS
440444
self.code_colors = NO_COLORS
441445

446+
def _safe_repr(self, obj):
447+
try:
448+
return repr(obj)
449+
except Exception as exc:
450+
return "{internal-failure}!!! FAILED REPR: {internal-detail}{!r}".format(exc, **self.event_colors)
451+
442452

443453
class CodePrinter(Fields.stream.filename_alignment, ColorStreamAction):
444454
"""
@@ -480,11 +490,11 @@ def __call__(self, event, sep=os.path.sep, join=os.path.join):
480490
))
481491

482492
if event.kind in ('return', 'exception'):
483-
self.stream.write("{:>{align}} {continuation}{:9} {color}{} value: {detail}{!r}{reset}\n".format(
493+
self.stream.write("{:>{align}} {continuation}{:9} {color}{} value: {detail}{}{reset}\n".format(
484494
"",
485495
"...",
486496
event.kind,
487-
event.arg,
497+
self._safe_repr(event.arg),
488498
align=self.filename_alignment,
489499
color=self.event_colors[event.kind],
490500
**self.event_colors
@@ -536,7 +546,7 @@ def _safe_eval(self, code, event):
536546
try:
537547
return eval(code, event.globals if self.globals else {}, event.locals)
538548
except Exception as exc:
539-
return "{exception}FAILED EVAL: {}".format(exc, **self.event_colors)
549+
return "{internal-failure}FAILED EVAL: {internal-detail}{!r}".format(exc, **self.event_colors)
540550

541551
def __call__(self, event):
542552
"""
@@ -549,11 +559,11 @@ def __call__(self, event):
549559

550560
for code, symbols in self.names.items():
551561
if frame_symbols >= symbols:
552-
self.stream.write("{:>{align}} {vars}{:9} {reset}{} {vars}=> {reset}{!r}\n".format(
562+
self.stream.write("{:>{align}} {vars}{:9} {vars-name}{} {vars}=> {reset}{}{reset}\n".format(
553563
"",
554564
"vars" if first else "...",
555565
code,
556-
self._safe_eval(code, event),
566+
self._safe_repr(self._safe_eval(code, event)),
557567
align=self.filename_alignment,
558568
**self.event_colors
559569
))

tests/sample3.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class Bad(Exception):
2+
def __repr__(self):
3+
raise RuntimeError("I'm a bad class!")
4+
5+
6+
def a():
7+
x = Bad()
8+
return x
9+
10+
def b():
11+
x = Bad()
12+
raise x
13+
14+
a()
15+
try:
16+
b()
17+
except Exception as exc:
18+
print(exc)

tests/test_hunter.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,64 @@ def a():
184184
], fillvalue="MISSING"):
185185
assert fnmatchcase(line, expected), "%r didn't match %r" % (line, expected)
186186

187+
def test_tracing_printing_failures():
188+
lines = StringIO()
189+
with trace(CodePrinter(stream=lines),VarsPrinter("x", stream=lines)):
190+
class Bad(Exception):
191+
def __repr__(self):
192+
raise RuntimeError("I'm a bad class!")
193+
194+
195+
def a():
196+
x = Bad()
197+
return x
198+
199+
def b():
200+
x = Bad()
201+
raise x
202+
203+
a()
204+
try:
205+
b()
206+
except Exception as exc:
207+
pass
208+
print(lines.getvalue())
209+
for line, expected in izip_longest(lines.getvalue().splitlines(), [
210+
"""* src/hunter.py:* call def __enter__(self):""",
211+
"""* src/hunter.py:* line return self""",
212+
"""* src/hunter.py:* return return self""",
213+
"""* * ... return value: <hunter.Tracer *""",
214+
"""* tests/test_hunter.py:* call class Bad(Exception):""",
215+
"""* tests/test_hunter.py:* line class Bad(Exception):""",
216+
"""* tests/test_hunter.py:* line def __repr__(self):""",
217+
"""* tests/test_hunter.py:* return def __repr__(self):""",
218+
"""* * ... return value: *""",
219+
"""* tests/test_hunter.py:* call def a():""",
220+
"""* tests/test_hunter.py:* line x = Bad()""",
221+
"""* tests/test_hunter.py:* line return x""",
222+
"""* * vars x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
223+
"""* tests/test_hunter.py:* return return x""",
224+
"""* * ... return value: !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
225+
"""* * vars x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
226+
"""* tests/test_hunter.py:* call def b():""",
227+
"""* tests/test_hunter.py:* line x = Bad()""",
228+
"""* tests/test_hunter.py:* line raise x""",
229+
"""* * vars x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
230+
"""* tests/test_hunter.py:* exception raise x""",
231+
"""* * ... exception value: !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
232+
"""* * vars x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
233+
"""* tests/test_hunter.py:* return raise x""",
234+
"""* * ... return value: None""",
235+
"""* * vars x => !!! FAILED REPR: RuntimeError("I'm a bad class!",)""",
236+
"""* src/hunter.py:* call def __exit__(self, exc_type, exc_val, exc_tb):""",
237+
"""* src/hunter.py:* line self.stop()""",
238+
"""* src/hunter.py:* call def stop(self):""",
239+
"""* src/hunter.py:* line sys.settrace(self._previous_tracer)""",
240+
241+
], fillvalue="MISSING"):
242+
assert fnmatchcase(line, expected), "%r didn't match %r" % (line, expected)
243+
244+
187245

188246
def test_tracing_vars():
189247
lines = StringIO()

0 commit comments

Comments
 (0)