diff --git a/doc/test/funcargs.txt b/doc/test/funcargs.txt index 29a574219..da58b059e 100644 --- a/doc/test/funcargs.txt +++ b/doc/test/funcargs.txt @@ -125,13 +125,14 @@ perform scoped setup and teardown .. sourcecode:: python def cached_setup(setup, teardown=None, scope="module", keyextra=None): - """ setup and return value of calling setup(), cache results and - optionally teardown the value by calling ``teardown(value)``. The scope - determines the key for cashing the setup value. Specify ``keyextra`` - to add to the cash-key. - scope == 'function': when test function run finishes. + """ cache and return result of calling setup(). + + The scope determines the cache key and ``keyextra`` adds to the cachekey. + The scope also determines when teardown(result) will be called. + valid scopes: + scope == 'function': when the single test function run finishes. scope == 'module': when tests in a different module are run - scope == 'run': when the test run has been finished. + scope == 'session': when tests of the session have run. """ example for providing a value that is to be setup only once during a test run: @@ -153,9 +154,9 @@ cleanup after test function execution def addfinalizer(func, scope="function"): """ register calling a a finalizer function. - scope == 'function': when test function run finishes. + scope == 'function': when the single test function run finishes. scope == 'module': when tests in a different module are run - scope == 'run': when all tests have been run. + scope == 'session': when tests of the session have run. """ Calling ``request.addfinalizer()`` is useful for scheduling teardown diff --git a/py/test/funcargs.py b/py/test/funcargs.py index dbe4f7075..72d37265c 100644 --- a/py/test/funcargs.py +++ b/py/test/funcargs.py @@ -148,6 +148,8 @@ class FuncargRequest: return self._pyfuncitem elif scope == "module": return self._pyfuncitem.getparent(py.test.collect.Module) + elif scope == "session": + return None raise ValueError("unknown finalization scope %r" %(scope,)) def addfinalizer(self, finalizer, scope="function"): diff --git a/py/test/runner.py b/py/test/runner.py index 8d066aaf1..e6a15e6e4 100644 --- a/py/test/runner.py +++ b/py/test/runner.py @@ -175,29 +175,37 @@ class SetupState(object): self._finalizers = {} def addfinalizer(self, finalizer, colitem): + """ attach a finalizer to the given colitem. + if colitem is None, this will add a finalizer that + is called at the end of teardown_all(). + """ assert callable(finalizer) #assert colitem in self.stack self._finalizers.setdefault(colitem, []).append(finalizer) - def _teardown(self, colitem): + def _pop_and_teardown(self): + colitem = self.stack.pop() + self._teardown_with_finalization(colitem) + + def _teardown_with_finalization(self, colitem): finalizers = self._finalizers.pop(colitem, None) while finalizers: fin = finalizers.pop() fin() - colitem.teardown() + if colitem: + colitem.teardown() for colitem in self._finalizers: - assert colitem in self.stack + assert colitem is None or colitem in self.stack def teardown_all(self): while self.stack: - col = self.stack.pop() - self._teardown(col) + self._pop_and_teardown() + self._teardown_with_finalization(None) assert not self._finalizers def teardown_exact(self, item): - if self.stack and self.stack[-1] == item: - col = self.stack.pop() - self._teardown(col) + assert self.stack and self.stack[-1] == item + self._pop_and_teardown() def prepare(self, colitem): """ setup objects along the collector chain to the test-method @@ -206,8 +214,7 @@ class SetupState(object): while self.stack: if self.stack == needed_collectors[:len(self.stack)]: break - col = self.stack.pop() - self._teardown(col) + self._pop_and_teardown() for col in needed_collectors[len(self.stack):]: col.setup() self.stack.append(col) diff --git a/py/test/testing/test_funcargs.py b/py/test/testing/test_funcargs.py index 5ffd86e9b..566ade322 100644 --- a/py/test/testing/test_funcargs.py +++ b/py/test/testing/test_funcargs.py @@ -147,17 +147,36 @@ class TestRequest: req._fillfuncargs() assert item.funcargs == {'something': 1} - def test_request_addfinalizer(self, testdir): + def test_request_addfinalizer_scopes(self, testdir): item = testdir.getitem(""" - def pytest_funcarg__something(request): pass + teardownlist = [] + def pytest_funcarg__something(request): + for scope in ("function", "module", "session"): + request.addfinalizer( + lambda x=scope: teardownlist.append(x), + scope=scope) + def test_func(something): pass """) req = funcargs.FuncargRequest(item) + req.config._setupstate.prepare(item) # XXX + req._fillfuncargs() + # successively check finalization calls + teardownlist = item.getparent(py.test.collect.Module).obj.teardownlist + ss = item.config._setupstate + assert not teardownlist + ss.teardown_exact(item) + print ss.stack + assert teardownlist == ['function'] + ss.teardown_exact(item.parent) + assert teardownlist == ['function', 'module'] + ss.teardown_all() + assert teardownlist == ['function', 'module', 'session'] + + def test_request_addfinalizer_unknown_scope(self, testdir): + item = testdir.getitem("def test_func(): pass") + req = funcargs.FuncargRequest(item) py.test.raises(ValueError, "req.addfinalizer(None, scope='xyz')") - l = [1] - req.addfinalizer(l.pop) - req.config._setupstate._teardown(item) - assert not l def test_request_getmodulepath(self, testdir): modcol = testdir.getmodulecol("def test_somefunc(): pass") diff --git a/py/test/testing/test_runner.py b/py/test/testing/test_runner.py index 30953d1ab..9bc8638c1 100644 --- a/py/test/testing/test_runner.py +++ b/py/test/testing/test_runner.py @@ -6,8 +6,24 @@ class TestSetupState: ss = SetupState() item = testdir.getitem("def test_func(): pass") l = [1] + ss.prepare(item) ss.addfinalizer(l.pop, colitem=item) - ss._teardown(item) + assert l + ss._pop_and_teardown() + assert not l + + def test_setup_scope_None(self, testdir): + item = testdir.getitem("def test_func(): pass") + ss = SetupState() + l = [1] + ss.prepare(item) + ss.addfinalizer(l.pop, colitem=None) + assert l + ss._pop_and_teardown() + assert l + ss._pop_and_teardown() + assert l + ss.teardown_all() assert not l class TestSetupStateFunctional: