3

I have a plugin that implements the following hooks:

def pytest_runtest_setup(item):
    item.config.bla = Bla()

def pytest_runtest_teardown(item):
    item.config.bla.do_bla()
    item.config.bla = None

All works fine until some tests start to throw AttributeError: 'NoneType' object has no attribute 'do_bla' and indeed, item.config.bla is None

This happens in tests which I marked as

@pytest.mark.skip(reason='bla bla')
def test_bla():
    pass

I tried ipdb-ing the setup hook - but it is not called, while the teardown is. Does it make sense that setups are not called for skip tests while teardowns are?

I can wrap my teardown with try, except but I want to verify the root cause...

CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • why would item.config.blaa be none? you should get AttributeError if item.config.bla was never setup, I think it got setup, and then teardown multiple times (first one ssets it to None, second faild) – E.Serra Jun 03 '20 at 15:14

1 Answers1

2

The problem seems to be that the pytest_runtest_setup hook is implemented by several components in pytest itself, one being the skipping module (_pytest/skipping.py).
The implementation does something like this:

@hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
    ...
    for skip_info in item.iter_markers(name="skip"):
        item._store[skipped_by_mark_key] = True
        if "reason" in skip_info.kwargs:
            skip(skip_info.kwargs["reason"])
        elif skip_info.args:
            skip(skip_info.args[0])
        else:
            skip("unconditional skip")

e.g. if it finds a skip mark, it raises Skipped() (which is basically all that skip does), which is caught only after any pre-test and test hooks would be executed.

I don't know if this intentional, but you obviously have to expect this behavior (which you can easily do in your case by catching the AttributeError, as you wrote).

MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • I hoped for something like checking if the test skipped and then skipping the teardown, bit couldn't find it from the item object, so yes, the attribute error is my best option. – CIsForCookies Jun 04 '20 at 00:21
  • 1
    The information _is_ actually stored in the item (`item._store[skipped_by_mark_key] = True`), but this is undocumented / private stuff I wouldn't rely on. I didn't find any documented way to access it. – MrBean Bremen Jun 04 '20 at 05:01