Repository navigation
Parametrized fixture content depends on test function order. #5693
Copy link
Copy link
Closed
Labels
invalidtopic: fixturesanything involving fixtures directly or indirectlyanything involving fixtures directly or indirectlytype: questiongeneral question, might be closed after 2 weeks of inactivitygeneral question, might be closed after 2 weeks of inactivity
Description
Fixture parametrized at the function definition, parameter scope='class'
When the same fixture (scope='class') is used by 2 functions:
- function1 without parameters (path_default)
- function2 with 2 parameters (path_param_0, path_param_1)
Result:
- function1 (Passed)
- function2['path_param_0'] (Failed)
function2 gets the ids of the first parameter, however it receives the default value.
The fixture is not called for the first parameter, the 'slot' seems to be already used by the default value. - function2['path_param_1'] (Passed)
But, if your reverse the call order, every test passes.
- function2 with 2 parameters (path_param_0, path_param_1)
- function1 without parameters (default value)
Code:
import pytest
@pytest.fixture(scope='class')
def menunode_path(request):
result = getattr(request, 'param', None)
if result is not None:
assert(result in ('path_param_0', 'path_param_1'))
else:
result = 'path_default'
return result
class TestFailed(object):
def test_path(self, menunode_path):
assert(menunode_path == 'path_default')
@pytest.mark.parametrize('menunode_path', ('path_param_0', 'path_param_1'),
indirect=True, scope='class')
def test_path_param(self, menunode_path):
assert(menunode_path in ('path_param_0', 'path_param_1'))
class TestPassed(object):
@pytest.mark.parametrize('menunode_path', ('path_param_0', 'path_param_1'),
indirect=True, scope='class')
def test_path_param(self, menunode_path):
assert(menunode_path in ('path_param_0', 'path_param_1'))
def test_path(self, menunode_path):
assert(menunode_path == 'path_default')
Result:
========================== test session starts ===========================
platform linux -- Python 3.6.8, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- /home/laurent/Documents/Development/bboss/kconfigrobot/.venv/bin/python
cachedir: .pytest_cache
rootdir: /home/laurent/Documents/Development/bboss/kconfigrobot
collected 6 items
test_bugg.py::TestFailed::test_path PASSED [ 16%]
test_bugg.py::TestFailed::test_path_param[path_param_0] FAILED [ 33%]
test_bugg.py::TestFailed::test_path_param[path_param_1] PASSED [ 50%]
test_bugg.py::TestPassed::test_path_param[path_param_0] PASSED [ 66%]
test_bugg.py::TestPassed::test_path_param[path_param_1] PASSED [ 83%]
test_bugg.py::TestPassed::test_path PASSED [100%]
================================ FAILURES ================================
________________ TestFailed.test_path_param[path_param_0] ________________
self = <test_bugg.TestFailed object at 0x7f5337e96630>
menunode_path = 'path_default'
@pytest.mark.parametrize('menunode_path', ('path_param_0', 'path_param_1'),
indirect=True, scope='class')
def test_path_param(self, menunode_path):
> assert(menunode_path in ('path_param_0', 'path_param_1'))
E AssertionError: assert 'path_default' in ('path_param_0', 'path_param_1')
test_bugg.py:24: AssertionError
=================== 1 failed, 5 passed in 0.05 seconds ===================
System info:
Linux4.15.0-55-generic #60-Ubuntu SMP Tue Jul 2 18:22:20 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
anytree==2.6.0
atomicwrites==1.3.0
attrs==19.1.0
bleach==3.1.0
bumpversion==0.5.3
certifi==2019.6.16
chardet==3.0.4
Cython==0.29.12
docopt==0.6.2
docutils==0.15.1
idna==2.8
importlib-metadata==0.18
kconfiglib==12.12.1
more-itertools==7.2.0
packaging==19.0
pexpect==4.7.0
pkginfo==1.5.0.1
pluggy==0.12.0
ptyprocess==0.6.0
py==1.8.0
pydevd==1.6.1
Pygments==2.4.2
pyparsing==2.4.2a1
pytest==5.0.1
readme-renderer==24.0
requests==2.22.0
requests-toolbelt==0.9.1
robotframework==3.1.2
six==1.12.0
tqdm==4.32.2
twine==1.13.0
urllib3==1.25.3
wcwidth==0.1.7
webencodings==0.5.1
zipp==0.5.2
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
invalidtopic: fixturesanything involving fixtures directly or indirectlyanything involving fixtures directly or indirectlytype: questiongeneral question, might be closed after 2 weeks of inactivitygeneral question, might be closed after 2 weeks of inactivity
Activity
asottile commentedon Aug 4, 2019
the output seems correct to me -- a
classscoped fixture is only going to be constructed once per class -- you should refactor your tests to not have a backwards relation between fixtures and parameterization and/or refactor your fixture to not be dependent on call order and/or dropscope='class'such that the fixture is properly rebuilt for each test methodLaurentMarchelli commentedon Aug 5, 2019
Hi asottile,
Could you please tell me, where I am wrong in my understanding:
Thank's.
asottile commentedon Aug 5, 2019
This is correct, but your fixture doesn't have parameters (the
params=keyword to thefixturedecorator), your test has parametersLaurentMarchelli commentedon Aug 5, 2019
You're right it is not a Fixture problem, but a SubRequest bug:
A Subrequest without parameter must not have the same param_index as the first param in a list and should not have a param_index at all, otherwise FixtureDef.execute will return the cached value instead than calling hook.pytest_fixture_setup.
A quick fix could be:
my_cache_key = getattr(request, 'param_index', None)
Hope it helps ;-)
Regards,
asottile commentedon Aug 5, 2019
I don't think that's right, but if you think it will fix the problem please propose a PR so it can be discussed there
LaurentMarchelli commentedon Aug 5, 2019
Could you please elaborate why you don't think that's right.
asottile commentedon Aug 5, 2019
the fixture has no
params=so it shouldn't be being rebuilt due to an related@parametrize-- at least that's my understanding of thisLaurentMarchelli commentedon Aug 5, 2019
So how do you explain indirect=True ?
asottile commentedon Aug 5, 2019
that only makes sense with
scope='function'right?RonnyPfannschmidt commentedon Aug 5, 2019
Indirect is supposed to work with all scopes
asottile commentedon Aug 5, 2019
@RonnyPfannschmidt won't that cause all fixtures of all scopes to constantly be recreated whenever using
indirect? that doesn't seem rightLaurentMarchelli commentedon Aug 5, 2019
No it's seems to make sense with all scopes except the 'function', as the 'function' scope is the easier to manage, your data is not shared by anyone.
LaurentMarchelli commentedon Aug 5, 2019
My understanding, is the fixture is recreated when the cache does not contains the requested param_index.
asottile commentedon Aug 5, 2019
which would be always (unless your params is of length 1, which is not a useful
parametrize):LaurentMarchelli commentedon Aug 5, 2019
So, if we are in line, the fixture is created for each parameter as soon as his value is not in the cache, and the teardown is called for the one that "was" in the cached, whatever the scope.
asottile commentedon Aug 5, 2019
yes, which would essentially demote all fixtures to
functionscopedLaurentMarchelli commentedon Aug 5, 2019
Not really, only all fixtures that are called by functions indirect parameter.
But there is other big differences between these two scopes, the 'function' scope runs in its own private address space and all fixtures in the 'class' scope run in the same address space.
asottile commentedon Aug 5, 2019
not sure what you mean by address space, there's only one address space per process
asottile commentedon Aug 5, 2019
the
scopereally just controls the lifetime of a fixtureLaurentMarchelli commentedon Aug 5, 2019
Yes, it is also why they do not use the same 'address space'.
In the following example all 'class' fixtures share the same
selfbut a 'function' fixture does not share itselfwith anyone.It's what I meant by 'address space', in the following sample there is at least a 'class instance' for test execution, one for all 'class' fixtures and one per 'function' fixture.
RonnyPfannschmidt commentedon Aug 5, 2019
the general correct term for that is
namespaceLaurentMarchelli commentedon Aug 5, 2019
@RonnyPfannschmidt
Thank's I will use it in the future if it makes more sense to you.
I you have a sec, have a look on the proposed fixed, I hope it will help.
I apologize, but I do not have any more available time to fill a PR.
Regards,
RonnyPfannschmidt commentedon Aug 5, 2019
i wont in near future, im still on my hiatus fro general project work
LaurentMarchelli commentedon Aug 6, 2019
Thank's guys for your time, it was a nice tchat.
As you can see below, it fixes at least the described problem.
I attached the file with modifications from 3173815, and also the test file.
If someone has the time to create a PR and take the credit ... feel free.
By the way, it could also help [#4948]
Regards,
5693_bug_fixed.zip