fix issue396 -- properly sort tests using class-scoped parametrization
also refix issue323 in a better way to avoid recursion for the fixture-grouping algorithm alltogether.
This commit is contained in:
parent
daec4c70b8
commit
10edfa65dc
|
@ -10,6 +10,15 @@ Unreleased
|
||||||
hook and expecting certain fixture instances are torn down within (very
|
hook and expecting certain fixture instances are torn down within (very
|
||||||
unlikely and would have been unreliable anyway).
|
unlikely and would have been unreliable anyway).
|
||||||
|
|
||||||
|
- fix issue396 - correctly sort and finalize class-scoped parametrized
|
||||||
|
tests independently from number of methods on the class.
|
||||||
|
|
||||||
|
- refix issue323 in a better way -- parametrization should now never
|
||||||
|
cause Runtime Recursion errors because the underlying algorithm
|
||||||
|
for re-ordering tests per-scope/per-fixture is not recursive
|
||||||
|
anymore (it was tail-call recursive before which could lead
|
||||||
|
to problems for more than >966 non-function scoped parameters).
|
||||||
|
|
||||||
- fix issue290 - there is preliminary support now for parametrizing
|
- fix issue290 - there is preliminary support now for parametrizing
|
||||||
with repeated same values (sometimes useful to to test if calling
|
with repeated same values (sometimes useful to to test if calling
|
||||||
a second time works as with the first time).
|
a second time works as with the first time).
|
||||||
|
|
|
@ -1826,41 +1826,45 @@ def getfuncargnames(function, startindex=None):
|
||||||
# setups and teardowns
|
# setups and teardowns
|
||||||
|
|
||||||
def reorder_items(items, ignore, cache, scopenum):
|
def reorder_items(items, ignore, cache, scopenum):
|
||||||
if scopenum >= scopenum_function:
|
if scopenum >= scopenum_function or len(items) < 3:
|
||||||
return items
|
return items
|
||||||
if len(items) < 2:
|
items_done = []
|
||||||
return items
|
while 1:
|
||||||
#print "\nparametrize_Sorted", items, ignore, cache, scopenum
|
items_before, items_same, items_other, newignore = \
|
||||||
|
slice_items(items, ignore, cache, scopenum)
|
||||||
|
items_before = reorder_items(items_before, ignore, cache, scopenum+1)
|
||||||
|
if items_same is None:
|
||||||
|
# nothing to reorder in this scope
|
||||||
|
assert items_other is None
|
||||||
|
return items_done + items_before
|
||||||
|
items_done.extend(items_before)
|
||||||
|
items = items_same + items_other
|
||||||
|
ignore = newignore
|
||||||
|
|
||||||
|
|
||||||
|
def slice_items(items, ignore, cache, scopenum):
|
||||||
# we pick the first item which uses a fixture instance in the requested scope
|
# we pick the first item which uses a fixture instance in the requested scope
|
||||||
# and which we haven't seen yet. We slice the input items list into
|
# and which we haven't seen yet. We slice the input items list into
|
||||||
# a list of items_nomatch, items_using_same_fixtureinstance and
|
# a list of items_nomatch, items_same and items_other
|
||||||
# items_remaining
|
|
||||||
slicing_argkey = None
|
slicing_argkey = None
|
||||||
for i, item in enumerate(items):
|
for i, item in enumerate(items):
|
||||||
argkeys = get_parametrized_fixture_keys(item, ignore, scopenum, cache)
|
argkeys = get_parametrized_fixture_keys(item, ignore, scopenum, cache)
|
||||||
if slicing_argkey is None:
|
if slicing_argkey is None:
|
||||||
if argkeys:
|
if argkeys:
|
||||||
slicing_argkey = argkeys.pop()
|
slicing_argkey = argkeys.pop()
|
||||||
items_using_same_fixtureinstance = [item]
|
items_before = items[:i]
|
||||||
items_nomatch = items[:i]
|
items_same = [item]
|
||||||
items_remaining = []
|
items_other = []
|
||||||
continue
|
continue
|
||||||
if slicing_argkey in argkeys:
|
if slicing_argkey in argkeys:
|
||||||
items_using_same_fixtureinstance.append(item)
|
items_same.append(item)
|
||||||
else:
|
else:
|
||||||
items_remaining.append(item)
|
items_other.append(item)
|
||||||
|
if slicing_argkey is None:
|
||||||
if slicing_argkey is None or len(items_using_same_fixtureinstance) == 1:
|
return items, None, None, None
|
||||||
# nothing to sort on this level
|
|
||||||
return reorder_items(items, ignore, cache, scopenum+1)
|
|
||||||
|
|
||||||
items_nomatch = reorder_items(items_nomatch, ignore, cache, scopenum+1)
|
|
||||||
newignore = ignore.copy()
|
newignore = ignore.copy()
|
||||||
newignore.add(slicing_argkey)
|
newignore.add(slicing_argkey)
|
||||||
part2 = reorder_items(items_using_same_fixtureinstance + items_remaining,
|
return (items_before, items_same, items_other, newignore)
|
||||||
newignore, cache, scopenum)
|
|
||||||
return items_nomatch + part2
|
|
||||||
|
|
||||||
def get_parametrized_fixture_keys(item, ignore, scopenum, cache):
|
def get_parametrized_fixture_keys(item, ignore, scopenum, cache):
|
||||||
""" return list of keys for all parametrized arguments which match
|
""" return list of keys for all parametrized arguments which match
|
||||||
|
@ -1882,13 +1886,14 @@ def get_parametrized_fixture_keys(item, ignore, scopenum, cache):
|
||||||
elif scopenum == 1: # module
|
elif scopenum == 1: # module
|
||||||
key = (argname, param_index, item.fspath)
|
key = (argname, param_index, item.fspath)
|
||||||
elif scopenum == 2: # class
|
elif scopenum == 2: # class
|
||||||
|
# enumerate classes per fspath
|
||||||
l = cache.setdefault(item.fspath, [])
|
l = cache.setdefault(item.fspath, [])
|
||||||
try:
|
try:
|
||||||
i = l.index(item.cls)
|
numclass = l.index(item.cls)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
i = len(l)
|
numclass = len(l)
|
||||||
l.append(item.cls)
|
l.append(item.cls)
|
||||||
key = (argname, param_index, item.fspath, i)
|
key = (argname, param_index, item.fspath, item.cls)
|
||||||
if key not in ignore:
|
if key not in ignore:
|
||||||
keys.add(key)
|
keys.add(key)
|
||||||
return keys
|
return keys
|
||||||
|
|
|
@ -1889,6 +1889,34 @@ class TestFixtureMarker:
|
||||||
reprec = testdir.inline_run("-lvs")
|
reprec = testdir.inline_run("-lvs")
|
||||||
reprec.assertoutcome(passed=3)
|
reprec.assertoutcome(passed=3)
|
||||||
|
|
||||||
|
@pytest.mark.issue396
|
||||||
|
def test_class_scope_parametrization_ordering(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
l = []
|
||||||
|
@pytest.fixture(params=["John", "Doe"], scope="class")
|
||||||
|
def human(request):
|
||||||
|
request.addfinalizer(lambda: l.append("fin %s" % request.param))
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
class TestGreetings:
|
||||||
|
def test_hello(self, human):
|
||||||
|
l.append("test_hello")
|
||||||
|
|
||||||
|
class TestMetrics:
|
||||||
|
def test_name(self, human):
|
||||||
|
l.append("test_name")
|
||||||
|
|
||||||
|
def test_population(self, human):
|
||||||
|
l.append("test_population")
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(passed=6)
|
||||||
|
l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
|
||||||
|
assert l == ["test_hello", "fin John", "test_hello", "fin Doe",
|
||||||
|
"test_name", "test_population", "fin John",
|
||||||
|
"test_name", "test_population", "fin Doe"]
|
||||||
|
|
||||||
def test_parametrize_setup_function(self, testdir):
|
def test_parametrize_setup_function(self, testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -592,6 +592,8 @@ class TestMetafuncFunctional:
|
||||||
|
|
||||||
def test_it(foo):
|
def test_it(foo):
|
||||||
pass
|
pass
|
||||||
|
def test_it2(foo):
|
||||||
|
pass
|
||||||
""")
|
""")
|
||||||
reprec = testdir.inline_run("--collect-only")
|
reprec = testdir.inline_run("--collect-only")
|
||||||
assert not reprec.getcalls("pytest_internalerror")
|
assert not reprec.getcalls("pytest_internalerror")
|
||||||
|
|
Loading…
Reference in New Issue