Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass at unhandled errback reporting #62

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions pytest_twisted.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
import gc
import inspect
import sys
import warnings
Expand All @@ -9,6 +10,7 @@

from twisted.internet import error, defer
from twisted.internet.threads import blockingCallFromThread
from twisted.logger import globalLogPublisher
from twisted.python import failure


Expand Down Expand Up @@ -102,6 +104,60 @@ def block_from_thread(d):
return blockingCallFromThread(_instances.reactor, lambda x: x, d)


class _Observer:
def __init__(self):
self.failures = []
self.asserted = False

def register(self):
globalLogPublisher.addObserver(self)

def __call__(self, event_dict):
is_error = event_dict.get('isError')
s = 'Unhandled error in Deferred'.lower()
log_format = event_dict.get('log_format')
if log_format is None:
log_format = ''
is_unhandled = s in log_format.lower()

if is_error and is_unhandled:
self.failures.append(event_dict)

def assert_empty(self):
self.asserted = True

gc.collect()
globalLogPublisher.removeObserver(self)

assert self.failures == []


@pytest.fixture(scope='function')
def unhandled_errback_observer():
observer = _Observer()
observer.register()

yield observer

if not observer.asserted:
observer.assert_empty()


def assert_on_unhandled_errbacks(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
observer = _Observer()
observer.register()

result = f(*args, **kwargs)

observer.assert_empty()

return result

return wrapped


@decorator.decorator
def inlineCallbacks(fun, *args, **kw):
return defer.inlineCallbacks(fun)(*args, **kw)
Expand Down
68 changes: 68 additions & 0 deletions testing/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,71 @@ def test_succeed():
testdir.makepyfile(test_file)
rr = testdir.run(sys.executable, "-m", "pytest", "-v", *cmd_opts)
assert "WrongReactorAlreadyInstalledError" in rr.stderr.str()


def test_no_unhandled_errbacks_to_assert(testdir, cmd_opts):
test_file = """
def test_fail(unhandled_errback_observer):
unhandled_errback_observer.assert_empty()
"""
testdir.makepyfile(test_file)
rr = testdir.run(sys.executable, "-m", "pytest", *cmd_opts)
assert_outcomes(rr, {"passed": 1, "error": None})


def test_an_unhandled_errback_to_auto_assert(testdir, cmd_opts):
test_file = """
from twisted.internet.defer import Deferred

def test_fail(unhandled_errback_observer):
d = Deferred()
d.errback(42)
"""
testdir.makepyfile(test_file)
rr = testdir.run(sys.executable, "-m", "pytest", *cmd_opts)
assert_outcomes(rr, {"passed": 1, "error": 1})


def test_an_unhandled_errback_to_manually_assert(testdir, cmd_opts):
test_file = """
from twisted.internet.defer import Deferred

def test_fail(unhandled_errback_observer):
d = Deferred()
d.errback(42)

del d
unhandled_errback_observer.assert_empty()
"""
testdir.makepyfile(test_file)
rr = testdir.run(sys.executable, "-m", "pytest", *cmd_opts)
assert_outcomes(rr, {"failed": 1})


def test_no_unhandled_errbacks_to_assert_decorator(testdir, cmd_opts):
test_file = """
import pytest_twisted

@pytest_twisted.assert_on_unhandled_errbacks
def test_fail():
pass
"""
testdir.makepyfile(test_file)
rr = testdir.run(sys.executable, "-m", "pytest", *cmd_opts)
assert_outcomes(rr, {"passed": 1})


def test_an_unhandled_errback_to_assert_decorator(testdir, cmd_opts):
test_file = """
from twisted.internet.defer import Deferred

import pytest_twisted

@pytest_twisted.assert_on_unhandled_errbacks
def test_fail():
d = Deferred()
d.errback(42)
"""
testdir.makepyfile(test_file)
rr = testdir.run(sys.executable, "-m", "pytest", *cmd_opts)
assert_outcomes(rr, {"failed": 1})