Repository navigation
pytest 8.0 sorting tests with multiple parameterization is broken #12008
Copy link
Copy link
Closed
Labels
topic: fixturesanything involving fixtures directly or indirectlyanything involving fixtures directly or indirectlytopic: parametrizerelated to @pytest.mark.parametrizerelated to @pytest.mark.parametrizetype: bugproblem that needs to be addressedproblem that needs to be addressedtype: regressionindicates a problem that was introduced in a release which was working previouslyindicates a problem that was introduced in a release which was working previously
Description
Сontinue #11976
The solution from #11976 did not solve the problem with sorting tests with multiple parameters
Tests
import pytest
@pytest.mark.parametrize('proto', ['serial', 'telnet', 'ssh'], scope='class')
@pytest.mark.parametrize('unit', [1, 2, 3], scope='class')
class TestA:
def test_one(self, proto, unit):
pass
def test_two(self, proto, unit):
passCollecting items with pytest 7.4.4
11:07 $ pytest --co
=========================================================================== test session starts ===========================================================================
platform linux -- Python 3.11.6, pytest-7.4.4, pluggy-1.4.0 -- /home/lolik/.cache/pypoetry/virtualenvs/pytest_strain-FQAfhJsh-py3.11/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.11.6', 'Platform': 'Linux-6.7.4-arch1-1-x86_64-with-glibc2.39', 'Packages': {'pytest': '7.4.4', 'pluggy': '1.4.0'}, 'Plugins': {'metadata': '3.1.0'}, 'GIT_BRANCH': 'master'}
rootdir: /home/lolik/Projects/straing/pytest
configfile: pytest.ini
plugins: metadata-3.1.0
collected 18 items
<Package tests>
<Module test_asd.py>
<Class TestA>
<Function test_one[1-serial]>
<Function test_two[1-serial]>
<Function test_one[1-telnet]>
<Function test_two[1-telnet]>
<Function test_one[1-ssh]>
<Function test_two[1-ssh]>
<Function test_one[2-serial]>
<Function test_two[2-serial]>
<Function test_one[2-telnet]>
<Function test_two[2-telnet]>
<Function test_one[2-ssh]>
<Function test_two[2-ssh]>
<Function test_one[3-serial]>
<Function test_two[3-serial]>
<Function test_one[3-telnet]>
<Function test_two[3-telnet]>
<Function test_one[3-ssh]>
<Function test_two[3-ssh]>
======================================================================= 18 tests collected in 0.01s =======================================================================
Collecting items with pytest 8.0.1
11:08 $ pytest --co
=========================================================================== test session starts ===========================================================================
platform linux -- Python 3.11.6, pytest-8.0.1, pluggy-1.4.0 -- /home/lolik/.cache/pypoetry/virtualenvs/pytest_strain-FQAfhJsh-py3.11/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.11.6', 'Platform': 'Linux-6.7.4-arch1-1-x86_64-with-glibc2.39', 'Packages': {'pytest': '8.0.1', 'pluggy': '1.4.0'}, 'Plugins': {'metadata': '3.1.0'}, 'GIT_BRANCH': 'master'}
rootdir: /home/lolik/Projects/straing/pytest
configfile: pytest.ini
plugins: metadata-3.1.0
collected 18 items
<Dir pytest>
<Package tests>
<Module test_asd.py>
<Class TestA>
<Function test_one[1-serial]>
<Function test_two[1-serial]>
<Function test_one[2-serial]>
<Function test_two[2-serial]>
<Function test_one[2-telnet]>
<Function test_two[2-telnet]>
<Function test_one[1-telnet]>
<Function test_two[1-telnet]>
<Function test_one[3-telnet]>
<Function test_two[3-telnet]>
<Function test_one[3-serial]>
<Function test_two[3-serial]>
<Function test_one[3-ssh]>
<Function test_two[3-ssh]>
<Function test_one[2-ssh]>
<Function test_two[2-ssh]>
<Function test_one[1-ssh]>
<Function test_two[1-ssh]>
======================================================================= 18 tests collected in 0.01s =======================================================================
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
topic: fixturesanything involving fixtures directly or indirectlyanything involving fixtures directly or indirectlytopic: parametrizerelated to @pytest.mark.parametrizerelated to @pytest.mark.parametrizetype: bugproblem that needs to be addressedproblem that needs to be addressedtype: regressionindicates a problem that was introduced in a release which was working previouslyindicates a problem that was introduced in a release which was working previously
Activity
bluetech commentedon Feb 19, 2024
Thanks, this does look wrong at first glance, I will look into it. Bisected to 09b7873 (PR #11220).
bluetech commentedon Feb 20, 2024
Minimized example:
The items are reordered by
reorder_items()in fixture.py (surely the most inscrutable function in all of pytest), whose aim is to minimize fixture setups and teardowns. In this respect it achieves its goal:Setup plan in pytest 7, has 6 setups/teardowns:
in pytest 8, has 5 setups/teardowns:
The way
@pytest.mark.parametrizeworks behind the scenes is that it basically desugars to this:In this framing, it is clearer why we want to minimize the setups/teardowns -- the fixtures can do real work, not just return the param. Both pytest 7 and 8 reorder this version to have 5 setups/teardowns.
I can't say why before 09b7873 the reordering didn't happen, and if this was intentional or accidental. Will look into it more.
RonnyPfannschmidt commentedon Feb 21, 2024
@bluetech i believe the change by @sadra-barikbin fixed a bug in pytest wrt scope ordering
it dates back to #519 and the fix corrects the test scope ordering based on values and fixture values and scopes
for further validation we might want to add a variant of the example for #519 that mixes class scope in (in which case the old order is actually correct
but the basic gist to my current understanding is that pytest no longer has ordering differences between sugared and de-sugared parameterize as now parameterize is expressed in pseudo fixtures all the way
bluetech commentedon Feb 22, 2024
Technical details of the change:
Before 09b7873, Metafunc will generate CallSpec's with separate
params(indirect parametrizations, i.e. through fixtures) andfuncargs(direct parametrizations), and the desugaring of direct to indirect (i.e.gfuncargstoparams) happened as a separate step afterpytest_generate_functions: https://github.com/pytest-dev/pytest/blob/7.4.4/src/_pytest/fixtures.py#L156.After 09b7873, Metafunc handles the desugaring itself, and there is no more
funcargs.All of this happens before
reorder_itemsanyway, so why does it affect the item ordering? The difference is theparam_indexthat the desugaring assigns to the (desguared) direct params. Before the code was this:https://github.com/pytest-dev/pytest/blob/7.4.4/src/_pytest/fixtures.py#L168-L181
The important bit is
callspec.indices[argname] = len(arg2params_list)- this basically assigns a fresh param index across all callspecs. This results in the following arg keys inreorder_items:After, there is no special handling of the param indexes of direct params, they are handled same as parametrized fixtures. This results in the following arg keys:
Rephrasing the above in a way that may be clearer:
Before, indexes for direct params were assigned in a sequential manner per
argnameafter all parametrizations are exploded, so we have a table ofargname,item(= callspec) and we assign the param index:After, the param indexes are assigned as they would for the desugaring I gave above:
bluetech commentedon Feb 22, 2024
@ShurikMen I wonder, is the example you gave realistic or do you use
indirectparams maybe? I'm mainly curious why you're settingscope='class'on yourparametrizes when the parameters are simple values. Withfunctionscope the ordering is as you expect.ShurikMen commentedon Feb 22, 2024
This is one of the simplest examples that are actually used in my projects. There are many examples with an even larger set of parameters including "mixed" scopes (class/function).
Not quite like that. Changing the order causes unwanted fixture calls. They are very expensive in terms of execution time.
It is important for me that tests with the same set of parameters are called sequentially.
With scope class (same optimal):
With scope function
Class params with function fixture
Here I experimented a lot with various combinations of scope parameters and scope fixtures, including parameterization in fixtures (pytest.fixture(param=...)) to obtain the most optimal ways to perform fixtures and tests.
RonnyPfannschmidt commentedon Feb 22, 2024
Currently when parameterize is taken for consideration, compound dependent fixtures are not
Id recommend having a single parameterset that creates the correct compound parameters to a single fixture so it will no longer be considered as independent parameters
[-]pytest 8.0.1 sorting tests with multiple parameterization is broken[/-][+]pytest 8.0 sorting tests with multiple parameterization is broken[/+]ShurikMen commentedon Mar 6, 2024
@bluetech take a look at #12082
sadra-barikbin commentedon Mar 22, 2024
Here are the three approaches:

sadra-barikbin commentedon Mar 23, 2024
Seems we have an appearance-efficiency trade-off, with @ShurikMen's suggestion and v7.4.4 approach yielding better appearance but having lower efficiency in setup-teardowns in comparison with v8.0.0.
The three approaches could also be compared in terms of robustness to parametrization varieties which @ShurikMen 's suggestion for example performs better than v7.4.4 and solves the first example of #11257 but fails on the example below which v8.0.0 solves, unless the user be cautious and swap the order of parametrizations on
test1.Considering robustness alone, @ShurikMen method's gain over v7.4.4 seems remarkable but I'm not sure about that of v8.0.0 over @ShurikMen 's.
sadra-barikbin commentedon Mar 24, 2024
Aside by the comments above,I'm for the notion of bug for current reordering in v8.0.0 as it has not been introduced by the intention to become more efficient which we discussed here about. It either has been an unnoticed side-effect of #11220 or something in my large initial PR, responsible for the first example of #11257 (which @ShurikMen 's method now solves as well). Sorry for inconvenience, mates!
ShurikMen commentedon Mar 24, 2024
I have updated the pr. Unified indexing of parameters added through a marker and through fixtures. I think it's the right thing to do. Along the way, I corrected the tests related to parameterization through fixtures.
ShurikMen commentedon May 7, 2024
@bluetech what about solving the problem on this issue?
Revert the unintended change in tests reordering from #11220 (#12542)
chore(deps): update dependency pytest to v9 (#305)