18

When I run pytest in the vscode debugger with "Uncaught Exceptions" checked, and there are test errors, no uncaught exceptions occur, because pytest catches them do its results reporting. How can I tell pytest to just let the exceptions happen? So that I can catch them in the vscode debugger?

Basically I want behavior like --pdb but I want it to launch the vscode debugger not pdb. The flag --pdbcls sounds promising but not sure what <module>:<class> to give it.

Note: Normally I would just have it break on Raised Exceptions. But our code has tons of raised-but-caught exceptions so this option is not useful.

Here's a video of vscode not breaking when an AssertionError fires while debugging a pytest test:

enter image description here

@rioV8's suggestion below does break the on the exception, but for some reason there is no stack which means you can't debug from there:

enter image description here

I must be missing something because no one else seems to need this capability. But to me this seems like absolutely the most basic simplest thing one could do with a testing framework and a debugger: as a developer I want to debug from the point where the error is raised.

There must be some totally other way people are using a debugger with pytest, some obvious technique I'm overlooking.

Philip
  • 1,691
  • 4
  • 21
  • 41
  • it just works fine for me, since you have given any code snippet I can only guess. If you have used `with pytest.raises` in your tests then you have actually caught the exception so debugger wont break – Jahan Balasubramaniam Jun 19 '20 at 17:11
  • It works fine for you in that pytest itself exits with an exception? I doubt that unless you arranged for it somehow, because normally pytest catches exceptions so that it can report at the end of the run which tests failed. Don't you agree that normally pytest catches all exceptions so that it can report test failures? – Philip Jun 19 '20 at 18:44
  • it does break if i dont have `with pytest.raises` – Jahan Balasubramaniam Jun 20 '20 at 02:15
  • We are not using `pytest.raises` and it does not break. I'm running with the `Debug` button to the right of the test in the `TEST:PYTHON` tree view. It prints the name of the exception in the `Python Test Log` output pane but it does not break. If I check **Raised Exceptions** in the debugger it will break, but I can't do that because there are too many false positives, unrelated exceptions we raise and catch. If vscode let you choose which exceptions to break on it would help a lot. – Philip Jun 21 '20 at 22:05
  • I added a video to the original question showing how vscode does not break for me. – Philip Jun 21 '20 at 23:28

4 Answers4

28

I raised an issue with pytest and they made a suggestion that works. Add this to your conftest.py:

import os
import pytest

if os.getenv('_PYTEST_RAISE', "0") != "0":

    @pytest.hookimpl(tryfirst=True)
    def pytest_exception_interact(call):
        raise call.excinfo.value

    @pytest.hookimpl(tryfirst=True)
    def pytest_internalerror(excinfo):
        raise excinfo.value

Then in your "request": "test" launch.json you can toggle that env var:

    {
        "name": "Debug Tests",
        "type": "python",
        "request": "test",
        "console": "integratedTerminal",
        "justMyCode": true,
        "env": {
            "_PYTEST_RAISE": "1"
        },
    }

If _PYTEST_RAISE is set and you check only the Uncaught Exceptions box you will break right where the uncaught exception was raised not before.

I also opened an issue with debugpy and they had some ideas, but no solution today. The _PYTEST_RAISE trick works 100% solves this for me.

Brent
  • 4,153
  • 4
  • 30
  • 63
Philip
  • 1,691
  • 4
  • 21
  • 41
  • 5
    When I tried this today (2022-05-17), VSCode complained that "test" is an invalid request type. It says `Request type of configuration. Can be "launch" or "attach".` – Ben Lindsay May 17 '22 at 16:34
  • 1
    @BenLindsay mentioned in docs https://code.visualstudio.com/docs/editor/debugging, supposedly "purpose" should help, but I haven't had a lot of luck https://code.visualstudio.com/docs/python/testing#_debug-tests – Sterling Jun 25 '22 at 17:56
1

I've noticed a few things that might be relevant. Even though I'm not ready to dig deeper right now, I'll post them here in case they might be useful to someone.

Workaround:

A simple hack I've used is to just (temporarily) call the relevant test case directly and "Run and Debug" the file rather than using the "Debug Test Case" button:


def test_example():
    assert False


test_example()

Result:

enter image description here

