add plan for better fixture implementation, an xfailing test

and a slight refactoring of Metafunc tests/creation
This commit is contained in:
holger krekel 2012-10-16 13:47:59 +02:00
parent f5d796b093
commit 4541456a96
3 changed files with 96 additions and 33 deletions

40
IMPL.txt Normal file
View File

@ -0,0 +1,40 @@
internal fixture mechanics
----------------------------
fixture mechanics are contained in the python.py plugin.
pytest fixtures are stored and managed from the FixtureManager (fm) class.
Upon collection fm.parsefactories() is called multiple times to parse
fixture function definitions into FixtureDef objects.
During collection of test functions, metafunc needs to grow a "fixturenames"
list so that pytest_generate_tests() hooks can check it. These fixturenames
are a closure of all known fixtures to be used for this function:
- ini-defined usefixtures
- autouse-marked fixtures along the collection chain up from the function
- usefixtures markers at module/class/function level
- test function funcargs
The latter list (without the closure) is also called "_initialfixtures"
and will be used during the test setup phase.
Upon the test-setup phases initialfixtures are instantiated which
will subsequently create the full fixture closure (as was computed in
metafunc.fixturenames during collection). As fixture functions
can invoke request.getfuncargvalue() the actual closure may be even
bigger.
object model
---------------------
As part of the metafunc-protocol parents of Function nodes get a
FuncFixtureInfos() object, containing helping/caching for
for a function. .getfixtureinfo(func) returns a FixtureInfo with these
attributes:
- names_initial: list of initial fixture names (see above)
- names_closure: closure of all fixture names
- name2fixturedeflist: for creating the value

View File

@ -309,8 +309,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
clscol = self.getparent(Class) clscol = self.getparent(Class)
cls = clscol and clscol.obj or None cls = clscol and clscol.obj or None
transfer_markers(funcobj, cls, module) transfer_markers(funcobj, cls, module)
metafunc = Metafunc(funcobj, parentnode=self, config=self.config, metafunc = Metafunc(funcobj, parentnode=self, cls=cls, module=module)
cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests gentesthook = self.config.hook.pytest_generate_tests
extra = [module] extra = [module]
if cls is not None: if cls is not None:
@ -612,25 +611,23 @@ class FuncargnamesCompatAttr:
return self.fixturenames return self.fixturenames
class Metafunc(FuncargnamesCompatAttr): class Metafunc(FuncargnamesCompatAttr):
def __init__(self, function, config=None, cls=None, module=None, def __init__(self, function, parentnode, cls=None, module=None):
parentnode=None): self.config = parentnode.config
self.config = config
self.module = module self.module = module
self.function = function self.function = function
self.parentnode = parentnode self.parentnode = parentnode
self._parentid = getattr(parentnode, "nodeid", "")
argnames = getfuncargnames(function, startindex=int(cls is not None)) argnames = getfuncargnames(function, startindex=int(cls is not None))
if parentnode is not None: try:
fm = parentnode.session._fixturemanager fm = parentnode.session._fixturemanager
except AttributeError:
self.fixturenames = argnames
else:
self.fixturenames, self._arg2fixturedeflist = fm.getfixtureclosure( self.fixturenames, self._arg2fixturedeflist = fm.getfixtureclosure(
argnames, parentnode) argnames, parentnode)
else:
self.fixturenames = argnames
self.cls = cls self.cls = cls
self.module = module self.module = module
self._calls = [] self._calls = []
self._ids = py.builtin.set() self._ids = py.builtin.set()
self._arg2scopenum = {}
def parametrize(self, argnames, argvalues, indirect=False, ids=None, def parametrize(self, argnames, argvalues, indirect=False, ids=None,
scope=None): scope=None):

View File

@ -284,22 +284,22 @@ class TestFunction:
pass pass
def func2(): def func2():
pass pass
f1 = pytest.Function(name="name", config=config, f1 = pytest.Function(name="name", parent=session, config=config,
args=(1,), callobj=func1, session=session) args=(1,), callobj=func1)
f2 = pytest.Function(name="name",config=config, f2 = pytest.Function(name="name",config=config,
args=(1,), callobj=func2, session=session) args=(1,), callobj=func2, parent=session)
assert not f1 == f2 assert not f1 == f2
assert f1 != f2 assert f1 != f2
f3 = pytest.Function(name="name", config=config, f3 = pytest.Function(name="name", parent=session, config=config,
args=(1,2), callobj=func2, session=session) args=(1,2), callobj=func2)
assert not f3 == f2 assert not f3 == f2
assert f3 != f2 assert f3 != f2
assert not f3 == f1 assert not f3 == f1
assert f3 != f1 assert f3 != f1
f1_b = pytest.Function(name="name", config=config, f1_b = pytest.Function(name="name", parent=session, config=config,
args=(1,), callobj=func1, session=session) args=(1,), callobj=func1)
assert f1 == f1_b assert f1 == f1_b
assert not f1 != f1_b assert not f1 != f1_b
@ -909,15 +909,20 @@ class TestRequestCachedSetup:
]) ])
class TestMetafunc: class TestMetafunc:
def Metafunc(self, func):
class parent:
config = None
return funcargs.Metafunc(func, parentnode=parent)
def test_no_funcargs(self, testdir): def test_no_funcargs(self, testdir):
def function(): pass def function(): pass
metafunc = funcargs.Metafunc(function) metafunc = self.Metafunc(function)
assert not metafunc.fixturenames assert not metafunc.fixturenames
repr(metafunc._calls) repr(metafunc._calls)
def test_function_basic(self): def test_function_basic(self):
def func(arg1, arg2="qwe"): pass def func(arg1, arg2="qwe"): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
assert len(metafunc.fixturenames) == 1 assert len(metafunc.fixturenames) == 1
assert 'arg1' in metafunc.fixturenames assert 'arg1' in metafunc.fixturenames
assert metafunc.function is func assert metafunc.function is func
@ -925,7 +930,7 @@ class TestMetafunc:
def test_addcall_no_args(self): def test_addcall_no_args(self):
def func(arg1): pass def func(arg1): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
metafunc.addcall() metafunc.addcall()
assert len(metafunc._calls) == 1 assert len(metafunc._calls) == 1
call = metafunc._calls[0] call = metafunc._calls[0]
@ -934,7 +939,7 @@ class TestMetafunc:
def test_addcall_id(self): def test_addcall_id(self):
def func(arg1): pass def func(arg1): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
pytest.raises(ValueError, "metafunc.addcall(id=None)") pytest.raises(ValueError, "metafunc.addcall(id=None)")
metafunc.addcall(id=1) metafunc.addcall(id=1)
@ -947,7 +952,7 @@ class TestMetafunc:
def test_addcall_param(self): def test_addcall_param(self):
def func(arg1): pass def func(arg1): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
class obj: pass class obj: pass
metafunc.addcall(param=obj) metafunc.addcall(param=obj)
metafunc.addcall(param=obj) metafunc.addcall(param=obj)
@ -959,7 +964,7 @@ class TestMetafunc:
def test_addcall_funcargs(self): def test_addcall_funcargs(self):
def func(x): pass def func(x): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
class obj: pass class obj: pass
metafunc.addcall(funcargs={"x": 2}) metafunc.addcall(funcargs={"x": 2})
metafunc.addcall(funcargs={"x": 3}) metafunc.addcall(funcargs={"x": 3})
@ -971,7 +976,7 @@ class TestMetafunc:
def test_parametrize_error(self): def test_parametrize_error(self):
def func(x, y): pass def func(x, y): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1,2]) metafunc.parametrize("x", [1,2])
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6])) pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6])) pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
@ -981,7 +986,7 @@ class TestMetafunc:
def test_parametrize_and_id(self): def test_parametrize_and_id(self):
def func(x, y): pass def func(x, y): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1,2], ids=['basic', 'advanced']) metafunc.parametrize("x", [1,2], ids=['basic', 'advanced'])
metafunc.parametrize("y", ["abc", "def"]) metafunc.parametrize("y", ["abc", "def"])
@ -990,7 +995,7 @@ class TestMetafunc:
def test_parametrize_with_userobjects(self): def test_parametrize_with_userobjects(self):
def func(x, y): pass def func(x, y): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
class A: class A:
pass pass
metafunc.parametrize("x", [A(), A()]) metafunc.parametrize("x", [A(), A()])
@ -1002,7 +1007,7 @@ class TestMetafunc:
def test_addcall_and_parametrize(self): def test_addcall_and_parametrize(self):
def func(x, y): pass def func(x, y): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
metafunc.addcall({'x': 1}) metafunc.addcall({'x': 1})
metafunc.parametrize('y', [2,3]) metafunc.parametrize('y', [2,3])
assert len(metafunc._calls) == 2 assert len(metafunc._calls) == 2
@ -1013,7 +1018,7 @@ class TestMetafunc:
def test_parametrize_indirect(self): def test_parametrize_indirect(self):
def func(x, y): pass def func(x, y): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
metafunc.parametrize('x', [1], indirect=True) metafunc.parametrize('x', [1], indirect=True)
metafunc.parametrize('y', [2,3], indirect=True) metafunc.parametrize('y', [2,3], indirect=True)
metafunc.parametrize('unnamed', [1], indirect=True) metafunc.parametrize('unnamed', [1], indirect=True)
@ -1025,7 +1030,7 @@ class TestMetafunc:
def test_addcalls_and_parametrize_indirect(self): def test_addcalls_and_parametrize_indirect(self):
def func(x, y): pass def func(x, y): pass
metafunc = funcargs.Metafunc(func) metafunc = self.Metafunc(func)
metafunc.addcall(param="123") metafunc.addcall(param="123")
metafunc.parametrize('x', [1], indirect=True) metafunc.parametrize('x', [1], indirect=True)
metafunc.parametrize('y', [2,3], indirect=True) metafunc.parametrize('y', [2,3], indirect=True)
@ -1038,7 +1043,6 @@ class TestMetafunc:
def test_parametrize_functional(self, testdir): def test_parametrize_functional(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
assert "test_parametrize_functional" in metafunc._parentid
metafunc.parametrize('x', [1,2], indirect=True) metafunc.parametrize('x', [1,2], indirect=True)
metafunc.parametrize('y', [2]) metafunc.parametrize('y', [2])
def pytest_funcarg__x(request): def pytest_funcarg__x(request):
@ -1058,7 +1062,7 @@ class TestMetafunc:
]) ])
def test_parametrize_onearg(self): def test_parametrize_onearg(self):
metafunc = funcargs.Metafunc(lambda x: None) metafunc = self.Metafunc(lambda x: None)
metafunc.parametrize("x", [1,2]) metafunc.parametrize("x", [1,2])
assert len(metafunc._calls) == 2 assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == dict(x=1) assert metafunc._calls[0].funcargs == dict(x=1)
@ -1067,7 +1071,7 @@ class TestMetafunc:
assert metafunc._calls[1].id == "2" assert metafunc._calls[1].id == "2"
def test_parametrize_onearg_indirect(self): def test_parametrize_onearg_indirect(self):
metafunc = funcargs.Metafunc(lambda x: None) metafunc = self.Metafunc(lambda x: None)
metafunc.parametrize("x", [1,2], indirect=True) metafunc.parametrize("x", [1,2], indirect=True)
assert metafunc._calls[0].params == dict(x=1) assert metafunc._calls[0].params == dict(x=1)
assert metafunc._calls[0].id == "1" assert metafunc._calls[0].id == "1"
@ -1075,7 +1079,7 @@ class TestMetafunc:
assert metafunc._calls[1].id == "2" assert metafunc._calls[1].id == "2"
def test_parametrize_twoargs(self): def test_parametrize_twoargs(self):
metafunc = funcargs.Metafunc(lambda x,y: None) metafunc = self.Metafunc(lambda x,y: None)
metafunc.parametrize(("x", "y"), [(1,2), (3,4)]) metafunc.parametrize(("x", "y"), [(1,2), (3,4)])
assert len(metafunc._calls) == 2 assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == dict(x=1, y=2) assert metafunc._calls[0].funcargs == dict(x=1, y=2)
@ -2004,6 +2008,28 @@ class TestSetupDiscovery:
reprec = testdir.inline_run("-s") reprec = testdir.inline_run("-s")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
@pytest.mark.xfail
def test_two_classes_separated_autouse(self, testdir):
testdir.makepyfile("""
import pytest
class TestA:
l = []
@pytest.fixture(autouse=True)
def setup1(self):
self.l.append(1)
def test_setup1(self):
assert self.l == [1]
class TestB:
l = []
@pytest.fixture(autouse=True)
def setup2(self):
self.l.append(1)
def test_setup2(self):
assert self.l == [1]
""")
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2)
def test_setup_at_classlevel(self, testdir): def test_setup_at_classlevel(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest