Skip to content

Commit a546a61

Browse files
committed
Fix nondeterminism in fixture collection order
fixtures.reorder_items is non-deterministic because it reorders based on iteration over an (unordered) set. Change the code to use an OrderedDict instead so that we get deterministic behaviour, fixes #920.
1 parent b39f957 commit a546a61

File tree

5 files changed

+16
-9
lines changed

5 files changed

+16
-9
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Kale Kundert
9191
Katarzyna Jachim
9292
Kevin Cox
9393
Kodi B. Arfer
94+
Lawrence Mitchell
9495
Lee Kamentsky
9596
Lev Maximov
9697
Llandy Riveron Del Risco

_pytest/fixtures.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
from _pytest.runner import fail
2020
from _pytest.compat import FuncargnamesCompatAttr
2121

22+
if sys.version_info[:2] == (2, 6):
23+
from ordereddict import OrderedDict
24+
else:
25+
from collections import OrderedDict
26+
2227

2328
def pytest_sessionstart(session):
2429
import _pytest.python
@@ -136,10 +141,10 @@ def get_parametrized_fixture_keys(item, scopenum):
136141
except AttributeError:
137142
pass
138143
else:
139-
# cs.indictes.items() is random order of argnames but
140-
# then again different functions (items) can change order of
141-
# arguments so it doesn't matter much probably
142-
for argname, param_index in cs.indices.items():
144+
# cs.indices.items() is random order of argnames. Need to
145+
# sort this so that different calls to
146+
# get_parametrized_fixture_keys will be deterministic.
147+
for argname, param_index in sorted(cs.indices.items()):
143148
if cs._arg2scopenum[argname] != scopenum:
144149
continue
145150
if scopenum == 0: # session
@@ -161,7 +166,7 @@ def reorder_items(items):
161166
for scopenum in range(0, scopenum_function):
162167
argkeys_cache[scopenum] = d = {}
163168
for item in items:
164-
keys = set(get_parametrized_fixture_keys(item, scopenum))
169+
keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
165170
if keys:
166171
d[item] = keys
167172
return reorder_items_atscope(items, set(), argkeys_cache, 0)
@@ -196,9 +201,9 @@ def slice_items(items, ignore, scoped_argkeys_cache):
196201
for i, item in enumerate(it):
197202
argkeys = scoped_argkeys_cache.get(item)
198203
if argkeys is not None:
199-
argkeys = argkeys.difference(ignore)
200-
if argkeys: # found a slicing key
201-
slicing_argkey = argkeys.pop()
204+
newargkeys = OrderedDict.fromkeys(k for k in argkeys if k not in ignore)
205+
if newargkeys: # found a slicing key
206+
slicing_argkey, _ = newargkeys.popitem()
202207
items_before = items[:i]
203208
items_same = [item]
204209
items_other = []

changelog/920.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix non-determinism in order of fixture collection.

testing/python/fixture.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2548,7 +2548,6 @@ def test_foo(fix):
25482548
'*test_foo*beta*'])
25492549

25502550
@pytest.mark.issue920
2551-
@pytest.mark.xfail(reason="Fixture reordering not deterministic with hash randomisation")
25522551
def test_deterministic_fixture_collection(self, testdir, monkeypatch):
25532552
testdir.makepyfile("""
25542553
import pytest

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ deps =
3434
hypothesis<3.0
3535
nose
3636
mock<1.1
37+
ordereddict
3738

3839
[testenv:py27-subprocess]
3940
changedir = .

0 commit comments

Comments
 (0)