def test_send_message(self): def test1(subject, message): self.assertRegexpMatches(subject, r"Soft Assert: \[\w+\] This should fail") things_that_should_show_up_in_message = [ r"Message: This should fail", r"Traceback:", r'File ".*corehq/util/tests/test_soft_assert.py", line \d+, in test_send_message', r"self\.soft_assert\(False, 'This should fail'\)", r"Occurrences to date: 1", ] for thing in things_that_should_show_up_in_message: self.assertRegexpMatches( message, thing, '{!r}\ndoes not match\n---\n{}---\n'.format(thing, message) ) def test2(subject, message): self.assertRegexpMatches( subject, r'Soft Assert: \[\w+\] None', ) things_that_should_show_up_in_message = [ r"Message: None", r"Traceback:", r'File ".*corehq/util/tests/test_soft_assert.py", line \d+, in test_send_message', r"self\.soft_assert\(False\)", r"Occurrences to date: 1", ] for thing in things_that_should_show_up_in_message: self.assertRegexpMatches( message, thing, '{!r}\ndoes not match\n---\n{}---\n'.format(thing, message) ) tests = iter([test1, test2]) def backend(subject, message): test_ = next(tests) test_(subject, message) self.soft_assert = SoftAssert( send=lambda info: _send_message(info, backend=backend), ) # sent to test1 self.soft_assert(False, 'This should fail') # sent to test2 self.soft_assert(False)
def soft_assert(to=None, notify_admins=False, fail_if_debug=False, exponential_backoff=True, skip_frames=0, send_to_ops=True, log_to_file=False, include_breadcrumbs=False): """ send an email with stack trace if assertion is not True Parameters: - to: Email address or list of email addresses that should receive the email - notify_admins: Send to all admins (using mail_admins) as well - fail_if_debug: if True, will fail hard (like a normal assert) if called in a developer environment (settings.DEBUG = True). If False, behavior will not depend on DEBUG setting. - exponential_backoff: if True, will only email every time an assert has failed 2**n times (1, 2, 4, 8, 16 times, etc.). If False, it will email every time. - skip_frames: number of frames of the traceback (from the bottom) to ignore. Useful if you're calling this from within a helper function. In that case if you want the call _to_ the helper function to be the last frame in the stack trace, then pass in skip_frames=1. This affects both the traceback in the email as the "key" by which errors are grouped. - log_to_file: write the message to a log file For the purposes of grouping errors into email threads, counting occurrences (sent in the email), and implementing exponential backoff, errors are always grouped by a key that varies on the last two frames (that aren't skipped by skip_frames) of the stack. Returns assertion. This makes it easy to do something like the following: if not soft_assert(to=['*****@*****.**']).call( isinstance(n, float), 'myfunction should be passed a float'): n = float(n) etc. """ assert not isinstance(to, bytes) if isinstance(to, six.text_type): to = [to] if to is None: to = [] if to is not None and send_to_ops and settings.SOFT_ASSERT_EMAIL: to = to + [settings.SOFT_ASSERT_EMAIL] def send_to_recipients(subject, message): from corehq.apps.hqwebapp.tasks import send_mail_async send_mail_async.delay( # this prefix is automatically added in mail_admins # but not send mail subject=settings.EMAIL_SUBJECT_PREFIX + subject, message=message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=to, ) def send_to_admins(subject, message): from corehq.apps.hqwebapp.tasks import mail_admins_async if settings.DEBUG: return mail_admins_async.delay( subject=subject, message=message, ) def write_to_log_file(subject, message): logger.debug(message) if not (notify_admins or to or log_to_file): raise ValueError('You must call soft assert with either a ' 'list of recipients or notify_admins=True') def send(info): send_email = settings.ENABLE_SOFT_ASSERT_EMAILS if send_email and to: _send_message(info, backend=send_to_recipients) if send_email and notify_admins: _send_message(info, backend=send_to_admins) if log_to_file: _send_message(info, backend=write_to_log_file) if fail_if_debug: debug = settings.DEBUG else: debug = False return SoftAssert(debug=debug, send=send, use_exponential_backoff=exponential_backoff, skip_frames=skip_frames, include_breadcrumbs=include_breadcrumbs)
def setUp(self): self.infos = [] self.soft_assert = SoftAssert(send=self.send, use_exponential_backoff=False)
def soft_assert(to=None, notify_admins=False, fail_if_debug=False, exponential_backoff=True, skip_frames=0): """ send an email with stack trace if assertion is not True Parameters: - to: Email address or list of email addresses that should receive the email - notify_admins: Send to all admins (using mail_admins) as well - fail_if_debug: if True, will fail hard (like a normal assert) if called in a developer environment (settings.DEBUG = True). If False, behavior will not depend on DEBUG setting. - exponential_backoff: if True, will only email every time an assert has failed 2**n times (1, 2, 4, 8, 16 times, etc.). If False, it will email every time. - skip_frames: number of frames of the traceback (from the bottom) to ignore. Useful if you're calling this from within a helper function. In that case if you want the call _to_ the helper function to be the last frame in the stack trace, then pass in skip_frames=1. This affects both the traceback in the email as the "key" by which errors are grouped. For the purposes of grouping errors into email threads, counting occurrences (sent in the email), and implementing exponential backoff, errors are always grouped by a key that varies on the last two frames (that aren't skipped by skip_frames) of the stack. Returns assertion. This makes it easy to do something like the following: if not soft_assert(to=['*****@*****.**']).call( isinstance(n, float), 'myfunction should be passed a float'): n = float(n) etc. """ if isinstance(to, basestring): to = [to] def send_to_recipients(subject, message): send_mail( # this prefix is automatically added in mail_admins # but not send mail subject=settings.EMAIL_SUBJECT_PREFIX + subject, message=message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=to, ) if to and notify_admins: def send(info): _send_message(info, backend=mail_admins) _send_message(info, backend=send_to_recipients) elif to: def send(info): _send_message(info, backend=send_to_recipients) elif notify_admins: def send(info): _send_message(info, backend=mail_admins) else: raise ValueError('You must call soft assert with either a ' 'list of recipients or notify_admins=True') if fail_if_debug: debug = settings.DEBUG else: debug = False return SoftAssert( debug=debug, send=send, use_exponential_backoff=exponential_backoff, skip_frames=skip_frames, )