From 4541456a96910a634e0dfa6f2d78205fc37b1901 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 16 Oct 2012 13:47:59 +0200 Subject: [PATCH] add plan for better fixture implementation, an xfailing test and a slight refactoring of Metafunc tests/creation --- IMPL.txt | 40 +++++++++++++++++++++++ _pytest/python.py | 17 ++++------ testing/test_python.py | 72 ++++++++++++++++++++++++++++-------------- 3 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 IMPL.txt diff --git a/IMPL.txt b/IMPL.txt new file mode 100644 index 000000000..2ddd66101 --- /dev/null +++ b/IMPL.txt @@ -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 + diff --git a/_pytest/python.py b/_pytest/python.py index cca58bf2c..eaa1e7883 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -309,8 +309,7 @@ class PyCollector(PyobjMixin, pytest.Collector): clscol = self.getparent(Class) cls = clscol and clscol.obj or None transfer_markers(funcobj, cls, module) - metafunc = Metafunc(funcobj, parentnode=self, config=self.config, - cls=cls, module=module) + metafunc = Metafunc(funcobj, parentnode=self, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests extra = [module] if cls is not None: @@ -612,25 +611,23 @@ class FuncargnamesCompatAttr: return self.fixturenames class Metafunc(FuncargnamesCompatAttr): - def __init__(self, function, config=None, cls=None, module=None, - parentnode=None): - self.config = config + def __init__(self, function, parentnode, cls=None, module=None): + self.config = parentnode.config self.module = module self.function = function self.parentnode = parentnode - self._parentid = getattr(parentnode, "nodeid", "") argnames = getfuncargnames(function, startindex=int(cls is not None)) - if parentnode is not None: + try: fm = parentnode.session._fixturemanager + except AttributeError: + self.fixturenames = argnames + else: self.fixturenames, self._arg2fixturedeflist = fm.getfixtureclosure( argnames, parentnode) - else: - self.fixturenames = argnames self.cls = cls self.module = module self._calls = [] self._ids = py.builtin.set() - self._arg2scopenum = {} def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None): diff --git a/testing/test_python.py b/testing/test_python.py index 72ed66618..daf66ebd9 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -284,22 +284,22 @@ class TestFunction: pass def func2(): pass - f1 = pytest.Function(name="name", config=config, - args=(1,), callobj=func1, session=session) + f1 = pytest.Function(name="name", parent=session, config=config, + args=(1,), callobj=func1) f2 = pytest.Function(name="name",config=config, - args=(1,), callobj=func2, session=session) + args=(1,), callobj=func2, parent=session) assert not f1 == f2 assert f1 != f2 - f3 = pytest.Function(name="name", config=config, - args=(1,2), callobj=func2, session=session) + f3 = pytest.Function(name="name", parent=session, config=config, + args=(1,2), callobj=func2) assert not f3 == f2 assert f3 != f2 assert not f3 == f1 assert f3 != f1 - f1_b = pytest.Function(name="name", config=config, - args=(1,), callobj=func1, session=session) + f1_b = pytest.Function(name="name", parent=session, config=config, + args=(1,), callobj=func1) assert f1 == f1_b assert not f1 != f1_b @@ -909,15 +909,20 @@ class TestRequestCachedSetup: ]) class TestMetafunc: + def Metafunc(self, func): + class parent: + config = None + return funcargs.Metafunc(func, parentnode=parent) + def test_no_funcargs(self, testdir): def function(): pass - metafunc = funcargs.Metafunc(function) + metafunc = self.Metafunc(function) assert not metafunc.fixturenames repr(metafunc._calls) def test_function_basic(self): def func(arg1, arg2="qwe"): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) assert len(metafunc.fixturenames) == 1 assert 'arg1' in metafunc.fixturenames assert metafunc.function is func @@ -925,7 +930,7 @@ class TestMetafunc: def test_addcall_no_args(self): def func(arg1): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) metafunc.addcall() assert len(metafunc._calls) == 1 call = metafunc._calls[0] @@ -934,7 +939,7 @@ class TestMetafunc: def test_addcall_id(self): def func(arg1): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) pytest.raises(ValueError, "metafunc.addcall(id=None)") metafunc.addcall(id=1) @@ -947,7 +952,7 @@ class TestMetafunc: def test_addcall_param(self): def func(arg1): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) class obj: pass metafunc.addcall(param=obj) metafunc.addcall(param=obj) @@ -959,7 +964,7 @@ class TestMetafunc: def test_addcall_funcargs(self): def func(x): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) class obj: pass metafunc.addcall(funcargs={"x": 2}) metafunc.addcall(funcargs={"x": 3}) @@ -971,7 +976,7 @@ class TestMetafunc: def test_parametrize_error(self): def func(x, y): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) metafunc.parametrize("x", [1,2]) 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 func(x, y): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) metafunc.parametrize("x", [1,2], ids=['basic', 'advanced']) metafunc.parametrize("y", ["abc", "def"]) @@ -990,7 +995,7 @@ class TestMetafunc: def test_parametrize_with_userobjects(self): def func(x, y): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) class A: pass metafunc.parametrize("x", [A(), A()]) @@ -1002,7 +1007,7 @@ class TestMetafunc: def test_addcall_and_parametrize(self): def func(x, y): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) metafunc.addcall({'x': 1}) metafunc.parametrize('y', [2,3]) assert len(metafunc._calls) == 2 @@ -1013,7 +1018,7 @@ class TestMetafunc: def test_parametrize_indirect(self): def func(x, y): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) metafunc.parametrize('x', [1], indirect=True) metafunc.parametrize('y', [2,3], indirect=True) metafunc.parametrize('unnamed', [1], indirect=True) @@ -1025,7 +1030,7 @@ class TestMetafunc: def test_addcalls_and_parametrize_indirect(self): def func(x, y): pass - metafunc = funcargs.Metafunc(func) + metafunc = self.Metafunc(func) metafunc.addcall(param="123") metafunc.parametrize('x', [1], indirect=True) metafunc.parametrize('y', [2,3], indirect=True) @@ -1038,7 +1043,6 @@ class TestMetafunc: def test_parametrize_functional(self, testdir): testdir.makepyfile(""" def pytest_generate_tests(metafunc): - assert "test_parametrize_functional" in metafunc._parentid metafunc.parametrize('x', [1,2], indirect=True) metafunc.parametrize('y', [2]) def pytest_funcarg__x(request): @@ -1058,7 +1062,7 @@ class TestMetafunc: ]) def test_parametrize_onearg(self): - metafunc = funcargs.Metafunc(lambda x: None) + metafunc = self.Metafunc(lambda x: None) metafunc.parametrize("x", [1,2]) assert len(metafunc._calls) == 2 assert metafunc._calls[0].funcargs == dict(x=1) @@ -1067,7 +1071,7 @@ class TestMetafunc: assert metafunc._calls[1].id == "2" 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) assert metafunc._calls[0].params == dict(x=1) assert metafunc._calls[0].id == "1" @@ -1075,7 +1079,7 @@ class TestMetafunc: assert metafunc._calls[1].id == "2" 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)]) assert len(metafunc._calls) == 2 assert metafunc._calls[0].funcargs == dict(x=1, y=2) @@ -2004,6 +2008,28 @@ class TestSetupDiscovery: reprec = testdir.inline_run("-s") 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): testdir.makepyfile(""" import pytest