def wrappedfn(*a, **kw): try: return fn(*a, **kw) except AssertionError as e: # TODO: check that this isn't failing at an early line in a different # file? _, lineno = frame_summary_for_fn( fn, traceback.extract_tb(e.__traceback__)) if lineno >= first_body_line: raise
def attempt_call(conditions: Conditions, fn: Callable, short_circuit: ShortCircuitingContext, enforced_conditions: EnforcedConditions) -> CallAnalysis: space = context_statespace() bound_args = gen_args(conditions.sig) msg_gen = MessageGenerator(fn) with space.framework(): # TODO: looks wrong(-ish) to guard this with space.framework(). # Copy on custom objects may require patched builtins. (datetime.timedelta is one such case) original_args = copy.deepcopy(bound_args) space.checkpoint() lcls: Mapping[str, object] = bound_args.arguments # In preconditions, __old__ exists but is just bound to the same args. # This lets people write class invariants using `__old__` to, for example, # demonstrate immutability. lcls = {'__old__': AttributeHolder(lcls), **lcls} expected_exceptions = conditions.raises for precondition in conditions.pre: with ExceptionFilter(expected_exceptions) as efilter: with enforced_conditions.enabled_enforcement(), short_circuit: precondition_ok = precondition.evaluate(lcls) if not precondition_ok: debug('Failed to meet precondition', precondition.expr_source) return CallAnalysis(failing_precondition=precondition) if efilter.ignore: debug('Ignored exception in precondition.', efilter.analysis) return efilter.analysis elif efilter.user_exc is not None: (user_exc, tb) = efilter.user_exc debug('Exception attempting to meet precondition', precondition.expr_source, ':', user_exc, tb.format()) return CallAnalysis( failing_precondition=precondition, failing_precondition_reason= f'it raised "{repr(user_exc)} at {tb.format()[-1]}"') with ExceptionFilter(expected_exceptions) as efilter: with enforced_conditions.enabled_enforcement(), short_circuit: assert not space.running_framework_code __return__ = fn(*bound_args.args, **bound_args.kwargs) lcls = { **bound_args.arguments, '__return__': __return__, '_': __return__, '__old__': AttributeHolder(original_args.arguments), fn.__name__: fn } if efilter.ignore: debug('Ignored exception in function.', efilter.analysis) return efilter.analysis elif efilter.user_exc is not None: space.check_deferred_assumptions() (e, tb) = efilter.user_exc detail = name_of_type(type(e)) + ': ' + str(e) frame_filename, frame_lineno = frame_summary_for_fn(tb, fn) debug('exception while evaluating function body:', detail, frame_filename, 'line', frame_lineno) detail += ' ' + get_input_description(fn.__name__, original_args, _MISSING) return CallAnalysis(VerificationStatus.REFUTED, [ msg_gen.make(MessageType.EXEC_ERR, detail, frame_filename, frame_lineno, ''.join(tb.format())) ]) for argname, argval in bound_args.arguments.items(): if (conditions.mutable_args is not None and argname not in conditions.mutable_args): old_val, new_val = original_args.arguments[argname], argval if not deep_eq(old_val, new_val, set()): space.check_deferred_assumptions() detail = 'Argument "{}" is not marked as mutable, but changed from {} to {}'.format( argname, old_val, new_val) debug('Mutablity problem:', detail) return CallAnalysis( VerificationStatus.REFUTED, [msg_gen.make(MessageType.POST_ERR, detail, None, 0, '')]) (post_condition, ) = conditions.post with ExceptionFilter(expected_exceptions) as efilter: # TODO: re-enable post-condition short circuiting. This will require refactoring how # enforced conditions and short curcuiting interact, so that post-conditions are # selectively run when, and only when, performing a short circuit. #with enforced_conditions.enabled_enforcement(), short_circuit: isok = bool(post_condition.evaluate(lcls)) if efilter.ignore: debug('Ignored exception in postcondition.', efilter.analysis) return efilter.analysis elif efilter.user_exc is not None: space.check_deferred_assumptions() (e, tb) = efilter.user_exc detail = repr(e) + ' ' + get_input_description( fn.__name__, original_args, __return__, post_condition.addl_context) debug('exception while calling postcondition:', detail) failures = [ msg_gen.make(MessageType.POST_ERR, detail, post_condition.filename, post_condition.line, ''.join(tb.format())) ] return CallAnalysis(VerificationStatus.REFUTED, failures) if isok: debug('Postcondition confirmed.') return CallAnalysis(VerificationStatus.CONFIRMED) else: space.check_deferred_assumptions() detail = 'false ' + \ get_input_description( fn.__name__, original_args, __return__, post_condition.addl_context) debug(detail) failures = [ msg_gen.make(MessageType.POST_FAIL, detail, post_condition.filename, post_condition.line, '') ] return CallAnalysis(VerificationStatus.REFUTED, failures)