Skip to content

Commit 9b5248f

Browse files
committed
Show traceback of exception in output area
Also terminate the visualization on the first exception in user code.
1 parent ae67cfa commit 9b5248f

File tree

5 files changed

+54
-32
lines changed

5 files changed

+54
-32
lines changed

media/programflow-visualization/webview.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,7 @@ body {
188188
.return-value {
189189
color: blue;
190190
}
191+
192+
.traceback-text {
193+
color: red;
194+
}

pytrace-generator/main.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import re
1111
import socket
1212
import sys
13+
import traceback
1314
import types
1415

1516
def eprint(*args, **kwargs):
@@ -249,15 +250,19 @@ class TraceStep:
249250
stack: Stack
250251
heap: Heap
251252
stdout: str
253+
traceback_text: str
252254

253255
def format(self):
254-
return {
256+
step = {
255257
"line": self.line,
256258
"filePath": self.file_path,
257259
"stack": self.stack.format(),
258260
"heap": self.heap.format(),
259261
"stdout": self.stdout,
260262
}
263+
if self.traceback_text is not None:
264+
step["traceback"] = self.traceback_text
265+
return step
261266

262267

263268
def should_ignore(variable_name, value, script_path, ignore_list = []):
@@ -373,7 +378,8 @@ def trace_dispatch(self, frame, event, arg):
373378
self.import_following = import_regex.search(next_source_line) is not None
374379

375380
display_return = event == "return" and self.last_event != "exception" and len(self.stack.frames) > 1
376-
if event == "line" or display_return:
381+
display_exception = event == "exception" and self.last_event != "return"
382+
if event == "line" or display_return or display_exception:
377383
for variable_name in frame.f_locals:
378384
if should_ignore_on_stack(variable_name, frame.f_locals[variable_name], self.filename, self.stack_ignore):
379385
continue
@@ -385,7 +391,15 @@ def trace_dispatch(self, frame, event, arg):
385391
heap = generate_heap(frame, self.filename, self.stack_ignore)
386392
accumulated_stdout = self.accumulated_stdout + self.captured_stdout.getvalue()
387393

388-
step = TraceStep(line, filename, copy.deepcopy(self.stack), copy.deepcopy(heap), accumulated_stdout)
394+
traceback_text = None
395+
if event == "exception":
396+
exception_value = arg[1]
397+
traceback_text_tmp = io.StringIO()
398+
traceback.print_exception(exception_value, limit=0, file=traceback_text_tmp)
399+
traceback_text = traceback_text_tmp.getvalue()
400+
401+
402+
step = TraceStep(line, filename, copy.deepcopy(self.stack), copy.deepcopy(heap), accumulated_stdout, traceback_text)
389403

390404
is_annotation = next_source_line.startswith("@")
391405
should_display_step = not is_annotation
@@ -408,6 +422,10 @@ def trace_dispatch(self, frame, event, arg):
408422
self.shown_class_defs.append(filename, line)
409423
self.last_step_was_class = is_class_def
410424
self.prev_num_frames = num_frames
425+
426+
if event == "exception":
427+
# Terminate visualization after first exception in user code
428+
self.set_quit()
411429
if event == "call":
412430
self.stack.push_frame(frame)
413431
self.shown_class_defs.push_frame()

pytrace-generator/test/test-cases/divideByZero.py.json

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@
276276
"stdout": ""
277277
},
278278
{
279-
"line": 11,
279+
"line": 3,
280280
"filePath": "divideByZero.py",
281281
"stack": [
282282
{
@@ -288,45 +288,40 @@
288288
"name": "bar"
289289
}
290290
]
291-
}
292-
],
293-
"heap": {},
294-
"stdout": ""
295-
},
296-
{
297-
"line": 12,
298-
"filePath": "divideByZero.py",
299-
"stack": [
291+
},
300292
{
301-
"frameName": "<module>",
293+
"frameName": "bar",
302294
"locals": [
303295
{
304-
"type": "function",
305-
"value": "<function bar>",
306-
"name": "bar"
296+
"type": "int",
297+
"value": 2,
298+
"name": "i"
307299
}
308300
]
309-
}
310-
],
311-
"heap": {},
312-
"stdout": ""
313-
},
314-
{
315-
"line": 14,
316-
"filePath": "divideByZero.py",
317-
"stack": [
301+
},
318302
{
319-
"frameName": "<module>",
303+
"frameName": "bar",
320304
"locals": [
321305
{
322-
"type": "function",
323-
"value": "<function bar>",
324-
"name": "bar"
306+
"type": "int",
307+
"value": 1,
308+
"name": "i"
309+
}
310+
]
311+
},
312+
{
313+
"frameName": "bar",
314+
"locals": [
315+
{
316+
"type": "int",
317+
"value": 0,
318+
"name": "i"
325319
}
326320
]
327321
}
328322
],
329323
"heap": {},
330-
"stdout": ""
324+
"stdout": "",
325+
"traceback": "ZeroDivisionError: division by zero\n"
331326
}
332327
]

src/programflow-visualization/frontend/HTMLGenerator.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ export class HTMLGenerator {
4646
${keys.map((name, index) => this.objectItem(name, values[index])).join('')}
4747
<div>
4848
`;
49-
return [traceElement.line, frameItems, objectItems, traceElement.filePath, traceElement.stdout];
49+
let output = traceElement.stdout;
50+
if (traceElement.traceback !== undefined) {
51+
output += `<span class="traceback-text">${traceElement.traceback}</span>`;
52+
}
53+
return [traceElement.line, frameItems, objectItems, traceElement.filePath, output];
5054
}
5155

5256
private objectItem(name: string, value: HeapValue): string {

src/programflow-visualization/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type BackendTraceElem = {
2121
stack: Array<StackElem>;
2222
heap: Map<Address, HeapValue>;
2323
stdout: string;
24+
traceback: string | undefined;
2425
};
2526

2627
type Address = number;

0 commit comments

Comments
 (0)