get to a workable state for cached_setup() and docs, move msot related code to SetupState class

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-05-18 19:06:16 +02:00
parent 767dcc69f3
commit 4035fa6326
8 changed files with 179 additions and 27 deletions

View File

@ -120,21 +120,54 @@ to access test configuration and test context:
``request.param``: if exists was passed by a `parametrizing test generator`_
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.
scope == 'module': when tests in a different module are run
scope == 'run': when the test run has been finished.
"""
example for providing a value that is to be setup only once during a test run:
.. sourcecode:: python
def pytest_funcarg__db(request):
return request.cached_setup(
lambda: ExpensiveSetup(request.config.option.db),
lambda val: val.close(),
scope="run"
)
cleanup after test function execution
---------------------------------------------
Request objects allow to **register a finalizer method** which is
called after a test function has finished running.
This is useful for tearing down or cleaning up
test state related to a function argument. Here is a basic
example for providing a ``myfile`` object that will be
closed upon test function finish:
.. sourcecode:: python
def addfinalizer(func, scope="function"):
""" register calling a a finalizer function.
scope == 'function': when test function run finishes.
scope == 'module': when tests in a different module are run
scope == 'run': when all tests have been run.
"""
Calling ``request.addfinalizer()`` is useful for scheduling teardown
functions. The given scope determines when the teardown function
will be called. Here is a basic example for providing a ``myfile``
object that is to be closed when the test function finishes.
.. sourcecode:: python
def pytest_funcarg__myfile(self, request):
# ... create and open a "myfile" object ...
# ... create and open a unique per-function "myfile" object ...
request.addfinalizer(lambda: myfile.close())
return myfile

View File

@ -89,14 +89,36 @@ class FuncargRequest:
attrname=self._argprefix + str(argname)
)
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
if not hasattr(self.config, '_setupcache'):
self.config._setupcache = {}
cachekey = (self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
val = cache[cachekey]
except KeyError:
val = setup()
cache[cachekey] = val
if teardown is not None:
self.addfinalizer(lambda: teardown(val), scope=scope)
return val
def call_next_provider(self):
if not self._provider:
raise self.Error("no provider methods left")
next_provider = self._provider.pop()
return next_provider(request=self)
def addfinalizer(self, finalizer):
self._pyfuncitem.addfinalizer(finalizer)
def _getscopeitem(self, scope):
if scope == "function":
return self._pyfuncitem
elif scope == "module":
return self._pyfuncitem._getparent(py.test.collect.Module)
raise ValueError("unknown finalization scope %r" %(scope,))
def addfinalizer(self, finalizer, scope="function"):
colitem = self._getscopeitem(scope)
self.config._setupstate.addfinalizer(finalizer=finalizer, colitem=colitem)
def __repr__(self):
return "<FuncargRequest %r for %r>" %(self.argname, self._pyfuncitem)

View File

@ -328,7 +328,6 @@ class Function(FunctionMixin, py.test.collect.Item):
def __init__(self, name, parent=None, config=None, args=(),
callspec=None, callobj=_dummy):
super(Function, self).__init__(name, parent, config=config)
self._finalizers = []
self._args = args
if args:
assert not callspec, "yielded functions (deprecated) cannot have funcargs"
@ -339,16 +338,9 @@ class Function(FunctionMixin, py.test.collect.Item):
if callobj is not _dummy:
self._obj = callobj
def addfinalizer(self, func):
self._finalizers.append(func)
#def addfinalizer(self, func):
# self.config._setupstate.ddfinalizer(func, colitem=self)
def teardown(self):
finalizers = self._finalizers
while finalizers:
call = finalizers.pop()
call()
super(Function, self).teardown()
def readkeywords(self):
d = super(Function, self).readkeywords()
d.update(self.obj.func_dict)
@ -370,7 +362,10 @@ class Function(FunctionMixin, py.test.collect.Item):
return (self.name == other.name and
self._args == other._args and
self.parent == other.parent and
self.obj == other.obj)
self.obj == other.obj and
getattr(self, '_requestparam', None) ==
getattr(other, '_requestparam', None)
)
except AttributeError:
pass
return False

View File

@ -172,16 +172,32 @@ class SetupState(object):
""" shared state for setting up/tearing down test items or collectors. """
def __init__(self):
self.stack = []
self._finalizers = {}
def addfinalizer(self, finalizer, colitem):
assert callable(finalizer)
#assert colitem in self.stack
self._finalizers.setdefault(colitem, []).append(finalizer)
def _teardown(self, colitem):
finalizers = self._finalizers.pop(colitem, None)
while finalizers:
fin = finalizers.pop()
fin()
colitem.teardown()
for colitem in self._finalizers:
assert colitem in self.stack
def teardown_all(self):
while self.stack:
col = self.stack.pop()
col.teardown()
self._teardown(col)
assert not self._finalizers
def teardown_exact(self, item):
if self.stack and self.stack[-1] == item:
col = self.stack.pop()
col.teardown()
self._teardown(col)
def prepare(self, colitem):
""" setup objects along the collector chain to the test-method
@ -191,7 +207,7 @@ class SetupState(object):
if self.stack == needed_collectors[:len(self.stack)]:
break
col = self.stack.pop()
col.teardown()
self._teardown(col)
for col in needed_collectors[len(self.stack):]:
col.setup()
self.stack.append(col)

