Skip to content

request.getfixturevalue crashes in certain cases. #9984

@petebman

Description

@petebman

conftest.py

import pytest

@pytest.fixture
def fixture_a(request):
  yield 'a'

@pytest.fixture(scope="session")
def accumulator():
  # This fixture that accumulates info from each test case (via the record_data fixture)
  # and then writes it to a database once we're all done
  accumulator = []
  yield accumulator
  # IRL we'd upload this to a database or something.  In this minimum repro we'll just print it.
  print(f'Printed by accumulator.  All tests recorded {accumulator}')

@pytest.fixture
def record_data(accumulator, request):

  test_data = {}
  yield test_data
  print(f"Printed by record_data fixture - test provided data: {test_data}")

  # If fixture_a was used by the test case, record its value too.
  if 'fixture_a' in request.fixturenames:
    test_data['fixture_a'] = request.getfixturevalue('fixture_a')

  accumulator.append(test_data)

I have a fixture called record_data that lets a test case store some extra info and then send it off to a database once all the tests are done running. If I run the following test case:

def test_01(record_data):
  record_data['foo'] = 'blah'
  assert True

I get the following output:

Printed by record_data fixture - test provided data: {'foo': 'blah'}
Printed by accumulator.  All tests recorded [{'foo': 'blah'}]

If I add a second test case that uses fixture_a then the record_data fixture will pick that up and record some information about fixture_a too:

def test_02(fixture_a, record_data):
  record_data['foo'] = 'blah from test_02'
  assert fixture_a == 'a'

Now I get this output:

Printed by record_data fixture - test provided data: {'foo': 'blah'}
Printed by record_data fixture - test provided data: {'foo': 'blah from test_02'}
Printed by accumulator.  All tests recorded [{'foo': 'blah'}, {'foo': 'blah from test_02', 'fixture_a': 'a'}]

By using request.fixturenames and request.getfixturevalue I can snoop the value of fixture_a without explicitly adding it to the arg list of record_data and thus not forcing tests to use fixture_a. I thought this was pretty smart and clever. Unforunately, this working appears to depend on the order that the fixtures are listed in the arg list. If I do

def test_03(record_data, fixture_a):  # The only change is the order of the fixtures
  record_data['foo'] = 'test_03 is where the bug happens'
  assert fixture_a == 'a'

Then things go wrong and I get the following error message:

    @pytest.fixture
    def record_data(accumulator, request):
     
      test_data = {}
      yield test_data
      print(f'Printed by record_data fixture - test provided data: {test_data}')
     
      # If fixture_a was used by the test case, record its value too.
      if 'fixture_a' in request.fixturenames:
>       test_data['fixture_a'] = request.getfixturevalue('fixture_a')
E       AssertionError

I dove in with my trusty debugger. The AssertionError I'm getting comes from request.getfixturevalue because cached_result got set to None here when the fixture was finalized.

Here's the version of everything I'm using:

(pytest_venv) petebman@petebman:~/pytest_fixture_bug$ python3 -m pip freeze
attrs==21.4.0
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.0.9
pytest==7.1.2
tomli==2.0.1

I originally ran into this issue in pytest version 6.2.3 but I did the minimum viable repro on the latest version to make sure I wasn't complaining about something that had already been fixed.

I wonder if there's a way to keep track of which fixtures have been finalized and which ones haven't yet other than setting cached_value to None? Alternatively is there a different way I should try to do what I'm trying to do here? It seems reasonable that if a fixture's name is in request.fixturenames then you should be able to get a value via request.getfixturevalue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: questiongeneral question, might be closed after 2 weeks of inactivity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions