Call Python 3.8 doClassCleanups (#8033)

This commit is contained in:
Petter Strandmark 2020-11-19 11:07:15 +01:00 committed by GitHub
parent b7ba76653d
commit eda681af2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 210 additions and 10 deletions

View File

@ -232,6 +232,7 @@ Pauli Virtanen
Pavel Karateev
Paweł Adamczak
Pedro Algarvio
Petter Strandmark
Philipp Loose
Pieter Mulder
Piotr Banaszkiewicz

View File

@ -0,0 +1 @@
`doClassCleanups` (introduced in `unittest` in Python and 3.8) is now called.

View File

@ -99,26 +99,48 @@ class UnitTestCase(Class):
"""Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
teardown functions (#517)."""
class_fixture = _make_xunit_fixture(
cls, "setUpClass", "tearDownClass", scope="class", pass_self=False
cls,
"setUpClass",
"tearDownClass",
"doClassCleanups",
scope="class",
pass_self=False,
)
if class_fixture:
cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined]
method_fixture = _make_xunit_fixture(
cls, "setup_method", "teardown_method", scope="function", pass_self=True
cls,
"setup_method",
"teardown_method",
None,
scope="function",
pass_self=True,
)
if method_fixture:
cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined]
def _make_xunit_fixture(
obj: type, setup_name: str, teardown_name: str, scope: "_Scope", pass_self: bool
obj: type,
setup_name: str,
teardown_name: str,
cleanup_name: Optional[str],
scope: "_Scope",
pass_self: bool,
):
setup = getattr(obj, setup_name, None)
teardown = getattr(obj, teardown_name, None)
if setup is None and teardown is None:
return None
if cleanup_name:
cleanup = getattr(obj, cleanup_name, lambda *args: None)
else:
def cleanup(*args):
pass
@pytest.fixture(
scope=scope,
autouse=True,
@ -130,16 +152,32 @@ def _make_xunit_fixture(
reason = self.__unittest_skip_why__
pytest.skip(reason)
if setup is not None:
if pass_self:
setup(self, request.function)
else:
setup()
try:
if pass_self:
setup(self, request.function)
else:
setup()
# unittest does not call the cleanup function for every BaseException, so we
# follow this here.
except Exception:
if pass_self:
cleanup(self)
else:
cleanup()
raise
yield
if teardown is not None:
try:
if teardown is not None:
if pass_self:
teardown(self, request.function)
else:
teardown()
finally:
if pass_self:
teardown(self, request.function)
cleanup(self)
else:
teardown()
cleanup()
return fixture

View File

@ -1260,3 +1260,163 @@ def test_plain_unittest_does_not_support_async(testdir):
"*1 passed*",
]
result.stdout.fnmatch_lines(expected_lines)
@pytest.mark.skipif(
sys.version_info < (3, 8), reason="Feature introduced in Python 3.8"
)
def test_do_class_cleanups_on_success(testdir):
testpath = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
values = []
@classmethod
def setUpClass(cls):
def cleanup():
cls.values.append(1)
cls.addClassCleanup(cleanup)
def test_one(self):
pass
def test_two(self):
pass
def test_cleanup_called_exactly_once():
assert MyTestCase.values == [1]
"""
)
reprec = testdir.inline_run(testpath)
passed, skipped, failed = reprec.countoutcomes()
assert failed == 0
assert passed == 3
@pytest.mark.skipif(
sys.version_info < (3, 8), reason="Feature introduced in Python 3.8"
)
def test_do_class_cleanups_on_setupclass_failure(testdir):
testpath = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
values = []
@classmethod
def setUpClass(cls):
def cleanup():
cls.values.append(1)
cls.addClassCleanup(cleanup)
assert False
def test_one(self):
pass
def test_cleanup_called_exactly_once():
assert MyTestCase.values == [1]
"""
)
reprec = testdir.inline_run(testpath)
passed, skipped, failed = reprec.countoutcomes()
assert failed == 1
assert passed == 1
@pytest.mark.skipif(
sys.version_info < (3, 8), reason="Feature introduced in Python 3.8"
)
def test_do_class_cleanups_on_teardownclass_failure(testdir):
testpath = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
values = []
@classmethod
def setUpClass(cls):
def cleanup():
cls.values.append(1)
cls.addClassCleanup(cleanup)
@classmethod
def tearDownClass(cls):
assert False
def test_one(self):
pass
def test_two(self):
pass
def test_cleanup_called_exactly_once():
assert MyTestCase.values == [1]
"""
)
reprec = testdir.inline_run(testpath)
passed, skipped, failed = reprec.countoutcomes()
assert passed == 3
def test_do_cleanups_on_success(testdir):
testpath = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
values = []
def setUp(self):
def cleanup():
self.values.append(1)
self.addCleanup(cleanup)
def test_one(self):
pass
def test_two(self):
pass
def test_cleanup_called_the_right_number_of_times():
assert MyTestCase.values == [1, 1]
"""
)
reprec = testdir.inline_run(testpath)
passed, skipped, failed = reprec.countoutcomes()
assert failed == 0
assert passed == 3
def test_do_cleanups_on_setup_failure(testdir):
testpath = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
values = []
def setUp(self):
def cleanup():
self.values.append(1)
self.addCleanup(cleanup)
assert False
def test_one(self):
pass
def test_two(self):
pass
def test_cleanup_called_the_right_number_of_times():
assert MyTestCase.values == [1, 1]
"""
)
reprec = testdir.inline_run(testpath)
passed, skipped, failed = reprec.countoutcomes()
assert failed == 2
assert passed == 1
def test_do_cleanups_on_teardown_failure(testdir):
testpath = testdir.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
values = []
def setUp(self):
def cleanup():
self.values.append(1)
self.addCleanup(cleanup)
def tearDown(self):
assert False
def test_one(self):
pass
def test_two(self):
pass
def test_cleanup_called_the_right_number_of_times():
assert MyTestCase.values == [1, 1]
"""
)
reprec = testdir.inline_run(testpath)
passed, skipped, failed = reprec.countoutcomes()
assert failed == 2
assert passed == 1