Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unittest: do not use TestCase.debug() with --pdb #5996

Merged
merged 4 commits into from
Dec 3, 2019

Conversation

blueyed
Copy link
Contributor

@blueyed blueyed commented Oct 18, 2019

Fixes #5991.

/cc @mbyt

TODO:

@blueyed blueyed added type: bug problem that needs to be addressed plugin: unittest related to the unittest integration builtin plugin plugin: debugging related to the debugging builtin plugin labels Oct 18, 2019
@blueyed blueyed changed the base branch from master to features October 18, 2019 19:24
Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you positive this is the right approach? I ask because the original change was meant for people to be able to debug teardown methods in unittest.TestCase subclasses.

@blueyed

This comment has been minimized.

@blueyed blueyed changed the title unittest: do not use TestCase.debug() with --pdb [WIP] unittest: do not use TestCase.debug() with --pdb Oct 18, 2019
@nicoddemus

This comment has been minimized.

@blueyed

This comment has been minimized.

blueyed added a commit to blueyed/pytest that referenced this pull request Oct 20, 2019
Reverts pytest-dev#1890, which needs to
be fixed/addressed in another way
(pytest-dev#5996).

Fixes pytest-dev#5991.
blueyed added a commit to blueyed/pytest that referenced this pull request Oct 20, 2019
Reverts pytest-dev#1890, which needs to
be fixed/addressed in another way
(pytest-dev#5996).

Fixes pytest-dev#5991.
blueyed added a commit to blueyed/pytest that referenced this pull request Oct 21, 2019
Reverts pytest-dev#1890, which needs to
be fixed/addressed in another way
(pytest-dev#5996).

Fixes pytest-dev#5991.
@blueyed
Copy link
Contributor Author

blueyed commented Oct 26, 2019

=> #6014 for now.

@blueyed blueyed closed this Oct 26, 2019
@blueyed blueyed deleted the unittest-debug branch October 26, 2019 01:41
@blueyed blueyed restored the unittest-debug branch November 9, 2019 05:00
@blueyed blueyed reopened this Nov 9, 2019
blueyed added a commit to pytest-dev/pytest-django that referenced this pull request Nov 9, 2019
@blueyed blueyed force-pushed the unittest-debug branch 5 times, most recently from 013a538 to 4b922e5 Compare November 9, 2019 12:27
@blueyed
Copy link
Contributor Author

blueyed commented Nov 9, 2019

I've also tried invoking the pdbinvoke plugin directly from within the wrapped function (simpler in general), but it missing properties that come via from_call etc normally.
This would basically make it more behave like "just put your pdb.set_trace" where you need it.
So given that it might be better to have a more specific method that could be used here then (from within the wrapped call), when --pdb is used - or give using the existing post_mortem a try.

Fixes pytest-dev#5991
Fixes pytest-dev#3823

Ref: pytest-dev/pytest-django#772
Ref: pytest-dev#1890
Ref: pytest-dev/pytest-django#782

- inject wrapped testMethod

- adjust test_trial_error

- add test for `--trace` with unittests
@nicoddemus
Copy link
Member

Thanks @blueyed, I'm convinced that we should not use TestCase.debug() at all.

I think we should pdbinvoke directly in the addError and addFailure if --pdb is passed in the command line. I will try to make this in a separate PR, if I'm successful, I believe this will supersed this one and #6014.

@nicoddemus
Copy link
Member

I think we should pdbinvoke directly in the addError and addFailure if --pdb is passed in the command line.

Just tried that, but unfortunately unittest calls tearDown before reporting errors and failures to the underlying "result" object (comments are mine):

            if outcome.success:
                outcome.expecting_failure = expecting_failure
                with outcome.testPartExecutor(self, isTest=True):
                    self._callTestMethod(testMethod)  # <-- call test method
                outcome.expecting_failure = False
                with outcome.testPartExecutor(self):
                    self._callTearDown()  # <-- call teardown

            self.doCleanups()
            for test, reason in outcome.skipped:
                self._addSkip(result, test, reason)
            self._feedErrorsToResult(result, outcome.errors)  # <-- call addError, addFailure, etc.

So unittest doesn't really gives us a chance to "hook" into the result of the test method call for pytest to jump into the debugger, before tearDown is called.

You approach (wrapping the actual method call) seems like the only one that can actually work, thanks for looking at it into such detail.

- Isolate logic for getting expected exceptions
- Use original method name, as users see it when entering the debugger
Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @blueyed, for the research and patience.

While making my comments I was also making the changes myself and testing them, so I decided to just push my changes intead of posting a bunch of diff/suggestions. Feel free to take a look at my comment and accept/reject what you think it is relevant. 👍

Otherwise this LGTM and ready to merge once CI passes.

Thanks again.


raise _GetOutOf_testPartExecutor(exc)

self._testcase._wrapped_testMethod = wrapped_testMethod
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should override the original method, otherwise the internal name will show up to the user when entering PDB:

self = <foo.T testMethod=_wrapped_testMethod>

    def test_foo(self):
        self.filename = 'hello'
>       assert 0

class _GetOutOf_testPartExecutor(KeyboardInterrupt):
"""Helper exception to get out of unittests's testPartExecutor."""

unittest = sys.modules.get("unittest")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point unittest has definitely been imported, no need to guard against that. 👍

@@ -115,6 +117,8 @@ def setup(self):
self._request._fillfixtures()

def teardown(self):
if self._need_tearDown:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we need this? Even if I comment these two lines, tear down still gets called for this example:

from unittest import TestCase, expectedFailure

class T(TestCase):

    def tearDown(self):
        print('*********** tearDown')
        self.filename = None

    @expectedFailure
    def test_foo(self):
        self.filename = 'hello'
        assert 0
λ pytest  .tmp\foo.py -sq
*********** tearDown
x
1 xfailed in 0.05s

Uncommenting them actually shows tearDown being called twice:

 λ pytest  .tmp\foo.py -sq
*********** tearDown
x*********** tearDown

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, we actually need it, but only if a test is not an expected failure

@@ -238,17 +238,6 @@ was executed ahead of the ``test_method``.

.. _pdb-unittest-note:

.. note::

Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Otherwise 'normal' failures won't call teardown explicitly
@@ -217,6 +220,7 @@ def wrapped_testMethod(*args, **kwargs):
expecting_failure = self._expecting_failure(testMethod)
if expecting_failure:
raise
self._needs_explicit_tearDown = True
raise _GetOutOf_testPartExecutor(exc)

setattr(self._testcase, self._testcase._testMethodName, wrapped_testMethod)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it pass #5996 (comment) then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! 👍

@blueyed
Copy link
Contributor Author

blueyed commented Nov 12, 2019

Thanks for looking into this!
I think it is worth revisiting completely though.
After all using post_mortem(sys.exc_info()[2]) in except Exception: comes a long way already, and it should be possible to invoke the normal hooks from there, so that e.g. capturing etc is handled.
It likely means to decouple / change the code around CallInfo.from_call etc though.
I will look into that later.

@nicoddemus
Copy link
Member

I will look into that later.

Sounds good, but we can consider merging it already (as it solves a bunch of issues and doesn't introduce regressions or external changes), and follow up later with any other changes that improve it. What do you think?

@blueyed
Copy link
Contributor Author

blueyed commented Nov 12, 2019

I'd like to avoid unnecessary diff churn, and think it might be a good opportunity to improve internals, since it shows that it is currently not flexible enough.
See blueyed#107 for a dirty POC.

@blueyed blueyed closed this Nov 27, 2019
@nicoddemus nicoddemus reopened this Dec 3, 2019
@nicoddemus nicoddemus merged commit 1c4a672 into pytest-dev:features Dec 3, 2019
@nicoddemus
Copy link
Member

@blueyed I figured you won't be working on this anymore, so decided to re-open it, fix conflicts and merge it.

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin: debugging related to the debugging builtin plugin plugin: unittest related to the unittest integration builtin plugin type: bug problem that needs to be addressed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants