merging the new function generators, addresses issue 2
- introduce a new pytest_genfuncruns hook for generating tests with multiple funcargs - new and extended docs: doc/test/funcargs.txt - factor all funcargs related code into py/test/funcargs.py - remove request.maketempdir call (you can use request.config.mktemp) --HG-- branch : trunk
This commit is contained in:
parent
1cb83de0ab
commit
d9ad2cf761
|
@ -28,8 +28,10 @@ per-testrun temporary directories
|
|||
-------------------------------------------
|
||||
|
||||
``py.test`` runs provide means to create per-test session
|
||||
temporary (sub) directories. You can create such directories
|
||||
like this:
|
||||
temporary (sub) directories through the config object.
|
||||
You can create directories like this:
|
||||
|
||||
.. XXX use a more local example, just with "config"
|
||||
|
||||
.. sourcecode: python
|
||||
|
||||
|
|
|
@ -1,52 +1,76 @@
|
|||
======================================================
|
||||
**funcargs**: powerful and simple test setup
|
||||
**funcargs**: powerful test setup and parametrization
|
||||
======================================================
|
||||
|
||||
In version 1.0 py.test introduces a new mechanism for setting up test
|
||||
state for use by Python test functions. It is particularly useful
|
||||
for functional and integration testing but also for unit testing.
|
||||
Using funcargs you can easily:
|
||||
Since version 1.0 it is possible to provide arguments to test functions,
|
||||
often called "funcargs". The funcarg mechanisms were developed with
|
||||
these goals in mind:
|
||||
|
||||
* write self-contained, simple to read and debug test functions
|
||||
* cleanly encapsulate glue code between your app and your tests
|
||||
* setup test state depending on command line options or environment
|
||||
* **no boilerplate**: cleanly encapsulate test setup and fixtures
|
||||
* **flexibility**: easily setup test state depending on command line options or environment
|
||||
* **readability**: write simple to read and debug test functions
|
||||
* **parametrizing tests**: run a test function multiple times with different parameters
|
||||
|
||||
Using the funcargs mechanism will increase readability
|
||||
and allow for easier refactoring of your application
|
||||
and its test suites.
|
||||
|
||||
.. contents:: Contents:
|
||||
:depth: 2
|
||||
|
||||
The basic funcarg request/provide mechanism
|
||||
Basic mechanisms by example
|
||||
=============================================
|
||||
|
||||
To use funcargs you only need to specify
|
||||
a named argument for your test function:
|
||||
providing single function arguments as needed
|
||||
---------------------------------------------------------
|
||||
|
||||
Let's look at a simple example of using funcargs within a test module:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_function(myarg):
|
||||
# use myarg
|
||||
def pytest_funcarg__myfuncarg(request):
|
||||
return 42
|
||||
|
||||
For each test function that requests this ``myarg``
|
||||
argument a matching so called funcarg provider
|
||||
will be invoked. A Funcarg provider for ``myarg``
|
||||
is written down liks this:
|
||||
def test_function(myfuncarg):
|
||||
assert myfuncarg == 42
|
||||
|
||||
1. To setup the running of the ``test_function()`` call, py.test
|
||||
looks up a provider for the ``myfuncarg`` argument.
|
||||
The provider method is recognized by its ``pytest_funcarg__`` prefix
|
||||
followed by the requested function argument name.
|
||||
The `request object`_ gives access to test context.
|
||||
|
||||
2. A ``test_function(42)`` call is executed. If the test fails
|
||||
one can see the original provided value.
|
||||
|
||||
|
||||
generating test runs with multiple function argument values
|
||||
----------------------------------------------------------------------
|
||||
|
||||
You can parametrize multiple runs of a test function by
|
||||
providing multiple values for function arguments. Here
|
||||
is an example for running the same test function three times.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def pytest_funcarg__myarg(self, request):
|
||||
# return value for myarg here
|
||||
def pytest_genfuncruns(runspec):
|
||||
if "arg1" in runspec.funcargnames:
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 20)
|
||||
runspec.addfuncarg("arg1", 30)
|
||||
|
||||
Such a provider method can live on a test class,
|
||||
test module or on a local or global plugin.
|
||||
The method is recognized by the ``pytest_funcarg__``
|
||||
prefix and is correlated to the argument
|
||||
name which follows this prefix. The passed in
|
||||
``request`` object allows to interact
|
||||
with test configuration, test collection
|
||||
and test running aspects.
|
||||
def test_function(arg1):
|
||||
assert myfuncarg in (10, 20, 30)
|
||||
|
||||
Here is what happens:
|
||||
|
||||
1. The ``pytest_genfuncruns()`` hook will be called once for each test
|
||||
function. The if-statement makes sure that we only add function
|
||||
arguments (and runs) for functions that need it. The `runspec object`_
|
||||
provides access to context information.
|
||||
|
||||
2. Subsequently the ``test_function()`` will be called three times
|
||||
with three different values for ``arg1``.
|
||||
|
||||
Funcarg rules and support objects
|
||||
====================================
|
||||
|
||||
.. _`request object`:
|
||||
|
||||
|
@ -65,11 +89,13 @@ Attributes of request objects
|
|||
|
||||
``request.function``: python function object requesting the argument
|
||||
|
||||
``request.fspath``: filesystem path of containing module
|
||||
``request.cls``: class object where the test function is defined in or None.
|
||||
|
||||
``runspec.module``: module object where the test function is defined in.
|
||||
|
||||
``request.config``: access to command line opts and general config
|
||||
|
||||
finalizing after test function executed
|
||||
cleanup after test function execution
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Request objects allow to **register a finalizer method** which is
|
||||
|
@ -86,33 +112,8 @@ function finish:
|
|||
request.addfinalizer(lambda: myfile.close())
|
||||
return myfile
|
||||
|
||||
a unique temporary directory
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
request objects allow to create unique temporary
|
||||
directories. These directories will be created
|
||||
as subdirectories under the `per-testsession
|
||||
temporary directory`_. Each request object
|
||||
receives its own unique subdirectory whose
|
||||
basenames starts with the name of the function
|
||||
that triggered the funcarg request. You
|
||||
can further work with the provided `py.path.local`_
|
||||
object to e.g. create subdirs or config files::
|
||||
|
||||
def pytest_funcarg__mysetup(self, request):
|
||||
tmpdir = request.maketempdir()
|
||||
tmpdir.mkdir("mysubdir")
|
||||
tmpdir.join("config.ini").write("[default")
|
||||
return tmpdir
|
||||
|
||||
Note that you do not need to perform finalization,
|
||||
i.e. remove the temporary directory as this is
|
||||
part of the global management of the base temporary
|
||||
directory.
|
||||
|
||||
.. _`per-testsession temporary directory`: config.html#basetemp
|
||||
|
||||
decorating/adding to existing funcargs
|
||||
decorating other funcarg providers
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
If you want to **decorate a function argument** that is
|
||||
|
@ -131,33 +132,73 @@ is no next provider left. See the `decorator example`_
|
|||
for a use of this method.
|
||||
|
||||
|
||||
.. _`funcarg lookup order`:
|
||||
.. _`lookup order`:
|
||||
|
||||
Order of funcarg provider lookup
|
||||
----------------------------------------
|
||||
Order of provider and test generator lookup
|
||||
----------------------------------------------
|
||||
|
||||
For any funcarg argument request here is the
|
||||
lookup order for provider methods:
|
||||
|
||||
1. test class (if we are executing a method)
|
||||
2. test module
|
||||
3. local plugins
|
||||
4. global plugins
|
||||
Both test generators as well as funcarg providers
|
||||
are looked up in the following three scopes:
|
||||
|
||||
1. test module
|
||||
2. local plugins
|
||||
3. global plugins
|
||||
|
||||
Using multiple funcargs
|
||||
----------------------------------------
|
||||
|
||||
A test function may receive more than one
|
||||
function arguments. For each of the
|
||||
function arguments a lookup of a
|
||||
matching provider will be performed.
|
||||
Test functions can have multiple arguments
|
||||
which can either come from a test generator
|
||||
or from a provider.
|
||||
|
||||
.. _`runspec object`:
|
||||
|
||||
runspec objects
|
||||
------------------------
|
||||
|
||||
Runspecs help to inspect a testfunction and
|
||||
to generate tests with combinations of function argument values.
|
||||
|
||||
generating and combining funcargs
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Calling ``runspec.addfuncarg(argname, value)`` will trigger
|
||||
tests function calls with the given function
|
||||
argument value. For each already existing
|
||||
funcarg combination, the added funcarg value will
|
||||
|
||||
* be merged to the existing funcarg combination if the
|
||||
new argument name isn't part of the funcarg combination yet.
|
||||
|
||||
* otherwise generate a new test call where the existing
|
||||
funcarg combination is copied and updated
|
||||
with the newly added funcarg value.
|
||||
|
||||
For simple usage, e.g. test functions with a single
|
||||
generated function argument, each call to ``addfuncarg``
|
||||
will just trigger a new call.
|
||||
|
||||
This scheme allows two sources to generate
|
||||
function arguments independently from each other.
|
||||
|
||||
Attributes of runspec objects
|
||||
++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
``runspec.funcargnames``: set of required function arguments for given function
|
||||
|
||||
``runspec.function``: underlying python test function
|
||||
|
||||
``runspec.cls``: class object where the test function is defined in or None.
|
||||
|
||||
``runspec.module``: the module object where the test function is defined in.
|
||||
|
||||
``runspec.config``: access to command line opts and general config
|
||||
|
||||
|
||||
Funcarg Tutorial Examples
|
||||
============================
|
||||
Useful Funcarg Tutorial Examples
|
||||
=======================================
|
||||
|
||||
tutorial example: the "test/app-specific" setup pattern
|
||||
application specific test setup
|
||||
---------------------------------------------------------
|
||||
|
||||
Here is a basic useful step-wise example for handling application
|
||||
|
@ -202,7 +243,7 @@ following code into a local ``conftest.py``:
|
|||
return MyApp()
|
||||
|
||||
py.test finds the ``pytest_funcarg__mysetup`` method by
|
||||
name, see `funcarg lookup order`_ for more on this mechanism.
|
||||
name, see also `lookup order`_.
|
||||
|
||||
To run the example we put a pseudo MyApp object into ``myapp.py``:
|
||||
|
||||
|
@ -265,29 +306,8 @@ Now any test functions can use the ``mysetup.getsshconnection()`` method like th
|
|||
conn = mysetup.getsshconnection()
|
||||
# work with conn
|
||||
|
||||
Running this without the command line will yield this run result::
|
||||
|
||||
XXX fill in
|
||||
|
||||
|
||||
Example: specifying funcargs in test modules or classes
|
||||
---------------------------------------------------------
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def pytest_funcarg__mysetup(request):
|
||||
result = request.call_next_provider()
|
||||
result.extra = "..."
|
||||
return result
|
||||
|
||||
You can put such a function into a test class like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class TestClass:
|
||||
def pytest_funcarg__mysetup(self, request):
|
||||
# ...
|
||||
#
|
||||
Running this without specifying a command line option will result in a skipped
|
||||
test_function.
|
||||
|
||||
.. _`accept example`:
|
||||
|
||||
|
@ -309,16 +329,12 @@ example: specifying and selecting acceptance tests
|
|||
def __init__(self, request):
|
||||
if not request.config.option.acceptance:
|
||||
py.test.skip("specify -A to run acceptance tests")
|
||||
self.tmpdir = request.config.maketempdir(request.argname)
|
||||
self._old = self.tmpdir.chdir()
|
||||
request.addfinalizer(self.finalize)
|
||||
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
|
||||
|
||||
def run(self):
|
||||
return py.process.cmdexec("echo hello")
|
||||
|
||||
def finalize(self):
|
||||
self._old.chdir()
|
||||
# cleanup any other resources
|
||||
def run(self, cmd):
|
||||
""" called by test code to execute an acceptance test. """
|
||||
self.tmpdir.chdir()
|
||||
return py.process.cmdexec(cmd)
|
||||
|
||||
|
||||
and the actual test function example:
|
||||
|
@ -327,48 +343,116 @@ and the actual test function example:
|
|||
|
||||
def test_some_acceptance_aspect(accept):
|
||||
accept.tmpdir.mkdir("somesub")
|
||||
result = accept.run()
|
||||
assert result
|
||||
|
||||
That's it! This test will get automatically skipped with
|
||||
an appropriate message if you just run ``py.test``::
|
||||
|
||||
... OUTPUT of py.test on this example ...
|
||||
result = accept.run("ls -la")
|
||||
assert "somesub" in result
|
||||
|
||||
If you run this test without specifying a command line option
|
||||
the test will get skipped with an appropriate message. Otherwise
|
||||
you can start to add convenience and test support methods
|
||||
to your AcceptFuncarg and drive running of tools or
|
||||
applications and provide ways to do assertions about
|
||||
the output.
|
||||
|
||||
.. _`decorator example`:
|
||||
|
||||
example: decorating/extending a funcarg in a TestClass
|
||||
example: decorating a funcarg in a test module
|
||||
--------------------------------------------------------------
|
||||
|
||||
For larger scale setups it's sometimes useful to decorare
|
||||
a funcarg just for a particular test module or even
|
||||
a particular test class. We can extend the `accept example`_
|
||||
by putting this in our test class:
|
||||
a funcarg just for a particular test module. We can
|
||||
extend the `accept example`_ by putting this in our test class:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class TestSpecialAcceptance:
|
||||
def pytest_funcarg__accept(self, request):
|
||||
arg = request.call_next_provider()
|
||||
# create a special layout in our tempdir
|
||||
arg.tmpdir.mkdir("special")
|
||||
return arg
|
||||
|
||||
class TestSpecialAcceptance:
|
||||
def test_sometest(self, accept):
|
||||
assert accept.tmpdir.join("special").check()
|
||||
|
||||
According to the `funcarg lookup order`_ our class-specific provider will
|
||||
be invoked first. Here, we just ask our request object to
|
||||
call the next provider and decorate its result. This simple
|
||||
According to the the `lookup order`_ our module level provider
|
||||
will be invoked first and it can ask ask its request object to
|
||||
call the next provider and then decorate its result. This
|
||||
mechanism allows us to stay ignorant of how/where the
|
||||
function argument is provided.
|
||||
|
||||
Note that we make use here of `py.path.local`_ objects
|
||||
that provide uniform access to the local filesystem.
|
||||
sidenote: the temporary directory used here are instances of
|
||||
the `py.path.local`_ class which provides many of the os.path
|
||||
methods in a convenient way.
|
||||
|
||||
.. _`py.path.local`: ../path.html#local
|
||||
|
||||
.. _`combine multiple funcarg values`:
|
||||
|
||||
|
||||
parametrize test functions by combining generated funcargs
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
Adding different funcargs will generate test calls with
|
||||
all combinations of added funcargs. Consider this example:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def makearg1(runspec):
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 11)
|
||||
|
||||
def makearg2(runspec):
|
||||
runspec.addfuncarg("arg2", 20)
|
||||
runspec.addfuncarg("arg2", 21)
|
||||
|
||||
def pytest_genfuncruns(runspec):
|
||||
makearg1(runspec)
|
||||
makearg2(runspec)
|
||||
|
||||
# the actual test function
|
||||
|
||||
def test_function(arg1, arg2):
|
||||
assert arg1 in (10, 20)
|
||||
assert arg2 in (20, 30)
|
||||
|
||||
Running this test module will result in ``test_function``
|
||||
being called four times, in the following order::
|
||||
|
||||
test_function(10, 20)
|
||||
test_function(10, 21)
|
||||
test_function(11, 20)
|
||||
test_function(11, 21)
|
||||
|
||||
|
||||
example: test functions with generated and provided funcargs
|
||||
-------------------------------------------------------------------
|
||||
|
||||
You can mix generated function arguments and normally
|
||||
provided ones. Consider this module:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def pytest_genfuncruns(runspec):
|
||||
if "arg1" in runspec.funcargnames: # test_function2 does not have it
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 20)
|
||||
|
||||
def pytest_funcarg__arg2(request):
|
||||
return [10, 20]
|
||||
|
||||
def test_function(arg1, arg2):
|
||||
assert arg1 in arg2
|
||||
|
||||
def test_function2(arg2):
|
||||
assert args2 == [10, 20]
|
||||
|
||||
Running this test module will result in ``test_function``
|
||||
being called twice, with these arguments::
|
||||
|
||||
test_function(10, [10, 20])
|
||||
test_function(20, [10, 20])
|
||||
|
||||
|
||||
Questions and Answers
|
||||
==================================
|
||||
|
||||
|
@ -377,14 +461,14 @@ Questions and Answers
|
|||
Why ``pytest_funcarg__*`` methods?
|
||||
------------------------------------
|
||||
|
||||
When experimenting with funcargs we also considered an explicit
|
||||
registration mechanism, i.e. calling a register method e.g. on the
|
||||
config object. But lacking a good use case for this indirection and
|
||||
flexibility we decided to go for `Convention over Configuration`_
|
||||
and allow to directly specify the provider. It has the
|
||||
positive implication that you should be able to
|
||||
"grep" for ``pytest_funcarg__MYARG`` and will find all
|
||||
providing sites (usually exactly one).
|
||||
When experimenting with funcargs we also
|
||||
considered an explicit registration mechanism, i.e. calling a register
|
||||
method on the config object. But lacking a good use case for this
|
||||
indirection and flexibility we decided to go for `Convention over
|
||||
Configuration`_ and allow to directly specify the provider. It has the
|
||||
positive implication that you should be able to "grep" for
|
||||
``pytest_funcarg__MYARG`` and will find all providing sites (usually
|
||||
exactly one).
|
||||
|
||||
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
||||
|
||||
|
|
|
@ -11,14 +11,18 @@ quickstart_: for getting started immediately.
|
|||
|
||||
features_: a walk through basic features and usage.
|
||||
|
||||
funcargs_: powerful parametrized test function setup
|
||||
|
||||
`distributed testing`_: distribute test runs to other machines and platforms.
|
||||
|
||||
plugins_: using available plugins.
|
||||
|
||||
extend_: writing plugins and advanced configuration.
|
||||
|
||||
`distributed testing`_ how to distribute test runs to other machines and platforms.
|
||||
|
||||
.. _quickstart: quickstart.html
|
||||
.. _features: features.html
|
||||
.. _funcargs: funcargs.html
|
||||
.. _plugins: plugins.html
|
||||
.. _extend: ext.html
|
||||
.. _`distributed testing`: dist.html
|
||||
|
|
|
@ -123,10 +123,14 @@ class Hooks:
|
|||
return "<Hooks %r %r>" %(self._hookspecs, self._plugins)
|
||||
|
||||
class HookCall:
|
||||
def __init__(self, registry, name, firstresult):
|
||||
def __init__(self, registry, name, firstresult, extralookup=None):
|
||||
self.registry = registry
|
||||
self.name = name
|
||||
self.firstresult = firstresult
|
||||
self.extralookup = extralookup and [extralookup] or ()
|
||||
|
||||
def clone(self, extralookup):
|
||||
return HookCall(self.registry, self.name, self.firstresult, extralookup)
|
||||
|
||||
def __repr__(self):
|
||||
mode = self.firstresult and "firstresult" or "each"
|
||||
|
@ -136,7 +140,8 @@ class HookCall:
|
|||
if args:
|
||||
raise TypeError("only keyword arguments allowed "
|
||||
"for api call to %r" % self.name)
|
||||
mc = MultiCall(self.registry.listattr(self.name), **kwargs)
|
||||
attr = self.registry.listattr(self.name, extra=self.extralookup)
|
||||
mc = MultiCall(attr, **kwargs)
|
||||
return mc.execute(firstresult=self.firstresult)
|
||||
|
||||
comregistry = Registry()
|
||||
|
|
|
@ -114,7 +114,7 @@ class TestGatewayManagerPopen:
|
|||
|
||||
class pytest_funcarg__mysetup:
|
||||
def __init__(self, request):
|
||||
tmp = request.maketempdir()
|
||||
tmp = request.config.mktemp(request.function.__name__, numbered=True)
|
||||
self.source = tmp.mkdir("source")
|
||||
self.dest = tmp.mkdir("dest")
|
||||
|
||||
|
|
|
@ -190,3 +190,23 @@ class TestHooks:
|
|||
class Api: pass
|
||||
mcm = Hooks(hookspecs=Api)
|
||||
assert mcm.registry == py._com.comregistry
|
||||
|
||||
def test_hooks_extra_plugins(self):
|
||||
registry = Registry()
|
||||
class Api:
|
||||
def hello(self, arg):
|
||||
pass
|
||||
hook_hello = Hooks(hookspecs=Api, registry=registry).hello
|
||||
class Plugin:
|
||||
def hello(self, arg):
|
||||
return arg + 1
|
||||
registry.register(Plugin())
|
||||
class Plugin2:
|
||||
def hello(self, arg):
|
||||
return arg + 2
|
||||
newhook = hook_hello.clone(extralookup=Plugin2())
|
||||
l = newhook(arg=3)
|
||||
assert l == [5, 4]
|
||||
l2 = hook_hello(arg=3)
|
||||
assert l2 == [4]
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ from py.__.test.dist.nodemanage import NodeManager
|
|||
|
||||
class pytest_funcarg__mysetup:
|
||||
def __init__(self, request):
|
||||
basetemp = request.maketempdir()
|
||||
basetemp = basetemp.mkdir(request.function.__name__)
|
||||
basetemp = request.config.mktemp(
|
||||
"mysetup:%s" % request.function.__name__,
|
||||
numbered=True)
|
||||
self.source = basetemp.mkdir("source")
|
||||
self.dest = basetemp.mkdir("dest")
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import py
|
||||
|
||||
def getfuncargnames(function):
|
||||
argnames = py.std.inspect.getargs(function.func_code)[0]
|
||||
startindex = hasattr(function, 'im_self') and 1 or 0
|
||||
numdefaults = len(function.func_defaults or ())
|
||||
if numdefaults:
|
||||
return argnames[startindex:-numdefaults]
|
||||
return argnames[startindex:]
|
||||
|
||||
def fillfuncargs(function):
|
||||
""" fill missing funcargs. """
|
||||
if function._args:
|
||||
# functions yielded from a generator: we don't want
|
||||
# to support that because we want to go here anyway:
|
||||
# http://bitbucket.org/hpk42/py-trunk/issue/2/next-generation-generative-tests
|
||||
pass
|
||||
else:
|
||||
# standard Python Test function/method case
|
||||
for argname in getfuncargnames(function.obj):
|
||||
if argname not in function.funcargs:
|
||||
request = FuncargRequest(pyfuncitem=function, argname=argname)
|
||||
try:
|
||||
function.funcargs[argname] = request.call_next_provider()
|
||||
except request.Error:
|
||||
request._raiselookupfailed()
|
||||
|
||||
class RunSpecs:
|
||||
def __init__(self, function, config=None, cls=None, module=None):
|
||||
self.config = config
|
||||
self.module = module
|
||||
self.function = function
|
||||
self.funcargnames = getfuncargnames(function)
|
||||
self.cls = cls
|
||||
self.module = module
|
||||
self._combinations = []
|
||||
|
||||
def addfuncarg(self, argname, value):
|
||||
if argname not in self.funcargnames:
|
||||
raise ValueError("function %r has no funcarg %r" %(
|
||||
self.function, argname))
|
||||
newcombi = []
|
||||
if not self._combinations:
|
||||
newcombi.append({argname:value})
|
||||
else:
|
||||
for combi in self._combinations:
|
||||
if argname in combi:
|
||||
combi = combi.copy()
|
||||
newcombi.append(combi)
|
||||
combi[argname] = value
|
||||
self._combinations.extend(newcombi)
|
||||
|
||||
class FunctionCollector(py.test.collect.Collector):
|
||||
def __init__(self, name, parent, combinations):
|
||||
super(FunctionCollector, self).__init__(name, parent)
|
||||
self.combinations = combinations
|
||||
self.obj = getattr(self.parent.obj, name)
|
||||
|
||||
def collect(self):
|
||||
l = []
|
||||
for i, funcargs in py.builtin.enumerate(self.combinations):
|
||||
function = self.parent.Function(name="%s[%s]" %(self.name, i),
|
||||
parent=self, funcargs=funcargs, callobj=self.obj)
|
||||
l.append(function)
|
||||
return l
|
||||
|
||||
class FuncargRequest:
|
||||
_argprefix = "pytest_funcarg__"
|
||||
|
||||
class Error(LookupError):
|
||||
""" error on performing funcarg request. """
|
||||
|
||||
def __init__(self, pyfuncitem, argname):
|
||||
self._pyfuncitem = pyfuncitem
|
||||
self.argname = argname
|
||||
self.function = pyfuncitem.obj
|
||||
self.module = pyfuncitem.getmodulecollector().obj
|
||||
self.cls = getattr(self.function, 'im_class', None)
|
||||
self.config = pyfuncitem.config
|
||||
self.fspath = pyfuncitem.fspath
|
||||
self._plugins = self.config.pluginmanager.getplugins()
|
||||
self._plugins.append(pyfuncitem.getmodulecollector().obj)
|
||||
self._provider = self.config.pluginmanager.listattr(
|
||||
plugins=self._plugins,
|
||||
attrname=self._argprefix + str(argname)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FuncargRequest %r for %r>" %(self.argname, self._pyfuncitem)
|
||||
|
||||
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 _raiselookupfailed(self):
|
||||
available = []
|
||||
for plugin in self._plugins:
|
||||
for name in vars(plugin.__class__):
|
||||
if name.startswith(self._argprefix):
|
||||
name = name[len(self._argprefix):]
|
||||
if name not in available:
|
||||
available.append(name)
|
||||
fspath, lineno, msg = self._pyfuncitem.metainfo()
|
||||
line = "%s:%s" %(fspath, lineno)
|
||||
msg = "funcargument %r not found for: %s" %(self.argname, line)
|
||||
msg += "\n available funcargs: %s" %(", ".join(available),)
|
||||
raise LookupError(msg)
|
||||
|
||||
|
||||
|
|
@ -53,13 +53,15 @@ class PluginHooks:
|
|||
""" return custom item/collector for a python object in a module, or None. """
|
||||
pytest_pycollect_obj.firstresult = True
|
||||
|
||||
def pytest_genfuncruns(self, runspec):
|
||||
""" generate (multiple) parametrized calls to a test function."""
|
||||
|
||||
def pytest_collectstart(self, collector):
|
||||
""" collector starts collecting. """
|
||||
|
||||
def pytest_collectreport(self, rep):
|
||||
""" collector finished collecting. """
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# runtest related hooks
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -142,7 +142,7 @@ class ReSTSyntaxTest(py.test.collect.Item):
|
|||
directives.register_directive('sourcecode', pygments_directive)
|
||||
|
||||
def resolve_linkrole(self, name, text, check=True):
|
||||
apigen_relpath = self.project.hookgen_relpath
|
||||
apigen_relpath = self.project.apigen_relpath
|
||||
|
||||
if name == 'api':
|
||||
if text == 'py':
|
||||
|
|
|
@ -27,9 +27,10 @@ def test_generic(plugintester):
|
|||
plugintester.hookcheck(TmpdirPlugin)
|
||||
|
||||
def test_funcarg(testdir):
|
||||
from py.__.test.funcargs import FuncargRequest
|
||||
item = testdir.getitem("def test_func(tmpdir): pass")
|
||||
plugin = TmpdirPlugin()
|
||||
p = plugin.pytest_funcarg__tmpdir(item.getrequest("tmpdir"))
|
||||
p = plugin.pytest_funcarg__tmpdir(FuncargRequest(item, "tmpdir"))
|
||||
assert p.check()
|
||||
bn = p.basename.strip("0123456789-")
|
||||
assert bn.endswith("test_func")
|
||||
|
|
|
@ -20,6 +20,7 @@ import py
|
|||
from py.__.test.collect import configproperty, warnoldcollect
|
||||
from py.__.code.source import findsource
|
||||
pydir = py.path.local(py.__file__).dirpath()
|
||||
from py.__.test import funcargs
|
||||
|
||||
class PyobjMixin(object):
|
||||
def obj():
|
||||
|
@ -37,6 +38,16 @@ class PyobjMixin(object):
|
|||
def _getobj(self):
|
||||
return getattr(self.parent.obj, self.name)
|
||||
|
||||
def getmodulecollector(self):
|
||||
return self._getparent(Module)
|
||||
def getclasscollector(self):
|
||||
return self._getparent(Class)
|
||||
def _getparent(self, cls):
|
||||
current = self
|
||||
while current and not isinstance(current, cls):
|
||||
current = current.parent
|
||||
return current
|
||||
|
||||
def getmodpath(self, stopatmodule=True, includemodule=False):
|
||||
""" return python path relative to the containing module. """
|
||||
chain = self.listchain()
|
||||
|
@ -150,9 +161,24 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
|||
if res is not None:
|
||||
return res
|
||||
if obj.func_code.co_flags & 32: # generator function
|
||||
# XXX deprecation warning
|
||||
return self.Generator(name, parent=self)
|
||||
else:
|
||||
return self._genfunctions(name, obj)
|
||||
|
||||
def _genfunctions(self, name, funcobj):
|
||||
module = self.getmodulecollector().obj
|
||||
# due to _buildname2items funcobj is the raw function, we need
|
||||
# to work to get at the class
|
||||
clscol = self.getclasscollector()
|
||||
cls = clscol and clscol.obj or None
|
||||
runspec = funcargs.RunSpecs(funcobj, config=self.config, cls=cls, module=module)
|
||||
gentesthook = self.config.hook.pytest_genfuncruns.clone(extralookup=module)
|
||||
gentesthook(runspec=runspec)
|
||||
if not runspec._combinations:
|
||||
return self.Function(name, parent=self)
|
||||
return funcargs.FunctionCollector(name=name,
|
||||
parent=self, combinations=runspec._combinations)
|
||||
|
||||
class Module(py.test.collect.File, PyCollectorMixin):
|
||||
def _getobj(self):
|
||||
|
@ -320,11 +346,13 @@ class Function(FunctionMixin, py.test.collect.Item):
|
|||
""" a Function Item is responsible for setting up
|
||||
and executing a Python callable test object.
|
||||
"""
|
||||
def __init__(self, name, parent=None, config=None, args=(), callobj=_dummy):
|
||||
def __init__(self, name, parent=None, config=None, args=(), funcargs=None, callobj=_dummy):
|
||||
super(Function, self).__init__(name, parent, config=config)
|
||||
self._finalizers = []
|
||||
self._args = args
|
||||
self.funcargs = {}
|
||||
if funcargs is None:
|
||||
funcargs = {}
|
||||
self.funcargs = funcargs
|
||||
if callobj is not _dummy:
|
||||
self._obj = callobj
|
||||
|
||||
|
@ -350,31 +378,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
|||
|
||||
def setup(self):
|
||||
super(Function, self).setup()
|
||||
self._setupfuncargs()
|
||||
|
||||
def _setupfuncargs(self):
|
||||
if self._args:
|
||||
# functions yielded from a generator: we don't want
|
||||
# to support that because we want to go here anyway:
|
||||
# http://bitbucket.org/hpk42/py-trunk/issue/2/next-generation-generative-tests
|
||||
pass
|
||||
else:
|
||||
# standard Python Test function/method case
|
||||
funcobj = self.obj
|
||||
startindex = getattr(funcobj, 'im_self', None) and 1 or 0
|
||||
argnames = py.std.inspect.getargs(self.obj.func_code)[0]
|
||||
for i, argname in py.builtin.enumerate(argnames):
|
||||
if i < startindex:
|
||||
continue
|
||||
request = self.getrequest(argname)
|
||||
try:
|
||||
self.funcargs[argname] = request.call_next_provider()
|
||||
except request.Error:
|
||||
numdefaults = len(funcobj.func_defaults or ())
|
||||
if i + numdefaults >= len(argnames):
|
||||
continue # our args have defaults XXX issue warning?
|
||||
else:
|
||||
request._raiselookupfailed()
|
||||
funcargs.fillfuncargs(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
|
@ -385,74 +389,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
|||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def getrequest(self, argname):
|
||||
return FuncargRequest(pyfuncitem=self, argname=argname)
|
||||
|
||||
|
||||
class FuncargRequest:
|
||||
_argprefix = "pytest_funcarg__"
|
||||
|
||||
class Error(LookupError):
|
||||
""" error on performing funcarg request. """
|
||||
|
||||
def __init__(self, pyfuncitem, argname):
|
||||
# XXX make pyfuncitem _pyfuncitem
|
||||
self._pyfuncitem = pyfuncitem
|
||||
self.argname = argname
|
||||
self.function = pyfuncitem.obj
|
||||
self.config = pyfuncitem.config
|
||||
self.fspath = pyfuncitem.fspath
|
||||
self._plugins = self._getplugins()
|
||||
self._methods = self.config.pluginmanager.listattr(
|
||||
plugins=self._plugins,
|
||||
attrname=self._argprefix + str(argname)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FuncargRequest %r for %r>" %(self.argname, self._pyfuncitem)
|
||||
|
||||
|
||||
def _getplugins(self):
|
||||
plugins = []
|
||||
current = self._pyfuncitem
|
||||
while not isinstance(current, Module):
|
||||
current = current.parent
|
||||
if isinstance(current, (Instance, Module)):
|
||||
plugins.insert(0, current.obj)
|
||||
return self.config.pluginmanager.getplugins() + plugins
|
||||
|
||||
def call_next_provider(self):
|
||||
if not self._methods:
|
||||
raise self.Error("no provider methods left")
|
||||
nextmethod = self._methods.pop()
|
||||
return nextmethod(request=self)
|
||||
|
||||
def addfinalizer(self, finalizer):
|
||||
self._pyfuncitem.addfinalizer(finalizer)
|
||||
|
||||
def maketempdir(self):
|
||||
basetemp = self.config.getbasetemp()
|
||||
tmp = py.path.local.make_numbered_dir(
|
||||
prefix=self.function.__name__ + "_",
|
||||
keep=0, rootdir=basetemp)
|
||||
return tmp
|
||||
|
||||
def _raiselookupfailed(self):
|
||||
available = []
|
||||
for plugin in self._plugins:
|
||||
for name in vars(plugin.__class__):
|
||||
if name.startswith(self._argprefix):
|
||||
name = name[len(self._argprefix):]
|
||||
if name not in available:
|
||||
available.append(name)
|
||||
fspath, lineno, msg = self._pyfuncitem.metainfo()
|
||||
line = "%s:%s" %(fspath, lineno)
|
||||
msg = "funcargument %r not found for: %s" %(self.argname, line)
|
||||
msg += "\n available funcargs: %s" %(", ".join(available),)
|
||||
raise LookupError(msg)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
import py
|
||||
from py.__.test import funcargs
|
||||
|
||||
class TestFuncargs:
|
||||
def test_getfuncargnames():
|
||||
def f(): pass
|
||||
assert not funcargs.getfuncargnames(f)
|
||||
def g(arg): pass
|
||||
assert funcargs.getfuncargnames(g) == ['arg']
|
||||
def h(arg1, arg2="hello"): pass
|
||||
assert funcargs.getfuncargnames(h) == ['arg1']
|
||||
def h(arg1, arg2, arg3="hello"): pass
|
||||
assert funcargs.getfuncargnames(h) == ['arg1', 'arg2']
|
||||
class A:
|
||||
def f(self, arg1, arg2="hello"):
|
||||
pass
|
||||
assert funcargs.getfuncargnames(A().f) == ['arg1']
|
||||
assert funcargs.getfuncargnames(A.f) == ['arg1']
|
||||
|
||||
class TestFillFuncArgs:
|
||||
def test_funcarg_lookupfails(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
class ConftestPlugin:
|
||||
|
@ -8,7 +24,7 @@ class TestFuncargs:
|
|||
return 42
|
||||
""")
|
||||
item = testdir.getitem("def test_func(some): pass")
|
||||
exc = py.test.raises(LookupError, "item._setupfuncargs()")
|
||||
exc = py.test.raises(LookupError, "funcargs.fillfuncargs(item)")
|
||||
s = str(exc.value)
|
||||
assert s.find("xyzsomething") != -1
|
||||
|
||||
|
@ -18,21 +34,9 @@ class TestFuncargs:
|
|||
def pytest_funcarg__some(self, request):
|
||||
return request.function.__name__
|
||||
item.config.pluginmanager.register(Provider())
|
||||
item._setupfuncargs()
|
||||
funcargs.fillfuncargs(item)
|
||||
assert len(item.funcargs) == 1
|
||||
|
||||
def test_funcarg_lookup_default_gets_overriden(self, testdir):
|
||||
item = testdir.getitem("def test_func(some=42, other=13): pass")
|
||||
class Provider:
|
||||
def pytest_funcarg__other(self, request):
|
||||
return request.function.__name__
|
||||
item.config.pluginmanager.register(Provider())
|
||||
item._setupfuncargs()
|
||||
assert len(item.funcargs) == 1
|
||||
name, value = item.funcargs.popitem()
|
||||
assert name == "other"
|
||||
assert value == item.name
|
||||
|
||||
def test_funcarg_basic(self, testdir):
|
||||
item = testdir.getitem("def test_func(some, other): pass")
|
||||
class Provider:
|
||||
|
@ -41,7 +45,7 @@ class TestFuncargs:
|
|||
def pytest_funcarg__other(self, request):
|
||||
return 42
|
||||
item.config.pluginmanager.register(Provider())
|
||||
item._setupfuncargs()
|
||||
funcargs.fillfuncargs(item)
|
||||
assert len(item.funcargs) == 2
|
||||
assert item.funcargs['some'] == "test_func"
|
||||
assert item.funcargs['other'] == 42
|
||||
|
@ -58,9 +62,9 @@ class TestFuncargs:
|
|||
pass
|
||||
""")
|
||||
item1, item2 = testdir.genitems([modcol])
|
||||
item1._setupfuncargs()
|
||||
funcargs.fillfuncargs(item1)
|
||||
assert item1.funcargs['something'] == "test_method"
|
||||
item2._setupfuncargs()
|
||||
funcargs.fillfuncargs(item2)
|
||||
assert item2.funcargs['something'] == "test_func"
|
||||
|
||||
class TestRequest:
|
||||
|
@ -69,37 +73,44 @@ class TestRequest:
|
|||
def pytest_funcarg__something(request): pass
|
||||
def test_func(something): pass
|
||||
""")
|
||||
req = item.getrequest("other")
|
||||
req = funcargs.FuncargRequest(item, argname="other")
|
||||
assert req.argname == "other"
|
||||
assert req.function == item.obj
|
||||
assert hasattr(req.module, 'test_func')
|
||||
assert req.cls is None
|
||||
assert req.function.__name__ == "test_func"
|
||||
assert req.config == item.config
|
||||
assert repr(req).find(req.function.__name__) != -1
|
||||
|
||||
def test_request_contains_funcargs_methods(self, testdir):
|
||||
def test_request_attributes_method(self, testdir):
|
||||
item, = testdir.getitems("""
|
||||
class TestB:
|
||||
def test_func(self, something):
|
||||
pass
|
||||
""")
|
||||
req = funcargs.FuncargRequest(item, argname="something")
|
||||
assert req.cls.__name__ == "TestB"
|
||||
|
||||
def test_request_contains_funcargs_provider(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def pytest_funcarg__something(request):
|
||||
pass
|
||||
class TestClass:
|
||||
def pytest_funcarg__something(self, request):
|
||||
pass
|
||||
def test_method(self, something):
|
||||
pass
|
||||
""")
|
||||
item1, = testdir.genitems([modcol])
|
||||
assert item1.name == "test_method"
|
||||
methods = item1.getrequest("something")._methods
|
||||
assert len(methods) == 2
|
||||
method1, method2 = methods
|
||||
assert not hasattr(method1, 'im_self')
|
||||
assert method2.im_self is not None
|
||||
provider = funcargs.FuncargRequest(item1, "something")._provider
|
||||
assert len(provider) == 1
|
||||
assert provider[0].__name__ == "pytest_funcarg__something"
|
||||
|
||||
def test_request_call_next_provider(self, testdir):
|
||||
item = testdir.getitem("""
|
||||
def pytest_funcarg__something(request): pass
|
||||
def test_func(something): pass
|
||||
""")
|
||||
req = item.getrequest("something")
|
||||
req = funcargs.FuncargRequest(item, "something")
|
||||
val = req.call_next_provider()
|
||||
assert val is None
|
||||
py.test.raises(req.Error, "req.call_next_provider()")
|
||||
|
@ -109,22 +120,147 @@ class TestRequest:
|
|||
def pytest_funcarg__something(request): pass
|
||||
def test_func(something): pass
|
||||
""")
|
||||
req = item.getrequest("something")
|
||||
req = funcargs.FuncargRequest(item, "something")
|
||||
l = [1]
|
||||
req.addfinalizer(l.pop)
|
||||
item.teardown()
|
||||
|
||||
def test_request_maketemp(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
req = item.getrequest("xxx")
|
||||
tmpdir = req.maketempdir()
|
||||
tmpdir2 = req.maketempdir()
|
||||
assert tmpdir != tmpdir2
|
||||
assert tmpdir.basename.startswith("test_func")
|
||||
assert tmpdir2.basename.startswith("test_func")
|
||||
|
||||
def test_request_getmodulepath(self, testdir):
|
||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||
item, = testdir.genitems([modcol])
|
||||
req = item.getrequest("hello")
|
||||
req = funcargs.FuncargRequest(item, "xxx")
|
||||
assert req.fspath == modcol.fspath
|
||||
|
||||
class TestRunSpecs:
|
||||
def test_no_funcargs(self, testdir):
|
||||
def function(): pass
|
||||
runspec = funcargs.RunSpecs(function)
|
||||
assert not runspec.funcargnames
|
||||
|
||||
def test_function_basic(self):
|
||||
def func(arg1, arg2="qwe"): pass
|
||||
runspec = funcargs.RunSpecs(func)
|
||||
assert len(runspec.funcargnames) == 1
|
||||
assert 'arg1' in runspec.funcargnames
|
||||
assert runspec.function is func
|
||||
assert runspec.cls is None
|
||||
|
||||
def test_addfuncarg_basic(self):
|
||||
def func(arg1): pass
|
||||
runspec = funcargs.RunSpecs(func)
|
||||
py.test.raises(ValueError, """
|
||||
runspec.addfuncarg("notexists", 100)
|
||||
""")
|
||||
runspec.addfuncarg("arg1", 100)
|
||||
assert len(runspec._combinations) == 1
|
||||
assert runspec._combinations[0] == {'arg1': 100}
|
||||
|
||||
def test_addfuncarg_two(self):
|
||||
def func(arg1): pass
|
||||
runspec = funcargs.RunSpecs(func)
|
||||
runspec.addfuncarg("arg1", 100)
|
||||
runspec.addfuncarg("arg1", 101)
|
||||
assert len(runspec._combinations) == 2
|
||||
assert runspec._combinations[0] == {'arg1': 100}
|
||||
assert runspec._combinations[1] == {'arg1': 101}
|
||||
|
||||
def test_addfuncarg_combined(self):
|
||||
runspec = funcargs.RunSpecs(lambda arg1, arg2: 0)
|
||||
runspec.addfuncarg('arg1', 1)
|
||||
runspec.addfuncarg('arg1', 2)
|
||||
runspec.addfuncarg('arg2', 100)
|
||||
combinations = runspec._combinations
|
||||
assert len(combinations) == 2
|
||||
assert combinations[0] == {'arg1': 1, 'arg2': 100}
|
||||
assert combinations[1] == {'arg1': 2, 'arg2': 100}
|
||||
runspec.addfuncarg('arg2', 101)
|
||||
assert len(combinations) == 4
|
||||
assert combinations[-1] == {'arg1': 2, 'arg2': 101}
|
||||
|
||||
class TestGenfuncFunctional:
|
||||
def test_attributes(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import py
|
||||
def pytest_genfuncruns(runspec):
|
||||
runspec.addfuncarg("runspec", runspec)
|
||||
|
||||
def test_function(runspec):
|
||||
assert runspec.config == py.test.config
|
||||
assert runspec.module.__name__ == __name__
|
||||
assert runspec.function == test_function
|
||||
assert runspec.cls is None
|
||||
class TestClass:
|
||||
def test_method(self, runspec):
|
||||
assert runspec.config == py.test.config
|
||||
assert runspec.module.__name__ == __name__
|
||||
# XXX actually have the unbound test function here?
|
||||
assert runspec.function == TestClass.test_method.im_func
|
||||
assert runspec.cls == TestClass
|
||||
""")
|
||||
result = testdir.runpytest(p, "-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed in*",
|
||||
])
|
||||
|
||||
def test_arg_twice(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
class ConftestPlugin:
|
||||
def pytest_genfuncruns(self, runspec):
|
||||
assert "arg" in runspec.funcargnames
|
||||
runspec.addfuncarg("arg", 10)
|
||||
runspec.addfuncarg("arg", 20)
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def test_myfunc(arg):
|
||||
assert arg == 10
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
assert result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*PASS*", # case for 10
|
||||
"*test_myfunc*FAIL*", # case for 20
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
||||
def test_two_functions(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_genfuncruns(runspec):
|
||||
runspec.addfuncarg("arg1", 10)
|
||||
runspec.addfuncarg("arg1", 20)
|
||||
|
||||
def test_func1(arg1):
|
||||
assert arg1 == 10
|
||||
def test_func2(arg1):
|
||||
assert arg1 in (10, 20)
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
assert result.stdout.fnmatch_lines([
|
||||
"*test_func1*0*PASS*",
|
||||
"*test_func1*1*FAIL*",
|
||||
"*test_func2*PASS*",
|
||||
"*1 failed, 3 passed*"
|
||||
])
|
||||
|
||||
def test_genfuncarg_inmodule(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
class ConftestPlugin:
|
||||
def pytest_genfuncruns(self, runspec):
|
||||
assert "arg" in runspec.funcargnames
|
||||
runspec.addfuncarg("arg", 10)
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_genfuncruns(runspec):
|
||||
runspec.addfuncarg("arg2", 10)
|
||||
runspec.addfuncarg("arg2", 20)
|
||||
runspec.addfuncarg("classarg", 17)
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg, arg2, classarg):
|
||||
assert classarg == 17
|
||||
assert arg == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
assert result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*0*PASS*",
|
||||
"*test_myfunc*1*FAIL*",
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
|
|
@ -34,9 +34,9 @@ class ImmutablePickleTransport:
|
|||
p2config._initafterpickle(config.topdir)
|
||||
return p2config
|
||||
|
||||
class TestImmutablePickling:
|
||||
pytest_funcarg__pickletransport = ImmutablePickleTransport
|
||||
pytest_funcarg__pickletransport = ImmutablePickleTransport
|
||||
|
||||
class TestImmutablePickling:
|
||||
def test_pickle_config(self, testdir, pickletransport):
|
||||
config1 = testdir.parseconfig()
|
||||
assert config1.topdir == testdir.tmpdir
|
||||
|
|
|
@ -215,6 +215,12 @@ class TestGenerator:
|
|||
assert not skipped and not failed
|
||||
|
||||
class TestFunction:
|
||||
def test_getmodulecollector(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
modcol = item.getmodulecollector()
|
||||
assert isinstance(modcol, py.test.collect.Module)
|
||||
assert hasattr(modcol.obj, 'test_func')
|
||||
|
||||
def test_function_equality(self, tmpdir):
|
||||
config = py.test.config._reparse([tmpdir])
|
||||
f1 = py.test.collect.Function(name="name", config=config,
|
||||
|
|
Loading…
Reference in New Issue