Notes:

  1. The important difference in behavior correlates with specifying "request": "test" vs specifying "request": "launch" in the launch.json configuration. (There is also a difference between the two types in whether or not the justMyCode option gets applied from a global launch configuration as described here).
  2. If you have a python "request": "test" specified, then VSCode doesn't appear to let you debug the file in launch mode as I did above.
  3. In my scenario, I had a test failing because of an exception I was causing in a third-party library (my fault, of course). Breakpoints in third-party code are apparently only triggered if the "justMyCode": false option is added.

launch.json:

{
    "version": "0.2.0",
    "configurations": [
        
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": false,            
        },
        // {
        //     "name": "Debug Unit Test",
        //     "type": "python",
        //     "request": "test",
        //     "console": "integratedTerminal",
        //     "justMyCode": false,
        // }        
    ]
}
teichert
  • 3,963
  • 1
  • 31
  • 37
  • I've used a similar temporary hack. Any idea on how to have a temporary hack that still runs some slightly more advanced pytest functionality such as using fixtures? I tried [calling pytest from python code](https://docs.pytest.org/en/7.1.x/how-to/usage.html#calling-pytest-from-python-code) but it doesn't stop on the breakpoints. – Sterling Jun 25 '22 at 17:44
0

The behavior is not identical but the test runner stops at the uncaught exception.

Add the file conftest.py to the directory with your tests.

# conftest.py
def pytest_exception_interact(node, call, report):
  print( call.excinfo.traceback[0] )
  pass

This adds a hook for any uncaught exceptions.

Place a break point at the line with pass.

Then Debug the test.

Look in the Debug console and you see the file and line number where the exception happened.

test_example.py F  File 'path\\test_example.py':4 in test_example
  assert False

And the debugger stops at the pass line.

You can make this hook function as fancy as you like.

rioV8
  • 24,506
  • 3
  • 32
  • 49
  • 2
    Thanks I can try this. But I have a very hard time understanding how pytest does not have a flag or mode for this. It seems like the most normal natural thing in the world to want the debugger to break where you your test fails. How do people debug tests in general? – Philip Jun 22 '20 at 13:26
  • 1
    I tried this. It does break there but there's no stack, so it can't be used to debug. I put picture and comments in the original question. – Philip Jun 23 '20 at 21:27
  • I think they are working on something to help with this: https://github.com/microsoft/debugpy/issues/275 – Philip Jun 23 '20 at 22:04
  • @Philip : Then add a break point at the line shown in the debug console and try again – rioV8 Jun 24 '20 at 02:15
  • 1
    Yes it's possible to run pytest, see where the exception happened in the console, find that spot in the code, then put a breakpoint there before it raises. I don't consider that an answer to this question or a very good solution. But yes it works in a pinch, I've been doing that. – Philip Jun 24 '20 at 12:08
  • @Philip Your initial question was: "How can I find the exceptions that my code does not catch/handle?". This without modifying pytest. With this method you know where the exception happens. Before, you did **NOT** know that. This is the only method using an unmodified pytest. How often does this happen? The only other way that makes it more easier for you is let somebody else fix your bugs. – rioV8 Jun 24 '20 at 16:30
  • I added answer from pytest folks. Using `@pytest.hookiml` they are able to make pytest raise the exception. Then vscode breaks at the right spot, works well. You had the right idea though, but this is taking it one test further to use `@pytest.hookiml`. – Philip Jun 24 '20 at 20:00
0

This is similar to the answer above, however I prefer to have it automatically engage when debugging a test. Put the following in the conftest.py:

# conftest.py
import sys
import pytest

def is_debugging():
    if 'debugpy' in sys.modules:
        return True
    return False


# enable_stop_on_exceptions if the debugger is running during a test
if is_debugging():
    @pytest.hookimpl(tryfirst=True)
    def pytest_exception_interact(call):
        raise call.excinfo.value

    @pytest.hookimpl(tryfirst=True)
    def pytest_internalerror(excinfo):
        raise excinfo.value

What is happening is that it checks to see if "debugpy" is active. If it is, we assume we are debugging a test and stop on uncaught exceptions. Otherwise, it behaves as normal. This means if you run pytest from the command-line, the "debugpy" is not active, and it will behave as normal. One nice side effect is that this will also stop the debugger if there is any exception including import exceptions, language exceptions, etc.

Bryce
  • 83
  • 3