diff --git a/media/programflow-visualization/webview.css b/media/programflow-visualization/webview.css index fa729591..500af36d 100644 --- a/media/programflow-visualization/webview.css +++ b/media/programflow-visualization/webview.css @@ -188,3 +188,7 @@ body { .return-value { color: blue; } + +.traceback-text { + color: red; +} diff --git a/pytrace-generator/main.py b/pytrace-generator/main.py index 96237237..3bff2807 100644 --- a/pytrace-generator/main.py +++ b/pytrace-generator/main.py @@ -10,6 +10,7 @@ import re import socket import sys +import traceback import types import typing @@ -253,15 +254,19 @@ class TraceStep: stack: Stack heap: Heap stdout: str + traceback_text: str def format(self): - return { + step = { "line": self.line, "filePath": self.file_path, "stack": self.stack.format(), "heap": self.heap.format(), "stdout": self.stdout, } + if self.traceback_text is not None: + step["traceback"] = self.traceback_text + return step def should_ignore(variable_name, value, script_path, ignore_list = []): @@ -377,7 +382,8 @@ def trace_dispatch(self, frame, event, arg): self.import_following = import_regex.search(next_source_line) is not None display_return = event == "return" and self.last_event != "exception" and len(self.stack.frames) > 1 - if event == "line" or display_return: + display_exception = event == "exception" and self.last_event != "return" + if event == "line" or display_return or display_exception: for variable_name in frame.f_locals: if should_ignore_on_stack(variable_name, frame.f_locals[variable_name], self.filename, self.stack_ignore): continue @@ -389,7 +395,15 @@ def trace_dispatch(self, frame, event, arg): heap = generate_heap(frame, self.filename, self.stack_ignore) accumulated_stdout = self.accumulated_stdout + self.captured_stdout.getvalue() - step = TraceStep(line, filename, copy.deepcopy(self.stack), copy.deepcopy(heap), accumulated_stdout) + traceback_text = None + if event == "exception": + exception_value = arg[1] + traceback_text_tmp = io.StringIO() + traceback.print_exception(exception_value, limit=0, file=traceback_text_tmp) + traceback_text = traceback_text_tmp.getvalue() + + + step = TraceStep(line, filename, copy.deepcopy(self.stack), copy.deepcopy(heap), accumulated_stdout, traceback_text) is_annotation = next_source_line.startswith("@") should_display_step = not is_annotation @@ -412,6 +426,10 @@ def trace_dispatch(self, frame, event, arg): self.shown_class_defs.append(filename, line) self.last_step_was_class = is_class_def self.prev_num_frames = num_frames + + if event == "exception": + # Terminate visualization after first exception in user code + self.set_quit() if event == "call": self.stack.push_frame(frame) self.shown_class_defs.push_frame() diff --git a/pytrace-generator/test/test-cases/divideByZero.py.json b/pytrace-generator/test/test-cases/divideByZero.py.json index 28eff15f..45aae18f 100644 --- a/pytrace-generator/test/test-cases/divideByZero.py.json +++ b/pytrace-generator/test/test-cases/divideByZero.py.json @@ -276,7 +276,7 @@ "stdout": "" }, { - "line": 11, + "line": 3, "filePath": "divideByZero.py", "stack": [ { @@ -288,45 +288,40 @@ "name": "bar" } ] - } - ], - "heap": {}, - "stdout": "" - }, - { - "line": 12, - "filePath": "divideByZero.py", - "stack": [ + }, { - "frameName": "", + "frameName": "bar", "locals": [ { - "type": "function", - "value": "", - "name": "bar" + "type": "int", + "value": 2, + "name": "i" } ] - } - ], - "heap": {}, - "stdout": "" - }, - { - "line": 14, - "filePath": "divideByZero.py", - "stack": [ + }, { - "frameName": "", + "frameName": "bar", "locals": [ { - "type": "function", - "value": "", - "name": "bar" + "type": "int", + "value": 1, + "name": "i" + } + ] + }, + { + "frameName": "bar", + "locals": [ + { + "type": "int", + "value": 0, + "name": "i" } ] } ], "heap": {}, - "stdout": "" + "stdout": "", + "traceback": "ZeroDivisionError: division by zero\n" } ] \ No newline at end of file diff --git a/src/programflow-visualization/frontend/HTMLGenerator.ts b/src/programflow-visualization/frontend/HTMLGenerator.ts index aa6cfc1b..1da5e8f4 100644 --- a/src/programflow-visualization/frontend/HTMLGenerator.ts +++ b/src/programflow-visualization/frontend/HTMLGenerator.ts @@ -46,7 +46,11 @@ export class HTMLGenerator { ${keys.map((name, index) => this.objectItem(name, values[index])).join('')}
`; - return [traceElement.line, frameItems, objectItems, traceElement.filePath, traceElement.stdout]; + let output = traceElement.stdout; + if (traceElement.traceback !== undefined) { + output += `${traceElement.traceback}`; + } + return [traceElement.line, frameItems, objectItems, traceElement.filePath, output]; } private objectItem(name: string, value: HeapValue): string { diff --git a/src/programflow-visualization/types.ts b/src/programflow-visualization/types.ts index 128b0bf3..7e4c4dc9 100644 --- a/src/programflow-visualization/types.ts +++ b/src/programflow-visualization/types.ts @@ -21,6 +21,7 @@ type BackendTraceElem = { stack: Array; heap: Map; stdout: string; + traceback: string | undefined; }; type Address = number;