unittest: do not use TestCase.debug() with `--pdb`
Fixes https://github.com/pytest-dev/pytest/issues/5991 Fixes https://github.com/pytest-dev/pytest/issues/3823 Ref: https://github.com/pytest-dev/pytest-django/issues/772 Ref: https://github.com/pytest-dev/pytest/pull/1890 Ref: https://github.com/pytest-dev/pytest-django/pull/782 - inject wrapped testMethod - adjust test_trial_error - add test for `--trace` with unittests
This commit is contained in:
		
							parent
							
								
									710e3c40e0
								
							
						
					
					
						commit
						04f27d4eb4
					
				|  | @ -0,0 +1 @@ | |||
| ``--trace`` now works with unittests. | ||||
|  | @ -0,0 +1 @@ | |||
| Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``. | ||||
|  | @ -238,17 +238,6 @@ was executed ahead of the ``test_method``. | |||
| 
 | ||||
| .. _pdb-unittest-note: | ||||
| 
 | ||||
| .. note:: | ||||
| 
 | ||||
|     Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will | ||||
|     disable tearDown and cleanup methods for the case that an Exception | ||||
|     occurs. This allows proper post mortem debugging for all applications | ||||
|     which have significant logic in their tearDown machinery. However, | ||||
|     supporting this feature has the following side effect: If people | ||||
|     overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to | ||||
|     to overwrite ``debug`` in the same way  (this is also true for standard | ||||
|     unittest). | ||||
| 
 | ||||
| .. note:: | ||||
| 
 | ||||
|     Due to architectural differences between the two frameworks, setup and | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| """ discovery and running of std-library "unittest" style tests. """ | ||||
| import functools | ||||
| import sys | ||||
| import traceback | ||||
| 
 | ||||
|  | @ -107,6 +108,7 @@ class TestCaseFunction(Function): | |||
|     nofuncargs = True | ||||
|     _excinfo = None | ||||
|     _testcase = None | ||||
|     _need_tearDown = None | ||||
| 
 | ||||
|     def setup(self): | ||||
|         self._testcase = self.parent.obj(self.name) | ||||
|  | @ -115,6 +117,8 @@ class TestCaseFunction(Function): | |||
|             self._request._fillfixtures() | ||||
| 
 | ||||
|     def teardown(self): | ||||
|         if self._need_tearDown: | ||||
|             self._testcase.tearDown() | ||||
|         self._testcase = None | ||||
|         self._obj = None | ||||
| 
 | ||||
|  | @ -187,29 +191,45 @@ class TestCaseFunction(Function): | |||
|     def stopTest(self, testcase): | ||||
|         pass | ||||
| 
 | ||||
|     def _handle_skip(self): | ||||
|         # implements the skipping machinery (see #2137) | ||||
|         # analog to pythons Lib/unittest/case.py:run | ||||
|         testMethod = getattr(self._testcase, self._testcase._testMethodName) | ||||
|         if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr( | ||||
|             testMethod, "__unittest_skip__", False | ||||
|         ): | ||||
|             # If the class or method was skipped. | ||||
|             skip_why = getattr( | ||||
|                 self._testcase.__class__, "__unittest_skip_why__", "" | ||||
|             ) or getattr(testMethod, "__unittest_skip_why__", "") | ||||
|             self._testcase._addSkip(self, self._testcase, skip_why) | ||||
|             return True | ||||
|         return False | ||||
| 
 | ||||
|     def runtest(self): | ||||
|         if self.config.pluginmanager.get_plugin("pdbinvoke") is None: | ||||
|         testMethod = getattr(self._testcase, self._testcase._testMethodName) | ||||
| 
 | ||||
|         class _GetOutOf_testPartExecutor(KeyboardInterrupt): | ||||
|             """Helper exception to get out of unittests's testPartExecutor.""" | ||||
| 
 | ||||
|         unittest = sys.modules.get("unittest") | ||||
| 
 | ||||
|         reraise = () | ||||
|         if unittest: | ||||
|             reraise += (unittest.SkipTest,) | ||||
| 
 | ||||
|         @functools.wraps(testMethod) | ||||
|         def wrapped_testMethod(*args, **kwargs): | ||||
|             try: | ||||
|                 self.ihook.pytest_pyfunc_call(pyfuncitem=self) | ||||
|             except reraise: | ||||
|                 raise | ||||
|             except Exception as exc: | ||||
|                 expecting_failure_method = getattr( | ||||
|                     testMethod, "__unittest_expecting_failure__", False | ||||
|                 ) | ||||
|                 expecting_failure_class = getattr( | ||||
|                     self, "__unittest_expecting_failure__", False | ||||
|                 ) | ||||
|                 expecting_failure = expecting_failure_class or expecting_failure_method | ||||
|                 self._need_tearDown = True | ||||
| 
 | ||||
