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:
parent
767dcc69f3
commit
4035fa6326
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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("""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue