8000 Fix traceback issue (#265) · SerAcero/python-tutorial@7bce206 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7bce206

Browse files
authored
Fix traceback issue (empa-scientific-it#265)
Also: * Remove ANSI escape sequences from tests output * UI fix: hide scrollbar from test results title
1 parent 6d6452e commit 7bce206

File tree

2 files changed

+65
-50
lines changed

2 files changed

+65
-50
lines changed

tutorial/tests/testsuite/helpers.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import html
2+
import re
23
from dataclasses import dataclass
34
from enum import Enum
45
from pathlib import Path
@@ -14,6 +15,12 @@
1415
from .ai_helpers import AIExplanation, OpenAIWrapper
1516

1617

18+
def strip_ansi_codes(text: str) -> str:
19+
"""Remove ANSI escape sequences from text"""
20+
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
21+
return ansi_escape.sub("", text)
22+
23+
1724
class TestOutcome(Enum):
1825
PASS = 1
1926
FAIL = 2
@@ -293,7 +300,7 @@ def to_html(self) -> str:
293300
# Exception information if test failed
294301
if self.exception is not None:
295302
exception_type = type(self.exception).__name__
296-
exception_message = str(self.exception)
303+
exception_message = strip_ansi_codes(str(self.exception))
297304

298305
html_parts.append(
299306
f"""
@@ -329,10 +336,10 @@ def to_html(self) -> str:
329336
</div>
330337
<div class="output-content">
331338
<div id="{tab_id}_output" class="output-pane active">
332-
<pre>{html.escape(self.stdout) if self.stdout else 'No output'}</pre>
339+
<pre>{html.escape(strip_ansi_codes(self.stdout)) if self.stdout else 'No output'}</pre>
333340
</div>
334341
<div id="{tab_id}_error" class="output-pane">
335-
<pre>{html.escape(self.stderr) if self.stderr else 'No errors'}</pre>
342+
<pre>{html.escape(strip_ansi_codes(self.stderr)) if self.stderr else 'No errors'}</pre>
336343
</div>
337344
</div>
338345
</div>
@@ -605,7 +612,7 @@ def prepare_output_cell(self) -> ipywidgets.Output:
605612
title = "Test Results for " if function else "Test Results "
606613
output_cell.append_display_data(
607614
HTML(
608-
"<div>"
615+
'<div style="overflow: hidden;">'
609616
f'<h2 style="font-size: 1.5rem; margin: 0;">{title}'
610617
'<code style="font-size: 1.1rem; background: #f3f4f6; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-family: ui-monospace, monospace;">'
611618
f"solution_{function.name}</code></h2>"

tutorial/tests/testsuite/testsuite.py

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import os
88
import pathlib
99
from collections import defaultdict
10-
from contextlib import redirect_stderr, redirect_stdout
10+
from contextlib import contextmanager, redirect_stderr, redirect_stdout
1111
from queue import Queue
1212
from threading import Thread
1313
from typing import Dict, List, Optional
@@ -248,6 +248,17 @@ def run_cell(self) -> List[IPytestResult]:
248248

249249
return test_results
250250

251+
@contextmanager
252+
def traceback_handling(self, debug: bool):
253+
"""Context manager to temporarily modify traceback behavior"""
254+
original_traceback = self.shell._showtraceback
255+
try:
256+
if not debug:
257+
self.shell._showtraceback = lambda *args, **kwargs: None
258+
yield
259+
finally:
260+
self.shell._showtraceback = original_traceback
261+
251262
@cell_magic
252263
def ipytest(self, line: str, cell: str):
253264
"""The `%%ipytest` cell magic"""
@@ -270,56 +281,53 @@ def ipytest(self, line: str, cell: str):
270281
self.threaded = True
271282
self.test_queue = Queue()
272283

273-
# If debug is in the line, then we want to show the traceback
274-
if self.debug:
275-
self.shell._showtraceback = self._orig_traceback
276-
else:
277-
self.shell._showtraceback = lambda *args, **kwargs: None
278-
279-
# Get the module containing the test(s)
280-
if (
281-
module_name := get_module_name(
282-
" ".join(line_contents), self.shell.user_global_ns
283-
)
284-
) is None:
285-
raise TestModuleNotFoundError
284+
with self.traceback_handling(self.debug):
285+
# Get the module containing the test(s)
286+
if (
287+
module_name := get_module_name(
288+
" ".join(line_contents), self.shell.user_global_ns
289+
)
290+
) is None:
291+
raise TestModuleNotFoundError
286292

287-
self.module_name = module_name
293+
self.module_name = module_name
288294

289-
# Check that the test module file exists
290-
if not (
291-
module_file := pathlib.Path(f"tutorial/tests/test_{self.module_name}.py")
292-
).exists():
293-
raise FileNotFoundError(module_file)
295+
# Check that the test module file exists
296+
if not (
297+
module_file := pathlib.Path(
298+
f"tutorial/tests/test_{self.module_name}.py"
299+
)
300+
).exists():
301+
raise FileNotFoundError(module_file)
294302

295-
self.module_file = module_file
303< D95F span class="diff-text-marker">+
self.module_file = module_file
296304

297-
# Run the cell
298-
results = self.run_cell()
305+
# Run the cell
306+
results = self.run_cell()
299307

300-
# If in debug mode, display debug information first
301-
if self.debug:
302-
debug_output = DebugOutput(
303-
module_name=self.module_name,
304-
module_file=self.module_file,
305-
results=results,
306-
)
307-
display(HTML(debug_output.to_html()))
308-
309-
# Parse the AST of the test module to retrieve the solution code
310-
ast_parser = AstParser(self.module_file)
311-
# Display the test results and the solution code
312-
for result in results:
313-
solution = (
314-
ast_parser.get_solution_code(result.function.name)
315-
if result.function and result.function.name
316-
else None
317-
)
318-
TestResultOutput(
319-
result,
320-
solution,
321-
self.shell.openai_client, # type: ignore
322-
).display_results()
308+
# If in debug mode, display debug information first
309+
if self.debug:
310+
debug_output = DebugOutput(
311+
module_name=self.module_name,
312+
module_file=self.module_file,
313+
results=results,
314+
)
315+
display(HTML(debug_output.to_html()))
316+
317+
# Parse the AST of the test module to retrieve the solution code
318+
ast_parser = AstParser(self.module_file)
319+
# Display the test results and the solution code
320+
for result in results:
321+
solution = (
322+
ast_parser.get_solution_code(result.function.name)
323+
if result.function and result.function.name
324+
else None
325+
)
326+
TestResultOutput(
327+
result,
328+
solution,
329+
self.shell.openai_client, # type: ignore
330+
).display_results()
323331

324332

325333
def load_ipython_extension(ipython):

0 commit comments

Comments
 (0)
0