Skip to content

Exception in SubRequest finalizer prevents further registered SubRequest finalizer calls #2440

@rmfitzpatrick

Description

@rmfitzpatrick

When an exception occurs in a SubRequest's registered finalizer, no further registered finalizers are invoked, causing incomplete teardown. This is not the case for FixtureRequests:

import pytest


class Factory(object):

    @classmethod
    def __call__(cls, request, to_teardown):
        def teardown():
            print 'in ', teardown, request, to_teardown
            to_teardown()
        print 'register', teardown, request, to_teardown
        request.addfinalizer(teardown)


@pytest.fixture
def factory(request):
    def _factory(to_teardown):
        return Factory()(request, to_teardown)
    return _factory


def _in(num):
    print '_in({0})'.format(num)
    raise Exception(num)


def one():
    _in('one')


def two():
    _in('two')


def test_with_factory_and_fixturerequest(request):
    factory = Factory()
    for to_teardown in (one, two):
        factory(request, to_teardown)


def test_with_factory_and_subrequest(factory):
    for to_teardown in (one, two):
        factory(to_teardown)
(pytest) user$ pytest -vs test.py
====================================================================================== test session starts =======================================================================================
platform darwin -- Python 2.7.13, pytest-3.1.1.dev23+g7950c26, py-1.4.33, pluggy-0.4.0 -- /.virtualenvs/pytest/bin/python2.7
cachedir: .cache
rootdir: user, inifile:
plugins: hypothesis-3.11.0
collected 2 items

test.py::test_with_factory_and_fixturerequest register <function teardown at 0x110e6d2a8> <FixtureRequest for <Function 'test_with_factory_and_fixturerequest'>> <function one at 0x110e37e60>
register <function teardown at 0x110e6d320> <FixtureRequest for <Function 'test_with_factory_and_fixturerequest'>> <function two at 0x110e37ed8>
PASSEDin  <function teardown at 0x110e6d320> <FixtureRequest for <Function 'test_with_factory_and_fixturerequest'>> <function two at 0x110e37ed8>
_in(two)
in  <function teardown at 0x110e6d2a8> <FixtureRequest for <Function 'test_with_factory_and_fixturerequest'>> <function one at 0x110e37e60>
_in(one)

test.py::test_with_factory_and_fixturerequest ERROR
test.py::test_with_factory_and_subrequest register <function teardown at 0x110e6d320> <SubRequest 'factory' for <Function 'test_with_factory_and_subrequest'>> <function one at 0x110e37e60>
register <function teardown at 0x110e6d2a8> <SubRequest 'factory' for <Function 'test_with_factory_and_subrequest'>> <function two at 0x110e37ed8>
PASSEDin  <function teardown at 0x110e6d2a8> <SubRequest 'factory' for <Function 'test_with_factory_and_subrequest'>> <function two at 0x110e37ed8>
_in(two)

test.py::test_with_factory_and_subrequest ERROR

============================================================================================= ERRORS =============================================================================================
___________________________________________________________________ ERROR at teardown of test_with_factory_and_fixturerequest ____________________________________________________________________

    def teardown():
        print 'in ', teardown, request, to_teardown
>       to_teardown()

test.py:14:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test.py:41: in two
    _in('two')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

num = 'two'

    def _in(num):
        print '_in({0})'.format(num)
>       raise Exception(num)
E       Exception: two

test.py:33: Exception
_____________________________________________________________________ ERROR at teardown of test_with_factory_and_subrequest ______________________________________________________________________

    def teardown():
        print 'in ', teardown, request, to_teardown
>       to_teardown()

test.py:14:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test.py:41: in two
    _in('two')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

num = 'two'

    def _in(num):
        print '_in({0})'.format(num)
>       raise Exception(num)
E       Exception: two

test.py:33: Exception
=============================================================================== 2 passed, 2 error in 0.03 seconds ================================================================================

Notice in test_with_factory_and_fixturerequest that both registered finalizers are invoked but in test_with_factory_and_subrequest only two() is. A workaround is to wrap any teardown calls that can potentially raise exceptions in a try/except, but this isn't ideal since the exceptions are informative.

(pytest) user$ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
appdirs (1.4.3)
enum34 (1.1.6)
hypothesis (3.11.0)
packaging (16.8)
pip (9.0.1)
py (1.4.33)
pyparsing (2.2.0)
pytest (3.1.1.dev23+g7950c26, /user/pytest)
setuptools (35.0.2)
six (1.10.0)
wheel (0.30.0a0)

edit: removed unnecessary functions

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: fixturesanything involving fixtures directly or indirectlytype: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions