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
|
``py.test`` runs provide means to create per-test session
|
||||||
temporary (sub) directories. You can create such directories
|
temporary (sub) directories through the config object.
|
||||||
like this:
|
You can create directories like this:
|
||||||
|
|
||||||
|
.. XXX use a more local example, just with "config"
|
||||||
|
|
||||||
.. sourcecode: python
|
.. 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
|
Since version 1.0 it is possible to provide arguments to test functions,
|
||||||
state for use by Python test functions. It is particularly useful
|
often called "funcargs". The funcarg mechanisms were developed with
|
||||||
for functional and integration testing but also for unit testing.
|
these goals in mind:
|
||||||
Using funcargs you can easily:
|
|
||||||
|
|
||||||
* write self-contained, simple to read and debug test functions
|
* **no boilerplate**: cleanly encapsulate test setup and fixtures
|
||||||
* cleanly encapsulate glue code between your app and your tests
|
* **flexibility**: easily setup test state depending on command line options or environment
|
||||||
* 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:
|
.. contents:: Contents:
|
||||||
:depth: 2
|
:depth: 2
|
||||||
|
|
||||||
The basic funcarg request/provide mechanism
|
Basic mechanisms by example
|
||||||
=============================================
|
=============================================
|
||||||
|
|
||||||
To use funcargs you only need to specify
|
providing single function arguments as needed
|
||||||
a named argument for your test function:
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
Let's look at a simple example of using funcargs within a test module:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
def test_function(myarg):
|
def pytest_funcarg__myfuncarg(request):
|
||||||
# use myarg
|
return 42
|
||||||
|
|
||||||
For each test function that requests this ``myarg``
|
def test_function(myfuncarg):
|
||||||
argument a matching so called funcarg provider
|
assert myfuncarg == 42
|
||||||
will be invoked. A Funcarg provider for ``myarg``
|
|
||||||
is written down liks this:
|
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
|
.. sourcecode:: python
|
||||||
|
|
||||||
def pytest_funcarg__myarg(self, request):
|
def pytest_genfuncruns(runspec):
|
||||||
# return value for myarg here
|
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,
|
def test_function(arg1):
|
||||||
test module or on a local or global plugin.
|
assert myfuncarg in (10, 20, 30)
|
||||||
The method is recognized by the ``pytest_funcarg__``
|
|
||||||
prefix and is correlated to the argument
|
Here is what happens:
|
||||||
name which follows this prefix. The passed in
|
|
||||||
``request`` object allows to interact
|
1. The ``pytest_genfuncruns()`` hook will be called once for each test
|
||||||
with test configuration, test collection
|
function. The if-statement makes sure that we only add function
|
||||||
and test running aspects.
|
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`:
|
.. _`request object`:
|
||||||
|
|
||||||
|
@ -65,11 +89,13 @@ Attributes of request objects
|
||||||
|
|
||||||
``request.function``: python function object requesting the argument
|
``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
|
``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
|
Request objects allow to **register a finalizer method** which is
|
||||||
|
@ -86,33 +112,8 @@ function finish:
|
||||||
request.addfinalizer(lambda: myfile.close())
|
request.addfinalizer(lambda: myfile.close())
|
||||||
return myfile
|
return myfile
|
||||||
|
|
||||||
a unique temporary directory
|
|
||||||
++++++++++++++++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
request objects allow to create unique temporary
|
decorating other funcarg providers
|
||||||
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
|
|
||||||
++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
If you want to **decorate a function argument** that is
|
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.
|
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
|
Both test generators as well as funcarg providers
|
||||||
lookup order for provider methods:
|
are looked up in the following three scopes:
|
||||||
|
|
||||||
1. test class (if we are executing a method)
|
|
||||||
2. test module
|
|
||||||
3. local plugins
|
|
||||||
4. global plugins
|
|
||||||
|
|
||||||
|
1. test module
|
||||||
|
2. local plugins
|
||||||
|
3. global plugins
|
||||||
|
|
||||||
Using multiple funcargs
|
Using multiple funcargs
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
A test function may receive more than one
|
Test functions can have multiple arguments
|
||||||
function arguments. For each of the
|
which can either come from a test generator
|
||||||
function arguments a lookup of a
|
or from a provider.
|
||||||
matching provider will be performed.
|
|
||||||
|
.. _`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
|
Here is a basic useful step-wise example for handling application
|
||||||
|
@ -202,7 +243,7 @@ following code into a local ``conftest.py``:
|
||||||
return MyApp()
|
return MyApp()
|
||||||
|
|
||||||
py.test finds the ``pytest_funcarg__mysetup`` method by
|
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``:
|
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()
|
conn = mysetup.getsshconnection()
|
||||||
# work with conn
|
# work with conn
|
||||||
|
|
||||||
Running this without the command line will yield this run result::
|
Running this without specifying a command line option will result in a skipped
|
||||||
|
test_function.
|
||||||
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):
|
|
||||||
# ...
|
|
||||||
#
|
|
||||||
|
|
||||||
.. _`accept example`:
|
.. _`accept example`:
|
||||||
|
|
||||||
|
@ -309,16 +329,12 @@ example: specifying and selecting acceptance tests
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
if not request.config.option.acceptance:
|
if not request.config.option.acceptance:
|
||||||
py.test.skip("specify -A to run acceptance tests")
|
py.test.skip("specify -A to run acceptance tests")
|
||||||
self.tmpdir = request.config.maketempdir(request.argname)
|
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
|
||||||
self._old = self.tmpdir.chdir()
|
|
||||||
request.addfinalizer(self.finalize)
|
def run(self, cmd):
|
||||||
|
""" called by test code to execute an acceptance test. """
|
||||||
def run(self):
|
self.tmpdir.chdir()
|
||||||
return py.process.cmdexec("echo hello")
|
return py.process.cmdexec(cmd)
|
||||||
|
|
||||||
def finalize(self):
|
|
||||||
self._old.chdir()
|
|
||||||
# cleanup any other resources
|
|
||||||
|
|
||||||
|
|
||||||
and the actual test function example:
|
and the actual test function example:
|
||||||
|
@ -327,48 +343,116 @@ and the actual test function example:
|
||||||
|
|
||||||
def test_some_acceptance_aspect(accept):
|
def test_some_acceptance_aspect(accept):
|
||||||
accept.tmpdir.mkdir("somesub")
|
accept.tmpdir.mkdir("somesub")
|
||||||
result = accept.run()
|
result = accept.run("ls -la")
|
||||||
assert result
|
assert "somesub" in result
|
||||||
|
|
||||||
That's it! This test will get automatically skipped with
|
If you run this test without specifying a command line option
|
||||||
an appropriate message if you just run ``py.test``::
|
the test will get skipped with an appropriate message. Otherwise
|
||||||
|
you can start to add convenience and test support methods
|
||||||
... OUTPUT of py.test on this example ...
|
to your AcceptFuncarg and drive running of tools or
|
||||||
|
applications and provide ways to do assertions about
|
||||||
|
the output.
|
||||||
|
|
||||||
.. _`decorator example`:
|
.. _`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
|
For larger scale setups it's sometimes useful to decorare
|
||||||
a funcarg just for a particular test module or even
|
a funcarg just for a particular test module. We can
|
||||||
a particular test class. We can extend the `accept example`_
|
extend the `accept example`_ by putting this in our test class:
|
||||||
by putting this in our test class:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
class TestSpecialAcceptance:
|
def pytest_funcarg__accept(self, request):
|
||||||
def pytest_funcarg__accept(self, request):
|
arg = request.call_next_provider()
|
||||||
arg = request.call_next_provider()
|
# create a special layout in our tempdir
|
||||||
# create a special layout in our tempdir
|
arg.tmpdir.mkdir("special")
|
||||||
arg.tmpdir.mkdir("special")
|
return arg
|
||||||
return arg
|
|
||||||
|
|
||||||
|
class TestSpecialAcceptance:
|
||||||
def test_sometest(self, accept):
|
def test_sometest(self, accept):
|
||||||
assert accept.tmpdir.join("special").check()
|
assert accept.tmpdir.join("special").check()
|
||||||
|
|
||||||
According to the `funcarg lookup order`_ our class-specific provider will
|
According to the the `lookup order`_ our module level provider
|
||||||
be invoked first. Here, we just ask our request object to
|
will be invoked first and it can ask ask its request object to
|
||||||
call the next provider and decorate its result. This simple
|
call the next provider and then decorate its result. This
|
||||||
mechanism allows us to stay ignorant of how/where the
|
mechanism allows us to stay ignorant of how/where the
|
||||||
function argument is provided.
|
function argument is provided.
|
||||||
|
|
||||||
Note that we make use here of `py.path.local`_ objects
|
sidenote: the temporary directory used here are instances of
|
||||||
that provide uniform access to the local filesystem.
|
the `py.path.local`_ class which provides many of the os.path
|
||||||
|
methods in a convenient way.
|
||||||
|
|
||||||
.. _`py.path.local`: ../path.html#local
|
.. _`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
|
Questions and Answers
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
|
@ -377,14 +461,14 @@ Questions and Answers
|
||||||
Why ``pytest_funcarg__*`` methods?
|
Why ``pytest_funcarg__*`` methods?
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
When experimenting with funcargs we also considered an explicit
|
When experimenting with funcargs we also
|
||||||
registration mechanism, i.e. calling a register method e.g. on the
|
considered an explicit registration mechanism, i.e. calling a register
|
||||||
config object. But lacking a good use case for this indirection and
|
method on the config object. But lacking a good use case for this
|
||||||
flexibility we decided to go for `Convention over Configuration`_
|
indirection and flexibility we decided to go for `Convention over
|
||||||
and allow to directly specify the provider. It has the
|
Configuration`_ and allow to directly specify the provider. It has the
|
||||||
positive implication that you should be able to
|
positive implication that you should be able to "grep" for
|
||||||
"grep" for ``pytest_funcarg__MYARG`` and will find all
|
``pytest_funcarg__MYARG`` and will find all providing sites (usually
|
||||||
providing sites (usually exactly one).
|
exactly one).
|
||||||
|
|
||||||
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
.. _`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.
|
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.
|
plugins_: using available plugins.
|
||||||
|
|
||||||
extend_: writing plugins and advanced configuration.
|
extend_: writing plugins and advanced configuration.
|
||||||
|
|
||||||
`distributed testing`_ how to distribute test runs to other machines and platforms.
|
|
||||||
|
|
||||||
.. _quickstart: quickstart.html
|
.. _quickstart: quickstart.html
|
||||||
.. _features: features.html
|
.. _features: features.html
|
||||||
|
.. _funcargs: funcargs.html
|
||||||
.. _plugins: plugins.html
|
.. _plugins: plugins.html
|
||||||
.. _extend: ext.html
|
.. _extend: ext.html
|
||||||
.. _`distributed testing`: dist.html
|
.. _`distributed testing`: dist.html
|
||||||
|
|
|
@ -123,10 +123,14 @@ class Hooks:
|
||||||
return "<Hooks %r %r>" %(self._hookspecs, self._plugins)
|
return "<Hooks %r %r>" %(self._hookspecs, self._plugins)
|
||||||
|
|
||||||
class HookCall:
|
class HookCall:
|
||||||
def __init__(self, registry, name, firstresult):
|
def __init__(self, registry, name, firstresult, extralookup=None):
|
||||||
self.registry = registry
|
self.registry = registry
|
||||||
self.name = name
|
self.name = name
|
||||||
self.firstresult = firstresult
|
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):
|
def __repr__(self):
|
||||||
mode = self.firstresult and "firstresult" or "each"
|
mode = self.firstresult and "firstresult" or "each"
|
||||||
|
@ -136,7 +140,8 @@ class HookCall:
|
||||||
if args:
|
if args:
|
||||||
raise TypeError("only keyword arguments allowed "
|
raise TypeError("only keyword arguments allowed "
|
||||||
"for api call to %r" % self.name)
|
"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)
|
return mc.execute(firstresult=self.firstresult)
|
||||||
|
|
||||||
comregistry = Registry()
|
comregistry = Registry()
|
||||||
|
|
|
@ -114,7 +114,7 @@ class TestGatewayManagerPopen:
|
||||||
|
|
||||||
class pytest_funcarg__mysetup:
|
class pytest_funcarg__mysetup:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
tmp = request.maketempdir()
|
tmp = request.config.mktemp(request.function.__name__, numbered=True)
|
||||||
self.source = tmp.mkdir("source")
|
self.source = tmp.mkdir("source")
|
||||||
self.dest = tmp.mkdir("dest")
|
self.dest = tmp.mkdir("dest")
|
||||||
|
|
||||||
|
|
|
@ -190,3 +190,23 @@ class TestHooks:
|
||||||
class Api: pass
|
class Api: pass
|
||||||
mcm = Hooks(hookspecs=Api)
|
mcm = Hooks(hookspecs=Api)
|
||||||
assert mcm.registry == py._com.comregistry
|
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:
|
class pytest_funcarg__mysetup:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
basetemp = request.maketempdir()
|
basetemp = request.config.mktemp(
|
||||||
basetemp = basetemp.mkdir(request.function.__name__)
|
"mysetup:%s" % request.function.__name__,
|
||||||
|
numbered=True)
|
||||||
self.source = basetemp.mkdir("source")
|
self.source = basetemp.mkdir("source")
|
||||||
self.dest = basetemp.mkdir("dest")
|
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. """
|
""" return custom item/collector for a python object in a module, or None. """
|
||||||
pytest_pycollect_obj.firstresult = True
|
pytest_pycollect_obj.firstresult = True
|
||||||
|
|
||||||
|
def pytest_genfuncruns(self, runspec):
|
||||||
|
""" generate (multiple) parametrized calls to a test function."""
|
||||||
|
|
||||||
def pytest_collectstart(self, collector):
|
def pytest_collectstart(self, collector):
|
||||||
""" collector starts collecting. """
|
""" collector starts collecting. """
|
||||||
|
|
||||||
def pytest_collectreport(self, rep):
|
def pytest_collectreport(self, rep):
|
||||||
""" collector finished collecting. """
|
""" collector finished collecting. """
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# runtest related hooks
|
# runtest related hooks
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -142,7 +142,7 @@ class ReSTSyntaxTest(py.test.collect.Item):
|
||||||
directives.register_directive('sourcecode', pygments_directive)
|
directives.register_directive('sourcecode', pygments_directive)
|
||||||
|
|
||||||
def resolve_linkrole(self, name, text, check=True):
|
def resolve_linkrole(self, name, text, check=True):
|
||||||
apigen_relpath = self.project.hookgen_relpath
|
apigen_relpath = self.project.apigen_relpath
|
||||||
|
|
||||||
if name == 'api':
|
if name == 'api':
|
||||||
if text == 'py':
|
if text == 'py':
|
||||||
|
|
|
@ -27,9 +27,10 @@ def test_generic(plugintester):
|
||||||
plugintester.hookcheck(TmpdirPlugin)
|
plugintester.hookcheck(TmpdirPlugin)
|
||||||
|
|
||||||
def test_funcarg(testdir):
|
def test_funcarg(testdir):
|
||||||
|
from py.__.test.funcargs import FuncargRequest
|
||||||
item = testdir.getitem("def test_func(tmpdir): pass")
|
item = testdir.getitem("def test_func(tmpdir): pass")
|
||||||
plugin = TmpdirPlugin()
|
plugin = TmpdirPlugin()
|
||||||
p = plugin.pytest_funcarg__tmpdir(item.getrequest("tmpdir"))
|
p = plugin.pytest_funcarg__tmpdir(FuncargRequest(item, "tmpdir"))
|
||||||
assert p.check()
|
assert p.check()
|
||||||
bn = p.basename.strip("0123456789-")
|
bn = p.basename.strip("0123456789-")
|
||||||
assert bn.endswith("test_func")
|
assert bn.endswith("test_func")
|
||||||
|
|
|
@ -20,6 +20,7 @@ import py
|
||||||
from py.__.test.collect import configproperty, warnoldcollect
|
from py.__.test.collect import configproperty, warnoldcollect
|
||||||
from py.__.code.source import findsource
|
from py.__.code.source import findsource
|
||||||
pydir = py.path.local(py.__file__).dirpath()
|
pydir = py.path.local(py.__file__).dirpath()
|
||||||
|
from py.__.test import funcargs
|
||||||
|
|
||||||
class PyobjMixin(object):
|
class PyobjMixin(object):
|
||||||
def obj():
|
def obj():
|
||||||
|
@ -37,6 +38,16 @@ class PyobjMixin(object):
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
return getattr(self.parent.obj, self.name)
|
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):
|
def getmodpath(self, stopatmodule=True, includemodule=False):
|
||||||
""" return python path relative to the containing module. """
|
""" return python path relative to the containing module. """
|
||||||
chain = self.listchain()
|
chain = self.listchain()
|
||||||
|
@ -150,10 +161,25 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
if res is not None:
|
if res is not None:
|
||||||
return res
|
return res
|
||||||
if obj.func_code.co_flags & 32: # generator function
|
if obj.func_code.co_flags & 32: # generator function
|
||||||
|
# XXX deprecation warning
|
||||||
return self.Generator(name, parent=self)
|
return self.Generator(name, parent=self)
|
||||||
else:
|
else:
|
||||||
return self.Function(name, parent=self)
|
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):
|
class Module(py.test.collect.File, PyCollectorMixin):
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
return self._memoizedcall('_obj', self._importtestmodule)
|
return self._memoizedcall('_obj', self._importtestmodule)
|
||||||
|
@ -320,11 +346,13 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
""" a Function Item is responsible for setting up
|
""" a Function Item is responsible for setting up
|
||||||
and executing a Python callable test object.
|
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)
|
super(Function, self).__init__(name, parent, config=config)
|
||||||
self._finalizers = []
|
self._finalizers = []
|
||||||
self._args = args
|
self._args = args
|
||||||
self.funcargs = {}
|
if funcargs is None:
|
||||||
|
funcargs = {}
|
||||||
|
self.funcargs = funcargs
|
||||||
if callobj is not _dummy:
|
if callobj is not _dummy:
|
||||||
self._obj = callobj
|
self._obj = callobj
|
||||||
|
|
||||||
|
@ -350,31 +378,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
super(Function, self).setup()
|
super(Function, self).setup()
|
||||||
self._setupfuncargs()
|
funcargs.fillfuncargs(self)
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
try:
|
try:
|
||||||
|
@ -385,74 +389,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not 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
|
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):
|
def test_funcarg_lookupfails(self, testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
class ConftestPlugin:
|
class ConftestPlugin:
|
||||||
|
@ -8,7 +24,7 @@ class TestFuncargs:
|
||||||
return 42
|
return 42
|
||||||
""")
|
""")
|
||||||
item = testdir.getitem("def test_func(some): pass")
|
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)
|
s = str(exc.value)
|
||||||
assert s.find("xyzsomething") != -1
|
assert s.find("xyzsomething") != -1
|
||||||
|
|
||||||
|
@ -18,21 +34,9 @@ class TestFuncargs:
|
||||||
def pytest_funcarg__some(self, request):
|
def pytest_funcarg__some(self, request):
|
||||||
return request.function.__name__
|
return request.function.__name__
|
||||||
item.config.pluginmanager.register(Provider())
|
item.config.pluginmanager.register(Provider())
|
||||||
item._setupfuncargs()
|
funcargs.fillfuncargs(item)
|
||||||
assert len(item.funcargs) == 1
|
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):
|
def test_funcarg_basic(self, testdir):
|
||||||
item = testdir.getitem("def test_func(some, other): pass")
|
item = testdir.getitem("def test_func(some, other): pass")
|
||||||
class Provider:
|
class Provider:
|
||||||
|
@ -41,7 +45,7 @@ class TestFuncargs:
|
||||||
def pytest_funcarg__other(self, request):
|
def pytest_funcarg__other(self, request):
|
||||||
return 42
|
return 42
|
||||||
item.config.pluginmanager.register(Provider())
|
item.config.pluginmanager.register(Provider())
|
||||||
item._setupfuncargs()
|
funcargs.fillfuncargs(item)
|
||||||
assert len(item.funcargs) == 2
|
assert len(item.funcargs) == 2
|
||||||
assert item.funcargs['some'] == "test_func"
|
assert item.funcargs['some'] == "test_func"
|
||||||
assert item.funcargs['other'] == 42
|
assert item.funcargs['other'] == 42
|
||||||
|
@ -58,9 +62,9 @@ class TestFuncargs:
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
item1, item2 = testdir.genitems([modcol])
|
item1, item2 = testdir.genitems([modcol])
|
||||||
item1._setupfuncargs()
|
funcargs.fillfuncargs(item1)
|
||||||
assert item1.funcargs['something'] == "test_method"
|
assert item1.funcargs['something'] == "test_method"
|
||||||
item2._setupfuncargs()
|
funcargs.fillfuncargs(item2)
|
||||||
assert item2.funcargs['something'] == "test_func"
|
assert item2.funcargs['something'] == "test_func"
|
||||||
|
|
||||||
class TestRequest:
|
class TestRequest:
|
||||||
|
@ -69,37 +73,44 @@ class TestRequest:
|
||||||
def pytest_funcarg__something(request): pass
|
def pytest_funcarg__something(request): pass
|
||||||
def test_func(something): pass
|
def test_func(something): pass
|
||||||
""")
|
""")
|
||||||
req = item.getrequest("other")
|
req = funcargs.FuncargRequest(item, argname="other")
|
||||||
assert req.argname == "other"
|
assert req.argname == "other"
|
||||||
assert req.function == item.obj
|
assert req.function == item.obj
|
||||||
|
assert hasattr(req.module, 'test_func')
|
||||||
|
assert req.cls is None
|
||||||
assert req.function.__name__ == "test_func"
|
assert req.function.__name__ == "test_func"
|
||||||
assert req.config == item.config
|
assert req.config == item.config
|
||||||
assert repr(req).find(req.function.__name__) != -1
|
assert repr(req).find(req.function.__name__) != -1
|
||||||
|
|
||||||
|
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_methods(self, testdir):
|
def test_request_contains_funcargs_provider(self, testdir):
|
||||||
modcol = testdir.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def pytest_funcarg__something(request):
|
def pytest_funcarg__something(request):
|
||||||
pass
|
pass
|
||||||
class TestClass:
|
class TestClass:
|
||||||
def pytest_funcarg__something(self, request):
|
|
||||||
pass
|
|
||||||
def test_method(self, something):
|
def test_method(self, something):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
item1, = testdir.genitems([modcol])
|
item1, = testdir.genitems([modcol])
|
||||||
assert item1.name == "test_method"
|
assert item1.name == "test_method"
|
||||||
methods = item1.getrequest("something")._methods
|
provider = funcargs.FuncargRequest(item1, "something")._provider
|
||||||
assert len(methods) == 2
|
assert len(provider) == 1
|
||||||
method1, method2 = methods
|
assert provider[0].__name__ == "pytest_funcarg__something"
|
||||||
assert not hasattr(method1, 'im_self')
|
|
||||||
assert method2.im_self is not None
|
|
||||||
|
|
||||||
def test_request_call_next_provider(self, testdir):
|
def test_request_call_next_provider(self, testdir):
|
||||||
item = testdir.getitem("""
|
item = testdir.getitem("""
|
||||||
def pytest_funcarg__something(request): pass
|
def pytest_funcarg__something(request): pass
|
||||||
def test_func(something): pass
|
def test_func(something): pass
|
||||||
""")
|
""")
|
||||||
req = item.getrequest("something")
|
req = funcargs.FuncargRequest(item, "something")
|
||||||
val = req.call_next_provider()
|
val = req.call_next_provider()
|
||||||
assert val is None
|
assert val is None
|
||||||
py.test.raises(req.Error, "req.call_next_provider()")
|
py.test.raises(req.Error, "req.call_next_provider()")
|
||||||
|
@ -109,22 +120,147 @@ class TestRequest:
|
||||||
def pytest_funcarg__something(request): pass
|
def pytest_funcarg__something(request): pass
|
||||||
def test_func(something): pass
|
def test_func(something): pass
|
||||||
""")
|
""")
|
||||||
req = item.getrequest("something")
|
req = funcargs.FuncargRequest(item, "something")
|
||||||
l = [1]
|
l = [1]
|
||||||
req.addfinalizer(l.pop)
|
req.addfinalizer(l.pop)
|
||||||
item.teardown()
|
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):
|
def test_request_getmodulepath(self, testdir):
|
||||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||||
item, = testdir.genitems([modcol])
|
item, = testdir.genitems([modcol])
|
||||||
req = item.getrequest("hello")
|
req = funcargs.FuncargRequest(item, "xxx")
|
||||||
assert req.fspath == modcol.fspath
|
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)
|
p2config._initafterpickle(config.topdir)
|
||||||
return p2config
|
return p2config
|
||||||
|
|
||||||
class TestImmutablePickling:
|
pytest_funcarg__pickletransport = ImmutablePickleTransport
|
||||||
pytest_funcarg__pickletransport = ImmutablePickleTransport
|
|
||||||
|
|
||||||
|
class TestImmutablePickling:
|
||||||
def test_pickle_config(self, testdir, pickletransport):
|
def test_pickle_config(self, testdir, pickletransport):
|
||||||
config1 = testdir.parseconfig()
|
config1 = testdir.parseconfig()
|
||||||
assert config1.topdir == testdir.tmpdir
|
assert config1.topdir == testdir.tmpdir
|
||||||
|
|
|
@ -215,6 +215,12 @@ class TestGenerator:
|
||||||
assert not skipped and not failed
|
assert not skipped and not failed
|
||||||
|
|
||||||
class TestFunction:
|
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):
|
def test_function_equality(self, tmpdir):
|
||||||
config = py.test.config._reparse([tmpdir])
|
config = py.test.config._reparse([tmpdir])
|
||||||
f1 = py.test.collect.Function(name="name", config=config,
|
f1 = py.test.collect.Function(name="name", config=config,
|
||||||
|
|
Loading…
Reference in New Issue