|                 if expecting_failure: | ||||
|                     raise | ||||
| 
 | ||||
|                 raise _GetOutOf_testPartExecutor(exc) | ||||
| 
 | ||||
|         self._testcase._wrapped_testMethod = wrapped_testMethod | ||||
|         self._testcase._testMethodName = "_wrapped_testMethod" | ||||
|         try: | ||||
|             self._testcase(result=self) | ||||
|         else: | ||||
|             # disables tearDown and cleanups for post mortem debugging (see #1890) | ||||
|             if self._handle_skip(): | ||||
|                 return | ||||
|             self._testcase.debug() | ||||
|         except _GetOutOf_testPartExecutor as exc: | ||||
|             raise exc.args[0] from exc.args[0] | ||||
| 
 | ||||
|     def _prunetraceback(self, excinfo): | ||||
|         Function._prunetraceback(self, excinfo) | ||||
|  |  | |||
|  | @ -537,24 +537,28 @@ class TestTrialUnittest: | |||
|         ) | ||||
|         result.stdout.fnmatch_lines( | ||||
|             [ | ||||
|                 "test_trial_error.py::TC::test_four FAILED", | ||||
|                 "test_trial_error.py::TC::test_four SKIPPED", | ||||
|                 "test_trial_error.py::TC::test_four ERROR", | ||||
|                 "test_trial_error.py::TC::test_one FAILED", | ||||
|                 "test_trial_error.py::TC::test_three FAILED", | ||||
|                 "test_trial_error.py::TC::test_two FAILED", | ||||
|                 "test_trial_error.py::TC::test_two SKIPPED", | ||||
|                 "test_trial_error.py::TC::test_two ERROR", | ||||
|                 "*ERRORS*", | ||||
|                 "*_ ERROR at teardown of TC.test_four _*", | ||||
|                 "NOTE: Incompatible Exception Representation, displaying natively:", | ||||
|                 "*DelayedCalls*", | ||||
|                 "*_ ERROR at teardown of TC.test_two _*", | ||||
|                 "NOTE: Incompatible Exception Representation, displaying natively:", | ||||
|                 "*DelayedCalls*", | ||||
|                 "*= FAILURES =*", | ||||
|                 "*_ TC.test_four _*", | ||||
|                 "*NameError*crash*", | ||||
|                 # "*_ TC.test_four _*", | ||||
|                 # "*NameError*crash*", | ||||
|                 "*_ TC.test_one _*", | ||||
|                 "*NameError*crash*", | ||||
|                 "*_ TC.test_three _*", | ||||
|                 "NOTE: Incompatible Exception Representation, displaying natively:", | ||||
|                 "*DelayedCalls*", | ||||
|                 "*_ TC.test_two _*", | ||||
|                 "*NameError*crash*", | ||||
|                 "*= 4 failed, 1 error in *", | ||||
|                 "*= 2 failed, 2 skipped, 2 errors in *", | ||||
|             ] | ||||
|         ) | ||||
| 
 | ||||
|  | @ -1096,3 +1100,32 @@ def test_exit_outcome(testdir): | |||
|     ) | ||||
|     result = testdir.runpytest() | ||||
|     result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"]) | ||||
| 
 | ||||
| 
 | ||||
| def test_trace(testdir, monkeypatch): | ||||
|     calls = [] | ||||
| 
 | ||||
|     def check_call(*args, **kwargs): | ||||
|         calls.append((args, kwargs)) | ||||
|         assert args == ("runcall",) | ||||
| 
 | ||||
|         class _pdb: | ||||
|             def runcall(*args, **kwargs): | ||||
|                 calls.append((args, kwargs)) | ||||
| 
 | ||||
|         return _pdb | ||||
| 
 | ||||
|     monkeypatch.setattr("_pytest.debugging.pytestPDB._init_pdb", check_call) | ||||
| 
 | ||||
|     p1 = testdir.makepyfile( | ||||
|         """ | ||||
|         import unittest | ||||
| 
 | ||||
|         class MyTestCase(unittest.TestCase): | ||||
|             def test(self): | ||||
|                 self.assertEqual('foo', 'foo') | ||||
|     """ | ||||
|     ) | ||||
|     result = testdir.runpytest("--trace", str(p1)) | ||||
|     assert len(calls) == 2 | ||||
|     assert result.ret == 0 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue