exhuma
exhuma

Reputation: 21747

How do I ensure tearDown is called (in the case of an uncaught exception in the test) with nosetests?

I have a test-case with both a setUp and tearDown method:

TESTCONF = SafeConfigParser(...)
ENGINE = create_engine(TESTCONF.get('database', 'dsn'))

class TestBase(TestCase):

    def setUp(self):
        self.config = TESTCONF
        self.connection = ENGINE.connect()
        self.trans = self.connection.begin()
        self.session = Session(bind=self.connection)

    def tearDown(self):
        print "post teardown 0", ENGINE.pool.status()
        self.trans.rollback()
        print "post teardown 1", ENGINE.pool.status()
        self.session.close()
        print "post teardown 2", ENGINE.pool.status()
        self.connection.close()
        print "post teardown 3", ENGINE.pool.status(), "\n"

All DB related test-cases inherit from this class. It seems as if tearDown is not always called. I am having trouble to localize the error. At one point, the test-runner hangs on

self.connection = ENGINE.connect()

I assume that the close method was not always called to release a connection from the pool.

Any ideas what to look for?

Update: I how added some print statements (also added them in the example code above), and my initial though was correct. Some connections are not properly closed and are not handed back to the pool. This happens for all "Errors" (not "Failures") in the tests. The following block shows the output I get using the above tearDown method (cut off prematurely for brevity). As you can see, the lines with errors (those starting with Epre instead of .pre) do not call any tearDown lines. Not even the post tearDown 0 message shows up!

I have now traced back the error to using self.assertRaisesRegEx instead of self.assertRaisesRegExp, so the exception is raised inside the unit-test, not inside the tested code!

pre setup Pool size: 5  Connections in pool: 0 Current Overflow: -5 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
'TestCommonBase' object has no attribute 'assertRaisesRegex'
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
'TestCommonBase' object has no attribute 'assertRaisesRegex'
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0 

.SSpre setup Pool size: 5  Connections in pool: 1 Current Overflow: -4 Current Checked out connections: 0
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
ESpre setup Pool size: 5  Connections in pool: 0 Current Overflow: -4 Current Checked out connections: 1
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -3 Current Checked out connections: 2
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: -3 Current Checked out connections: 2
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -2 Current Checked out connections: 3
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: -2 Current Checked out connections: 3
post connect Pool size: 5  Connections in pool: 0 Current Overflow: -1 Current Checked out connections: 4
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: -1 Current Checked out connections: 4
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 0 Current Checked out connections: 5
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 0 Current Checked out connections: 5
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 1 Current Checked out connections: 6
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 1 Current Checked out connections: 6
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 2 Current Checked out connections: 7
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 2 Current Checked out connections: 7
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 3 Current Checked out connections: 8
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 3 Current Checked out connections: 8
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 4 Current Checked out connections: 9
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 4 Current Checked out connections: 9
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 5 Current Checked out connections: 10
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 5 Current Checked out connections: 10
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 6 Current Checked out connections: 11
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 6 Current Checked out connections: 11
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 7 Current Checked out connections: 12
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 7 Current Checked out connections: 12
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 8 Current Checked out connections: 13
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 8 Current Checked out connections: 13
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 9 Current Checked out connections: 14
Epre setup Pool size: 5  Connections in pool: 0 Current Overflow: 9 Current Checked out connections: 14
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: 10 Current Checked out connections: 14 

.pre setup Pool size: 5  Connections in pool: 1 Current Overflow: 10 Current Checked out connections: 14
post connect Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
Epost teardown 0 Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 1 Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 2 Pool size: 5  Connections in pool: 0 Current Overflow: 10 Current Checked out connections: 15
post teardown 3 Pool size: 5  Connections in pool: 1 Current Overflow: 10 Current Checked out connections: 14 

Additional details:

For the sake of this question, I wrote a very small reproducible example. Unfortunately that one did not exhibit this behaviour:

class MyTest(TestCase):
    """
    Example test case meant to demonstrate that ``tearDown`` is not called.

    It turns out, in this case, ``tearDown`` *is* called as expected!
    """

    def setUp(self):
        print "setup"

    def tearDown(self):
        print "tearDown"

    def test_failing(self):
        print int('yes')


if __name__ == '__main__':
    main()

So what makes this example different from my real-world code? Why is tearDown called in the simple example, but not in my production code?

I will continue to investigate...

Upvotes: 5

Views: 2861

Answers (1)

Mr. Deathless
Mr. Deathless

Reputation: 1391

If you suspect that tearDown is not called, then I can propose you to use context manager for resource allocation in your tests. The with statement guarantees that if the __enter__() method returns without an error, then __exit__() will always be called.

Here is your example modified to use context manager for connection allocation:

TESTCONF = SafeConfigParser(...)
ENGINE = create_engine(TESTCONF.get('database', 'dsn'))

class DBConnection(object):
    def __init__(self, engine):
        self.engine = engine

    def __enter__(self):
        self.connection = engine.connect()
        self.trans = self.connection.begin()
        self.session = Session(bind=self.connection)
        # return value can be accessed using `as` directive
        return self.connection, self.trans, self.session

    def __exit__(self, exc_type, exc_val, traceback):
        self.trans.rollback()
        self.session.close()
        self.connection.close()


class TestBase(unittest.TestCase):

    def setUp(self):
        self.config = TESTCONF

    def run(self, result=None):
        with DBConnection(ENGINE) as db_conn:
            self.connection, self.trans, self.session = db_conn
            super(MyTest, self).run(result)

Also you can use contextlib if DBConnection class seems too bulky:

from contextlib import contextmanager

@contextmanager
def DBConnection(engine):
    connection = engine.connect()
    trans = connection.begin()
    session = Session(bind=connection)
    yield connection, trans, session
    trans.close()
    session.close()
    connection.close()

Upvotes: 1

Related Questions