Skip to content

Regression: unittest.TestCase.tearDown executed on skipped tests under --pdb #26

@rowan-stein

Description

@rowan-stein

User Report

When running pytest with --pdb, unittest.TestCase.tearDown is executed for skipped tests, causing errors. In pytest==5.4.1, skipped tests remained skipped under --pdb, but in pytest==5.4.2, teardown runs and raises.

Minimal test:

import unittest

class MyTestCase(unittest.TestCase):
    def setUp(self):
        xxx
    @unittest.skip("hello")
    def test_one(self):
        pass
    def tearDown(self):
        xxx

Environment:

$ python --version
Python 3.6.10
$ pip freeze
attrs==19.3.0
importlib-metadata==1.6.0
more-itertools==8.2.0
packaging==20.3
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.7
pytest==5.4.2
six==1.14.0
wcwidth==0.1.9
zipp==3.1.0

Behavior without --pdb:

$ pytest test_repro.py 
============================= test session starts ==============================
platform linux -- Python 3.6.10, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: /srv/slapgrid/slappart3/srv/runner/project/repro_pytest
collected 1 item                                          

test_repro.py s                                    [100%]

============================== 1 skipped in 0.02s ==============================

Behavior with --pdb (unexpected teardown execution):

$ pytest --pdb test_repro.py 
============================= test session starts ==============================
platform linux -- Python 3.6.10, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: /srv/slapgrid/slappart3/srv/runner/project/repro_pytest
collected 1 item                                          

test_repro.py sE
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

self = <test_repro.MyTestCase testMethod=test_one>

    def tearDown(self):
>       xxx
E       NameError: name 'xxx' is not defined

test_repro.py:10: NameError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

>>>>>>>>>>>>>>>>>> PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>
*** NameError: name 'execfile' is not defined
> /srv/slapgrid/slappart3/srv/runner/project/repro_pytest/test_repro.py(10)tearDown()
-> xxx
(Pdb) q

=========================== short test summary info ============================
ERROR test_repro.py::MyTestCase::test_one - NameError: name 'xxx' is not defined
!!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Quitting debugger !!!!!!!!!!!!!!!!!!!
========================= 1 skipped, 1 error in 1.83s ==========================

This appears to be a regression introduced between 5.4.1 and 5.4.2 (likely related to changes in pdb/skip/teardown logic, see PR pytest-dev#7151).


Researcher Specification (Emerson Gray)

Root cause:

  • In src/_pytest/unittest.py, when --pdb is active, TestCaseFunction.runtest defers unittest.TestCase.tearDown by capturing the original into self._explicit_tearDown and monkeypatching tearDown to a no-op to preserve object state for debugging.
  • TestCaseFunction.teardown then unconditionally calls self._explicit_tearDown() if set.
  • For skipped tests (via @unittest.skip or raising unittest.SkipTest), TestCaseFunction.addSkip maps this to pytest.skip() and sets a skip tracking key (skipped_by_mark_key) in self._store.
  • However, teardown does not check skip state; thus, even when skipped, deferred tearDown is invoked under --pdb.

Proposed fix (minimal and correct):

  • Modify src/_pytest/unittest.py in TestCaseFunction.teardown to guard the invocation of deferred tearDown based on skip state.
  • Pseudocode:
if self._explicit_tearDown is not None:
    # Only call tearDown if the test was not skipped
    if not self._store.get(skipped_by_mark_key, False):
        self._explicit_tearDown()
    self._explicit_tearDown = None
  • Uses existing skipped_by_mark_key set by unittest skip handling and pytest marks; preserves behavior for non-skipped tests under --pdb.

Tests to add:

  • testing/test_unittest.py::test_unittest_skip_method_does_not_call_teardown_with_pdb
    • A unittest.TestCase with @unittest.skip on a method and tearDown that would raise if called; run with --pdb; assert skipped and no teardown error.
  • testing/test_unittest.py::test_unittest_setUp_raises_SkipTest_does_not_call_teardown_with_pdb
    • setUp raises unittest.SkipTest; tearDown raises if called; run with --pdb; assert skipped.
  • testing/test_unittest.py::test_unittest_class_skip_does_not_call_teardown_with_pdb
    • Class-level @unittest.skip; tearDown raises if called; assert skipped under --pdb.

Edge cases & interactions:

  • Pytest skip marks: unaffected (runtest not executed, no deferred teardown).
  • xfail: unchanged (run=False skips in setup; expectedFailure still runs and calls teardown).
  • PDB post-mortem on failures: unchanged; still enters pdb and calls teardown afterward for non-skipped tests.

We will implement this fix and add the tests, targeting the branch pytest-dev__pytest-7236.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions