Skip to content

Commit a5c2598

Browse files
authored
Catch other exceptions in the test runner and print the traceback (#235)
1 parent 4a840d5 commit a5c2598

File tree

2 files changed

+51
-18
lines changed

2 files changed

+51
-18
lines changed

src/basilisp/test.lpy

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
[test-var]
1212
(swap! collected-tests conj test-var))
1313

14+
(def ^:dynamic *test-name* nil)
15+
(def ^:dynamic *test-section* nil)
16+
(def ^:dynamic *test-failures* nil)
17+
1418
(defmacro is
1519
"Assert that expr is true. Must appear inside of a deftest form."
1620
([expr]
@@ -32,13 +36,13 @@
3236
actual# ~actual
3337
expected# ~expected]
3438
(when-not computed#
35-
;; Collect test failures in the `failures` atom created
36-
;; by `deftest`.
37-
(swap! ~'failures
39+
;; Collect test failures in the atom bound to
40+
;; `*test-failures*` by `deftest`.
41+
(swap! *test-failures*
3842
conj
3943
[~msg
40-
{:test-name ~'test-name
41-
:test-section ~'test-section
44+
{:test-name *test-name*
45+
:test-section *test-section*
4246
:expr (quote ~expr)
4347
:line ~line-no
4448
:actual actual#
@@ -49,9 +53,9 @@
4953
around the test or group of tests contained inside. Must appear inside
5054
of a deftest form."
5155
[msg & body]
52-
`(let [~'test-section (if ~'test-section
53-
(str ~'test-section " :: " ~msg)
54-
~msg)]
56+
`(binding [*test-section* (if *test-section*
57+
(str *test-section* " :: " ~msg)
58+
~msg)]
5559
~@body))
5660

5761
(defmacro deftest
@@ -67,12 +71,12 @@
6771
`(do
6872
(defn ~test-name-sym
6973
[]
70-
(binding [*ns* (the-ns ~test-ns-name)]
71-
(let [~'test-name ~test-name-str
72-
~'test-section nil
73-
~'failures (atom [])]
74-
~@body
75-
{:failures (deref ~'failures)})))
74+
(binding [*ns* (the-ns ~test-ns-name)
75+
*test-name* ~test-name-str
76+
*test-section* nil
77+
*test-failures* (atom [])]
78+
~@body
79+
{:failures (deref *test-failures*)}))
7680

7781
(add-test! (var ~test-name-sym))
7882
(reset! current-ns (the-ns ~test-ns-name)))))

src/basilisp/testrunner.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import importlib
2+
import traceback
23
from typing import Optional, Callable
34

45
import pytest
56

6-
import basilisp.lang.exception as lexc
77
import basilisp.lang.keyword as kw
88
import basilisp.lang.map as lmap
99
import basilisp.lang.runtime as runtime
@@ -52,6 +52,29 @@ def _reset_collected_tests() -> None:
5252
return var.value.reset(vec.Vector.empty())
5353

5454

55+
class TestFailuresInfo(Exception):
56+
__slots__ = ('_msg', '_data',)
57+
58+
def __init__(self, message: str, data: lmap.Map) -> None:
59+
super().__init__()
60+
self._msg = message
61+
self._data = data
62+
63+
def __repr__(self):
64+
return f"basilisp.testrunner.TestFailuresInfo({self._msg}, {lrepr(self._data)})"
65+
66+
def __str__(self):
67+
return f"{self._msg} {lrepr(self._data)}"
68+
69+
@property
70+
def data(self) -> lmap.Map:
71+
return self._data
72+
73+
@property
74+
def message(self) -> str:
75+
return self._msg
76+
77+
5578
TestFunction = Callable[[], Optional[vec.Vector]]
5679

5780

@@ -112,12 +135,12 @@ def runtest(self):
112135
results: lmap.Map = self._run_test()
113136
failures: Optional[vec.Vector] = results.entry(_FAILURES_KW)
114137
if runtime.to_seq(failures):
115-
raise lexc.ExceptionInfo("Test failures", lmap.map(results))
138+
raise TestFailuresInfo("Test failures", lmap.map(results))
116139

117140
def repr_failure(self, excinfo):
118141
"""Representation function called when self.runtest() raises an
119142
exception."""
120-
if isinstance(excinfo.value, lexc.ExceptionInfo):
143+
if isinstance(excinfo.value, TestFailuresInfo):
121144
exc = excinfo.value
122145
failures = exc.data.entry(_FAILURES_KW)
123146
messages = []
@@ -142,7 +165,13 @@ def repr_failure(self, excinfo):
142165
]))
143166

144167
return "\n\n".join(messages)
145-
return None
168+
elif isinstance(excinfo.value, Exception):
169+
exc = excinfo.value
170+
messages = [f"ERROR in ({self.name}) ({self._filename})", "\n\n"]
171+
messages.extend(traceback.format_exception(Exception, exc, exc.__traceback__))
172+
return "".join(messages)
173+
else:
174+
return None
146175

147176
def reportinfo(self):
148177
return self.fspath, 0, self.name

0 commit comments

Comments
 (0)