View File

@ -1,7 +1,7 @@
import py
from py.__.test.conftesthandle import Conftest
def pytest_generate_tests(metafunc, generator):
def pytest_generate_tests(metafunc):
if "basedir" in metafunc.funcargnames:
metafunc.addcall(param="global")
metafunc.addcall(param="inpackage")
@ -15,8 +15,7 @@ def pytest_funcarg__basedir(request):
d.ensure("adir/__init__.py")
d.ensure("adir/b/__init__.py")
return d
return request.cached_setup(perclass=basedirmaker)
return request.cached_setup(perclass=basedirmaker)
return request.cached_setup(lambda: basedirmaker(request), extrakey=request.param)
class TestConftestValueAccessGlobal:
def test_basic_init(self, basedir):

View File

@ -121,9 +121,73 @@ class TestRequest:
def test_func(something): pass
""")
req = funcargs.FuncargRequest(item, "something")
py.test.raises(ValueError, "req.addfinalizer(None, scope='xyz')")
l = [1]
req.addfinalizer(l.pop)
item.teardown()
req.config._setupstate._teardown(item)
assert not l
def test_request_cachedsetup(self, testdir):
item1,item2 = testdir.getitems("""
class TestClass:
def test_func1(self, something):
pass
def test_func2(self, something):
pass
""")
req1 = funcargs.FuncargRequest(item1, "something")
l = ["hello"]
def setup():
return l.pop()
ret1 = req1.cached_setup(setup)
assert ret1 == "hello"
ret1b = req1.cached_setup(setup)
assert ret1 == ret1b
req2 = funcargs.FuncargRequest(item2, "something")
ret2 = req2.cached_setup(setup)
assert ret2 == ret1
def test_request_cachedsetup_extrakey(self, testdir):
item1 = testdir.getitem("def test_func(): pass")
req1 = funcargs.FuncargRequest(item1, "something")
l = ["hello", "world"]
def setup():
return l.pop()
ret1 = req1.cached_setup(setup, extrakey=1)
ret2 = req1.cached_setup(setup, extrakey=2)
assert ret2 == "hello"
assert ret1 == "world"
ret1b = req1.cached_setup(setup, extrakey=1)
ret2b = req1.cached_setup(setup, extrakey=2)
assert ret1 == ret1b
assert ret2 == ret2b
def test_request_cached_setup_functional(self, testdir):
testdir.makepyfile(test_0="""
l = []
def pytest_funcarg__something(request):
val = request.cached_setup(setup, teardown)
return val
def setup(mycache=[1]):
l.append(mycache.pop())
return l
def teardown(something):
l.remove(something[0])
l.append(2)
def test_list_once(something):
assert something == [1]
def test_list_twice(something):
assert something == [1]
""")
testdir.makepyfile(test_1="""
import test_0 # should have run already
def test_check_test0_has_teardown_correct():
assert test_0.l == [2]
""")
result = testdir.runpytest("-v")
result.stdout.fnmatch_lines([
"*3 passed*"
])
def test_request_getmodulepath(self, testdir):
modcol = testdir.getmodulecol("def test_somefunc(): pass")

View File

@ -242,6 +242,19 @@ class TestFunction:
assert f1 == f1_b
assert not f1 != f1_b
class callspec1:
param = 1
funcargs = {}
class callspec2:
param = 2
funcargs = {}
f5 = py.test.collect.Function(name="name", config=config,
callspec=callspec1, callobj=isinstance)
f5b = py.test.collect.Function(name="name", config=config,
callspec=callspec2, callobj=isinstance)
assert f5 != f5b
assert not (f5 == f5b)
class TestSorting:
def test_check_equality_and_cmp_basic(self, testdir):
modcol = testdir.getmodulecol("""

View File

@ -2,6 +2,15 @@
from py.__.test.config import SetupState
class TestSetupState:
def test_setup(self, testdir):
ss = SetupState()
item = testdir.getitem("def test_func(): pass")
l = [1]
ss.addfinalizer(l.pop, colitem=item)
ss._teardown(item)
assert not l
class TestSetupStateFunctional:
disabled = True
def test_setup_ok(self, testdir):
item = testdir.getitem("""
@ -90,3 +99,4 @@ class TestSetupState:
assert event.excinfo