[py-svn] r64076 - in py/trunk: . doc doc/test example/funcarg example/funcarg/mysetup py py/misc/testing py/test py/test/dist/testing py/test/plugin py/test/testing

hpk at codespeak.net hpk at codespeak.net
Tue Apr 14 22:36:47 CEST 2009


Author: hpk
Date: Tue Apr 14 22:36:45 2009
New Revision: 64076

Added:
   py/trunk/doc/test/xunit_setup.txt
   py/trunk/example/funcarg/
   py/trunk/example/funcarg/mysetup/
   py/trunk/example/funcarg/mysetup/conftest.py
   py/trunk/example/funcarg/mysetup/myapp.py
   py/trunk/example/funcarg/mysetup/test_sample.py
   py/trunk/py/test/testing/test_funcargs.py
Removed:
   py/trunk/doc/test/statemanage.txt
Modified:
   py/trunk/.hgignore
   py/trunk/doc/path.txt
   py/trunk/doc/test/config.txt
   py/trunk/doc/test/ext.txt
   py/trunk/doc/test/features.txt
   py/trunk/doc/test/funcargs.txt
   py/trunk/py/_com.py
   py/trunk/py/conftest.py
   py/trunk/py/misc/testing/test_com.py
   py/trunk/py/test/dist/testing/test_nodemanage.py
   py/trunk/py/test/dist/testing/test_txnode.py
   py/trunk/py/test/plugin/pytest__pytest.py
   py/trunk/py/test/plugin/pytest_default.py
   py/trunk/py/test/plugin/pytest_iocapture.py
   py/trunk/py/test/plugin/pytest_monkeypatch.py
   py/trunk/py/test/plugin/pytest_plugintester.py
   py/trunk/py/test/plugin/pytest_pytester.py
   py/trunk/py/test/plugin/pytest_restdoc.py
   py/trunk/py/test/plugin/pytest_resultdb.py
   py/trunk/py/test/plugin/pytest_resultlog.py
   py/trunk/py/test/plugin/pytest_tmpdir.py
   py/trunk/py/test/pluginmanager.py
   py/trunk/py/test/pycollect.py
   py/trunk/py/test/testing/conftest.py
   py/trunk/py/test/testing/test_pickling.py
   py/trunk/py/test/testing/test_pycollect.py
   py/trunk/py/test/testing/test_traceback.py
Log:
merging new funcargs docs and code.

doc/test/funcargs.txt now pretty much describes 
how funcargs are to work, including examples. 



Modified: py/trunk/.hgignore
==============================================================================
--- py/trunk/.hgignore	(original)
+++ py/trunk/.hgignore	Tue Apr 14 22:36:45 2009
@@ -9,3 +9,4 @@
 syntax:glob
 *.pyc
 *.pyo
+*.swp

Modified: py/trunk/doc/path.txt
==============================================================================
--- py/trunk/doc/path.txt	(original)
+++ py/trunk/doc/path.txt	Tue Apr 14 22:36:45 2009
@@ -11,6 +11,8 @@
 Path implementations provided by :api:`py.path`
 ===============================================
 
+.. _`local`: 
+
 :api:`py.path.local`
 --------------------
 

Modified: py/trunk/doc/test/config.txt
==============================================================================
--- py/trunk/doc/test/config.txt	(original)
+++ py/trunk/doc/test/config.txt	Tue Apr 14 22:36:45 2009
@@ -21,3 +21,26 @@
 IOW, you can set default values for options per project, per
 home-directoray, per shell session or per test-run. 
 
+
+.. _`basetemp`: 
+
+per-testrun temporary directories 
+-------------------------------------------
+
+``py.test`` runs provide means to create per-test session
+temporary (sub) directories.  You can create such directories 
+like this:
+
+.. sourcecode: python
+
+    import py
+    basetemp = py.test.config.ensuretemp() 
+    basetemp_subdir = py.test.config.ensuretemp("subdir") 
+
+By default, ``py.test`` creates a ``pytest-NUMBER`` directory
+and will keep around the directories of the last three 
+test runs.  You can also set the base temporary directory 
+with the `--basetemp`` option.  When distributing 
+tests on the same machine, ``py.test`` takes care to 
+pass around the basetemp directory such that all temporary
+files land below the same basetemp directory.  

Modified: py/trunk/doc/test/ext.txt
==============================================================================
--- py/trunk/doc/test/ext.txt	(original)
+++ py/trunk/doc/test/ext.txt	Tue Apr 14 22:36:45 2009
@@ -1,7 +1,20 @@
+======================================
+Writing plugins and extensions 
+======================================
+
+
+.. _`local plugin`: 
+
+Local Plugins
+==================================
+
+You can easily specify a project-specific or "local" 
+plugin by defining a ``ConftestPlugin`` in a ``conftest.py``
+file like this::
+
+    class ConftestPlugin:
+        """ my local plugin. """ 
 
-===============
-Writing plugins
-===============
 
 Learning by examples
 =====================

Modified: py/trunk/doc/test/features.txt
==============================================================================
--- py/trunk/doc/test/features.txt	(original)
+++ py/trunk/doc/test/features.txt	Tue Apr 14 22:36:45 2009
@@ -36,8 +36,19 @@
 Rapidly write integration, functional, unit tests 
 ===================================================
 
-py.test provides 
+XXX 
 
+funcargs and xUnit style setups 
+===================================================
+
+py.test provides powerful means for managing test
+state and fixtures.  Apart from the `traditional 
+xUnit style setup`_ for unittests it features the 
+simple and powerful `funcargs mechanism`_ for handling
+both complex and simple test scenarious. 
+
+.. _`funcargs mechanism`: funcargs.html
+.. _`traditional xUnit style setup`: xunit_setup.html
 
 load-balance tests to multiple CPUs
 ===================================

Modified: py/trunk/doc/test/funcargs.txt
==============================================================================
--- py/trunk/doc/test/funcargs.txt	(original)
+++ py/trunk/doc/test/funcargs.txt	Tue Apr 14 22:36:45 2009
@@ -1,142 +1,314 @@
+======================================================
+**funcargs**: powerful and simple test setup
+======================================================
 
-=====================================
-Python test function arguments 
-=====================================
-
-py.test enables a new way to separate test configuration 
-and test setup from actual test code in test functions. 
-When it runs a test functions it will lookup function
-arguments by name and provide a value.  
-Here is a simple example for such a test function: 
-
-    def test_function(mysetup): 
-        # work with mysetup 
-  
-To provide a value py.test looks for a ``pytest_funcargs``
-dictionary in the test module, for example::
+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:
 
-    class MySetup:
-        def __init__(self, pyfuncitem):
-            self.pyfuncitem = pyfuncitem
-    pytest_funcargs = {'mysetup': MySetup}
-
-This is already enough to run the test.  Of course
-up until now our ``mysetup`` does not provide 
-much value.  But it is now easy to add new 
-methods on the ``MySetup`` class that have 
-full access to the test collection process. 
+* 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
+
+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 
+=============================================
+
+To use funcargs you only need to specify 
+a named argument for your test function:
+
+.. sourcecode:: python
+
+    def test_function(myarg):
+        # use myarg  
+
+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:
+
+.. sourcecode:: python
+
+    def pytest_funcarg__myarg(self, request):
+        # return value for myarg here
+
+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.  
+
+.. _`request object`:
+
+funcarg request objects
+------------------------
+
+Request objects encapsulate a request for a function argument from a
+specific test function.  Request objects provide access to command line
+options, the underlying python function and allow interaction 
+with other providers and the test running process.  
+
+Attributes of request objects
+++++++++++++++++++++++++++++++++++++++++
+
+``request.argname``: name of the requested function argument 
+
+``request.function``: python function object requesting the argument
+
+``request.fspath``: filesystem path of containing module
+
+``request.config``: access to command line opts and general config
+
+finalizing after test function executed 
+++++++++++++++++++++++++++++++++++++++++
+
+Request objects allow to **register a finalizer method** which is 
+called after a test function has finished running. 
+This is useful for tearing down or cleaning up
+test state. Here is a basic example for providing
+a ``myfile`` object that will be closed upon test
+function finish:
+
+.. sourcecode:: python
+
+    def pytest_funcarg__myfile(self, request):
+        # ... create and open a "myfile" object ...
+        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 
 
-Plugins can register their funcargs via
-the config object, usually upon initial configure::
+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 
+provided elsewhere you can ask the request object 
+to provide the "next" value:
+
+.. sourcecode:: python
+
+    def pytest_funcarg__myfile(self, request):
+        myfile = request.call_next_provider()
+        # do something extra 
+        return myfile
+
+This will raise a ``request.Error`` exception if there 
+is no next provider left.  See the `decorator example`_ 
+for a use of this method. 
+
+
+.. _`lookup order`: 
+
+Order of funcarg provider 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 
+
+
+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. 
+
+
+Funcarg Examples
+=====================
+
+Example: basic application specific setup
+-----------------------------------------------------
+
+Here is a basic useful example for handling application 
+specific setup.  The goal is to have one place where
+we have the glue code for bootstrapping and configuring 
+application objects and allow test modules and 
+test functions to stay ignorant of involved details. 
+Let's start with the using side and consider a simple 
+test function living in a test file ``test_sample.py``:
+
+.. sourcecode:: python
+
+    def test_answer(mysetup): 
+        app = mysetup.myapp()
+        answer = app.question()
+        assert answer == 42
+
+To run this test py.test looks up and calls a provider to obtain the
+required ``mysetup`` function argument.  The test function simply
+interacts with the provided application specific setup. 
+
+To provide the ``mysetup`` function argument we write down
+a provider method in a `local plugin`_ by putting the
+following code into a local ``conftest.py``:
+
+.. sourcecode:: python
+
+    from myapp import MyApp
 
     class ConftestPlugin:
-        def pytest_configure(self, config):
-            config.register_funcargs(mysetup=MySetup) 
+        def pytest_funcarg__mysetup(self, request):
+            return MySetup()
+
+    class MySetup:
+        def myapp(self):
+            return MyApp()
+
+The ``pytest_funcarg__mysetup`` method is called to 
+provide a value for the requested ``mysetup`` test function argument. 
+To complete the example we put a pseudo MyApp object
+into ``myapp.py``:
+
+.. sourcecode:: python
+
+    class MyApp:
+        def question(self):
+            return 6 * 9
+
+You can now run the test with ``py.test test_sample.py`` which will
+show this failure:
+
+.. sourcecode:: python 
+
+        def test_answer(mysetup):
+            app = mysetup.myapp()
+            answer = app.question()
+    >       assert answer == 42
+    E       assert 54 == 42
+
+If you are confused as to that the question or answer is
+here, please visit here_.
+
+.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy
+
+.. _`local plugin`: ext.html#local-plugin
+
+
+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 also put such a function into a test class like this:
+
+.. sourcecode:: python
+
+    class TestClass:
+        def pytest_funcarg__mysetup(self, request):
+            # ...
+            #
+
+            
+Example: command line option for providing SSH-host
+-----------------------------------------------------------
 
 If you provide a "funcarg" from a plugin you can 
 easily make methods depend on command line options 
 or environment settings.  Here is a complete 
 example that allows to run tests involving
-an SSH connection if an ssh host is specified::
+an SSH connection if an ssh host is specified:
+
+.. sourcecode:: python
 
     class ConftestPlugin:
         def pytest_addoption(self, parser):
             parser.addoption("--ssh", action="store", default=None,
                 help="specify ssh host to run tests with")
 
-        def pytest_configure(self, config):
-            config.register_funcargs(mysetup=MySetup)
+        pytest_funcarg__mysetup = MySetupFuncarg
 
-    class MySetup:
-        def __init__(self, pyfuncitem):
-            self.pyfuncitem = pyfuncitem 
+    class MySetupFuncarg:
+        def __init__(self, request):
+            self.request = request 
         def ssh_gateway(self):
-            host = pyfuncitem.config.option.ssh
+            host = self.request.config.option.ssh
             if host is None:
                 py.test.skip("specify ssh host with --ssh to run this test")
             return py.execnet.SshGateway(host)
 
-Now any test functions can use the "mysetup" object, for example::
+Now any test functions can use the "mysetup.ssh_gateway()" method like this:
+
+.. sourcecode:: python
 
     class TestClass:
         def test_function(self, mysetup):
             ssh_gw = mysetup.ssh_gateway()
             # work with ssh_gw 
-       
-Without specifying a command line option the output looks like this::
+      
+Running this without the command line will yield this run result::
 
     ...
 
 
-Lookup rules 
-======================
+.. _`accept example`: 
 
-In order to run this test function a value for the 
-``mysetup`` needs to be found.  Here is how py.test
-finds a matching provider function:
+example: specifying and selecting acceptance tests 
+--------------------------------------------------------------
 
-1. see if there is a ``pytest_funcargs`` dictionary 
-   which maps ``mysetup`` to a provider function. 
-   if so, call the provider function. 
+.. sourcecode:: python
 
-XXX
-    
-
-
-example
-=====================
-
-You can run a test file ``test_some.py`` with this content: 
-
-    pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)}
-
-    def test_something(myarg):
-        assert myarg == 42
-
-You can also put this on a class:
-
-    class TestClass:
-        pytest_funcargs = {'myarg': (lambda pyfuncitem: 42)}
-
-        def test_something(self, myarg):
-            assert myarg == 42
-
-To separate funcarg setup you can also put a funcarg 
-definition into a conftest.py::
-
-    pytest_funcargs = {'myarg': decorate_myarg}
-    def decorate_myarg(pyfuncitem):
-        result = pyfuncitem.call_next_provider()
-        return result + 1
-
-for registering funcargs from a plugin, talk to the
-test configuration object like this::
-
-    class MyPlugin:
-        def pytest_configure(self, config):
-            config.register_funcargs(
-                myarg=decorate_myarg
-            )
-
-a local helper funcarg for doing acceptance tests maybe 
-by running shell commands could look like this::
-
-    class MyPlugin:
+    class ConftestPlugin:
         def pytest_option(self, parser):
-            group = parser.addgroup("myproject acceptance tests")
+            group = parser.getgroup("myproject")
             group.addoption("-A", dest="acceptance", action="store_true", 
                 help="run (slow) acceptance tests")
 
-        def pytest_configure(self, config):
-            config.register_funcargs(accept=AcceptFuncarg)
+        def pytest_funcarg__accept(self, request):
+            return AcceptFuncarg(request)
 
     class AcceptFuncarg:
-        def __init__(self, pyfuncitem):
-            if not pyfuncitem.config.option.acceptance:
+        def __init__(self, request):
+            if not request.config.option.acceptance:
                 py.test.skip("specify -A to run acceptance tests")
-            self.tmpdir = pyfuncitem.config.maketempdir(pyfuncitem.name)
+            self.tmpdir = request.config.maketempdir(request.argname)
             self._old = self.tmpdir.chdir()
-            pyfuncitem.addfinalizer(self.finalize)
+            request.addfinalizer(self.finalize)
 
         def run(self):
             return py.process.cmdexec("echo hello")
@@ -144,17 +316,72 @@
         def finalize(self):
             self._old.chdir()
             # cleanup any other resources
+
    
 and the actual test function example:
 
+.. sourcecode:: python
+
     def test_some_acceptance_aspect(accept):
         accept.tmpdir.mkdir("somesub")
         result = accept.run()
         assert result
-    
-for registering funcargs from a plugin, talk to the
-test configuration object like this::
+   
+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 ... 
+
+
+.. _`decorator example`: 
+
+example: decorating/extending a funcarg in a TestClass 
+--------------------------------------------------------------
+
+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:
+
+.. 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 
+
+        def test_sometest(self, accept):
+            assert accept.tmpdir.join("special").check()
+
+According to the `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
+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. 
+
+.. _`py.path.local`: ../path.html#local
+
+Questions and Answers
+==================================
+
+.. _`why pytest_pyfuncarg__ methods?`:
+
+Why ``pytest_funcarg__*`` methods? 
+------------------------------------
 
-    XXX
+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).  
 
+.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
 

Deleted: /py/trunk/doc/test/statemanage.txt
==============================================================================
--- /py/trunk/doc/test/statemanage.txt	Tue Apr 14 22:36:45 2009
+++ (empty file)
@@ -1,65 +0,0 @@
-=================
-Managing state
-=================
-
-funcargs: provding arguments for test functions
-===========================================================
-
-XXX write docs 
-
-Managing test state across test modules, classes and methods 
-============================================================
-
-Often you want to create some files, database connections or other
-state in order to run tests in a certain environment.  With
-``py.test`` there are three scopes for which you can provide hooks to
-manage such state.  Again, ``py.test`` will detect these hooks in
-modules on a name basis. The following module-level hooks will
-automatically be called by the session::
-
-    def setup_module(module):
-        """ setup up any state specific to the execution
-            of the given module. 
-        """
-
-    def teardown_module(module):
-        """ teardown any state that was previously setup 
-            with a setup_module method. 
-        """
-
-The following hooks are available for test classes::
-
-    def setup_class(cls): 
-        """ setup up any state specific to the execution
-            of the given class (which usually contains tests). 
-        """
-
-    def teardown_class(cls): 
-        """ teardown any state that was previously setup 
-            with a call to setup_class.
-        """
-
-    def setup_method(self, method):
-        """ setup up any state tied to the execution of the given 
-            method in a class.  setup_method is invoked for every 
-            test method of a class. 
-        """
-
-    def teardown_method(self, method): 
-        """ teardown any state that was previously setup 
-            with a setup_method call. 
-        """
-
-The last two hooks, ``setup_method`` and ``teardown_method``, are
-equivalent to ``setUp`` and ``tearDown`` in the Python standard
-library's ``unitest`` module.
-
-All setup/teardown methods are optional.  You could have a
-``setup_module`` but no ``teardown_module`` and the other way round.
-
-Note that while the test session guarantees that for every ``setup`` a
-corresponding ``teardown`` will be invoked (if it exists) it does
-*not* guarantee that any ``setup`` is called only happens once. For
-example, the session might decide to call the ``setup_module`` /
-``teardown_module`` pair more than once during the execution of a test
-module.

Added: py/trunk/doc/test/xunit_setup.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/xunit_setup.txt	Tue Apr 14 22:36:45 2009
@@ -0,0 +1,73 @@
+====================================
+xUnit style setup
+====================================
+
+.. _`funcargs`: funcargs.html
+.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
+
+Note: 
+
+    Since version 1.0 py.test offers funcargs_ for both 
+    simple and complex test setup needs.  Especially 
+    for functional and integration, but also for unit testing, it is
+    highly recommended that you use this new method. 
+
+Python, Java and other languages have a tradition
+of using xUnit_ style testing. This typically 
+involves the call of a ``setup`` method before
+a test function is run and ``teardown`` after
+it finishes.  With ``py.test`` there are three 
+scopes for which you can provide setup/teardown
+hooks to provide test fixtures: per-module, per-class 
+and per-method/function. ``py.test`` will 
+discover and call according methods automatically. 
+All setup/teardown methods are optional.  
+
+The following methods are called at module level if they exist:
+
+.. sourcecode:: python
+
+    def setup_module(module):
+        """ setup up any state specific to the execution
+            of the given module. 
+        """
+
+    def teardown_module(module):
+        """ teardown any state that was previously setup 
+            with a setup_module method. 
+        """
+
+The following hooks are available for test classes:
+
+.. sourcecode:: python
+
+    def setup_class(cls): 
+        """ setup up any state specific to the execution
+            of the given class (which usually contains tests). 
+        """
+
+    def teardown_class(cls): 
+        """ teardown any state that was previously setup 
+            with a call to setup_class.
+        """
+
+    def setup_method(self, method):
+        """ setup up any state tied to the execution of the given 
+            method in a class.  setup_method is invoked for every 
+            test method of a class. 
+        """
+
+    def teardown_method(self, method): 
+        """ teardown any state that was previously setup 
+            with a setup_method call. 
+        """
+
+The last two hooks, ``setup_method`` and ``teardown_method``, are
+equivalent to ``setUp`` and ``tearDown`` in the Python standard
+library's `unittest.py module`_.
+
+Note that it possible that setup/teardown pairs are invoked multiple 
+times per testing process. 
+
+.. _`unittest.py module`: http://docs.python.org/library/unittest.html
+

Added: py/trunk/example/funcarg/mysetup/conftest.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup/conftest.py	Tue Apr 14 22:36:45 2009
@@ -0,0 +1,10 @@
+
+from myapp import MyApp
+
+class ConftestPlugin:
+    def pytest_funcarg__mysetup(self, request):
+        return MySetup()
+
+class MySetup:
+    def myapp(self):
+        return MyApp()

Added: py/trunk/example/funcarg/mysetup/myapp.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup/myapp.py	Tue Apr 14 22:36:45 2009
@@ -0,0 +1,5 @@
+
+class MyApp:
+    def question(self):
+        return 6 * 9
+

Added: py/trunk/example/funcarg/mysetup/test_sample.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup/test_sample.py	Tue Apr 14 22:36:45 2009
@@ -0,0 +1,5 @@
+
+def test_answer(mysetup): 
+    app = mysetup.myapp()
+    answer = app.question()
+    assert answer == 42

Modified: py/trunk/py/_com.py
==============================================================================
--- py/trunk/py/_com.py	(original)
+++ py/trunk/py/_com.py	Tue Apr 14 22:36:45 2009
@@ -90,9 +90,7 @@
         l = []
         if plugins is None:
             plugins = self._plugins
-        if extra:
-            plugins += list(extra)
-        for plugin in plugins:
+        for plugin in list(plugins) + list(extra):
             try:
                 l.append(getattr(plugin, attrname))
             except AttributeError:

Modified: py/trunk/py/conftest.py
==============================================================================
--- py/trunk/py/conftest.py	(original)
+++ py/trunk/py/conftest.py	Tue Apr 14 22:36:45 2009
@@ -5,10 +5,10 @@
 
 import py
 class PylibTestconfigPlugin:
-    def pytest_funcarg__specssh(self, pyfuncitem):
-        return getspecssh(pyfuncitem.config)
-    def pytest_funcarg__specsocket(self, pyfuncitem):
-        return getsocketspec(pyfuncitem.config)
+    def pytest_funcarg__specssh(self, request):
+        return getspecssh(request.config)
+    def pytest_funcarg__specsocket(self, request):
+        return getsocketspec(request.config)
 
     def pytest_addoption(self, parser):
         group = parser.addgroup("pylib", "py lib testing options")

Modified: py/trunk/py/misc/testing/test_com.py
==============================================================================
--- py/trunk/py/misc/testing/test_com.py	(original)
+++ py/trunk/py/misc/testing/test_com.py	Tue Apr 14 22:36:45 2009
@@ -135,6 +135,12 @@
         l = list(plugins.listattr('x', reverse=True))
         assert l == [43, 42, 41]
 
+        class api4: 
+            x = 44
+        l = list(plugins.listattr('x', extra=(api4,)))
+        assert l == range(41, 45)
+        assert len(list(plugins)) == 3  # otherwise extra added
+
 def test_api_and_defaults():
     assert isinstance(py._com.comregistry, Registry)
 

Modified: py/trunk/py/test/dist/testing/test_nodemanage.py
==============================================================================
--- py/trunk/py/test/dist/testing/test_nodemanage.py	(original)
+++ py/trunk/py/test/dist/testing/test_nodemanage.py	Tue Apr 14 22:36:45 2009
@@ -1,28 +1,25 @@
-
-""" RSync filter test
-"""
-
 import py
 from py.__.test.dist.nodemanage import NodeManager
 
-def pytest_funcarg__source(pyfuncitem):
-    return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source")
-def pytest_funcarg__dest(pyfuncitem):
-    dest = py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest")
-    return dest 
+class pytest_funcarg__mysetup:
+    def __init__(self, request):
+        basetemp = request.maketempdir()
+        basetemp = basetemp.mkdir(request.function.__name__) 
+        self.source = basetemp.mkdir("source")
+        self.dest = basetemp.mkdir("dest")
 
 class TestNodeManager:
     @py.test.mark.xfail("consider / forbid implicit rsyncdirs?")
-    def test_rsync_roots_no_roots(self, source, dest):
-        source.ensure("dir1", "file1").write("hello")
+    def test_rsync_roots_no_roots(self, mysetup):
+        mysetup.source.ensure("dir1", "file1").write("hello")
         config = py.test.config._reparse([source])
-        nodemanager = NodeManager(config, ["popen//chdir=%s" % dest])
+        nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.dest])
         assert nodemanager.config.topdir == source == config.topdir
         nodemanager.rsync_roots()
         p, = nodemanager.gwmanager.multi_exec("import os ; channel.send(os.getcwd())").receive_each()
         p = py.path.local(p)
         print "remote curdir", p
-        assert p == dest.join(config.topdir.basename)
+        assert p == mysetup.dest.join(config.topdir.basename)
         assert p.join("dir1").check()
         assert p.join("dir1", "file1").check()
 
@@ -33,8 +30,9 @@
         nodemanager.setup_nodes([].append)
         nodemanager.wait_nodesready(timeout=2.0)
 
-    def test_popen_rsync_subdir(self, testdir, source, dest):
-        dir1 = source.mkdir("dir1")
+    def test_popen_rsync_subdir(self, testdir, mysetup):
+        source, dest = mysetup.source, mysetup.dest 
+        dir1 = mysetup.source.mkdir("dir1")
         dir2 = dir1.mkdir("dir2")
         dir2.ensure("hello")
         for rsyncroot in (dir1, source):
@@ -53,7 +51,8 @@
             assert dest.join("dir1", "dir2", 'hello').check()
             nodemanager.gwmanager.exit()
 
-    def test_init_rsync_roots(self, source, dest):
+    def test_init_rsync_roots(self, mysetup):
+        source, dest = mysetup.source, mysetup.dest
         dir2 = source.ensure("dir1", "dir2", dir=1)
         source.ensure("dir1", "somefile", dir=1)
         dir2.ensure("hello")
@@ -68,7 +67,8 @@
         assert not dest.join("dir1").check()
         assert not dest.join("bogus").check()
 
-    def test_rsyncignore(self, source, dest):
+    def test_rsyncignore(self, mysetup):
+        source, dest = mysetup.source, mysetup.dest
         dir2 = source.ensure("dir1", "dir2", dir=1)
         dir5 = source.ensure("dir5", "dir6", "bogus")
         dirf = source.ensure("dir5", "file")
@@ -86,7 +86,8 @@
         assert dest.join("dir5","file").check()
         assert not dest.join("dir6").check()
 
-    def test_optimise_popen(self, source, dest):
+    def test_optimise_popen(self, mysetup):
+        source, dest = mysetup.source, mysetup.dest
         specs = ["popen"] * 3
         source.join("conftest.py").write("rsyncdirs = ['a']")
         source.ensure('a', dir=1)
@@ -97,7 +98,8 @@
             assert gwspec._samefilesystem()
             assert not gwspec.chdir
 
-    def test_setup_DEBUG(self, source, testdir):
+    def test_setup_DEBUG(self, mysetup, testdir):
+        source = mysetup.source
         specs = ["popen"] * 2
         source.join("conftest.py").write("rsyncdirs = ['a']")
         source.ensure('a', dir=1)

Modified: py/trunk/py/test/dist/testing/test_txnode.py
==============================================================================
--- py/trunk/py/test/dist/testing/test_txnode.py	(original)
+++ py/trunk/py/test/dist/testing/test_txnode.py	Tue Apr 14 22:36:45 2009
@@ -31,8 +31,8 @@
                     print str(kwargs["excrepr"])
 
 class MySetup:
-    def __init__(self, pyfuncitem):
-        self.pyfuncitem = pyfuncitem
+    def __init__(self, request):
+        self.request = request
 
     def geteventargs(self, eventname, timeout=2.0):
         eq = EventQueue(self.config.pluginmanager, self.queue)
@@ -55,17 +55,11 @@
             print "exiting:", gw
             gw.exit()
 
-def pytest_funcarg__mysetup(pyfuncitem):
-    mysetup = MySetup(pyfuncitem)
+def pytest_funcarg__mysetup(request):
+    mysetup = MySetup(request)
     #pyfuncitem.addfinalizer(mysetup.finalize)
     return mysetup
 
-def pytest_funcarg__testdir(__call__, pyfuncitem):
-    # decorate to make us always change to testdir
-    testdir = __call__.execute(firstresult=True)
-    testdir.chdir()
-    return testdir 
-
 def test_node_hash_equality(mysetup):
     node = mysetup.makenode()
     node2 = mysetup.makenode()

Modified: py/trunk/py/test/plugin/pytest__pytest.py
==============================================================================
--- py/trunk/py/test/plugin/pytest__pytest.py	(original)
+++ py/trunk/py/test/plugin/pytest__pytest.py	Tue Apr 14 22:36:45 2009
@@ -1,19 +1,19 @@
 import py
 
 class _pytestPlugin:
-    def pytest_funcarg___pytest(self, pyfuncitem):
-        return PytestArg(pyfuncitem)
+    def pytest_funcarg___pytest(self, request):
+        return PytestArg(request)
 
 class PytestArg:
-    def __init__(self, pyfuncitem):
-        self.pyfuncitem = pyfuncitem 
+    def __init__(self, request):
+        self.request = request 
 
     def getcallrecorder(self, apiclass, comregistry=None):
         if comregistry is None:
-            comregistry = self.pyfuncitem.config.pluginmanager.comregistry
+            comregistry = self.request.config.pluginmanager.comregistry
         callrecorder = CallRecorder(comregistry)
         callrecorder.start_recording(apiclass)
-        self.pyfuncitem.addfinalizer(callrecorder.finalize)
+        self.request.addfinalizer(callrecorder.finalize)
         return callrecorder 
 
 

Modified: py/trunk/py/test/plugin/pytest_default.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_default.py	(original)
+++ py/trunk/py/test/plugin/pytest_default.py	Tue Apr 14 22:36:45 2009
@@ -22,7 +22,6 @@
         rep = runner.ItemTestReport(item, excinfo, "execute", outerr)
         item.config.api.pytest_itemtestreport(rep=rep) 
 
-    # XXX make this access pyfuncitem.args or funcargs 
     def pytest_pyfunc_call(self, pyfuncitem, args, kwargs):
         pyfuncitem.obj(*args, **kwargs)
 

Modified: py/trunk/py/test/plugin/pytest_iocapture.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_iocapture.py	(original)
+++ py/trunk/py/test/plugin/pytest_iocapture.py	Tue Apr 14 22:36:45 2009
@@ -2,14 +2,14 @@
 
 class IocapturePlugin:
     """ capture sys.stdout/sys.stderr / fd1/fd2. """
-    def pytest_funcarg__stdcapture(self, pyfuncitem):
+    def pytest_funcarg__stdcapture(self, request):
         capture = Capture(py.io.StdCapture)
-        pyfuncitem.addfinalizer(capture.finalize)
+        request.addfinalizer(capture.finalize)
         return capture 
 
-    def pytest_funcarg__stdcapturefd(self, pyfuncitem):
+    def pytest_funcarg__stdcapturefd(self, request):
         capture = Capture(py.io.StdCaptureFD)
-        pyfuncitem.addfinalizer(capture.finalize)
+        request.addfinalizer(capture.finalize)
         return capture 
 
 class Capture:

Modified: py/trunk/py/test/plugin/pytest_monkeypatch.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_monkeypatch.py	(original)
+++ py/trunk/py/test/plugin/pytest_monkeypatch.py	Tue Apr 14 22:36:45 2009
@@ -2,9 +2,9 @@
 
 class MonkeypatchPlugin:
     """ setattr-monkeypatching with automatical reversal after test. """
-    def pytest_funcarg__monkeypatch(self, pyfuncitem):
+    def pytest_funcarg__monkeypatch(self, request):
         monkeypatch = MonkeyPatch()
-        pyfuncitem.addfinalizer(monkeypatch.finalize)
+        request.addfinalizer(monkeypatch.finalize)
         return monkeypatch
 
 notset = object()

Modified: py/trunk/py/test/plugin/pytest_plugintester.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_plugintester.py	(original)
+++ py/trunk/py/test/plugin/pytest_plugintester.py	Tue Apr 14 22:36:45 2009
@@ -6,37 +6,23 @@
 
 class PlugintesterPlugin:
     """ test support code for testing pytest plugins. """
-    def pytest_funcarg__plugintester(self, pyfuncitem):
-        pt = PluginTester(pyfuncitem) 
-        pyfuncitem.addfinalizer(pt.finalize)
-        return pt
-
-class Support(object):
-    def __init__(self, pyfuncitem):
-        """ instantiated per function that requests it. """
-        self.pyfuncitem = pyfuncitem
-
-    def getmoditem(self):
-        for colitem in self.pyfuncitem.listchain():
-            if isinstance(colitem, colitem.Module):
-                return colitem 
+    def pytest_funcarg__plugintester(self, request):
+        return PluginTester(request) 
 
-    def finalize(self):
-        """ called after test function finished execution"""
+class PluginTester:
+    def __init__(self, request):
+        self.request = request
 
-class PluginTester(Support):
     def testdir(self):
-        # XXX import differently, eg. 
-        #     FSTester = self.pyfuncitem.config.pluginmanager.getpluginattr("pytester", "FSTester")
         from pytest_pytester import TmpTestdir
-        crunner = TmpTestdir(self.pyfuncitem)
-        self.pyfuncitem.addfinalizer(crunner.finalize)
+        crunner = TmpTestdir(self.request)
+        self.request.addfinalizer(crunner.finalize)
         # 
-        for colitem in self.pyfuncitem.listchain():
-            if isinstance(colitem, py.test.collect.Module) and \
-               colitem.name.startswith("pytest_"):
-                    crunner.plugins.append(colitem.fspath.purebasename)
-                    break 
+        #for colitem in self.request.listchain():
+        #    if isinstance(colitem, py.test.collect.Module) and \
+        #       colitem.name.startswith("pytest_"):
+        #            crunner.plugins.append(colitem.fspath.purebasename)
+        #            break 
         return crunner 
 
     def apicheck(self, pluginclass):

Modified: py/trunk/py/test/plugin/pytest_pytester.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_pytester.py	(original)
+++ py/trunk/py/test/plugin/pytest_pytester.py	Tue Apr 14 22:36:45 2009
@@ -11,19 +11,19 @@
 
 
 class PytesterPlugin:
