1414
1515from ..dynassign import dyn # for MacroPy's gen_sym
1616from ..env import env
17- from ..misc import callsite_filename , safeissubclass
17+ from ..misc import callsite_filename
1818from ..conditions import cerror , handlers , restarts , invoke
1919from ..collections import unbox
2020from ..symbol import sym , gensym
@@ -57,20 +57,17 @@ def istestmacro(tree):
5757_error = sym ("_error" ) # used by the error[] macro
5858_warn = sym ("_warn" ) # used by the warn[] macro
5959
60- _completed = sym ("_completed" ) # thunk returned normally
61- _signaled = sym ("_signaled" ) # via unpythonic.conditions.signal and its sisters
62- _raised = sym ("_raised" ) # via raise
6360def _observe (thunk ):
6461 """Run `thunk` and report how it fared.
6562
6663 Internal helper for implementing assert functions.
6764
6865 The return value is:
6966
70- - `(_completed , return_value)` if the thunk completed normally
71- - `(_signaled , condition_instance)` if a signal from inside
67+ - `(completed , return_value)` if the thunk completed normally
68+ - `(signaled , condition_instance)` if a signal from inside
7269 the dynamic extent of thunk propagated to this level.
73- - `(_raised , exception_instance)` if an exception from inside
70+ - `(raised , exception_instance)` if an exception from inside
7471 the dynamic extent of thunk propagated to this level.
7572 """
7673 def intercept (condition ):
@@ -81,8 +78,7 @@ def intercept(condition):
8178 # it and let it fall through to the nearest enclosing `testset`, for
8279 # reporting. This can happen if a `test[]` is nested within a `with
8380 # test:` block, or if `test[]` expressions are nested.
84- exctype = type (condition )
85- if issubclass (exctype , fixtures .TestingException ):
81+ if issubclass (type (condition ), fixtures .TestingException ):
8682 return # cancel and delegate to the next outer handler
8783 invoke ("_got_signal" , condition )
8884
@@ -92,12 +88,12 @@ def intercept(condition):
9288 ret = thunk ()
9389 # We only reach this point if the restart was not invoked,
9490 # i.e. if thunk() completed normally.
95- return _completed , ret
96- return _signaled , unbox (sig )
91+ return fixtures . completed , ret
92+ return fixtures . signaled , unbox (sig )
9793 # This testing framework always signals, never raises, so we don't need any
9894 # special handling here.
9995 except Exception as err : # including ControlError raised by an unhandled `unpythonic.conditions.error`
100- return _raised , err
96+ return fixtures . raised , err
10197
10298
10399_unassigned = gensym ("_unassigned" ) # runtime gensym / nonce value.
@@ -121,10 +117,11 @@ def unpythonic_assert(sourcecode, func, *, filename, lineno, message=None):
121117 `sourcecode` is a string representation of the source code expression
122118 that is being asserted.
123119
124- `func` is the test itself, in the form a 1-argument function that
125- takes as its only argument an `unpythonic.env`. (The `the[]`
126- mechanism uses the `env` to store the value of the captured
127- subexpression.)
120+ `func` is the test itself, as a 1-argument function that accepts
121+ as its only argument an `unpythonic.env`. The `the[]` mechanism
122+ uses this `env` to store the value of the captured subexpression.
123+ (It is also perfectly fine to not store anything there; the presence
124+ or absence of a captured value is detected automatically.)
128125
129126 The function should compute the desired test expression and return
130127 its value. If the result is falsey, the assertion fails.
@@ -134,7 +131,7 @@ def unpythonic_assert(sourcecode, func, *, filename, lineno, message=None):
134131
135132 `lineno` is the line number at the call site.
136133
137- These are best extracted automatically using the ` test[]` macro .
134+ These are best extracted automatically using the test macros .
138135
139136 `message` is an optional string, included in the generated error message
140137 if the assertion fails.
@@ -160,9 +157,11 @@ def unpythonic_assert(sourcecode, func, *, filename, lineno, message=None):
160157 custom_msg = ""
161158
162159 # special cases for unconditional failures
163- if mode is _completed and test_result is _fail : # fail[...], e.g. unreachable line reached
160+ origin = "test"
161+ if mode is fixtures .completed and test_result is _fail : # fail[...], e.g. unreachable line reached
164162 fixtures ._update (fixtures .tests_failed , + 1 )
165163 conditiontype = fixtures .TestFailure
164+ origin = "fail"
166165 if message is not None :
167166 # If a user-given message is specified for `fail[]`, it is all
168167 # that should be displayed. We don't want confusing noise such as
@@ -172,18 +171,20 @@ def unpythonic_assert(sourcecode, func, *, filename, lineno, message=None):
172171 error_msg = message
173172 else :
174173 error_msg = "Unconditional failure requested, no message."
175- elif mode is _completed and test_result is _error : # error[...], e.g. dependency not installed
174+ elif mode is fixtures . completed and test_result is _error : # error[...], e.g. dependency not installed
176175 fixtures ._update (fixtures .tests_errored , + 1 )
177176 conditiontype = fixtures .TestError
177+ origin = "error"
178178 if message is not None :
179179 error_msg = message
180180 else :
181181 error_msg = "Unconditional error requested, no message."
182- elif mode is _completed and test_result is _warn : # warn[...], e.g. some test disabled for now
182+ elif mode is fixtures . completed and test_result is _warn : # warn[...], e.g. some test disabled for now
183183 fixtures ._update (fixtures .tests_warned , + 1 )
184184 # HACK: warnings don't count into the test total
185185 fixtures ._update (fixtures .tests_run , - 1 )
186186 conditiontype = fixtures .TestWarning
187+ origin = "warn"
187188 if message is not None :
188189 error_msg = message
189190 else :
@@ -195,18 +196,18 @@ def unpythonic_assert(sourcecode, func, *, filename, lineno, message=None):
195196 #
196197 # So we may as well use the same code path as the fail and error cases.
197198 # general cases
198- elif mode is _completed :
199+ elif mode is fixtures . completed :
199200 if test_result :
200201 return
201202 fixtures ._update (fixtures .tests_failed , + 1 )
202203 conditiontype = fixtures .TestFailure
203204 error_msg = "Test failed: {}, due to result = {}{}" .format (sourcecode , value , custom_msg )
204- elif mode is _signaled :
205+ elif mode is fixtures . signaled :
205206 fixtures ._update (fixtures .tests_errored , + 1 )
206207 conditiontype = fixtures .TestError
207208 desc = fixtures .describe_exception (test_result )
208209 error_msg = "Test errored: {}{}, due to unexpected signal: {}" .format (sourcecode , custom_msg , desc )
209- else : # mode is _raised :
210+ else : # mode is fixtures.raised :
210211 fixtures ._update (fixtures .tests_errored , + 1 )
211212 conditiontype = fixtures .TestError
212213 desc = fixtures .describe_exception (test_result )
@@ -221,72 +222,79 @@ def unpythonic_assert(sourcecode, func, *, filename, lineno, message=None):
221222 # If the client code does not install a handler, then a `ControlError`
222223 # exception is raised by the condition system; leaving a cerror unhandled
223224 # is an error.
224- cerror (conditiontype (complete_msg ))
225+ #
226+ # As well as forming an error message for humans, we provide the data
227+ # in a machine-readable format for run-time inspection.
228+ cerror (conditiontype (complete_msg , origin = origin , custom_message = message ,
229+ filename = filename , lineno = lineno , sourcecode = sourcecode ,
230+ mode = mode , result = test_result , captured_value = value ))
225231
226232def unpythonic_assert_signals (exctype , sourcecode , thunk , * , filename , lineno , message = None ):
227233 """Like `unpythonic_assert`, but assert that running `sourcecode` signals `exctype`.
228234
229235 "Signal" as in `unpythonic.conditions.signal` and its sisters `error`, `cerror`, `warn`.
230236 """
231- mode , result = _observe (thunk )
237+ mode , test_result = _observe (thunk )
232238 fixtures ._update (fixtures .tests_run , + 1 )
233239
234240 if message is not None :
235241 custom_msg = ", with message '{}'" .format (message )
236242 else :
237243 custom_msg = ""
238244
239- if mode is _completed :
245+ if mode is fixtures . completed :
240246 fixtures ._update (fixtures .tests_failed , + 1 )
241247 conditiontype = fixtures .TestFailure
242248 error_msg = "Test failed: {}{}, expected signal: {}, nothing was signaled." .format (sourcecode , custom_msg , fixtures .describe_exception (exctype ))
243- elif mode is _signaled :
244- # allow both "signal(SomeError())" and "signal(SomeError)"
245- if isinstance (result , exctype ) or safeissubclass (result , exctype ):
249+ elif mode is fixtures .signaled :
250+ if isinstance (test_result , exctype ):
246251 return
247252 fixtures ._update (fixtures .tests_errored , + 1 )
248253 conditiontype = fixtures .TestError
249- desc = fixtures .describe_exception (result )
254+ desc = fixtures .describe_exception (test_result )
250255 error_msg = "Test errored: {}{}, expected signal: {}, got unexpected signal: {}" .format (sourcecode , custom_msg , fixtures .describe_exception (exctype ), desc )
251- else : # mode is _raised :
256+ else : # mode is fixtures.raised :
252257 fixtures ._update (fixtures .tests_errored , + 1 )
253258 conditiontype = fixtures .TestError
254- desc = fixtures .describe_exception (result )
259+ desc = fixtures .describe_exception (test_result )
255260 error_msg = "Test errored: {}{}, expected signal: {}, got unexpected exception: {}" .format (sourcecode , custom_msg , fixtures .describe_exception (exctype ), desc )
256261
257262 complete_msg = "[{}:{}] {}" .format (filename , lineno , error_msg )
258- cerror (conditiontype (complete_msg ))
263+ cerror (conditiontype (complete_msg , origin = "test_signals" , custom_message = message ,
264+ filename = filename , lineno = lineno , sourcecode = sourcecode ,
265+ mode = mode , result = test_result , captured_value = test_result ))
259266
260267def unpythonic_assert_raises (exctype , sourcecode , thunk , * , filename , lineno , message = None ):
261268 """Like `unpythonic_assert`, but assert that running `sourcecode` raises `exctype`."""
262- mode , result = _observe (thunk )
269+ mode , test_result = _observe (thunk )
263270 fixtures ._update (fixtures .tests_run , + 1 )
264271
265272 if message is not None :
266273 custom_msg = ", with message '{}'" .format (message )
267274 else :
268275 custom_msg = ""
269276
270- if mode is _completed :
277+ if mode is fixtures . completed :
271278 fixtures ._update (fixtures .tests_failed , + 1 )
272279 conditiontype = fixtures .TestFailure
273280 error_msg = "Test failed: {}{}, expected exception: {}, nothing was raised." .format (sourcecode , custom_msg , fixtures .describe_exception (exctype ))
274- elif mode is _signaled :
281+ elif mode is fixtures . signaled :
275282 fixtures ._update (fixtures .tests_errored , + 1 )
276283 conditiontype = fixtures .TestError
277- desc = fixtures .describe_exception (result )
284+ desc = fixtures .describe_exception (test_result )
278285 error_msg = "Test errored: {}{}, expected exception: {}, got unexpected signal: {}" .format (sourcecode , custom_msg , fixtures .describe_exception (exctype ), desc )
279- else : # mode is _raised:
280- # allow both "raise SomeError()" and "raise SomeError"
281- if isinstance (result , exctype ) or safeissubclass (result , exctype ):
286+ else : # mode is fixtures.raised:
287+ if isinstance (test_result , exctype ):
282288 return
283289 fixtures ._update (fixtures .tests_errored , + 1 )
284290 conditiontype = fixtures .TestError
285- desc = fixtures .describe_exception (result )
291+ desc = fixtures .describe_exception (test_result )
286292 error_msg = "Test errored: {}{}, expected exception: {}, got unexpected exception: {}" .format (sourcecode , custom_msg , fixtures .describe_exception (exctype ), desc )
287293
288294 complete_msg = "[{}:{}] {}" .format (filename , lineno , error_msg )
289- cerror (conditiontype (complete_msg ))
295+ cerror (conditiontype (complete_msg , origin = "test_raises" , custom_message = message ,
296+ filename = filename , lineno = lineno , sourcecode = sourcecode ,
297+ mode = mode , result = test_result , captured_value = test_result ))
290298
291299
292300# -----------------------------------------------------------------------------
0 commit comments