Execute pytest fixture teardown if debugging is quit

Viewed 310

The Problem

I set up a simple test like

import pytest
import pathlib


@pytest.fixture
def some_resource():
    testdir = pathlib.Path("i_want_to_be_deleted")
    testdir.mkdir()
    yield
    testdir.rmdir()


def test_dummy_succeeds(some_resource):
    assert pathlib.Path("i_want_to_be_deleted").exists()


def test_dummy_fails(some_resource):
    assert False

If I run this test using pytest, the directory i_want_to_be_deleted is created for the test, and deleted afterwards (also for the failed test). This is as expected.

But if I set a breakpoint somewhere after directory creation, run this in the debugger, and stop the debugging, the directory i_want_to_be_deleted persists.

Enhancing this example by implementing a context manager, as suggested in an answer by @BramAppel, unfortunately does not help:

import pytest
import pathlib


class ContextPath:
    def __init__(self, pathstring):
        self.path = pathlib.Path(pathstring)

    def __enter__(self):
        self.path.mkdir()

    # The latter 3 args are just a boilerplate convention
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.path.rmdir()


@pytest.fixture
def some_resource():
    with ContextPath("i_want_to_be_deleted"):
        yield


def test_dummy_succeeds(some_resource):
    assert pathlib.Path("i_want_to_be_deleted").exists()

Is there a way to have pytest execute the teardown part of the fixture, regardless of ending a debugging session?

As a remark, the Matlab debugger exhibits the requested behavior.

The Environment

I'm using pytest, which tells me that I'm on

platform linux -- Python 3.6.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1

Furthermore, I use the vs code August 2020 release with the Test Explorer UI and Python Test Explorer extensions to run and debug the tests.

1 Answers

Have you tried to implement a context manager? This guarantees that the __enter__ and __exit__ method will be executed. Although I have not tested this with your exact setup, this might be a solution to your problem.

import pathlib
import pytest


class ContextPath(pathlib.Path):
    def __init__(self, *args):
        super().__init__(*args)

    def __enter__(self):
        self.mkdir()

    # The latter 3 args are just a boilerplate convention
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.rmdir()


@pytest.fixture
def some_resource():
    with ContextPath("i_want_to_be_deleted"):
        yield
Related