-    def pytest_funcarg__linecomp(self, pyfuncitem):
+    def pytest_funcarg__linecomp(self, request):
         return LineComp()
 
-    def pytest_funcarg__LineMatcher(self, pyfuncitem):
+    def pytest_funcarg__LineMatcher(self, request):
         return LineMatcher
 
-    def pytest_funcarg__testdir(self, pyfuncitem):
-        tmptestdir = TmpTestdir(pyfuncitem)
+    def pytest_funcarg__testdir(self, request):
+        tmptestdir = TmpTestdir(request)
         return tmptestdir
  
-    def pytest_funcarg__eventrecorder(self, pyfuncitem):
+    def pytest_funcarg__eventrecorder(self, request):
         evrec = EventRecorder(py._com.comregistry)
-        pyfuncitem.addfinalizer(lambda: evrec.comregistry.unregister(evrec))
+        request.addfinalizer(lambda: evrec.comregistry.unregister(evrec))
         return evrec
 
 def test_generic(plugintester):
@@ -38,11 +38,11 @@
         self.stderr = LineMatcher(errlines)
 
 class TmpTestdir:
-    def __init__(self, pyfuncitem):
-        self.pyfuncitem = pyfuncitem
+    def __init__(self, request):
+        self.request = request
         # XXX remove duplication with tmpdir plugin 
-        basetmp = pyfuncitem.config.ensuretemp("testdir")
-        name = pyfuncitem.name
+        basetmp = request.config.ensuretemp("testdir")
+        name = request.function.__name__
         for i in range(100):
             try:
                 tmpdir = basetmp.mkdir(name + str(i))
@@ -57,7 +57,7 @@
         self._syspathremove = []
         self.chdir() # always chdir
         assert hasattr(self, '_olddir')
-        self.pyfuncitem.addfinalizer(self.finalize)
+        self.request.addfinalizer(self.finalize)
 
     def __repr__(self):
         return "<TmpTestdir %r>" % (self.tmpdir,)
@@ -78,7 +78,7 @@
         sorter.callrecorder = CallRecorder(registry)
         sorter.callrecorder.start_recording(api.PluginHooks)
         sorter.api = sorter.callrecorder.api
-        self.pyfuncitem.addfinalizer(sorter.callrecorder.finalize)
+        self.request.addfinalizer(sorter.callrecorder.finalize)
         return sorter
 
     def chdir(self):
@@ -90,7 +90,7 @@
         items = kwargs.items()
         if args:
             source = "\n".join(map(str, args))
-            basename = self.pyfuncitem.name 
+            basename = self.request.function.__name__
             items.insert(0, (basename, source))
         ret = None
         for name, value in items:
@@ -139,7 +139,7 @@
         # used from runner functional tests 
         item = self.getitem(source)
         # the test class where we are called from wants to provide the runner 
-        testclassinstance = self.pyfuncitem.obj.im_self
+        testclassinstance = self.request.function.im_self
         runner = testclassinstance.getrunner()
         return runner(item, **runnerargs)
 
@@ -200,7 +200,7 @@
         return self.config.getfsnode(path)
 
     def getmodulecol(self,  source, configargs=(), withinit=False):
-        kw = {self.pyfuncitem.name: py.code.Source(source).strip()}
+        kw = {self.request.function.__name__: py.code.Source(source).strip()}
         path = self.makepyfile(**kw)
         if withinit:
             self.makepyfile(__init__ = "#")
@@ -455,3 +455,10 @@
         return extralines 
 
 
+
+
+def test_parseconfig(testdir):
+    config1 = testdir.parseconfig()
+    config2 = testdir.parseconfig()
+    assert config2 != config1
+    assert config1 != py.test.config

Modified: py/trunk/py/test/plugin/pytest_restdoc.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_restdoc.py	(original)
+++ py/trunk/py/test/plugin/pytest_restdoc.py	Tue Apr 14 22:36:45 2009
@@ -382,10 +382,15 @@
                        "resolve_linkrole('source', 'py/foo/bar.py')")
 
 
-def pytest_funcarg__testdir(__call__, pyfuncitem):
-    testdir = __call__.execute(firstresult=True)
+def pytest_funcarg__testdir(request):
+    testdir = request.call_next_provider()
     testdir.makepyfile(confrest="from py.__.misc.rest import Project")
     testdir.plugins.append(RestdocPlugin())
+    count = 0
+    for p in testdir.plugins:
+        if isinstance(p, RestdocPlugin):
+            count += 1
+            assert count < 2
     return testdir
     
 class TestDoctest:

Modified: py/trunk/py/test/plugin/pytest_resultdb.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_resultdb.py	(original)
+++ py/trunk/py/test/plugin/pytest_resultdb.py	Tue Apr 14 22:36:45 2009
@@ -1,4 +1,3 @@
-import uuid
 import py
 from pytest_resultlog import ResultLog
 
@@ -65,7 +64,7 @@
             self._flush()
 
     def append_data(self, data):
-        runid = uuid.uuid4()
+        runid = py.std.uuid.uuid4()
         for item in data:
             item = item.copy()
             item['runid'] = str(runid)
@@ -100,7 +99,7 @@
 
     def append_data(self, data):
         flat_data = []
-        runid = uuid.uuid4()
+        runid = py.std.uuid.uuid4()
         for item in data:
             item = item.copy()
             item['runid'] = str(runid)

Modified: py/trunk/py/test/plugin/pytest_resultlog.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_resultlog.py	(original)
+++ py/trunk/py/test/plugin/pytest_resultlog.py	Tue Apr 14 22:36:45 2009
@@ -157,6 +157,7 @@
     # ignorant regarding formatting details.  
     def getresultlog(self, testdir, arg):
         resultlog = testdir.tmpdir.join("resultlog")
+        testdir.plugins.append("resultlog")
         args = ["--resultlog=%s" % resultlog] + [arg]
         testdir.runpytest(*args)
         return filter(None, resultlog.readlines(cr=0))
@@ -166,7 +167,6 @@
         ok = testdir.makepyfile(test_collection_ok="")
         skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
         fail = testdir.makepyfile(test_collection_fail="XXX")
-
         lines = self.getresultlog(testdir, ok) 
         assert not lines
 
@@ -226,6 +226,7 @@
 def test_generic(plugintester, LineMatcher):
     plugintester.apicheck(ResultlogPlugin)
     testdir = plugintester.testdir()
+    testdir.plugins.append("resultlog")
     testdir.makepyfile("""
         import py
         def test_pass():

Modified: py/trunk/py/test/plugin/pytest_tmpdir.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_tmpdir.py	(original)
+++ py/trunk/py/test/plugin/pytest_tmpdir.py	Tue Apr 14 22:36:45 2009
@@ -13,9 +13,9 @@
     """ provide temporary directories to test functions and methods. 
     """ 
 
-    def pytest_funcarg__tmpdir(self, pyfuncitem):
-        name = pyfuncitem.name
-        return pyfuncitem.config.mktemp(name, numbered=True)
+    def pytest_funcarg__tmpdir(self, request):
+        name = request.function.__name__ 
+        return request.config.mktemp(name, numbered=True)
 
 # ===============================================================================
 #
@@ -29,7 +29,7 @@
 def test_funcarg(testdir):
     item = testdir.getitem("def test_func(tmpdir): pass")
     plugin = TmpdirPlugin()
-    p = plugin.pytest_funcarg__tmpdir(item)
+    p = plugin.pytest_funcarg__tmpdir(item.getrequest("tmpdir"))
     assert p.check()
     bn = p.basename.strip("0123456789-")
     assert bn.endswith("test_func")

Modified: py/trunk/py/test/pluginmanager.py
==============================================================================
--- py/trunk/py/test/pluginmanager.py	(original)
+++ py/trunk/py/test/pluginmanager.py	Tue Apr 14 22:36:45 2009
@@ -18,6 +18,7 @@
 
     def register(self, plugin):
         self.api.pytest_plugin_registered(plugin=plugin)
+        import types
         self.comregistry.register(plugin)
 
     def unregister(self, plugin):
@@ -79,8 +80,8 @@
         for x in self.comregistry.listattr(attrname):
             return x
 
-    def listattr(self, attrname, plugins=None):
-        return self.comregistry.listattr(attrname, plugins=plugins)
+    def listattr(self, attrname, plugins=None, extra=()):
+        return self.comregistry.listattr(attrname, plugins=plugins, extra=extra)
 
     def call_firstresult(self, *args, **kwargs):
         return self.comregistry.call_firstresult(*args, **kwargs)

Modified: py/trunk/py/test/pycollect.py
==============================================================================
--- py/trunk/py/test/pycollect.py	(original)
+++ py/trunk/py/test/pycollect.py	Tue Apr 14 22:36:45 2009
@@ -177,7 +177,7 @@
             #print "*" * 20, "INVOKE assertion", self
             py.magic.invoke(assertion=1)
         mod = self.obj
-        self.config.pluginmanager.register(mod)
+        #self.config.pluginmanager.register(mod)
         if hasattr(mod, 'setup_module'): 
             self.obj.setup_module(mod)
 
@@ -187,7 +187,7 @@
         if not self.config.option.nomagic:
             #print "*" * 20, "revoke assertion", self
             py.magic.revoke(assertion=1)
-        self.config.pluginmanager.unregister(self.obj)
+        #self.config.pluginmanager.unregister(self.obj)
 
 class Class(PyCollectorMixin, py.test.collect.Collector): 
 
@@ -365,38 +365,15 @@
             for i, argname in py.builtin.enumerate(argnames):
                 if i < startindex:
                     continue 
+                request = self.getrequest(argname) 
                 try:
-                    self.funcargs[argname] = self.lookup_onearg(argname)
-                except LookupError, e:
+                    self.funcargs[argname] = request.call_next_provider()
+                except request.Error:
                     numdefaults = len(funcobj.func_defaults or ()) 
                     if i + numdefaults >= len(argnames):
-                        continue # continue # seems that our args have defaults 
+                        continue # our args have defaults XXX issue warning? 
                     else:
-                        raise
-
-    def lookup_onearg(self, argname):
-        prefix = "pytest_funcarg__"
-        #makerlist = self.config.pluginmanager.listattr(prefix + argname)
-        value = self.config.pluginmanager.call_firstresult(prefix + argname, pyfuncitem=self)
-        if value is not None:
-            return value
-        else:
-            self._raisefuncargerror(argname, prefix)
-
-    def _raisefuncargerror(self, argname, prefix="pytest_funcarg__"):
-        metainfo = self.repr_metainfo()
-        available = []
-        plugins = list(self.config.pluginmanager.comregistry)
-        #plugins.extend(self.config.pluginmanager.registry.plugins)
-        for plugin in plugins:
-            for name in vars(plugin.__class__):
-                if name.startswith(prefix):
-                    name = name[len(prefix):]
-                    if name not in available:
-                        available.append(name) 
-        msg = "funcargument %r not found for: %s" %(argname,metainfo.verboseline())
-        msg += "\n available funcargs: %s" %(", ".join(available),)
-        raise LookupError(msg)
+                        request._raiselookupfailed()
 
     def __eq__(self, other):
         try:
@@ -410,6 +387,70 @@
     def __ne__(self, other):
         return not self == other
 
+    def getrequest(self, argname):
+        return FuncargRequest(pyfuncitem=self, argname=argname) 
+        
+
+class FuncargRequest:
+    _argprefix = "pytest_funcarg__"
 
-# DEPRECATED
-#from py.__.test.plugin.pytest_doctest import DoctestFile 
+    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) 
+        metainfo = self._pyfuncitem.repr_metainfo()
+        msg = "funcargument %r not found for: %s" %(self.argname,metainfo.verboseline())
+        msg += "\n available funcargs: %s" %(", ".join(available),)
+        raise LookupError(msg)
+
+
+        

Modified: py/trunk/py/test/testing/conftest.py
==============================================================================
--- py/trunk/py/test/testing/conftest.py	(original)
+++ py/trunk/py/test/testing/conftest.py	Tue Apr 14 22:36:45 2009
@@ -1,2 +1,3 @@
 
 pytest_plugins = "pytest_xfail", "pytest_pytester", "pytest_tmpdir"
+

Added: py/trunk/py/test/testing/test_funcargs.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/testing/test_funcargs.py	Tue Apr 14 22:36:45 2009
@@ -0,0 +1,130 @@
+import py
+
+class TestFuncargs:
+    def test_funcarg_lookupfails(self, testdir):
+        testdir.makeconftest("""
+            class ConftestPlugin:
+                def pytest_funcarg__xyzsomething(self, request):
+                    return 42
+        """)
+        item = testdir.getitem("def test_func(some): pass")
+        exc = py.test.raises(LookupError, "item.setupargs()")
+        s = str(exc.value)
+        assert s.find("xyzsomething") != -1
+
+    def test_funcarg_lookup_default(self, testdir):
+        item = testdir.getitem("def test_func(some, other=42): pass")
+        class Provider:
+            def pytest_funcarg__some(self, request):
+                return request.function.__name__
+        item.config.pluginmanager.register(Provider())
+        item.setupargs()
+        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.setupargs()
+        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:
+            def pytest_funcarg__some(self, request):
+                return request.function.__name__ 
+            def pytest_funcarg__other(self, request):
+                return 42
+        item.config.pluginmanager.register(Provider())
+        item.setupargs()
+        assert len(item.funcargs) == 2
+        assert item.funcargs['some'] == "test_func"
+        assert item.funcargs['other'] == 42
+
+    def test_funcarg_lookup_modulelevel(self, testdir):
+        modcol = testdir.getmodulecol("""
+            def pytest_funcarg__something(request):
+                return request.function.__name__
+
+            class TestClass:
+                def test_method(self, something):
+                    pass 
+            def test_func(something):
+                pass 
+        """)
+        item1, item2 = testdir.genitems([modcol])
+        item1.setupargs()
+        assert item1.funcargs['something'] ==  "test_method"
+        item2.setupargs()
+        assert item2.funcargs['something'] ==  "test_func"
+
+class TestRequest:
+    def test_request_attributes(self, testdir):
+        item = testdir.getitem("""
+            def pytest_funcarg__something(request): pass
+            def test_func(something): pass
+        """)
+        req = item.getrequest("other")
+        assert req.argname == "other"
+        assert req.function == item.obj 
+        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):
+        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
+
+    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")
+        val = req.call_next_provider()
+        assert val is None
+        py.test.raises(req.Error, "req.call_next_provider()")
+
+    def test_request_addfinalizer(self, testdir):
+        item = testdir.getitem("""
+            def pytest_funcarg__something(request): pass
+            def test_func(something): pass
+        """)
+        req = item.getrequest("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")
+        assert req.fspath == modcol.fspath 

Modified: py/trunk/py/test/testing/test_pickling.py
==============================================================================
--- py/trunk/py/test/testing/test_pickling.py	(original)
+++ py/trunk/py/test/testing/test_pickling.py	Tue Apr 14 22:36:45 2009
@@ -1,31 +1,27 @@
 import py
 
-def pytest_funcarg__pickletransport(pyfuncitem):
-    return ImmutablePickleTransport()
-
-def pytest_pyfunc_call(__call__, pyfuncitem, args, kwargs):
-    # for each function call we patch py._com.comregistry
-    # so that the unpickling of config objects 
-    # (which bind to this mechanism) doesn't do harm 
-    # usually config objects are no meant to be unpickled in
-    # the same system 
+def setglobals(request):
     oldconfig = py.test.config 
     oldcom = py._com.comregistry 
     print "setting py.test.config to None"
     py.test.config = None
     py._com.comregistry = py._com.Registry()
-    try:
-        return __call__.execute(firstresult=True)
-    finally:
+    def resetglobals():
         print "setting py.test.config to", oldconfig
         py.test.config = oldconfig
         py._com.comregistry = oldcom
+    request.addfinalizer(resetglobals)
+
+def pytest_funcarg__testdir(request):
+    setglobals(request)
+    return request.call_next_provider()
 
 class ImmutablePickleTransport:
-    def __init__(self):
+    def __init__(self, request):
         from py.__.test.dist.mypickle import ImmutablePickler
         self.p1 = ImmutablePickler(uneven=0)
         self.p2 = ImmutablePickler(uneven=1)
+        setglobals(request)
 
     def p1_to_p2(self, obj):
         return self.p2.loads(self.p1.dumps(obj))
@@ -39,6 +35,8 @@
         return p2config
 
 class TestImmutablePickling:
+    pytest_funcarg__pickletransport = ImmutablePickleTransport
+
     def test_pickle_config(self, testdir, pickletransport):
         config1 = testdir.parseconfig()
         assert config1.topdir == testdir.tmpdir

Modified: py/trunk/py/test/testing/test_pycollect.py
==============================================================================
--- py/trunk/py/test/testing/test_pycollect.py	(original)
+++ py/trunk/py/test/testing/test_pycollect.py	Tue Apr 14 22:36:45 2009
@@ -34,13 +34,6 @@
         x = l.pop()
         assert x is None
 
-    def test_module_participates_as_plugin(self, testdir):
-        modcol = testdir.getmodulecol("")
-        modcol.setup()
-        assert modcol.config.pluginmanager.isregistered(modcol.obj)
-        modcol.teardown()
-        assert not modcol.config.pluginmanager.isregistered(modcol.obj)
-
     def test_module_considers_pluginmanager_at_import(self, testdir):
         modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
         py.test.raises(ImportError, "modcol.obj")
@@ -243,88 +236,6 @@
         assert f1 == f1_b
         assert not f1 != f1_b
 
-    def test_funcarg_lookupfails(self, testdir):
-        testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_funcarg__something(self, pyfuncitem):
-                    return 42
-        """)
-        item = testdir.getitem("def test_func(some): pass")
-        exc = py.test.raises(LookupError, "item.setupargs()")
-        s = str(exc.value)
-        assert s.find("something") != -1
-
-    def test_funcarg_lookup_default(self, testdir):
-        item = testdir.getitem("def test_func(some, other=42): pass")
-        class Provider:
-            def pytest_funcarg__some(self, pyfuncitem):
-                return pyfuncitem.name 
-        item.config.pluginmanager.register(Provider())
-        item.setupargs()
-        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, pyfuncitem):
-                return pyfuncitem.name 
-        item.config.pluginmanager.register(Provider())
-        item.setupargs()
-        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:
-            def pytest_funcarg__some(self, pyfuncitem):
-                return pyfuncitem.name 
-            def pytest_funcarg__other(self, pyfuncitem):
-                return 42
-        item.config.pluginmanager.register(Provider())
-        item.setupargs()
-        assert len(item.funcargs) == 2
-        assert item.funcargs['some'] == "test_func"
-        assert item.funcargs['other'] == 42
-
-    def test_funcarg_addfinalizer(self, testdir):
-        item = testdir.getitem("def test_func(some): pass")
-        l = []
-        class Provider:
-            def pytest_funcarg__some(self, pyfuncitem):
-                pyfuncitem.addfinalizer(lambda: l.append(42))
-                return 3
-        item.config.pluginmanager.register(Provider())
-        item.setupargs()
-        assert len(item.funcargs) == 1
-        assert item.funcargs['some'] == 3
-        assert len(l) == 0
-        item.teardown()
-        assert len(l) == 1
-        assert l[0] == 42
-
-    def test_funcarg_lookup_modulelevel(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def pytest_funcarg__something(pyfuncitem):
-                return pyfuncitem.name
-
-            class TestClass:
-                def test_method(self, something):
-                    pass 
-            def test_func(something):
-                pass 
-        """)
-        item1, item2 = testdir.genitems([modcol])
-        modcol.setup()
-        assert modcol.config.pluginmanager.isregistered(modcol.obj)
-        item1.setupargs()
-        assert item1.funcargs['something'] ==  "test_method"
-        item2.setupargs()
-        assert item2.funcargs['something'] ==  "test_func"
-        modcol.teardown()
-        assert not modcol.config.pluginmanager.isregistered(modcol.obj)
-
 class TestSorting:
     def test_check_equality_and_cmp_basic(self, testdir):
         modcol = testdir.getmodulecol("""

Modified: py/trunk/py/test/testing/test_traceback.py
==============================================================================
--- py/trunk/py/test/testing/test_traceback.py	(original)
+++ py/trunk/py/test/testing/test_traceback.py	Tue Apr 14 22:36:45 2009
@@ -10,7 +10,7 @@
     def test_traceback_argsetup(self, testdir):
         testdir.makeconftest("""
             class ConftestPlugin:
-                def pytest_funcarg__hello(self, pyfuncitem):
+                def pytest_funcarg__hello(self, request):
                     raise ValueError("xyz")
         """)
         p = testdir.makepyfile("def test(hello): pass")



More information about the pytest-commit mailing list