[py-svn] r65791 - in py/trunk: . contrib/pytest_coverage contrib/pytest_twisted doc/test example/funcarg example/funcarg/costlysetup example/funcarg/costlysetup/sub1 example/funcarg/costlysetup/sub2 example/funcarg/lazysetup example/funcarg/lazysetup/sub1 example/funcarg/lazysetup/sub2 example/funcarg/mysetup example/funcarg/mysetup2 example/funcarg/parametrize example/pytest py py/code py/code/testing py/execnet py/execnet/testing py/io py/io/testing py/misc/testing py/test py/test/dist py/test/dist/testing py/test/looponfail py/test/plugin py/test/testing py/thread/testing

hpk at codespeak.net hpk at codespeak.net
Tue Jun 16 18:23:22 CEST 2009


Author: hpk
Date: Tue Jun 16 18:23:18 2009
New Revision: 65791

Added:
   py/trunk/doc/test/extend.txt
   py/trunk/example/funcarg/costlysetup/
   py/trunk/example/funcarg/costlysetup/conftest.py
   py/trunk/example/funcarg/costlysetup/sub1/
   py/trunk/example/funcarg/costlysetup/sub1/test_quick.py
   py/trunk/example/funcarg/costlysetup/sub2/
   py/trunk/example/funcarg/costlysetup/sub2/test_two.py
   py/trunk/example/funcarg/lazysetup/
   py/trunk/example/funcarg/lazysetup/sub1/
   py/trunk/example/funcarg/lazysetup/sub2/
   py/trunk/example/funcarg/mysetup/__init__.py
   py/trunk/example/funcarg/mysetup2/
   py/trunk/example/funcarg/mysetup2/__init__.py
   py/trunk/example/funcarg/mysetup2/conftest.py
   py/trunk/example/funcarg/mysetup2/myapp.py
   py/trunk/example/funcarg/mysetup2/test_sample.py
   py/trunk/example/funcarg/mysetup2/test_ssh.py
   py/trunk/example/funcarg/parametrize/
   py/trunk/example/funcarg/parametrize/test_parametrize.py
   py/trunk/example/funcarg/parametrize/test_parametrize2.py
   py/trunk/example/funcarg/parametrize/test_parametrize3.py
   py/trunk/example/funcarg/test_simpleprovider.py
   py/trunk/py/test/plugin/hookspec.py
   py/trunk/py/test/plugin/pytest_hooklog.py
   py/trunk/py/test/plugin/test_pytest_runner.py
Removed:
   py/trunk/doc/test/ext.txt
   py/trunk/doc/test/plugins.txt
   py/trunk/py/test/custompdb.py
   py/trunk/py/test/plugin/api.py
   py/trunk/py/test/plugin/pytest_eventlog.py
   py/trunk/py/test/plugin/pytest_plugintester.py
   py/trunk/py/test/runner.py
   py/trunk/py/test/testing/test_api.py
   py/trunk/py/test/testing/test_runner.py
   py/trunk/py/test/testing/test_runner_functional.py
Modified:
   py/trunk/CHANGELOG
   py/trunk/MANIFEST
   py/trunk/TODO.txt
   py/trunk/contrib/pytest_coverage/__init__.py
   py/trunk/contrib/pytest_twisted/__init__.py
   py/trunk/doc/test/config.txt
   py/trunk/doc/test/examples.txt
   py/trunk/doc/test/features.txt
   py/trunk/doc/test/funcargs.txt
   py/trunk/doc/test/test.txt
   py/trunk/example/funcarg/mysetup/conftest.py
   py/trunk/example/pytest/test_failures.py
   py/trunk/py/LICENSE
   py/trunk/py/__init__.py
   py/trunk/py/_com.py
   py/trunk/py/code/source.py
   py/trunk/py/code/testing/test_source.py
   py/trunk/py/conftest.py
   py/trunk/py/execnet/testing/test_event.py
   py/trunk/py/execnet/testing/test_gwmanage.py
   py/trunk/py/execnet/testing/test_xspec.py
   py/trunk/py/execnet/xspec.py
   py/trunk/py/io/stdcapture.py
   py/trunk/py/io/terminalwriter.py
   py/trunk/py/io/testing/test_terminalwriter.py
   py/trunk/py/misc/testing/test_com.py
   py/trunk/py/test/collect.py
   py/trunk/py/test/config.py
   py/trunk/py/test/defaultconftest.py
   py/trunk/py/test/dist/dsession.py
   py/trunk/py/test/dist/testing/test_dsession.py
   py/trunk/py/test/dist/testing/test_mypickle.py
   py/trunk/py/test/dist/testing/test_nodemanage.py
   py/trunk/py/test/dist/testing/test_txnode.py
   py/trunk/py/test/dist/txnode.py
   py/trunk/py/test/funcargs.py
   py/trunk/py/test/looponfail/remote.py
   py/trunk/py/test/outcome.py
   py/trunk/py/test/plugin/conftest.py
   py/trunk/py/test/plugin/pytest__pytest.py
   py/trunk/py/test/plugin/pytest_default.py
   py/trunk/py/test/plugin/pytest_doctest.py
   py/trunk/py/test/plugin/pytest_execnetcleanup.py
   py/trunk/py/test/plugin/pytest_figleaf.py
   py/trunk/py/test/plugin/pytest_iocapture.py
   py/trunk/py/test/plugin/pytest_monkeypatch.py
   py/trunk/py/test/plugin/pytest_pdb.py
   py/trunk/py/test/plugin/pytest_pocoo.py
   py/trunk/py/test/plugin/pytest_pylint.py
   py/trunk/py/test/plugin/pytest_pytester.py
   py/trunk/py/test/plugin/pytest_recwarn.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_runner.py
   py/trunk/py/test/plugin/pytest_terminal.py
   py/trunk/py/test/plugin/pytest_tmpdir.py
   py/trunk/py/test/plugin/pytest_unittest.py
   py/trunk/py/test/plugin/pytest_xfail.py
   py/trunk/py/test/pluginmanager.py
   py/trunk/py/test/pycollect.py
   py/trunk/py/test/session.py
   py/trunk/py/test/testing/acceptance_test.py
   py/trunk/py/test/testing/test_collect.py
   py/trunk/py/test/testing/test_config.py
   py/trunk/py/test/testing/test_conftesthandle.py
   py/trunk/py/test/testing/test_funcargs.py
   py/trunk/py/test/testing/test_genitems.py
   py/trunk/py/test/testing/test_outcome.py
   py/trunk/py/test/testing/test_parseopt.py
   py/trunk/py/test/testing/test_pickling.py
   py/trunk/py/test/testing/test_pluginmanager.py
   py/trunk/py/test/testing/test_pycollect.py
   py/trunk/py/test/testing/test_session.py
   py/trunk/py/test/testing/test_setup_functional.py
   py/trunk/py/test/testing/test_traceback.py
   py/trunk/py/thread/testing/test_io.py
   py/trunk/setup.py
Log:
merge mainline py-trunk to svn py/trunk for setup.py experiments 


Modified: py/trunk/CHANGELOG
==============================================================================
--- py/trunk/CHANGELOG	(original)
+++ py/trunk/CHANGELOG	Tue Jun 16 18:23:18 2009
@@ -1,6 +1,18 @@
 $Id$
 
-Changes between 0.9.2 and 1.0 (UNRELEASED)
+Changes between 1.0.0b1 and 1.0.0b2
+=============================================
+
+* plugin classes are removed: one now defines 
+  hooks directly in conftest.py or global pytest_*.py 
+  files. 
+
+* documented and refined various hooks 
+
+* added new style of generative tests via pytest_generate_tests
+  hook
+
+Changes between 0.9.2 and 1.0.0b1
 =============================================
 
 * introduced new "funcarg" setup method, 

Modified: py/trunk/MANIFEST
==============================================================================
--- py/trunk/MANIFEST	(original)
+++ py/trunk/MANIFEST	Tue Jun 16 18:23:18 2009
@@ -248,6 +248,7 @@
 py/test/dist/testing/test_nodemanage.py
 py/test/dist/testing/test_txnode.py
 py/test/dist/txnode.py
+py/test/funcargs.py
 py/test/looponfail/__init__.py
 py/test/looponfail/remote.py
 py/test/looponfail/testing/__init__.py
@@ -259,12 +260,14 @@
 py/test/plugin/__init__.py
 py/test/plugin/api.py
 py/test/plugin/conftest.py
+py/test/plugin/hookspec.py
 py/test/plugin/pytest__pytest.py
 py/test/plugin/pytest_default.py
 py/test/plugin/pytest_doctest.py
 py/test/plugin/pytest_eventlog.py
 py/test/plugin/pytest_execnetcleanup.py
 py/test/plugin/pytest_figleaf.py
+py/test/plugin/pytest_hooklog.py
 py/test/plugin/pytest_iocapture.py
 py/test/plugin/pytest_monkeypatch.py
 py/test/plugin/pytest_pdb.py
@@ -272,6 +275,7 @@
 py/test/plugin/pytest_pocoo.py
 py/test/plugin/pytest_pylint.py
 py/test/plugin/pytest_pytester.py
+py/test/plugin/pytest_recwarn.py
 py/test/plugin/pytest_restdoc.py
 py/test/plugin/pytest_resultdb.py
 py/test/plugin/pytest_resultlog.py
@@ -280,6 +284,7 @@
 py/test/plugin/pytest_tmpdir.py
 py/test/plugin/pytest_unittest.py
 py/test/plugin/pytest_xfail.py
+py/test/plugin/test_pytest_runner.py
 py/test/pluginmanager.py
 py/test/pycollect.py
 py/test/runner.py
@@ -292,6 +297,7 @@
 py/test/testing/import_test/package/module_that_imports_shared_lib.py
 py/test/testing/import_test/package/shared_lib.py
 py/test/testing/import_test/package/test_import.py
+py/test/testing/test_api.py
 py/test/testing/test_collect.py
 py/test/testing/test_compat.py
 py/test/testing/test_config.py

Modified: py/trunk/TODO.txt
==============================================================================
--- py/trunk/TODO.txt	(original)
+++ py/trunk/TODO.txt	Tue Jun 16 18:23:18 2009
@@ -4,111 +4,39 @@
 py.test 
 --------------
 
-- compatilibity: honour/warn item.run() method of test items (so far
-  probably only execute() is warned about or the other way round)
+- clarify setup/run events and runner.py versus pytest_runner.py, 
+  introduce a general pytest_item_setup(item, setupstate)
+  and always isolate py._com.comregistry when py lib's own tests are run
 
-- introduce plugin arch, port existing things to plugins:
-  - importorskip 
-  - filelog 
-  - chtmpdir per method 
-  - apigen 
-  - xfail 
-  - acceptance/assertlines 
-  - dist-testing? 
-
-- introduce setuptools-style version checking, at least
-  for py lib itself, maybe also for other packages: 
-
-     py.checkversion("py>=1.0")
-
-- generative tests: it is somewhat misleading for 
-  the classical htmlviews that generated tests are identified 
-  by numbers rather than by its parameters.  unclear how
-  to fix this because we may not always be able to assume
-  that we can identify a generated tests by using its parameters
-  (it might not be hashable, doesn't have a sensical repr ...?)
+- hook review and hook docs 
 
 - turn deprecation / apiwarnings into events, report them at the end? 
 
-- get APIGEN back to work 
-
-- get web reporter back to work 
-
-- introduce decorator "shouldfail" or "xfail" 
-  as to mark a test as "expected to fail", 
-  report specially if it surprisingly passes 
-
 - nightly test runs on multiple platforms 
 
-- review and refactor architecture of py.test with particular
-  respect to: 
-  - writing (stacked) extensions / plugins (compared to Nose) 
-  - porting existing extensions (htmlconftest / buildbot / PyPy's conftest's ...) 
-  - fast and stable distributed testing 
-  - reliable cross-platform testing 
-
-- improve py.test documentation to reflect new
-  event architecture 
-
-- review and optimize skip-handling (it can be quite slow in
-  certain situations because e.g. setup/teardown is fully performed 
-  although we have "skip by keyword" and could detect this early)
-
 py.execnet
 --------------
 
 - cross-python version (2.2/2.3-2.5/6) and cross-platform testing of 
   setup/teardown semantics
 
-- optimize general setup and rsync timing? 
+py.test apigen plugin
+---------------------------
 
-py.apigen 
-----------------
+- make it work again with the new plugin arch
 
-- make it work again 
+packaging / svn-mercurial interaction
+--------------------------------------------
 
-see apigen_refactorings.txt
+- decide if to go with or without setuptools, check windows 2.6
+  availability 
 
-- check out CodeInvestigator 
-  http://codeinvestigator.googlepages.com/main
-
-  or other code that collects data from running a program 
-  (in our case running the tests)
-
-Criticism and solutions 
---------------------------------
-"too big": 
-    - too much code, you need entire py lib, hard to include into app 
-      + have a small pytest boostrap that loads pylib.zip from net
-      + provide smaller script ala simpy 
-    - lots of cmdline options, possibilities, documentation
-      rather unsorted 
-
-"needless differences between py.test and nosetests":
-    - py.test.skip 
-    - raises  
-
-"tutorial structure missing", e.g.:
-    - "how to get started" in a minimal way, also how to use
-      existing conftests/plugins 
-    - how to configure py.test 
-    - how to write plugins/extensions 
-
-"too much magic"
-   - re-execution of assert expressions 
-     + rename "--nomagic" to something that turns off "superassertions" 
-     + hint at --tb=... 
-   - get rid of py/magic directory 
-
-has a good ui but could be better
-    - support developer communication, e.g. py.test
-      --sendfailures=freenode-pypy 
-      --sendfailures=pocoo # prints out paste.pocoo.url with traceback 
-    - graphical interface, probably QT 
-    - generally store test results and use them for subsequent calls 
+- open a mercurial branch for releases? 
 
+- write a script to dumb-bridge the mercurial repo to svn 
+  (i.e. forget about svn history)
 
-ld (review and shift to above)
+1.1 and beyond 
 =================================
 
 refactorings
@@ -119,16 +47,6 @@
   - check if it works on win32 
   - refine error reporting (don't show python tracebacks) 
 
-- generalization of "host specifications" for execnet and
-  py.test --dist usages in particular (see also revision 37500 which 
-  contained a draft for that).  The goal is to have cross-platform 
-  testing and dist-testing and other usages of py.execnet all 
-  use a common syntax for specifiying connection methods and 
-  be able to instantiate gateways/connections through it. 
-
-- unification of "gateway"/host setup and teardown, including
-  rsyncing, i.e. cross-platform and dist-testing. 
-
 - py.log: unify API, possibly deprecate duplicate ones, 
   base things on a Config object  (hte latter almost a feature though) 
   (M988)
@@ -139,7 +57,9 @@
 --------------
 
 - (Harald Armin Massa): make py2exe work with py lib 
+
 - optimize file checking with --looponfailing (harald has code for win32)
+
 - have a py.test scan/run database for results and test names
   etc. (to allow quicker selection of tests and post-run 
   information on failures etc.)  (M760) 
@@ -168,116 +88,8 @@
 - references from ReST docs to modules, functions and classes
   of apigen generated html docs (M960)
 
-- review svn-testing (and escape characters), consider
-  svn-bindings (M634)
-
 - py.test.pdb - there is my hack for a while now, which integrates
   rlcompleter2 with pdb. First of all it requires some strange changes
   to rlcompleter itself, which has no tests. Long-term plan would be
   to have pyrepl+rlcompleter2+pdb fixes integrated into pylib and
   have it tested. This requires work though.
-
-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
---- below neeeds more review ---
-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-
-More random notes, goals
---------------------------------
-- REDUCE "MAGICALNESS", from an IRC discussion with ronny:
-  - integration into IDEs
-  - python2.6/python3 compat? 
-  - initpkg exports
-  - assert reinterpretation 
-  - greenlet compiles at runtime only in "dev-mode"
-  - conftest's are "scary", hum, what about 
-
-nicefications: 
-looponfailing shoudl nicely signal tests that failed but now PASS tests
-rename Node to itemtestloop
-refactor config and session tests to go into test_distsession.py 
-raises DID NOT RAISE: report the return value
-have node.shutdown perform out-of-band so that shutdowns happens more quickly
-tracebacks of importerrors of test modules should start with the test module file
-
-- fix hostmanage to care for setting PYTHONPATH properly
-
-- BRANCH: 
-  adding of options 
-
-- BRANCH: 
-    TEMPDIR handling 
-    syspath handling (notify on changes, restore for each test?) 
-
-- COMPLETE REPORTING FOR MERGE!
-  - remove ItemStart and CollectionStart 
-    which are only needed for collectonly. 
-    implement it some other way. 
-
-  - pre-counting of test items 
-
-  - move assert reinterp back to session? 
-
-  - merge "--tb" and "--fulltrace" option, --tb=full 
-  - implement --showouterr, don't show outerr by default 
-
-  - domainpath? 
-
-  - translate remote filenames to local filenames so that,
-    probably based on option
-
-  - review safe_repr
-
-  - move OutcomeRepr.where/exconly attr to ReprExceptionInfo 
-    or substitute as LocationRepr? 
-
-- reprcrash rename message to exconly 
-
-- test terminal reporter 
-  ACCEPTANCE test for eventlog writing
-  ACCEPTANCE test for acceptance custom reporting :) 
-
-  ACCEPTANCE showing nice Collection Errors  
-    
-  ACCEPTANCE test for "py.test2" exit signals 
-  ACCEPTANCE test for "py.test2 --traceconfig" 
-  ACCEPTANCE test for nice reprsentation of failures during Collection
-  ACCEPTANCE test for nice reprsentation of failures during Generator Collection 
-  ACCEPTANCE test for "py.test2" conftest containing syntax errors
-  
-  ACCEPTANCE test for "py.test2" honouring conftest specifying "extrainfo"
-  ACCEPTANCE test for "py.test2" on a simple example project 
- 
-- merge CollectionFinish and ItemTestReport
-  maybe with base class: BaseReport and some common attrs/methods
- 
-- merge terminal/remote and dist-testing
-  and make allocation of tests to hosts more dynamic
-
-- time setup/teardown and the actual test runs separately 
-  ACCEPTANCE:
-  py.test2 --
-  py.test2 --repeat=10
-
-- expect failing tests 
-  py.test2.expectfail(feature=138)
-
-
-while killing a process:
-Exception in thread receiver:
-Traceback (most recent call last):
-  File "threading.py", line 460, in __bootstrap
-    self.run()
-  File "threading.py", line 440, in run
-    self.__target(*self.__args, **self.__kwargs)
-  File "/home/hpk/py/branch/event/py/execnet/gateway.py", line 140, in _thread_receiver
-    self._stopsend()
-  File "/home/hpk/py/branch/event/py/execnet/gateway.py", line 329, in _stopsend
-    self._send(None)
-  File "/home/hpk/py/branch/event/py/execnet/gateway.py", line 147, in _send
-    self._io.close_write()
-  File "/home/hpk/py/branch/event/py/execnet/inputoutput.py", line 106, in close_write
-    self.outfile.close()
-IOError: [Errno 32] Broken pipe
-
-
-

Modified: py/trunk/contrib/pytest_coverage/__init__.py
==============================================================================
--- py/trunk/contrib/pytest_coverage/__init__.py	(original)
+++ py/trunk/contrib/pytest_coverage/__init__.py	Tue Jun 16 18:23:18 2009
@@ -327,22 +327,3 @@
             self.coverage.start()
 
 
-# ===============================================================================
-# plugin tests 
-# ===============================================================================
-# XXX
-'''
-def test_generic(plugintester):
-    plugintester.apicheck(EventlogPlugin)
-
-    testdir = plugintester.testdir()
-    testdir.makepyfile("""
-        def test_pass():
-            pass
-    """)
-    testdir.runpytest("--eventlog=event.log")
-    s = testdir.tmpdir.join("event.log").read()
-    assert s.find("TestrunStart") != -1
-    assert s.find("ItemTestReport") != -1
-    assert s.find("TestrunFinish") != -1
-'''

Modified: py/trunk/contrib/pytest_twisted/__init__.py
==============================================================================
--- py/trunk/contrib/pytest_twisted/__init__.py	(original)
+++ py/trunk/contrib/pytest_twisted/__init__.py	Tue Jun 16 18:23:18 2009
@@ -1,27 +1,27 @@
 """
-Notes: twisted's asynchrone behavior may have influence on the order of test-functions
+Allows to test twisted applications with pytest.
+
+Notes: twisted's asynchronous behavior may have influence on the order of test-functions
 
 TODO:
     + credits to Ralf Schmitt See: http://twistedmatrix.com/pipermail/twisted-python/2007-February/014872.html
     + get test to work
 """
 
-import os
 import sys
 
-import py
-
 try:
-    from twisted.internet.defer import Deferred
+    from twisted.internet import reactor, defer
+    from twisted.python import failure, log
 except ImportError:
     print "To use the twisted option you have to install twisted."
-    sys.exit(0)
+    sys.exit(10)
 try:
     from greenlet import greenlet
 except ImportError:
     print "Since pylib 1.0 greenlet are removed and separately packaged: " \
             "http://pypi.python.org/pypi/greenlet" 
-    sys.exit(0)
+    sys.exit(10)
 
 
 def _start_twisted_logging():
@@ -35,18 +35,26 @@
         def flush(self):
             sys.stdout.flush()
             # sys.stdout will be changed by py.test later.
-    import twisted.python.log
-    twisted.python.log.startLogging(Logger(), setStdout=0)
+    log.startLogging(Logger(), setStdout=0)
 
 def _run_twisted(logging=False):
     """Start twisted mainloop and initialize recursive calling of doit()."""
 
-    from twisted.internet import reactor, defer
-    from twisted.python import log, failure
     # make twisted copy traceback...
     failure.Failure.cleanFailure = lambda *args: None
     if logging:
         _start_twisted_logging()
+        
+    def fix_signal_handling():
+        # see http://twistedmatrix.com/trac/ticket/733
+        import signal
+        if hasattr(signal, "siginterrupt"):
+            signal.siginterrupt(signal.SIGCHLD, False)
+
+    def start():
+        fix_signal_handling()
+        doit(None)
+        
     # recursively called for each test-function/method due done()
     def doit(val): # val always None
         # switch context to wait that wrapper() passes back to test-method
@@ -63,50 +71,34 @@
             
         # the test-function *may* return a deferred
         # here the test-function will actually been called
-        # done() is finalizing a test-process by assureing recursive envoking
+        # done() is finalizing a test-process by assuring recursive invoking
         # of doit()
         defer.maybeDeferred(res).addCallback(done).addErrback(err)
-    # initialy preparing the calling of doit() and starting the reactor
-    reactor.callLater(0.0, doit, None)
+    # initially preparing the calling of doit() and starting the reactor
+    reactor.callLater(0.0, start)
     reactor.run()
 
-
-class TwistedPlugin:
-    """Allows to test twisted applications with pytest."""
-
-    def pytest_addoption(self, parser):
-        #parser.addoption("--twisted", dest="twisted", 
-        #    help="Allows to test twisted applications with pytest.")
-
-        group = parser.addgroup('twisted options')
-        group.addoption('-T', action='store_true', default=False,
-                dest = 'twisted',
-                help="Allows to test twisted applications.")
-        group.addoption('--twisted-logging', action='store', default=False,
-                dest='twisted_logging',
-                help="switch on twisted internal logging")
-        self.twisted = False
-
-    def pytest_configure(self, config):
-        twisted         = config.getvalue("twisted")
-        twisted_logging = config.getvalue("twisted_logging")
-        if twisted:
-            self.twisted = True
-            gr_twisted.switch(twisted_logging)
-
-    def pytest_unconfigure(self, config):
-        if self.twisted:
-            gr_twisted.switch(None)
-
-    def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs):
-        if self.twisted:
-            # XXX1 kwargs?  
-            # XXX2 we want to delegate actual call to next plugin
-            #      (which may want to produce test coverage, etc.) 
-            res = gr_twisted.switch(lambda: pyfuncitem.obj(*args))
-            if res:
-                res.raiseException()
-            return True # indicates that we performed the function call 
+def pytest_addoption(parser):
+    group = parser.addgroup('twisted options')
+    group.addoption('--twisted-logging', action='store_true', default=False,
+            dest='twisted_logging',
+            help="switch on twisted internal logging")
+
+def pytest_configure(config):
+    twisted_logging = config.getvalue("twisted_logging")
+    gr_twisted.switch(twisted_logging)
+
+def pytest_unconfigure(config):
+    gr_twisted.switch(None)
+
+def pytest_pyfunc_call(pyfuncitem):
+    # XXX1 kwargs?  
+    # XXX2 we want to delegate actual call to next plugin
+    #      (which may want to produce test coverage, etc.) 
+    res = gr_twisted.switch(lambda: pyfuncitem.call())
+    if res:
+        res.raiseException()
+    return True # indicates that we performed the function call 
 
 gr_twisted  = greenlet(_run_twisted)
 gr_tests    = greenlet.getcurrent()
@@ -115,10 +107,7 @@
 # plugin tests 
 # ===============================================================================
 
-def test_generic(plugintester):
-    plugintester.apicheck(TwistedPlugin)
-
-    testdir = plugintester.testdir()
+def test_generic(testdir):
     testdir.makepyfile('''
         def test_pass():
             pass

Modified: py/trunk/doc/test/config.txt
==============================================================================
--- py/trunk/doc/test/config.txt	(original)
+++ py/trunk/doc/test/config.txt	Tue Jun 16 18:23:18 2009
@@ -1,13 +1,58 @@
 Test configuration
 ========================
 
-test options and values 
+available test options 
 -----------------------------
 
-You can see all available command line options by running::
+You can see command line options by running::
 
     py.test -h 
 
+This will display all available command line options
+including the ones added by plugins `loaded at tool startup`_. 
+
+.. _`loaded at tool startup`: extend.html#tool-startup
+
+.. _conftestpy: 
+.. _collectignore:
+
+conftest.py: project specific test configuration
+--------------------------------------------------------
+
+A unique feature of py.test are its powerful ``conftest.py`` files which
+allow to `set option defaults`_, `implement hooks`_, `specify funcargs`_
+or set particular variables to influence the testing process:
+
+* ``pytest_plugins``: list of named plugins to load 
+
+* ``collect_ignore``: list of paths to ignore during test collection (relative to the containing
+  ``conftest.py`` file) 
+
+* ``rsyncdirs``: list of to-be-rsynced directories for distributed
+  testing 
+
+You may put a conftest.py files in your project root directory or into
+your package directory if you want to add project-specific test options. 
+
+``py.test`` loads all ``conftest.py`` files upwards from the command
+line specified test files.  It will lookup configuration values
+right-to-left, i.e. the closer conftest files will be checked first.
+You may have a ``conftest.py`` in your very home directory to have some
+global configuration values. 
+
+There is a flag that may help you debugging your conftest.py 
+configuration::
+
+    py.test --traceconfig 
+
+.. _`implement hooks`: extend.html#conftest.py-plugin
+.. _`specify funcargs`: funcargs.html#application-setup-tutorial-example
+
+.. _`set option defaults`: 
+
+setting option defaults 
+-------------------------------
+
 py.test will lookup values of options in this order:
 
 * option value supplied at command line 
@@ -16,12 +61,11 @@
 
 The name of an option usually is the one you find 
 in the longform of the option, i.e. the name 
-behind the ``--`` double-dash. 
+behind the ``--`` double-dash that you get with ``py.test -h``.
 
 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 

Modified: py/trunk/doc/test/examples.txt
==============================================================================
--- py/trunk/doc/test/examples.txt	(original)
+++ py/trunk/doc/test/examples.txt	Tue Jun 16 18:23:18 2009
@@ -1,3 +1,23 @@
+Learning by examples
+=====================
+
+adding custom options
+----------------------
+
+py.test supports adding of standard optparse_ Options. 
+A plugin may implement the ``addoption`` hook for registering 
+custom options:: 
+
+    def pytest_addoption(parser):
+        parser.addoption("-M", "--myopt", action="store", 
+            help="specify string to set myopt")
+
+    def pytest_configure(config):
+        if config.option.myopt:
+            # do action based on option value
+            #
+            
+.. _optparse: http://docs.python.org/library/optparse.html
 
 Working Examples
 ================

Deleted: /py/trunk/doc/test/ext.txt
==============================================================================
--- /py/trunk/doc/test/ext.txt	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,126 +0,0 @@
-======================================
-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. """ 
-
-
-Learning by examples
-=====================
-
-XXX
-
-adding custom options
-----------------------
-
-py.test supports adding of standard optparse_ Options. 
-A plugin may implement the ``addoption`` hook for registering 
-custom options:: 
-
-    class ConftestPlugin:
-        def pytest_addoption(self, parser):
-            parser.addoption("-M", "--myopt", action="store", 
-                help="specify string to set myopt")
-
-        def pytest_configure(self, config):
-            if config.option.myopt:
-                # do action based on option value
-
-.. _optparse: http://docs.python.org/library/optparse.html
-
-Setting default values for test options
-=======================================
-
-You can see all available command line options by running::
-
-    py.test -h 
-
-py.test will lookup values of options in this order:
-
-* option value supplied at command line 
-* content of environment variable ``PYTEST_OPTION_NAME=...``
-* ``name = ...`` setting in the nearest ``conftest.py`` file.
-
-The name of an option usually is the one you find 
-in the longform of the option, i.e. the name 
-behind the ``--`` double-dash. 
-
-IOW, you can set default values for options per project, per
-home-directoray, per shell session or per test-run. 
-
-.. _`collection process`: 
-
-Test Collection process 
-====================================================== 
-
-The collecting process is iterative so that distribution
-and execution of tests can start as soon as the first test
-item is collected.  Collection nodes with children are 
-called "Collectors" and terminal nodes are called "Items".  
-Here is an example of such a tree, generated with the 
-command ``py.test --collectonly py/xmlobj``:: 
-
-    <Directory 'xmlobj'>
-        <Directory 'testing'>
-            <Module 'test_html.py' (py.__.xmlobj.testing.test_html)>
-                <Function 'test_html_name_stickyness'>
-                <Function 'test_stylenames'>
-                <Function 'test_class_None'>
-                <Function 'test_alternating_style'>
-            <Module 'test_xml.py' (py.__.xmlobj.testing.test_xml)>
-                <Function 'test_tag_with_text'>
-                <Function 'test_class_identity'>
-                <Function 'test_tag_with_text_and_attributes'>
-                <Function 'test_tag_with_subclassed_attr_simple'>
-                <Function 'test_tag_nested'>
-                <Function 'test_tag_xmlname'>
-
-By default all directories not starting with a dot are traversed, 
-looking for ``test_*.py`` and ``*_test.py`` files.  Those Python 
-files are imported under their `package name`_. 
-
-The Module collector looks for test functions 
-and test classes and methods. Test functions and methods
-are prefixed ``test`` by default.  Test classes must 
-start with a capitalized ``Test`` prefix. 
-
-.. _`package name`: 
-
-constructing the package name for test modules
--------------------------------------------------
-
-Test modules are imported under their fully qualified 
-name.  Given a filesystem ``fspath`` it is constructed as follows:
-
-* walk the directories up to the last one that contains 
-  an ``__init__.py`` file.  
-
-* perform ``sys.path.insert(0, basedir)``.
-
-* import the root package as ``root``
-
-* determine the fully qualified name for ``fspath`` by either:
-
-  * calling ``root.__pkg__.getimportname(fspath)`` if the
-    ``__pkg__`` exists.` or 
-
-  * otherwise use the relative path of the module path to
-    the base dir and turn slashes into dots and strike
-    the trailing ``.py``. 
-
-
-Plugin hooks and events  
-=======================================
-
-XXX

Added: py/trunk/doc/test/extend.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/extend.txt	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,275 @@
+================================================
+Extending and customizing py.test 
+================================================
+
+.. _`local plugin`:
+
+py.test implements much of its functionality by calling `well specified 
+hooks`_.  Python modules which contain such hook functions are called 
+plugins.  Hook functions are discovered in ``conftest.py`` files or
+in **named** plugins.  ``conftest.py`` files are sometimes called "anonymous"
+or "local" plugins if they define hooks.  Named plugins are python modules 
+or packages that have an all lowercase ``pytest_`` prefixed name and who
+are imported during tool startup or the testing process.  
+
+.. _`tool startup`:
+
+Plugin discovery at tool startup
+--------------------------------------------
+
+py.test loads plugin modules at tool startup in the following way:
+
+* by reading the ``PYTEST_PLUGINS`` environment variable 
+  and importing the comma-separated list of named plugins. 
+
+* by pre-scanning the command line for the ``-p name`` option
+  and loading the specified plugin before actual command line parsing. 
+
+* by loading all `conftest.py plugin`_ files as inferred by the command line
+  invocation 
+
+* by recursively loading all plugins specified by the 
+  ``pytest_plugins`` variable in a ``conftest.py`` file 
+
+Note that at tool startup only ``conftest.py`` files in
+the directory of the specified test modules (or the current dir if None) 
+or any of the parent directories are found.  There is no try to 
+pre-scan all subdirectories to find ``conftest.py`` files or test 
+modules.  
+
+Specifying plugins in a test module or plugin
+-----------------------------------------------
+
+You can specify plugins in a test module or a plugin like this:
+
+.. sourcecode:: python
+
+    pytest_plugins = "name1", "name2", 
+
+When the test module or plugin is loaded the specified plugins
+will be loaded.  If you specify plugins without the ``pytest_``
+prefix it will be automatically added.  All plugin names 
+must be lowercase. 
+
+.. _`conftest.py plugin`:
+.. _`conftestplugin`:
+
+conftest.py as anonymous per-project plugins 
+--------------------------------------------------
+
+The purpose of ``conftest.py`` files is to allow `project-specific 
+test configuration`_.  But they also make for a good place to implement 
+project-specific test related features through hooks.  For example you may 
+set the `collect_ignore`_ variable depending on a command line option
+by defining the following hook in a ``conftest.py`` file:
+
+.. _`exclude-file-example`: 
+
+.. sourcecode:: python
+
+    # ./conftest.py in your root or package dir 
+    collect_ignore = ['hello', 'test_world.py'] 
+    def pytest_addoption(parser):
+        parser.addoption("--runall", action="store_true", default=False)
+    def pytest_configure(config):
+        if config.getvalue("runall"):
+            collect_ignore[:] = []
+
+.. _`project-specific test configuration`: config.html#conftestpy 
+.. _`collect_ignore`: config.html#collectignore
+
+.. _`well specified hooks`: 
+
+Available py.test hooks 
+====================================
+
+py.test calls hooks functions to implement its `test collection`_, running and 
+reporting process.  Upon loading of a plugin py.test performs
+strict checking on contained hook functions.  Function and argument names 
+need to match exactly the `original definition of the hook`_.  It thus 
+provides useful error reporting on mistyped hook or argument names
+and minimizes version incompatibilites. 
+
+.. _`original definition of the hook`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/hookspec.py
+
+generic "runtest" hooks 
+------------------------------
+
+Each test item is usually executed by calling the following three hooks:
+
+.. sourcecode:: python
+
+    pytest_runtest_setup(item) 
+    pytest_runtest_call(item) 
+    pytest_runtest_teardown(item) 
+
+For each of the three invocations a `call object`_ encapsulates
+information about the outcome of the call and is subsequently used 
+to make a report object: 
+
+.. sourcecode:: python
+
+    report = hook.pytest_runtest_makereport(item, call)
+
+For example, the `pytest_pdb plugin`_ uses this hook to activate
+interactive debugging on failures when ``--pdb`` is specified on the
+command line. 
+
+Usually three reports will be generated for a single test item.  However, 
+if the ``pytest_runtest_setup`` fails no call or teardown hooks 
+will be called and only one report will be created. 
+
+Each of the up to three reports is eventually fed to the logreport hook:
+
+.. sourcecode:: python
+
+    pytest_runtest_logreport(report) 
+
+A ``report`` object contains status and reporting information:
+
+.. sourcecode:: python
+
+    report.longrepr = string/lines/object to print 
+    report.when = "setup", "call" or "teardown" 
+    report.shortrepr = letter for progress-report 
+    report.passed = True or False
+    report.failed = True or False
+    report.skipped = True or False
+
+The `pytest_terminal plugin`_ uses this hook to print information
+about a test run. 
+    
+The protocol described here is implemented via this hook:
+
+.. sourcecode:: python
+ 
+    pytest_runtest_protocol(item) -> True 
+
+.. _`call object`: 
+
+The call object contains information about a performed call:
+
+.. sourcecode:: python
+
+    call.excinfo = ExceptionInfo object or None 
+    call.when = "setup", "call" or "teardown" 
+    call.outerr = None or tuple of strings representing captured stdout/stderr 
+
+.. _`pytest_pdb plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_pdb.py
+.. _`pytest_terminal plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_terminal.py
+
+
+generic collection hooks 
+------------------------------
+
+py.test calls the following two fundamental hooks for collecting files and directories: 
+
+.. sourcecode:: python
+
+    def pytest_collect_directory(path, parent):
+        """ return Collection node or None for the given path. """
+
+    def pytest_collect_file(path, parent):
+        """ return Collection node or None for the given path. """
+
+Both return a `collection node`_ for a given path.  All returned 
+nodes from all hook implementations will participate in the
+collection and running protocol.  The ``parent`` object is
+the parent node and may be used to access command line
+options via the ``parent.config`` object. 
+
+
+Python specific test function and module hooks 
+----------------------------------------------------
+
+For influencing the collection of objects in Python modules 
+you can use the following hook:
+
+.. sourcecode:: python
+
+    pytest_pycollect_makeitem(collector, name, obj)
+
+This hook will be called for each Python object in a collected
+Python module.  The return value is a custom `collection node`_. 
+
+
+
+.. XXX or ``False`` if you want to indicate that the given item should not be collected. 
+
+
+
+Included default plugins
+=============================
+
+You can find the source code of all default plugins in 
+
+    http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/
+
+Additionally you can check out some more contributed plugins here
+
+    http://bitbucket.org/hpk42/py-trunk/src/tip/contrib/
+
+
+.. _`collection process`: 
+.. _`collection node`: 
+.. _`test collection`:
+
+
+Test Collection process 
+====================================================== 
+
+The collecting process is iterative so that distribution
+and execution of tests can start as soon as the first test
+item is collected.  Collection nodes with children are 
+called "Collectors" and terminal nodes are called "Items".  
+Here is an example of such a tree, generated with the 
+command ``py.test --collectonly py/xmlobj``:: 
+
+    <Directory 'xmlobj'>
+        <Directory 'testing'>
+            <Module 'test_html.py' (py.__.xmlobj.testing.test_html)>
+                <Function 'test_html_name_stickyness'>
+                <Function 'test_stylenames'>
+                <Function 'test_class_None'>
+                <Function 'test_alternating_style'>
+            <Module 'test_xml.py' (py.__.xmlobj.testing.test_xml)>
+                <Function 'test_tag_with_text'>
+                <Function 'test_class_identity'>
+                <Function 'test_tag_with_text_and_attributes'>
+                <Function 'test_tag_with_subclassed_attr_simple'>
+                <Function 'test_tag_nested'>
+                <Function 'test_tag_xmlname'>
+
+By default all directories not starting with a dot are traversed, 
+looking for ``test_*.py`` and ``*_test.py`` files.  Those Python 
+files are imported under their `package name`_. 
+
+The Module collector looks for test functions 
+and test classes and methods. Test functions and methods
+are prefixed ``test`` by default.  Test classes must 
+start with a capitalized ``Test`` prefix. 
+
+.. _`package name`: 
+
+constructing the package name for test modules
+-------------------------------------------------
+
+Test modules are imported under their fully qualified 
+name.  Given a filesystem ``fspath`` it is constructed as follows:
+
+* walk the directories up to the last one that contains 
+  an ``__init__.py`` file.  
+
+* perform ``sys.path.insert(0, basedir)``.
+
+* import the root package as ``root``
+
+* determine the fully qualified name for ``fspath`` by either:
+
+  * calling ``root.__pkg__.getimportname(fspath)`` if the
+    ``__pkg__`` exists.` or 
+
+  * otherwise use the relative path of the module path to
+    the base dir and turn slashes into dots and strike
+    the trailing ``.py``. 
+

Modified: py/trunk/doc/test/features.txt
==============================================================================
--- py/trunk/doc/test/features.txt	(original)
+++ py/trunk/doc/test/features.txt	Tue Jun 16 18:23:18 2009
@@ -30,13 +30,8 @@
 From each test module every function with a leading ``test_``
 or class with a leading ``Test`` name is collected.  
 
-.. _`generative tests`: 
-.. _`collection process`: ext.html#collection-process
-
-Rapidly write integration, functional, unit tests 
-===================================================
+.. _`collection process`: extend.html#collection-process
 
-XXX 
 
 funcargs and xUnit style setups 
 ===================================================
@@ -96,17 +91,20 @@
 ----------------------------------
 
 By default, ``py.test`` catches text written to stdout/stderr during
-the execution of each individual test. This output will only be
+the execution of each individual test.  This output will only be
 displayed however if the test fails; you will not see it
 otherwise. This allows you to put debugging print statements in your
 code without being overwhelmed by all the output that might be
 generated by tests that do not fail.
 
 Each failing test that produced output during the running of the test
-will have its output displayed in the ``recorded stdout`` section.
+function will have its output displayed in the ``recorded stdout`` section.
+
+During Setup and Teardown ("Fixture") capturing is performed separately so 
+that you will only see this output if the actual fixture functions fail. 
 
 The catching of stdout/stderr output can be disabled using the 
-``--nocapture`` option to the ``py.test`` tool.  Any output will 
+``--nocapture`` or ``-s`` option to the ``py.test`` tool.  Any output will 
 in this case be displayed as soon as it is generated.
 
 test execution order
@@ -259,11 +257,17 @@
         def test_xxx(self):
             ... 
 
+.. _`test generators`: funcargs.html#test-generators
+
+.. _`generative tests`: 
+
 generative tests: yielding parametrized tests
 ====================================================
 
+Deprecated since 1.0 in favour of `test generators`_.
+
 *Generative tests* are test methods that are *generator functions* which
-``yield`` callables and their arguments.  This is most useful for running a
+``yield`` callables and their arguments.  This is useful for running a
 test function multiple times against different parameters.  Example::
 
     def test_generative(): 
@@ -283,19 +287,19 @@
         for x in (42,17,49): 
             yield "case %d" % x, check, x 
 
-extensible plugin system 
+easy to extend 
 =========================================
 
-py.test itself consists of many plugins 
-and you can easily write new `py.test plugins`_ 
-for these purposes:
+Since 1.0 py.test has advanced `extension mechanisms`_. 
+One can can easily modify or add aspects for for 
+purposes such as: 
 
 * reporting extensions
-* customizing collection and run of tests 
+* customizing collection and execution of tests 
 * running non-python tests
-* managing test state setup 
+* managing custom test state setup 
 
-.. _`py.test plugins`: plugins.html
+.. _`extension mechanisms`: extend.html
 
 .. _`reStructured Text`: http://docutils.sourceforge.net
 .. _`Python debugger`: http://docs.python.org/lib/module-pdb.html

Modified: py/trunk/doc/test/funcargs.txt
==============================================================================
--- py/trunk/doc/test/funcargs.txt	(original)
+++ py/trunk/doc/test/funcargs.txt	Tue Jun 16 18:23:18 2009
@@ -1,209 +1,314 @@
 ======================================================
-**funcargs**: powerful test setup and parametrization
+**funcargs**: test setup and parametrization
 ======================================================
 
-Since version 1.0 it is possible to provide arguments to test functions,
-often called "funcargs".  The funcarg mechanisms were developed with
-these goals in mind: 
-
-* **no boilerplate**: cleanly encapsulate test setup and fixtures
-* **flexibility**: easily setup test state depending on command line options or environment
-* **readability**: write simple to read and debug test functions 
-* **parametrizing tests**: run a test function multiple times with different parameters
-
-
-.. contents:: Contents:
-    :depth: 2
-
-Basic mechanisms by example
-=============================================
-
-providing single function arguments as needed 
----------------------------------------------------------
-
-Let's look at a simple example of using funcargs within a test module:
+Since version 1.0 py.test introduces test function arguments,
+in short "funcargs" for your Python test functions.  The basic idea 
+that your unit-, functional- or acceptance test functions can name
+arguments and py.test will discover a matching provider from your 
+test configuration.  The mechanism complements the automatic 
+discovery of test files, classes and functions which follows
+the `Convention over Configuration`_ strategy.  By discovering and
+calling functions ("funcarg providers") that provide values for your
+actual test functions it becomes easy to: 
+
+* separate test function code from test state setup/fixtures
+* manage test value setup and teardown depending on 
+  command line options or configuration
+* parametrize multiple runs of the same test functions 
+* present useful debug info if setting up test state goes wrong
+
+Using funcargs, test functions become more expressive, 
+more "templaty" and more test-aspect oriented.  In fact, 
+funcarg mechanisms are meant to be complete and 
+convenient enough to 
+
+* substitute and improve on most usages of `xUnit style`_ setup.  
+  For a simple example of how funcargs compare 
+  to xUnit setup, see the `blog post about 
+  the monkeypatch funcarg`_. 
+
+* substitute and improve on all usages of `old-style generative tests`_, 
+  i.e. test functions that use the "yield" statement. 
+  Using yield in test functions is deprecated since 1.0. 
+
+
+.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
+.. _`xUnit style`: xunit_setup.html 
+.. _`old-style generative tests`: features.html#generative-tests
+
+.. _`funcarg provider`:
+ 
+funcarg providers: setting up test function arguments 
+==============================================================
+
+Test functions can specify one ore more arguments ("funcargs") 
+and a test module or plugin can define functions that provide
+the function argument.  Let's look at a simple self-contained 
+example that you can put into a test module: 
 
 .. sourcecode:: python
 
+    # ./test_simpleprovider.py 
     def pytest_funcarg__myfuncarg(request):
         return 42
 
     def test_function(myfuncarg):
-        assert myfuncarg == 42
+        assert myfuncarg == 17
 
-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. 
+If you run this with ``py.test test_simpleprovider.py`` you see something like this:
 
-2. A ``test_function(42)`` call is executed.  If the test fails
-   one can see the original provided value. 
+.. sourcecode:: python
 
+    ============================ test session starts ============================
+    python: platform linux2 -- Python 2.6.2
+    test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simpleprovider.py
 
-generating test runs with multiple function argument values 
-----------------------------------------------------------------------
+    test_simpleprovider.py F
 
-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. 
+    ================================= FAILURES ==================================
+    _______________________________ test_function _______________________________
 
-.. sourcecode:: python
+    myfuncarg = 42
 
-    def pytest_genfuncruns(runspec):
-        if "arg1" in runspec.funcargnames:
-            runspec.addfuncarg("arg1", 10)
-            runspec.addfuncarg("arg1", 20)
-            runspec.addfuncarg("arg1", 30)
+        def test_function(myfuncarg):
+    >       assert myfuncarg == 17
+    E       assert 42 == 17
 
-    def test_function(arg1): 
-        assert myfuncarg in (10, 20, 30)
+    test_simpleprovider.py:6: AssertionError
+    ========================= 1 failed in 0.11 seconds ==========================
 
-Here is what happens:
 
-1. The ``pytest_genfuncruns()`` hook will be called once for each test
-   function.  The if-statement makes sure that we only add function
-   arguments (and runs) for functions that need it.   The `runspec object`_ 
-   provides access to context information. 
+This means that the test function got executed and the assertion failed. 
+Here is how py.test comes to execute this test function: 
 
-2. Subsequently the ``test_function()`` will be called three times 
-   with three different values for ``arg1``.  
+1. py.test discovers the ``test_function`` because of the ``test_`` prefix. 
+   The test function needs a function argument named ``myfuncarg``.  
+   A matching provider function is discovered by looking for the special 
+   name ``pytest_funcarg__myfuncarg``. 
 
-Funcarg rules and support objects
-====================================
+2. ``pytest_funcarg__myfuncarg(request)`` is called and 
+   returns the value for ``myfuncarg``.   
 
-.. _`request object`:
+3. ``test_function(42)`` call is executed. 
 
-funcarg request objects
-------------------------
+Note that if you misspell a function argument or want
+to use one that isn't available, an error with a list of
+available function argument is provided. 
+
+For more interesting provider functions that make good use of the 
+`request object`_ please see the `application setup tutorial example`_. 
 
-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.  
+.. _`request object`: 
 
-Attributes of request objects
-++++++++++++++++++++++++++++++++++++++++
+funcarg request objects
+------------------------------------------
 
-``request.argname``: name of the requested function argument 
+Request objects are passed to funcarg providers.  They 
+encapsulate a request for a function argument for a
+specific test function.  Request objects allow providers 
+to access test configuration and test context: 
 
 ``request.function``: python function object requesting the argument
 
 ``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.module``: module object where the test function is defined in. 
 
 ``request.config``: access to command line opts and general config
 
+``request.param``: if exists was passed by a `parametrizing test generator`_ 
+
+
+perform scoped setup and teardown 
+---------------------------------------------
+
+.. sourcecode:: python 
+
+    def cached_setup(setup, teardown=None, scope="module", keyextra=None):
+        """ cache and return result of calling setup().  
+
+        The scope determines the cache key and ``keyextra`` adds to the cachekey. 
+        The scope also determines when teardown(result) will be called. 
+        valid scopes: 
+        scope == 'function': when the single test function run finishes. 
+        scope == 'module': when tests in a different module are run
+        scope == 'session': when tests of the session have run. 
+        """
+
+example for providing a value that is to be setup only once during a test run: 
+
+.. sourcecode:: python 
+  
+    def pytest_funcarg__db(request):
+        return request.cached_setup(
+                 lambda: ExpensiveSetup(request.config.option.db), 
+                 lambda val: val.close(), 
+                 scope="run"
+        )
+
+
 cleanup after test function execution
-++++++++++++++++++++++++++++++++++++++++
+---------------------------------------------
+
+.. sourcecode:: python 
 
-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:
+    def addfinalizer(func, scope="function"): 
+        """ register calling a a finalizer function. 
+        scope == 'function': when the single test function run finishes. 
+        scope == 'module': when tests in a different module are run
+        scope == 'session': when tests of the session have run. 
+        """
+     
+Calling ``request.addfinalizer()`` is useful for scheduling teardown 
+functions.  The given scope determines when the teardown function 
+will be called.  Here is a basic example for providing a ``myfile`` 
+object that is to be closed when the test function finishes. 
 
 .. sourcecode:: python
 
     def pytest_funcarg__myfile(self, request):
-        # ... create and open a "myfile" object ...
+        # ... create and open a unique per-function "myfile" object ...
         request.addfinalizer(lambda: myfile.close())
         return myfile
 
+requesting values of other funcargs 
+---------------------------------------------
+
+Inside a funcarg provider, you sometimes may want to use a
+different function argument which  may be specified with
+the test function or not.  For such purposes you can 
+dynamically request a funcarg value:
+
+.. sourcecode:: python 
+
+    def getfuncargvalue(name): 
+        """ Lookup and call function argument provider for the given name. 
+        Each function argument is only requested once per function setup. 
+        """ 
+
+You can also use this function if you want to `decorate a funcarg`_ 
+locally, i.e. you want to provide the normal value but add/do something
+extra.  If a provider cannot be found a ``request.Error`` exception will be 
+raised. 
 
-decorating other funcarg providers 
-++++++++++++++++++++++++++++++++++++++++
 
-If you want to **decorate a function argument** that is 
-provided elsewhere you can ask the request object 
-to provide the "next" value:
+.. _`test generators`: 
+.. _`parametrizing test generator`: 
+
+generating parametrized tests with funcargs 
+===========================================================
+
+You can directly parametrize multiple runs of the same test 
+function by adding new test function calls with different
+function argument values. Let's look at a simple self-contained 
+example: 
 
 .. sourcecode:: python
 
-    def pytest_funcarg__myfile(self, request):
-        myfile = request.call_next_provider()
-        # do something extra 
-        return myfile
+    # ./test_example.py 
+    def pytest_generate_tests(metafunc):
+        if "numiter" in metafunc.funcargnames: 
+            for i in range(10):
+                metafunc.addcall(funcargs=dict(numiter=i))
+
+    def test_func(numiter):
+        assert numiter < 9
+
+If you run this with ``py.test test_example.py`` you'll get:
 
-This will raise a ``request.Error`` exception if there 
-is no next provider left.  See the `decorator example`_ 
-for a use of this method. 
+.. sourcecode:: python
+
+    ================================= test session starts =================================
+    python: platform linux2 -- Python 2.6.2
+    test object 1: /home/hpk/hg/py/trunk/test_example.py
+
+    test_example.py .........F
+
+    ====================================== FAILURES =======================================
+    _______________________________ test_func.test_func[9] ________________________________
+
+    numiter = 9
+
+        def test_func(numiter):
+    >       assert numiter < 9
+    E       assert 9 < 9
 
+    /home/hpk/hg/py/trunk/test_example.py:10: AssertionError
 
-.. _`lookup order`: 
 
-Order of provider and test generator lookup
-----------------------------------------------
+Here is what happens in detail:
 
-Both test generators as well as funcarg providers 
-are looked up in the following three scopes:
+1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
+   function.  It adds ten new function calls with explicit function arguments. 
 
-1. test module 
-2. local plugins
-3. global plugins 
+2. **execute tests**: ``test_func(numiter)`` is called ten times with 
+   ten different arguments. 
 
-Using multiple funcargs 
-----------------------------------------
+.. _`metafunc object`:
 
-Test functions can have multiple arguments
-which can either come from a test generator 
-or from a provider. 
+test generators and metafunc objects
+-------------------------------------------
 
-.. _`runspec object`:
+metafunc objects are passed to the ``pytest_generate_tests`` hook. 
+They help to inspect a testfunction and to generate tests 
+according to test configuration or values specified 
+in the class or module where a test function is defined: 
 
-runspec objects
-------------------------
+``metafunc.funcargnames``: set of required function arguments for given function
 
-Runspecs help to inspect a testfunction and 
-to generate tests with combinations of function argument values. 
+``metafunc.function``: underlying python test function 
 
-generating and combining funcargs 
-+++++++++++++++++++++++++++++++++++++++++++++++++++
+``metafunc.cls``: class object where the test function is defined in or None. 
 
-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
+``metafunc.module``: the module object where the test function is defined in. 
 
-* be merged to the existing funcarg combination if the
-  new argument name isn't part of the funcarg combination yet. 
+``metafunc.config``: access to command line opts and general config
 
-* 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. 
+the ``metafunc.addcall()`` method 
+-----------------------------------------------
 
-This scheme allows two sources to generate 
-function arguments independently from each other. 
+.. sourcecode:: python
 
-Attributes of runspec objects
-++++++++++++++++++++++++++++++++++++++++
+    def addcall(funcargs={}, id=None, param=None):
+        """ trigger a new test function call. """ 
 
-``runspec.funcargnames``: set of required function arguments for given function
+``funcargs`` can be a dictionary of argument names
+mapped to values - providing it is called *direct parametrization*. 
 
-``runspec.function``: underlying python test function 
+If you provide an `id`` it will be used for reporting
+and identification purposes.  If you don't supply an `id` 
+the stringified counter of the list of added calls will be used. 
+``id`` values needs to be unique between all 
+invocations for a given test function. 
 
-``runspec.cls``: class object where the test function is defined in or None. 
+``param`` if specified will be seen by any 
+`funcarg provider`_ as a ``request.param`` attribute. 
+Setting it is called *indirect parametrization*.  
 
-``runspec.module``: the module object where the test function is defined in. 
+Indirect parametrization is preferable if test values are 
+expensive to setup or can only be created in certain environments.  
+Test generators and thus ``addcall()`` invocations are performed 
+during test collection which is separate from the actual test 
+setup and test run phase.  With distributed testing collection 
+and test setup/run happens in different process.  
 
-``runspec.config``: access to command line opts and general config
 
+.. _`tutorial examples`: 
 
-Useful Funcarg Tutorial Examples
+Funcarg Tutorial Examples
 =======================================
 
+
+.. _`application setup tutorial example`: 
+
 application specific test setup 
 ---------------------------------------------------------
 
 Here is a basic useful step-wise example for handling application
 specific test setup.  The goal is to have one place where we have the 
-glue code for bootstrapping and configuring application objects and allow
+glue and test support code for bootstrapping and configuring application objects and allow
 test modules and test functions to stay ignorant of involved details. 
 
 step 1: use and implement a test/app-specific "mysetup"
@@ -232,20 +337,17 @@
 .. sourcecode:: python
 
     # ./conftest.py 
+    
     from myapp import MyApp
 
-    class ConftestPlugin:
-        def pytest_funcarg__mysetup(self, request):
-            return MySetup()
+    def pytest_funcarg__mysetup(request):
+        return MySetup()
 
     class MySetup:
         def myapp(self):
             return MyApp()
-
-py.test finds the ``pytest_funcarg__mysetup`` method by
-name, see also `lookup order`_.
-
-To run the example we put a pseudo MyApp object into ``myapp.py``:
+    
+To run the example we represent our application by putting a pseudo MyApp object into ``myapp.py``:
 
 .. sourcecode:: python
 
@@ -257,7 +359,18 @@
 You can now run the test with ``py.test test_sample.py`` which will
 show this failure:
 
-.. sourcecode:: python 
+.. sourcecode:: python
+
+    ========================= test session starts =========================
+    python: platform linux2 -- Python 2.6.2
+    test object 1: /home/hpk/hg/py/trunk/example/funcarg/mysetup
+
+    test_sample.py F
+
+    ============================== FAILURES ===============================
+    _____________________________ test_answer _____________________________
+
+    mysetup = <mysetup.conftest.MySetup instance at 0xa020eac>
 
         def test_answer(mysetup):
             app = mysetup.myapp()
@@ -265,49 +378,83 @@
     >       assert answer == 42
     E       assert 54 == 42
 
-If you are confused as to what the concrete question or answers 
-mean actually, please visit here_ :) 
+    test_sample.py:5: AssertionError
+    ====================== 1 failed in 0.11 seconds =======================
+
+This means that our ``mysetup`` object was successfully instantiated, 
+we asked it to provide an application instance and checking
+its ``question`` method resulted in the wrong answer.  If you are 
+confused as to what the concrete question or answers actually mean, 
+please see here_ :)  Otherwise proceed to step 2. 
 
 .. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy
-.. _`local plugin`: ext.html#local-plugin
+.. _`local plugin`: extend.html#local-plugin
 
 
 step 2: adding a command line option 
 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
 If you provide a "funcarg" from a plugin you can easily make methods
-depend on command line options or environment settings.  Let's write a
-local plugin that adds a command line option to ``py.test`` invocations: 
+depend on command line options or environment settings.  
+To add a command line option we update the conftest.py of
+the previous example to add a command line option 
+and to offer a new mysetup method:
 
 .. sourcecode:: python
 
-    class ConftestPlugin:
-        def pytest_addoption(self, parser):
-            parser.addoption("--ssh", action="store", default=None,
-                help="specify ssh host to run tests with")
+    # ./conftest.py 
+    import py
+    from myapp import MyApp
+
+    def pytest_funcarg__mysetup(request):
+        return MySetup(request)
 
-        pytest_funcarg__mysetup = MySetupFuncarg
+    def pytest_addoption(parser):
+        parser.addoption("--ssh", action="store", default=None,
+            help="specify ssh host to run tests with")
+        
 
-    class MySetupFuncarg:
+    class MySetup:
         def __init__(self, request):
-            self.request = request 
+            self.config = request.config 
+
+        def myapp(self):
+            return MyApp()
+
         def getsshconnection(self):
-            host = self.request.config.option.ssh
+            host = self.config.option.ssh
             if host is None:
-                py.test.skip("specify ssh host with --ssh to run this test")
+                py.test.skip("specify ssh host with --ssh")
             return py.execnet.SshGateway(host)
 
-Now any test functions can use the ``mysetup.getsshconnection()`` method like this:
+
+Now any test function can use the ``mysetup.getsshconnection()`` method like this:
 
 .. sourcecode:: python
 
+    # ./test_ssh.py 
     class TestClass:
         def test_function(self, mysetup):
             conn = mysetup.getsshconnection()
             # work with conn 
       
-Running this without specifying a command line option will result in a skipped
-test_function.  
+Running ``py.test test_ssh.py`` without specifying a command line option will result in a skipped test_function: 
+
+.. sourcecode:: python
+
+    ========================= test session starts =========================
+    python: platform linux2 -- Python 2.6.2
+    test object 1: test_ssh.py
+
+    test_ssh.py s
+
+    ________________________ skipped test summary _________________________
+    conftest.py:23: [1] Skipped: 'specify ssh host with --ssh'
+    ====================== 1 skipped in 0.11 seconds ======================
+
+Note especially how the test function could stay clear knowing about how to construct test state values or when to skip and with what message. The test function can concentrate on actual test code and test state providers can interact with execution of tests. 
+
+If you specify a command line option like ``py.test --ssh=python.org`` the test will get un-skipped and actually execute. 
 
 .. _`accept example`: 
 
@@ -316,14 +463,14 @@
 
 .. sourcecode:: python
 
-    class ConftestPlugin:
-        def pytest_option(self, parser):
-            group = parser.getgroup("myproject")
-            group.addoption("-A", dest="acceptance", action="store_true", 
-                help="run (slow) acceptance tests")
+    # ./conftest.py 
+    def pytest_option(parser):
+        group = parser.getgroup("myproject")
+        group.addoption("-A", dest="acceptance", action="store_true", 
+            help="run (slow) acceptance tests")
 
-        def pytest_funcarg__accept(self, request):
-            return AcceptFuncarg(request)
+    def pytest_funcarg__accept(request):
+        return AcceptFuncarg(request)
 
     class AcceptFuncarg:
         def __init__(self, request):
@@ -353,7 +500,7 @@
 applications and provide ways to do assertions about
 the output. 
 
-.. _`decorator example`: 
+.. _`decorate a funcarg`: 
 
 example: decorating a funcarg in a test module
 --------------------------------------------------------------
@@ -365,7 +512,7 @@
 .. sourcecode:: python
 
     def pytest_funcarg__accept(self, request):
-        arg = request.call_next_provider()
+        arg = request.getfuncargvalue("accept") # call the next provider
         # create a special layout in our tempdir
         arg.tmpdir.mkdir("special")
         return arg 
@@ -374,83 +521,18 @@
         def test_sometest(self, accept):
             assert accept.tmpdir.join("special").check()
 
-According to the the `lookup order`_ our module level provider 
-will be invoked first and it can ask ask its request object to 
-call the next provider and then decorate its result.  This 
-mechanism allows us to stay ignorant of how/where the 
-function argument is provided.  
+Our module level provider will be invoked first and it can 
+ask its request object to call the next provider and then 
+decorate its result.  This mechanism allows us to stay 
+ignorant of how/where the function argument is provided - 
+in our example from a `conftest plugin`_. 
 
 sidenote: the temporary directory used here are instances of 
 the `py.path.local`_ class which provides many of the os.path 
 methods in a convenient way. 
 
 .. _`py.path.local`: ../path.html#local
-
-.. _`combine multiple funcarg values`:
-
-
-parametrize test functions by combining generated funcargs 
---------------------------------------------------------------------------
-
-Adding different funcargs will generate test calls with
-all combinations of added funcargs.  Consider this example:
-
-.. sourcecode:: python
-
-    def makearg1(runspec):
-        runspec.addfuncarg("arg1", 10)
-        runspec.addfuncarg("arg1", 11)
-
-    def makearg2(runspec):
-        runspec.addfuncarg("arg2", 20)
-        runspec.addfuncarg("arg2", 21)
-
-    def pytest_genfuncruns(runspec):
-        makearg1(runspec) 
-        makearg2(runspec) 
-
-    # the actual test function
-
-    def test_function(arg1, arg2):
-        assert arg1 in (10, 20) 
-        assert arg2 in (20, 30) 
-
-Running this test module will result in ``test_function`` 
-being called four times, in the following order::
-
-    test_function(10, 20)
-    test_function(10, 21)
-    test_function(11, 20)
-    test_function(11, 21)
-
-
-example: test functions with generated and provided funcargs 
--------------------------------------------------------------------
-
-You can mix generated function arguments and normally 
-provided ones. Consider this module:
-
-.. sourcecode:: python
-
-    def pytest_genfuncruns(runspec):
-        if "arg1" in runspec.funcargnames: # test_function2 does not have it 
-            runspec.addfuncarg("arg1", 10)
-            runspec.addfuncarg("arg1", 20)
-
-    def pytest_funcarg__arg2(request):
-        return [10, 20]
-
-    def test_function(arg1, arg2):
-        assert arg1 in arg2 
-
-    def test_function2(arg2):
-        assert args2 == [10, 20]
-
-Running this test module will result in ``test_function`` 
-being called twice, with these arguments::
-
-    test_function(10, [10, 20])
-    test_function(20, [10, 20])
+.. _`conftest plugin`: extend.html#conftestplugin
 
 
 Questions and Answers

Deleted: /py/trunk/doc/test/plugins.txt
==============================================================================
--- /py/trunk/doc/test/plugins.txt	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,71 +0,0 @@
-================
-Included plugins
-================
-
-Many of py.test's features are implemented as a plugin. 
-
-Included plugins
-================
-
-You can find the source code of all default plugins in 
-http://codespeak.net/svn/py/trunk/py/test/plugin/
-
-plugins that add reporting asepcts
------------------------------------
-
-pytest_terminal: default reporter for writing info to terminals 
-
-pytest_resultlog: log test results in machine-readable form to a file 
-
-pytest_eventlog: log all internal pytest events to a file 
-
-plugins for adding new test types 
------------------------------------
-
-pytest_unittest: run traditional unittest TestCase instances
-
-pytest_doctest: run doctests in python modules or .txt files
-
-pytest_restdoc: provide RestructuredText syntax and link checking
-
-plugins for python test functions
------------------------------------
-
-pytest_xfail: provides "expected to fail" test marker 
-
-pytest_tmpdir: provide temporary directories to test functions 
-
-pytest_plugintester: generic plugin apichecks, support for functional plugin tests 
-
-pytest_apigen: tracing values of function/method calls when running tests
-
-Loading plugins and specifying dependencies 
-============================================
-
-py.test loads and configures plugins at tool startup:
-
-* by reading the ``PYTEST_PLUGINS`` environment variable 
-  and importing the comma-separated list of plugin names. 
-
-* by loading all plugins specified via one or more ``-p name`` 
-  command line options. 
-
-* by loading all plugins specified via a ``pytest_plugins``
-  variable in ``conftest.py`` files or test modules. 
-
-example: ensure a plugin is loaded 
------------------------------------
-
-If you create a ``conftest.py`` file with the following content:: 
-
-    pytest_plugins = "pytest_myextension",
-
-then all tests in that directory and below it will run with
-an instantiated "pytest_myextension".  Here is how instantiation
-takes place:
-
-* the module ``pytest_extension`` will be imported and 
-  and its contained `ExtensionPlugin`` class will 
-  be instantiated.  A plugin module may specify its 
-  dependencies via another ``pytest_plugins`` definition. 
-

Modified: py/trunk/doc/test/test.txt
==============================================================================
--- py/trunk/doc/test/test.txt	(original)
+++ py/trunk/doc/test/test.txt	Tue Jun 16 18:23:18 2009
@@ -15,16 +15,15 @@
 
 `distributed testing`_: distribute test runs to other machines and platforms. 
 
-plugins_: using available plugins. 
-
-extend_: writing plugins and advanced configuration. 
+extend_: intro to extend and customize py.test runs 
 
+config_: ``conftest.py`` files and general configuration 
 
 .. _quickstart: quickstart.html
 .. _features: features.html
 .. _funcargs: funcargs.html
-.. _plugins: plugins.html
-.. _extend: ext.html
+.. _extend: extend.html
+.. _config: config.html
 .. _`distributed testing`: dist.html
 
 

Added: py/trunk/example/funcarg/costlysetup/conftest.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/costlysetup/conftest.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,16 @@
+
+def pytest_funcarg__setup(request):
+    return request.cached_setup(
+        setup=lambda: CostlySetup(), 
+        teardown=lambda costlysetup: costlysetup.finalize(),
+        scope="session",
+    )
+
+class CostlySetup:
+    def __init__(self):
+        import time
+        time.sleep(5)
+        self.timecostly = 1
+
+    def finalize(self):
+        del self.timecostly 

Added: py/trunk/example/funcarg/costlysetup/sub1/test_quick.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/costlysetup/sub1/test_quick.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,3 @@
+
+def test_quick():
+    pass

Added: py/trunk/example/funcarg/costlysetup/sub2/test_two.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/costlysetup/sub2/test_two.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,6 @@
+def test_something(setup):
+    assert setup.timecostly == 1
+
+def test_something_more(setup):
+    assert setup.timecostly == 1
+

Added: py/trunk/example/funcarg/mysetup/__init__.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup/__init__.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1 @@
+# XXX this file should not need to be here but is here for proper sys.path mangling 

Modified: py/trunk/example/funcarg/mysetup/conftest.py
==============================================================================
--- py/trunk/example/funcarg/mysetup/conftest.py	(original)
+++ py/trunk/example/funcarg/mysetup/conftest.py	Tue Jun 16 18:23:18 2009
@@ -1,9 +1,8 @@
 
 from myapp import MyApp
 
-class ConftestPlugin:
-    def pytest_funcarg__mysetup(self, request):
-        return MySetup()
+def pytest_funcarg__mysetup(request):
+    return MySetup()
 
 class MySetup:
     def myapp(self):

Added: py/trunk/example/funcarg/mysetup2/__init__.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup2/__init__.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1 @@
+# XXX this file should not need to be here but is here for proper sys.path mangling 

Added: py/trunk/example/funcarg/mysetup2/conftest.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup2/conftest.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,24 @@
+import py
+from myapp import MyApp
+
+def pytest_funcarg__mysetup(request):
+    return MySetup(request)
+
+def pytest_addoption(parser):
+    parser.addoption("--ssh", action="store", default=None,
+        help="specify ssh host to run tests with")
+    
+
+class MySetup:
+    def __init__(self, request):
+        self.config = request.config 
+
+    def myapp(self):
+        return MyApp()
+
+    def getsshconnection(self):
+        host = self.config.option.ssh
+        if host is None:
+            py.test.skip("specify ssh host with --ssh")
+        return py.execnet.SshGateway(host)
+        

Added: py/trunk/example/funcarg/mysetup2/myapp.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup2/myapp.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,5 @@
+
+class MyApp:
+    def question(self):
+        return 6 * 9 
+

Added: py/trunk/example/funcarg/mysetup2/test_sample.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup2/test_sample.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,6 @@
+
+def test_answer(mysetup): 
+    app = mysetup.myapp()
+    answer = app.question()
+    assert answer == 42
+

Added: py/trunk/example/funcarg/mysetup2/test_ssh.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/mysetup2/test_ssh.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,5 @@
+
+class TestClass:
+    def test_function(self, mysetup):
+        conn = mysetup.getsshconnection()
+        # work with conn 

Added: py/trunk/example/funcarg/parametrize/test_parametrize.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/parametrize/test_parametrize.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,17 @@
+import py
+
+def pytest_generate_tests(metafunc):
+    for funcargs in metafunc.cls.params[metafunc.function.__name__]:
+        metafunc.addcall(funcargs=funcargs)
+ 
+class TestClass:
+    params = {
+        'test_equals': [dict(a=1, b=2), dict(a=3, b=3), dict(a=5, b=4)],
+        'test_zerodivision': [dict(a=1, b=0), dict(a=3, b=2)],
+    }
+   
+    def test_equals(self, a, b):
+        assert a == b
+
+    def test_zerodivision(self, a, b):
+        py.test.raises(ZeroDivisionError, "a/b")

Added: py/trunk/example/funcarg/parametrize/test_parametrize2.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/parametrize/test_parametrize2.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,25 @@
+import py
+
+# test support code
+def params(funcarglist):
+    def wrapper(function):
+        function.funcarglist = funcarglist
+        return function
+    return wrapper
+
+def pytest_generate_tests(metafunc):
+    for funcargs in getattr(metafunc.function, 'funcarglist', ()):
+        metafunc.addcall(funcargs=funcargs)
+
+
+# actual test code
+ 
+class TestClass:
+    @params([dict(a=1, b=2), dict(a=3, b=3), dict(a=5, b=4)], )
+    def test_equals(self, a, b):
+        assert a == b
+
+    @params([dict(a=1, b=0), dict(a=3, b=2)])
+    def test_zerodivision(self, a, b):
+        py.test.raises(ZeroDivisionError, "a/b")
+

Added: py/trunk/example/funcarg/parametrize/test_parametrize3.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/parametrize/test_parametrize3.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,15 @@
+
+# following hook can be put unchanged into a local or global plugin 
+def pytest_generate_tests(metafunc):
+    for scenario in metafunc.cls.scenarios:
+        metafunc.addcall(id=scenario[0], funcargs=scenario[1])
+
+
+scenario1 = ('basic', {'attribute': 'value'})
+scenario2 = ('advanced', {'attribute': 'value2'})
+
+class TestSampleWithScenarios:
+    scenarios = [scenario1, scenario2]
+
+    def test_demo(self, attribute):
+        assert isinstance(attribute, str) 

Added: py/trunk/example/funcarg/test_simpleprovider.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/test_simpleprovider.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,7 @@
+# ./test_simpleprovider.py
+def pytest_funcarg__myfuncarg(request):
+    return 42
+
+def test_function(myfuncarg):
+    assert myfuncarg == 17
+

Modified: py/trunk/example/pytest/test_failures.py
==============================================================================
--- py/trunk/example/pytest/test_failures.py	(original)
+++ py/trunk/example/pytest/test_failures.py	Tue Jun 16 18:23:18 2009
@@ -5,10 +5,10 @@
 pytest_plugins = "pytest_pytester"
 
 def test_failure_demo_fails_properly(testdir): 
-    sorter = testdir.inline_run(failure_demo)
-    passed, skipped, failed = sorter.countoutcomes() 
+    reprec = testdir.inline_run(failure_demo)
+    passed, skipped, failed = reprec.countoutcomes() 
     assert passed == 0 
     assert failed == 20, failed
-    colreports = sorter.getnamed("collectionreport")
+    colreports = reprec.getnamed("collectionreport")
     failed = len([x.failed for x in colreports])
     assert failed == 5

Modified: py/trunk/py/LICENSE
==============================================================================
--- py/trunk/py/LICENSE	(original)
+++ py/trunk/py/LICENSE	Tue Jun 16 18:23:18 2009
@@ -6,6 +6,7 @@
 
     Holger Krekel, holger at merlinux eu
     Guido Wesdorp, johnny at johnnydebris net 
+    Samuele Pedroni, pedronis at openend se 
     Carl Friedrich Bolz, cfbolz at gmx de
     Armin Rigo, arigo at tunes org 
     Maciek Fijalkowski, fijal at genesilico.pl
@@ -14,7 +15,6 @@
 
 Contributors include:: 
 
-    Samuele Pedroni 
     Chris Lamb 
     Harald Armin Massa
     Ralf Schmitt

Modified: py/trunk/py/__init__.py
==============================================================================
--- py/trunk/py/__init__.py	(original)
+++ py/trunk/py/__init__.py	Tue Jun 16 18:23:18 2009
@@ -1,17 +1,15 @@
 
 # -*- coding: utf-8 -*-
 """
-The py lib is an extensible library for testing, distributed processing and 
-interacting with filesystems. 
+advanced testing and development support library: 
 
 - `py.test`_: cross-project testing tool with many advanced features
 - `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes
 - `py.path`_: path abstractions over local and subversion files 
 - `py.code`_: dynamic code compile and traceback printing support
 
-The py lib and its tools should work well on Linux, Win32, 
-OSX, Python versions 2.3-2.6.  For questions please go to
-http://pylib.org/contact.html
+Compatibility: Linux, Win32, OSX, Python versions 2.3-2.6. 
+For questions please check out http://pylib.org/contact.html
 
 .. _`py.test`: http://pylib.org/test.html
 .. _`py.execnet`: http://pylib.org/execnet.html
@@ -24,12 +22,9 @@
 version = "1.0.0b2"
 
 initpkg(__name__,
-    description = "pylib and py.test: agile development and test support library",
-    revision = int('$LastChangedRevision$'.split(':')[1][:-1]),
-    lastchangedate = '$LastChangedDate$',
+    description = "py.test and pylib: advanced testing tool and networking lib", 
     version = version, 
     url = "http://pylib.org", 
-    download_url = "http://codespeak.net/py/%s/download.html" % version,
     license = "MIT license",
     platforms = ['unix', 'linux', 'osx', 'cygwin', 'win32'],
     author = "holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others",
@@ -49,7 +44,6 @@
         "Programming Language :: Python",
     ],
 
-
     # EXPORTED API 
     exportdefs = {
 
@@ -95,6 +89,7 @@
     'test.collect.Instance'  : ('./test/pycollect.py', 'Instance'),
     'test.collect.Generator' : ('./test/pycollect.py', 'Generator'),
     'test.collect.Function'  : ('./test/pycollect.py', 'Function'),
+    'test.collect._fillfuncargs' : ('./test/funcargs.py', 'fillfuncargs'),
 
     # thread related API (still in early design phase)
     '_thread.WorkerPool'      : ('./thread/pool.py', 'WorkerPool'),
@@ -133,6 +128,7 @@
     'code.Frame'             : ('./code/frame.py', 'Frame'),
     'code.ExceptionInfo'     : ('./code/excinfo.py', 'ExceptionInfo'),
     'code.Traceback'         : ('./code/traceback2.py', 'Traceback'),
+    'code.getfslineno'       : ('./code/source.py', 'getfslineno'),
 
     # backports and additions of builtins
     'builtin.__doc__'        : ('./builtin/__init__.py', '__doc__'),

Modified: py/trunk/py/_com.py
==============================================================================
--- py/trunk/py/_com.py	(original)
+++ py/trunk/py/_com.py	Tue Jun 16 18:23:18 2009
@@ -10,7 +10,6 @@
         Simple example: 
         MultiCall([list1.append, list2.append], 42).execute()
     """
-    NONEASRESULT = object()
 
     def __init__(self, methods, *args, **kwargs):
         self.methods = methods[:]
@@ -26,8 +25,6 @@
                 self.results = [res]
                 break
             if res is not None:
-                if res is self.NONEASRESULT:
-                    res = None
                 self.results.append(res) 
                 if firstresult:
                     break 
@@ -66,6 +63,7 @@
     """
         Manage Plugins: Load plugins and manage calls to plugins. 
     """
+    logfile = None
     MultiCall = MultiCall
 
     def __init__(self, plugins=None):
@@ -99,23 +97,14 @@
             l.reverse()
         return l
 
-    def call_firstresult(self, methname, *args, **kwargs):
-        """ return first non-None result of a plugin method. """ 
-        return MultiCall(self.listattr(methname), *args, **kwargs).execute(firstresult=True)
-
-    def call_plugin(self, plugin, methname, *args, **kwargs):
-        return MultiCall(self.listattr(methname, plugins=[plugin]), 
-                    *args, **kwargs).execute(firstresult=True)
-
-
 class Hooks: 
     def __init__(self, hookspecs, registry=None):
         self._hookspecs = hookspecs
         if registry is None:
-            registry = comregistry
+            registry = py._com.comregistry
         self.registry = registry
         for name, method in vars(hookspecs).items():
-            if name[:2] != "__":
+            if name[:1] != "_":
                 firstresult = getattr(method, 'firstresult', False)
                 mm = HookCall(registry, name, firstresult=firstresult)
                 setattr(self, name, mm)
@@ -142,6 +131,11 @@
                             "for api call to %r" % self.name)
         attr = self.registry.listattr(self.name, extra=self.extralookup)
         mc = MultiCall(attr, **kwargs)
+        # XXX this should be doable from a hook impl:
+        if self.registry.logfile:
+            self.registry.logfile.write("%s(**%s) # firstresult=%s\n" %
+                (self.name, kwargs, self.firstresult))
+            self.registry.logfile.flush()
         return mc.execute(firstresult=self.firstresult)
 
 comregistry = Registry()

Modified: py/trunk/py/code/source.py
==============================================================================
--- py/trunk/py/code/source.py	(original)
+++ py/trunk/py/code/source.py	Tue Jun 16 18:23:18 2009
@@ -217,6 +217,26 @@
     return co
 
 
+def getfslineno(obj):
+    try:
+        code = py.code.Code(obj)
+    except TypeError:
+        # fallback to 
+        fn = (py.std.inspect.getsourcefile(obj) or
+              py.std.inspect.getfile(obj))
+        fspath = fn and py.path.local(fn) or None
+        if fspath:
+            try:
+                _, lineno = findsource(obj)
+            except IOError:
+                lineno = None
+        else:
+            lineno = None
+    else:
+        fspath = code.path
+        lineno = code.firstlineno 
+    return fspath, lineno
+
 #
 # helper functions
 #

Modified: py/trunk/py/code/testing/test_source.py
==============================================================================
--- py/trunk/py/code/testing/test_source.py	(original)
+++ py/trunk/py/code/testing/test_source.py	Tue Jun 16 18:23:18 2009
@@ -355,3 +355,27 @@
     assert 'if 1:' in str(src)
     assert src[lineno] == "    def x():"
     
+
+def test_getfslineno():
+    from py.code import getfslineno
+
+    def f(x):
+        pass
+
+    fspath, lineno = getfslineno(f)
+
+    fname = __file__
+    if fname.lower().endswith('.pyc'):
+        fname = fname[:-1]
+
+    assert fspath == py.path.local(fname)
+    assert lineno == f.func_code.co_firstlineno-1 # see findsource
+
+    class A(object):
+        pass
+
+    fspath, lineno = getfslineno(A)
+
+    _, A_lineno = py.std.inspect.findsource(A)
+    assert fspath == py.path.local(fname)
+    assert lineno == A_lineno

Modified: py/trunk/py/conftest.py
==============================================================================
--- py/trunk/py/conftest.py	(original)
+++ py/trunk/py/conftest.py	Tue Jun 16 18:23:18 2009
@@ -3,25 +3,23 @@
 rsyncdirs = ['../doc']
 
 import py
-class PylibTestconfigPlugin:
-    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")
-        group.addoption('--sshhost', 
-               action="store", dest="sshhost", default=None,
-               help=("ssh xspec for ssh functional tests. "))
-        group.addoption('--gx', 
-               action="append", dest="gspecs", default=None,
-               help=("add a global test environment, XSpec-syntax. "))
-        group.addoption('--runslowtests',
-               action="store_true", dest="runslowtests", default=False,
-               help=("run slow tests"))
+def pytest_addoption(parser):
+    group = parser.addgroup("pylib", "py lib testing options")
+    group.addoption('--sshhost', 
+           action="store", dest="sshhost", default=None,
+           help=("ssh xspec for ssh functional tests. "))
+    group.addoption('--gx', 
+           action="append", dest="gspecs", default=None,
+           help=("add a global test environment, XSpec-syntax. "))
+    group.addoption('--runslowtests',
+           action="store_true", dest="runslowtests", default=False,
+           help=("run slow tests"))
+
+def pytest_funcarg__specssh(request):
+    return getspecssh(request.config)
+def pytest_funcarg__specsocket(request):
+    return getsocketspec(request.config)
 
-ConftestPlugin = PylibTestconfigPlugin
 
 # configuration information for tests 
 def getgspecs(config=None):

Modified: py/trunk/py/execnet/testing/test_event.py
==============================================================================
--- py/trunk/py/execnet/testing/test_event.py	(original)
+++ py/trunk/py/execnet/testing/test_event.py	Tue Jun 16 18:23:18 2009
@@ -4,7 +4,7 @@
 
 class TestExecnetEvents:
     def test_popengateway(self, _pytest):
-        rec = _pytest.getcallrecorder(ExecnetAPI)
+        rec = _pytest.gethookrecorder(ExecnetAPI)
         gw = py.execnet.PopenGateway()
         call = rec.popcall("pyexecnet_gateway_init") 
         assert call.gateway == gw

Modified: py/trunk/py/execnet/testing/test_gwmanage.py
==============================================================================
--- py/trunk/py/execnet/testing/test_gwmanage.py	(original)
+++ py/trunk/py/execnet/testing/test_gwmanage.py	Tue Jun 16 18:23:18 2009
@@ -21,7 +21,7 @@
             assert spec.chdir == "abc"
         
     def test_popen_makegateway_events(self, _pytest):
-        rec = _pytest.getcallrecorder(py.execnet._HookSpecs)
+        rec = _pytest.gethookrecorder(py.execnet._HookSpecs)
         hm = GatewayManager(["popen"] * 2)
         hm.makegateways()
         call = rec.popcall("pyexecnet_gwmanage_newgateway")
@@ -63,7 +63,7 @@
 
     def test_hostmanage_rsync_same_popen_twice(self, mysetup, _pytest):
         source, dest = mysetup.source, mysetup.dest 
-        rec = _pytest.getcallrecorder(py.execnet._HookSpecs)
+        rec = _pytest.gethookrecorder(py.execnet._HookSpecs)
         hm = GatewayManager(["popen//chdir=%s" %dest] * 2)
         hm.makegateways()
         source.ensure("dir1", "dir2", "hello")
@@ -117,6 +117,7 @@
         tmp = request.config.mktemp(request.function.__name__, numbered=True)
         self.source = tmp.mkdir("source")
         self.dest = tmp.mkdir("dest")
+        request.getfuncargvalue("_pytest") # to have patching of py._com.comregistry
 
 class TestHRSync:
     def test_hrsync_filter(self, mysetup):

Modified: py/trunk/py/execnet/testing/test_xspec.py
==============================================================================
--- py/trunk/py/execnet/testing/test_xspec.py	(original)
+++ py/trunk/py/execnet/testing/test_xspec.py	Tue Jun 16 18:23:18 2009
@@ -36,9 +36,10 @@
         for x in ("popen", "popen//python=this"):
             assert XSpec(x)._spec == x
 
-    def test_repr(self):
+    def test_repr_and_string(self):
         for x in ("popen", "popen//python=this"):
             assert repr(XSpec(x)).find("popen") != -1
+            assert str(XSpec(x)) == x
 
     def test_hash_equality(self):
         assert XSpec("popen") == XSpec("popen")

Modified: py/trunk/py/execnet/xspec.py
==============================================================================
--- py/trunk/py/execnet/xspec.py	(original)
+++ py/trunk/py/execnet/xspec.py	Tue Jun 16 18:23:18 2009
@@ -27,6 +27,8 @@
 
     def __repr__(self):
         return "<XSpec %r>" %(self._spec,)
+    def __str__(self):
+        return self._spec 
 
     def __hash__(self):
         return hash(self._spec)

Modified: py/trunk/py/io/stdcapture.py
==============================================================================
--- py/trunk/py/io/stdcapture.py	(original)
+++ py/trunk/py/io/stdcapture.py	Tue Jun 16 18:23:18 2009
@@ -22,14 +22,14 @@
     call = classmethod(call) 
 
     def reset(self): 
-        """ reset sys.stdout and sys.stderr
-
-            returns a tuple of file objects (out, err) for the captured
-            data
+        """ reset sys.stdout and sys.stderr and return captured output 
+            as strings and restore sys.stdout/err.
         """
-        outfile, errfile = self.done()
-        return outfile.read(), errfile.read()
-
+        x, y = self.done() 
+        outerr = x.read(), y.read() 
+        x.close()
+        y.close()
+        return outerr
 
 class StdCaptureFD(Capture): 
     """ This class allows to capture writes to FD1 and FD2 
@@ -58,11 +58,14 @@
 
     def done(self):
         """ return (outfile, errfile) and stop capturing. """
-        outfile = errfile = emptyfile
         if hasattr(self, 'out'): 
             outfile = self.out.done() 
+        else:
+            outfile = StringIO()
         if hasattr(self, 'err'): 
             errfile = self.err.done() 
+        else:
+            errfile = StringIO()
         if hasattr(self, '_oldin'):
             oldsys, oldfd = self._oldin 
             os.dup2(oldfd, 0)
@@ -94,15 +97,9 @@
             self.oldin  = sys.stdin
             sys.stdin  = self.newin  = DontReadFromInput()
 
-    def reset(self):
-        """ return captured output as strings and restore sys.stdout/err."""
-        x, y = self.done() 
-        return x.read(), y.read() 
-
     def done(self): 
         """ return (outfile, errfile) and stop capturing. """
         o,e = sys.stdout, sys.stderr
-        outfile = errfile = emptyfile
         if self._out: 
             try:
                 sys.stdout = self.oldout 
@@ -111,6 +108,8 @@
             del self.oldout
             outfile = self.newout
             outfile.seek(0)
+        else:
+            outfile = StringIO()
         if self._err: 
             try:
                 sys.stderr = self.olderr 
@@ -119,6 +118,8 @@
             del self.olderr 
             errfile = self.newerr 
             errfile.seek(0)
+        else:
+            errfile = StringIO()
         if self._in:
             sys.stdin = self.oldin 
         return outfile, errfile
@@ -144,5 +145,4 @@
     else:
         devnullpath = '/dev/null'
 
-emptyfile = StringIO()
 

Modified: py/trunk/py/io/terminalwriter.py
==============================================================================
--- py/trunk/py/io/terminalwriter.py	(original)
+++ py/trunk/py/io/terminalwriter.py	Tue Jun 16 18:23:18 2009
@@ -129,6 +129,10 @@
     if flush:
         file.flush()
 
+def should_do_markup(file):
+    return hasattr(file, 'isatty') and file.isatty() \
+           and os.environ.get('TERM') != 'dumb'
+
 class TerminalWriter(object):
     _esctable = dict(black=30, red=31, green=32, yellow=33, 
                      blue=34, purple=35, cyan=36, white=37,
@@ -146,7 +150,7 @@
             file = WriteFile(file)
         self._file = file
         self.fullwidth = get_terminal_width()
-        self.hasmarkup = hasattr(file, 'isatty') and file.isatty() 
+        self.hasmarkup = should_do_markup(file)
 
     def _escaped(self, text, esc):
         if esc and self.hasmarkup:
@@ -217,7 +221,7 @@
             file = WriteFile(file)
         self._file = file
         self.fullwidth = get_terminal_width()
-        self.hasmarkup = hasattr(file, 'isatty') and file.isatty()
+        self.hasmarkup = should_do_markup(file)
 
     def sep(self, sepchar, title=None, fullwidth=None, **kw):
         if fullwidth is None:

Modified: py/trunk/py/io/testing/test_terminalwriter.py
==============================================================================
--- py/trunk/py/io/testing/test_terminalwriter.py	(original)
+++ py/trunk/py/io/testing/test_terminalwriter.py	Tue Jun 16 18:23:18 2009
@@ -1,6 +1,7 @@
 import py
 import os, sys
 from py.__.io import terminalwriter 
+import StringIO
 
 def skip_win32():
     if sys.platform == 'win32':
@@ -20,6 +21,14 @@
     tw = py.io.TerminalWriter(stringio=True)
     assert hasattr(tw, 'stringio')
 
+def test_terminalwriter_dumb_term_no_markup(monkeypatch):
+    monkeypatch.setattr(os, 'environ', {'TERM': 'dumb', 'PATH': ''})
+    monkeypatch.setattr(sys, 'stdout', StringIO.StringIO())
+    monkeypatch.setattr(sys.stdout, 'isatty', lambda:True)
+    assert sys.stdout.isatty()
+    tw = py.io.TerminalWriter()
+    assert not tw.hasmarkup
+
 class BaseTests:
     def test_line(self):    
         tw = self.getwriter()

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 Jun 16 18:23:18 2009
@@ -73,7 +73,15 @@
         # we might not have had a chance to run at all. 
         #res = call.execute(firstresult=True)
         #assert res == 10
-                
+
+    def test_call_none_is_no_result(self):
+        def m1():
+            return 1
+        def m2():
+            return None
+        mc = MultiCall([m1, m2])
+        res = mc.execute(firstresult=True)
+        assert res == 1
 
 class TestRegistry:
     def test_MultiCall(self):
@@ -97,28 +105,6 @@
         assert not registry.isregistered(my)
         assert list(registry) == [my2]
 
-    def test_call_none_is_no_result(self):
-        plugins = Registry()
-        class api1:
-            def m(self):
-                return None
-        class api2:
-            def m(self, __call__):
-                return 41
-        plugins.register(api1())
-        plugins.register(api1())
-        plugins.register(api2())
-        assert plugins.call_firstresult('m') == 41
-
-    def test_call_noneasresult(self):
-        plugins = Registry()
-        class api1:
-            def m(self, __call__):
-                return __call__.NONEASRESULT
-        plugins.register(api1())
-        plugins.register(api1())
-        assert plugins.call_firstresult('m') is None
-
     def test_listattr(self):
         plugins = Registry()
         class api1:

Modified: py/trunk/py/test/collect.py
==============================================================================
--- py/trunk/py/test/collect.py	(original)
+++ py/trunk/py/test/collect.py	Tue Jun 16 18:23:18 2009
@@ -25,15 +25,12 @@
         - configuration/options for setup/teardown 
           stdout/stderr capturing and execution of test items 
     """
-    def __init__(self, name, parent=None, config=None):
+    def __init__(self, name, parent=None):
         self.name = name 
         self.parent = parent
-        if config is None:
-            config = parent.config
-        self.config = config 
+        self.config = getattr(parent, 'config', None)
         self.fspath = getattr(parent, 'fspath', None) 
 
-
     # 
     # note to myself: Pickling is uh.
     # 
@@ -79,13 +76,6 @@
     
     def __hash__(self):
         return hash((self.name, self.parent))
-
-    def __cmp__(self, other): 
-        if not isinstance(other, Node):
-            return -1
-        s1 = self._getsortvalue()
-        s2 = other._getsortvalue()
-        return cmp(s1, s2) 
  
     def setup(self): 
         pass
@@ -127,6 +117,12 @@
     def listnames(self): 
         return [x.name for x in self.listchain()]
 
+    def getparent(self, cls):
+        current = self
+        while current and not isinstance(current, cls):
+            current = current.parent
+        return current 
+
     def _getitembynames(self, namelist):
         cur = self
         for name in namelist:
@@ -224,9 +220,6 @@
                 return True
         return False
 
-    def _getsortvalue(self): 
-        return self.name 
-
     def _prunetraceback(self, traceback):
         return traceback 
 
@@ -285,7 +278,6 @@
     """
     Directory = configproperty('Directory')
     Module = configproperty('Module')
-    #DoctestFile = configproperty('DoctestFile')
 
     def collect(self):
         """ returns a list of children (items and collectors) 
@@ -339,9 +331,9 @@
         return self.collect_by_name(name)
 
 class FSCollector(Collector): 
-    def __init__(self, fspath, parent=None, config=None): 
+    def __init__(self, fspath, parent=None):
         fspath = py.path.local(fspath) 
-        super(FSCollector, self).__init__(fspath.basename, parent, config=config) 
+        super(FSCollector, self).__init__(fspath.basename, parent)
         self.fspath = fspath 
 
     def __getstate__(self):
@@ -392,7 +384,13 @@
                     l.append(res)
         return l
 
+    def _ignore(self, path):
+        ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path)
+        return ignore_paths and path in ignore_paths 
+
     def consider(self, path):
+        if self._ignore(path):
+            return
         if path.check(file=1):
             res = self.consider_file(path)
         elif path.check(dir=1):
@@ -400,10 +398,11 @@
         else:
             res = None            
         if isinstance(res, list):
-            # throw out identical modules
+            # throw out identical results
             l = []
             for x in res:
                 if x not in l:
+                    assert x.parent == self, "wrong collection tree construction"
                     l.append(x)
             res = l 
         return res
@@ -414,10 +413,8 @@
     def consider_dir(self, path, usefilters=None):
         if usefilters is not None:
             py.log._apiwarn("0.99", "usefilters argument not needed")
-        res = self.config.hook.pytest_collect_recurse(path=path, parent=self)
-        if res is None or res:
-            return self.config.hook.pytest_collect_directory(
-                path=path, parent=self)
+        return self.config.hook.pytest_collect_directory(
+            path=path, parent=self)
 
 class Item(Node): 
     """ a basic test item. """
@@ -433,13 +430,13 @@
 
     def run(self):
         """ deprecated, here because subclasses might call it. """
-        return self.execute(self.obj, *self._args)
+        return self.execute(self.obj)
 
-    def execute(self, obj, *args):
+    def execute(self, obj):
         """ deprecated, here because subclasses might call it. """
-        return obj(*args)
+        return obj()
 
-    def metainfo(self):
+    def reportinfo(self):
         return self.fspath, None, ""
         
 def warnoldcollect(function=None):

Modified: py/trunk/py/test/config.py
==============================================================================
--- py/trunk/py/test/config.py	(original)
+++ py/trunk/py/test/config.py	Tue Jun 16 18:23:18 2009
@@ -2,7 +2,6 @@
 from conftesthandle import Conftest
 
 from py.__.test import parseopt
-from py.__.test.runner import SetupState
 
 def ensuretemp(string, dir=1): 
     """ return temporary directory path with
@@ -41,7 +40,6 @@
         assert isinstance(pluginmanager, py.test._PluginManager)
         self.pluginmanager = pluginmanager
         self._conftest = Conftest(onimport=self._onimportconftest)
-        self._setupstate = SetupState() 
         self.hook = pluginmanager.hook
 
     def _onimportconftest(self, conftestmodule):
@@ -157,7 +155,8 @@
         if pkgpath is None:
             pkgpath = path.check(file=1) and path.dirpath() or path
         Dir = self._conftest.rget("Directory", pkgpath)
-        col = Dir(pkgpath, config=self)
+        col = Dir(pkgpath)
+        col.config = self 
         return col._getfsnode(path)
 
     def getconftest_pathlist(self, name, path=None):

Deleted: /py/trunk/py/test/custompdb.py
==============================================================================
--- /py/trunk/py/test/custompdb.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,77 +0,0 @@
-import pdb, sys, linecache
-
-class Pdb(pdb.Pdb):
-    def do_list(self, arg):
-        self.lastcmd = 'list'
-        last = None
-        if arg:
-            try:
-                x = eval(arg, {}, {})
-                if type(x) == type(()):
-                    first, last = x
-                    first = int(first)
-                    last = int(last)
-                    if last < first:
-                        # Assume it's a count
-                        last = first + last
-                else:
-                    first = max(1, int(x) - 5)
-            except:
-                print '*** Error in argument:', repr(arg)
-                return
-        elif self.lineno is None:
-            first = max(1, self.curframe.f_lineno - 5)
-        else:
-            first = self.lineno + 1
-        if last is None:
-            last = first + 10
-        filename = self.curframe.f_code.co_filename
-        breaklist = self.get_file_breaks(filename)
-        try:
-            for lineno in range(first, last+1):
-                # start difference from normal do_line
-                line = self._getline(filename, lineno)
-                # end difference from normal do_line
-                if not line:
-                    print '[EOF]'
-                    break
-                else:
-                    s = repr(lineno).rjust(3)
-                    if len(s) < 4: s = s + ' '
-                    if lineno in breaklist: s = s + 'B'
-                    else: s = s + ' '
-                    if lineno == self.curframe.f_lineno:
-                        s = s + '->'
-                    print s + '\t' + line,
-                    self.lineno = lineno
-        except KeyboardInterrupt:
-            pass
-    do_l = do_list
-
-    def _getline(self, filename, lineno):
-        if hasattr(filename, "__source__"):
-            try:
-                return filename.__source__.lines[lineno - 1] + "\n"
-            except IndexError:
-                return None
-        return linecache.getline(filename, lineno)
-
-    def get_stack(self, f, t):
-        # Modified from bdb.py to be able to walk the stack beyond generators,
-        # which does not work in the normal pdb :-(
-        stack, i = pdb.Pdb.get_stack(self, f, t)
-        if f is None:
-            i = max(0, len(stack) - 1)
-        return stack, i
-
-def post_mortem(t):
-    # modified from pdb.py for the new get_stack() implementation
-    p = Pdb()
-    p.reset()
-    p.interaction(None, t)
-
-def set_trace():
-    # again, a copy of the version in pdb.py
-    Pdb().set_trace(sys._getframe().f_back)
-
-

Modified: py/trunk/py/test/defaultconftest.py
==============================================================================
--- py/trunk/py/test/defaultconftest.py	(original)
+++ py/trunk/py/test/defaultconftest.py	Tue Jun 16 18:23:18 2009
@@ -10,5 +10,5 @@
 Function = py.test.collect.Function
 Instance = py.test.collect.Instance
 
-pytest_plugins = "default terminal xfail tmpdir execnetcleanup monkeypatch recwarn pdb".split()
+pytest_plugins = "default runner terminal xfail tmpdir execnetcleanup monkeypatch recwarn pdb".split()
 

Modified: py/trunk/py/test/dist/dsession.py
==============================================================================
--- py/trunk/py/test/dist/dsession.py	(original)
+++ py/trunk/py/test/dist/dsession.py	Tue Jun 16 18:23:18 2009
@@ -5,7 +5,6 @@
 """
 
 import py
-from py.__.test.runner import basic_run_report, basic_collect_report, ItemTestReport
 from py.__.test.session import Session
 from py.__.test import outcome 
 from py.__.test.dist.nodemanage import NodeManager
@@ -24,9 +23,9 @@
         self.shuttingdown = False
         self.testsfailed = False
 
-    def pytest_itemtestreport(self, rep):
-        if rep.colitem in self.dsession.item2nodes:
-            self.dsession.removeitem(rep.colitem, rep.node)
+    def pytest_runtest_logreport(self, rep):
+        if rep.item in self.dsession.item2nodes:
+            self.dsession.removeitem(rep.item, rep.node)
         if rep.failed:
             self.testsfailed = True
 
@@ -61,14 +60,14 @@
         self.item2nodes = {}
         super(DSession, self).__init__(config=config)
 
-    def pytest_configure(self, __call__, config):
-        __call__.execute()
-        try:
-            config.getxspecs()
-        except config.Error:
-            print
-            raise config.Error("dist mode %r needs test execution environments, "
-                               "none found." %(config.option.dist))
+    #def pytest_configure(self, __call__, config):
+    #    __call__.execute()
+    #    try:
+    #        config.getxspecs()
+    #    except config.Error:
+    #        print
+    #        raise config.Error("dist mode %r needs test execution environments, "
+    #                           "none found." %(config.option.dist))
 
     def main(self, colitems=None):
         colitems = self.getinitialitems(colitems)
@@ -177,7 +176,8 @@
                 senditems.append(next)
             else:
                 self.config.hook.pytest_collectstart(collector=next)
-                self.queueevent("pytest_collectreport", rep=basic_collect_report(next))
+                colrep = self.config.hook.pytest_make_collect_report(collector=next)
+                self.queueevent("pytest_collectreport", rep=colrep)
         if self.config.option.dist == "each":
             self.senditems_each(senditems)
         else:
@@ -238,10 +238,11 @@
         self.node2pending[node].remove(item)
 
     def handle_crashitem(self, item, node):
-        longrepr = "!!! Node %r crashed during running of test %r" %(node, item)
-        rep = ItemTestReport(item, when="???", excinfo=longrepr)
+        runner = item.config.pluginmanager.getplugin("runner") 
+        info = "!!! Node %r crashed during running of test %r" %(node, item)
+        rep = runner.ItemTestReport(item=item, excinfo=info, when="???")
         rep.node = node
-        self.config.hook.pytest_itemtestreport(rep=rep)
+        self.config.hook.pytest_runtest_logreport(rep=rep)
 
     def setup(self):
         """ setup any neccessary resources ahead of the test run. """

Modified: py/trunk/py/test/dist/testing/test_dsession.py
==============================================================================
--- py/trunk/py/test/dist/testing/test_dsession.py	(original)
+++ py/trunk/py/test/dist/testing/test_dsession.py	Tue Jun 16 18:23:18 2009
@@ -1,13 +1,13 @@
 from py.__.test.dist.dsession import DSession
-from py.__.test.runner import basic_collect_report 
 from py.__.test import outcome
 import py
 
 XSpec = py.execnet.XSpec
 
-def run(item, node):
-    from py.__.test.runner import basic_run_report
-    rep = basic_run_report(item)
+def run(item, node, excinfo=None):
+    runner = item.config.pluginmanager.getplugin("runner")
+    rep = runner.ItemTestReport(item=item, 
+        excinfo=excinfo, when="call", outerr=("", ""))
     rep.node = node
     return rep 
 
@@ -134,7 +134,7 @@
         session.queueevent(None)
         session.loop_once(loopstate)
         assert node.sent == [[item]]
-        session.queueevent("pytest_itemtestreport", rep=run(item, node))
+        session.queueevent("pytest_runtest_logreport", rep=run(item, node))
         session.loop_once(loopstate)
         assert loopstate.shuttingdown 
         assert not loopstate.testsfailed 
@@ -174,16 +174,16 @@
         session.senditems_load([item1, item2])
         node = session.item2nodes[item1] [0]
         session.queueevent("pytest_testnodedown", node=node, error=None)
-        evrec = testdir.geteventrecorder(session.pluginmanager)
+        reprec = testdir.getreportrecorder(session)
         print session.item2nodes
         loopstate = session._initloopstate([])
         session.loop_once(loopstate)
 
         assert loopstate.colitems == [item2] # do not reschedule crash item
-        testrep = evrec.matchreport(names="itemtestreport")
-        assert testrep.failed
-        assert testrep.colitem == item1
-        assert str(testrep.longrepr).find("crashed") != -1
+        rep = reprec.matchreport(names="pytest_runtest_logreport")
+        assert rep.failed
+        assert rep.item == item1
+        assert str(rep.longrepr).find("crashed") != -1
         #assert str(testrep.longrepr).find(node.gateway.spec) != -1
 
     def test_testnodeready_adds_to_available(self, testdir):
@@ -198,7 +198,7 @@
         session.loop_once(loopstate)
         assert len(session.node2pending) == 1
 
-    def runthrough(self, item):
+    def runthrough(self, item, excinfo=None):
         session = DSession(item.config)
         node = MockNode()
         session.addnode(node)
@@ -208,8 +208,8 @@
         session.loop_once(loopstate)
 
         assert node.sent == [[item]]
-        ev = run(item, node) 
-        session.queueevent("pytest_itemtestreport", rep=ev)
+        ev = run(item, node, excinfo=excinfo) 
+        session.queueevent("pytest_runtest_logreport", rep=ev)
         session.loop_once(loopstate)
         assert loopstate.shuttingdown  
         session.queueevent("pytest_testnodedown", node=node, error=None)
@@ -224,7 +224,7 @@
 
     def test_exit_completed_tests_fail(self, testdir):
         item = testdir.getitem("def test_func(): 0/0")
-        session, exitstatus = self.runthrough(item)
+        session, exitstatus = self.runthrough(item, excinfo="fail")
         assert exitstatus == outcome.EXIT_TESTSFAILED
 
     def test_exit_on_first_failing(self, testdir):
@@ -238,16 +238,16 @@
         session = DSession(modcol.config)
         node = MockNode()
         session.addnode(node)
-        items = basic_collect_report(modcol).result
+        items = modcol.config.hook.pytest_make_collect_report(collector=modcol).result
 
         # trigger testing  - this sends tests to the node
         session.triggertesting(items)
 
         # run tests ourselves and produce reports 
-        ev1 = run(items[0], node)
-        ev2 = run(items[1], node)
-        session.queueevent("pytest_itemtestreport", rep=ev1) # a failing one
-        session.queueevent("pytest_itemtestreport", rep=ev2)
+        ev1 = run(items[0], node, "fail")
+        ev2 = run(items[1], node, None)
+        session.queueevent("pytest_runtest_logreport", rep=ev1) # a failing one
+        session.queueevent("pytest_runtest_logreport", rep=ev2)
         # now call the loop
         loopstate = session._initloopstate(items)
         session.loop_once(loopstate)
@@ -261,13 +261,13 @@
         session.addnode(node)
         loopstate = session._initloopstate([])
         loopstate.shuttingdown = True
-        evrec = testdir.geteventrecorder(session.pluginmanager)
-        session.queueevent("pytest_itemtestreport", rep=run(item, node))
+        reprec = testdir.getreportrecorder(session)
+        session.queueevent("pytest_runtest_logreport", rep=run(item, node))
         session.loop_once(loopstate)
-        assert not evrec.getcalls("pytest_testnodedown")
+        assert not reprec.getcalls("pytest_testnodedown")
         session.queueevent("pytest_testnodedown", node=node, error=None)
         session.loop_once(loopstate)
-        assert evrec.getcall('pytest_testnodedown').node == node
+        assert reprec.getcall('pytest_testnodedown').node == node
 
     def test_filteritems(self, testdir):
         modcol = testdir.getmodulecol("""
@@ -282,18 +282,18 @@
         dsel = session.filteritems([modcol])
         assert dsel == [modcol] 
         items = modcol.collect()
-        callrecorder = testdir.geteventrecorder(session.pluginmanager).callrecorder
+        hookrecorder = testdir.getreportrecorder(session).hookrecorder
         remaining = session.filteritems(items)
         assert remaining == []
         
-        event = callrecorder.getcalls("pytest_deselected")[-1]
+        event = hookrecorder.getcalls("pytest_deselected")[-1]
         assert event.items == items 
 
         modcol.config.option.keyword = "test_fail"
         remaining = session.filteritems(items)
         assert remaining == [items[0]]
 
-        event = callrecorder.getcalls("pytest_deselected")[-1]
+        event = hookrecorder.getcalls("pytest_deselected")[-1]
         assert event.items == [items[1]]
 
     def test_testnodedown_shutdown_after_completion(self, testdir):
@@ -303,7 +303,7 @@
         node = MockNode()
         session.addnode(node)
         session.senditems_load([item])
-        session.queueevent("pytest_itemtestreport", rep=run(item, node))
+        session.queueevent("pytest_runtest_logreport", rep=run(item, node))
         loopstate = session._initloopstate([])
         session.loop_once(loopstate)
         assert node._shutdown is True
@@ -324,13 +324,12 @@
         node = MockNode()
         session.addnode(node)
 
-        colreport = basic_collect_report(modcol)
+        colreport = modcol.config.hook.pytest_make_collect_report(collector=modcol)
         item1, item2 = colreport.result
         session.senditems_load([item1])
         # node2pending will become empty when the loop sees the report 
         rep = run(item1, node)
-
-        session.queueevent("pytest_itemtestreport", rep=run(item1, node)) 
+        session.queueevent("pytest_runtest_logreport", rep=run(item1, node)) 
 
         # but we have a collection pending
         session.queueevent("pytest_collectreport", rep=colreport) 
@@ -355,16 +354,26 @@
         """)
         config = testdir.parseconfig('-d', p1, '--tx=popen')
         dsession = DSession(config)
-        callrecorder = testdir.geteventrecorder(config.pluginmanager).callrecorder
+        hookrecorder = testdir.getreportrecorder(config).hookrecorder
         dsession.main([config.getfsnode(p1)])
-        rep = callrecorder.popcall("pytest_itemtestreport").rep 
+        rep = hookrecorder.popcall("pytest_runtest_logreport").rep 
         assert rep.passed
-        rep = callrecorder.popcall("pytest_itemtestreport").rep
+        rep = hookrecorder.popcall("pytest_runtest_logreport").rep
         assert rep.skipped
-        rep = callrecorder.popcall("pytest_itemtestreport").rep
+        rep = hookrecorder.popcall("pytest_runtest_logreport").rep
         assert rep.failed
         # see that the node is really down 
-        node = callrecorder.popcall("pytest_testnodedown").node
+        node = hookrecorder.popcall("pytest_testnodedown").node
         assert node.gateway.spec.popen
-        #XXX eq.geteventargs("pytest_testrunfinish")
+        #XXX eq.geteventargs("pytest_sessionfinish")
 
+    @py.test.mark.xfail("test implementation missing")
+    def test_collected_function_causes_remote_skip_at_module_level(self, testdir):
+        p = testdir.makepyfile("""
+            import py
+            py.test.importorskip("xyz")
+            def test_func():
+                pass
+        """)
+        # we need to be able to collect test_func locally but not in the subprocess 
+        XXX

Modified: py/trunk/py/test/dist/testing/test_mypickle.py
==============================================================================
--- py/trunk/py/test/dist/testing/test_mypickle.py	(original)
+++ py/trunk/py/test/dist/testing/test_mypickle.py	Tue Jun 16 18:23:18 2009
@@ -4,22 +4,24 @@
 
 class A: 
     pass
-def test_pickle_and_back_IS_same():
-    def pickle_band_back_IS_same(obj, proto):
-        p1 = ImmutablePickler(uneven=False, protocol=proto)
-        p2 = ImmutablePickler(uneven=True, protocol=proto)
-        s1 = p1.dumps(obj)
-        d2 = p2.loads(s1)
-        s2 = p2.dumps(d2)
-        obj_back = p1.loads(s2)
-        assert obj is obj_back 
 
-    a1 = A()
-    a2 = A()
-    a2.a1 = a1
-    for proto in (0,1,2, -1):
-        for obj in {1:2}, [1,2,3], a1, a2:
-            yield pickle_band_back_IS_same, obj, proto
+def pytest_generate_tests(metafunc):
+    if "obj" in metafunc.funcargnames and "proto" in metafunc.funcargnames:
+        a1 = A()
+        a2 = A()
+        a2.a1 = a1
+        for proto in (0,1,2, -1):
+            for obj in {1:2}, [1,2,3], a1, a2:
+                metafunc.addcall(funcargs=dict(obj=obj, proto=proto))
+        
+def test_pickle_and_back_IS_same(obj, proto):
+    p1 = ImmutablePickler(uneven=False, protocol=proto)
+    p2 = ImmutablePickler(uneven=True, protocol=proto)
+    s1 = p1.dumps(obj)
+    d2 = p2.loads(s1)
+    s2 = p2.dumps(d2)
+    obj_back = p1.loads(s2)
+    assert obj is obj_back 
 
 def test_pickling_twice_before_unpickling():
     p1 = ImmutablePickler(uneven=False)

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 Jun 16 18:23:18 2009
@@ -8,6 +8,7 @@
             numbered=True)
         self.source = basetemp.mkdir("source")
         self.dest = basetemp.mkdir("dest")
+        request.getfuncargvalue("_pytest")
 
 class TestNodeManager:
     @py.test.mark.xfail("consider / forbid implicit rsyncdirs?")
@@ -107,10 +108,10 @@
         config = py.test.config._reparse([source, '--debug'])
         assert config.option.debug
         nodemanager = NodeManager(config, specs)
-        sorter = testdir.geteventrecorder(config.pluginmanager).callrecorder
+        reprec = testdir.getreportrecorder(config).hookrecorder
         nodemanager.setup_nodes(putevent=[].append)
         for spec in nodemanager.gwmanager.specs:
-            l = sorter.getcalls("pytest_trace")
+            l = reprec.getcalls("pytest_trace")
             assert l 
         nodemanager.teardown_nodes()
 
@@ -119,8 +120,8 @@
             def test_one():
                 pass
         """)
-        sorter = testdir.inline_run("-d", "--rsyncdir=%s" % testdir.tmpdir, 
-                "--tx=%s" % specssh, testdir.tmpdir)
-        ev = sorter.getfirstnamed(pytest_itemtestreport)
-        assert ev.passed 
+        reprec = testdir.inline_run("-d", "--rsyncdir=%s" % testdir.tmpdir, 
+                "--tx", specssh, testdir.tmpdir)
+        rep, = reprec.getreports("pytest_runtest_logreport")
+        assert rep.passed 
 

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 Jun 16 18:23:18 2009
@@ -111,11 +111,11 @@
         item = testdir.getitem("def test_func(): pass")
         node = mysetup.makenode(item.config)
         node.send(item)
-        kwargs = mysetup.geteventargs("pytest_itemtestreport")
+        kwargs = mysetup.geteventargs("pytest_runtest_logreport")
         rep = kwargs['rep'] 
         assert rep.passed 
         print rep
-        assert rep.colitem == item
+        assert rep.item == item
 
     def test_send_some(self, testdir, mysetup):
         items = testdir.getitems("""
@@ -131,11 +131,11 @@
         for item in items:
             node.send(item)
         for outcome in "passed failed skipped".split():
-            kwargs = mysetup.geteventargs("pytest_itemtestreport")
+            kwargs = mysetup.geteventargs("pytest_runtest_logreport")
             rep = kwargs['rep']
             assert getattr(rep, outcome) 
 
         node.sendlist(items)
         for outcome in "passed failed skipped".split():
-            rep = mysetup.geteventargs("pytest_itemtestreport")['rep']
+            rep = mysetup.geteventargs("pytest_runtest_logreport")['rep']
             assert getattr(rep, outcome) 

Modified: py/trunk/py/test/dist/txnode.py
==============================================================================
--- py/trunk/py/test/dist/txnode.py	(original)
+++ py/trunk/py/test/dist/txnode.py	Tue Jun 16 18:23:18 2009
@@ -50,10 +50,10 @@
             elif eventname == "slavefinished":
                 self._down = True
                 self.notify("pytest_testnodedown", error=None, node=self)
-            elif eventname == "pytest_itemtestreport":
+            elif eventname == "pytest_runtest_logreport":
                 rep = kwargs['rep']
                 rep.node = self
-                self.notify("pytest_itemtestreport", rep=rep)
+                self.notify("pytest_runtest_logreport", rep=rep)
             else:
                 self.notify(eventname, *args, **kwargs)
         except KeyboardInterrupt: 
@@ -105,8 +105,8 @@
     def sendevent(self, eventname, *args, **kwargs):
         self.channel.send((eventname, args, kwargs))
 
-    def pytest_itemtestreport(self, rep):
-        self.sendevent("pytest_itemtestreport", rep=rep)
+    def pytest_runtest_logreport(self, rep):
+        self.sendevent("pytest_runtest_logreport", rep=rep)
 
     def run(self):
         channel = self.channel
@@ -124,9 +124,9 @@
                     break
                 if isinstance(task, list):
                     for item in task:
-                        item.config.pluginmanager.do_itemrun(item)
+                        item.config.hook.pytest_runtest_protocol(item=item) 
                 else:
-                    task.config.pluginmanager.do_itemrun(item=task)
+                    task.config.hook.pytest_runtest_protocol(item=task) 
         except KeyboardInterrupt:
             raise
         except:

Modified: py/trunk/py/test/funcargs.py
==============================================================================
--- py/trunk/py/test/funcargs.py	(original)
+++ py/trunk/py/test/funcargs.py	Tue Jun 16 18:23:18 2009
@@ -10,22 +10,19 @@
     
 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()
+    request = FuncargRequest(pyfuncitem=function)
+    request._fillfuncargs()
 
-class RunSpecs:
+
+_notexists = object()
+class CallSpec:
+    def __init__(self, funcargs, id, param):
+        self.funcargs = funcargs 
+        self.id = id
+        if param is not _notexists:
+            self.param = param 
+
+class Metafunc:
     def __init__(self, function, config=None, cls=None, module=None):
         self.config = config
         self.module = module 
@@ -33,81 +30,136 @@
         self.funcargnames = getfuncargnames(function)
         self.cls = cls
         self.module = module
-        self._combinations = []
+        self._calls = []
+        self._ids = py.builtin.set()
 
-    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)
+    def addcall(self, funcargs=None, id=_notexists, param=_notexists):
+        assert funcargs is None or isinstance(funcargs, dict)
+        if id is None:
+            raise ValueError("id=None not allowed") 
+        if id is _notexists:
+            id = len(self._calls)
+        id = str(id)
+        if id in self._ids:
+            raise ValueError("duplicate id %r" % id)
+        self._ids.add(id)
+        self._calls.append(CallSpec(funcargs, id, param))
 
 class FunctionCollector(py.test.collect.Collector):
-    def __init__(self, name, parent, combinations):
+    def __init__(self, name, parent, calls):
         super(FunctionCollector, self).__init__(name, parent)
-        self.combinations = combinations
+        self.calls = calls 
         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)
+        for callspec in self.calls:
+            name = "%s[%s]" %(self.name, callspec.id)
+            function = self.parent.Function(name=name, parent=self, 
+                callspec=callspec, callobj=self.obj)
             l.append(function)
-        return l 
+        return l
+
+    def reportinfo(self):
+        try:
+            return self._fslineno, self.name
+        except AttributeError:
+            pass        
+        fspath, lineno = py.code.getfslineno(self.obj)
+        self._fslineno = fspath, lineno
+        return fspath, lineno, self.name
+    
 
 class FuncargRequest:
     _argprefix = "pytest_funcarg__"
+    _argname = None
 
     class Error(LookupError):
         """ error on performing funcarg request. """ 
-        
-    def __init__(self, pyfuncitem, argname):
+
+    def __init__(self, pyfuncitem):
         self._pyfuncitem = pyfuncitem
-        self.argname = argname 
         self.function = pyfuncitem.obj
-        self.module = pyfuncitem.getmodulecollector().obj 
+        self.module = pyfuncitem.getparent(py.test.collect.Module).obj
         self.cls = getattr(self.function, 'im_class', None)
+        self.instance = getattr(self.function, 'im_self', None)
         self.config = pyfuncitem.config
         self.fspath = pyfuncitem.fspath
+        if hasattr(pyfuncitem, '_requestparam'):
+            self.param = pyfuncitem._requestparam 
         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)
-        )
+        self._plugins.append(self.module)
+        if self.instance is not None:
+            self._plugins.append(self.instance)
+        self._funcargs  = self._pyfuncitem.funcargs.copy()
+        self._provider = {}
+
+    def _fillfuncargs(self):
+        argnames = getfuncargnames(self.function)
+        if argnames:
+            assert not getattr(self._pyfuncitem, '_args', None), "yielded functions cannot have funcargs" 
+        for argname in argnames:
+            if argname not in self._pyfuncitem.funcargs:
+                self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
+
+    def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
+        if not hasattr(self.config, '_setupcache'):
+            self.config._setupcache = {} # XXX weakref? 
+        cachekey = (self._getscopeitem(scope), extrakey)
+        cache = self.config._setupcache
+        try:
+            val = cache[cachekey]
+        except KeyError:
+            val = setup()
+            cache[cachekey] = val 
+            if teardown is not None:
+                self.addfinalizer(lambda: teardown(val), scope=scope)
+        return val 
+
+    def getfuncargvalue(self, argname):
+        try:
+            return self._funcargs[argname]
+        except KeyError:
+            pass
+        if argname not in self._provider:
+            self._provider[argname] = self.config.pluginmanager.listattr(
+                    plugins=self._plugins, 
+                    attrname=self._argprefix + str(argname)
+            )
+        #else: we are called recursively  
+        if not self._provider[argname]:
+            self._raiselookupfailed(argname)
+        funcargprovider = self._provider[argname].pop()
+        self._funcargs[argname] = res = funcargprovider(request=self)
+        return res
+
+    def _getscopeitem(self, scope):
+        if scope == "function":
+            return self._pyfuncitem
+        elif scope == "module":
+            return self._pyfuncitem.getparent(py.test.collect.Module)
+        elif scope == "session":
+            return None
+        raise ValueError("unknown finalization scope %r" %(scope,))
+
+    def addfinalizer(self, finalizer, scope="function"):
+        colitem = self._getscopeitem(scope)
+        self.config._setupstate.addfinalizer(finalizer=finalizer, colitem=colitem)
 
     def __repr__(self):
-        return "<FuncargRequest %r for %r>" %(self.argname, self._pyfuncitem)
-
-    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)
+        return "<FuncargRequest for %r>" %(self._pyfuncitem)
 
-    def _raiselookupfailed(self):
+    def _raiselookupfailed(self, argname):
         available = []
         for plugin in self._plugins:
-            for name in vars(plugin.__class__):
+            for name in vars(plugin):
                 if name.startswith(self._argprefix):
                     name = name[len(self._argprefix):]
                     if name not in available:
                         available.append(name) 
-        fspath, lineno, msg = self._pyfuncitem.metainfo()
+        fspath, lineno, msg = self._pyfuncitem.reportinfo()
         line = "%s:%s" %(fspath, lineno)
-        msg = "funcargument %r not found for: %s" %(self.argname, line)
+        msg = "funcargument %r not found for: %s" %(argname, line)
         msg += "\n available funcargs: %s" %(", ".join(available),)
         raise LookupError(msg)
 

Modified: py/trunk/py/test/looponfail/remote.py
==============================================================================
--- py/trunk/py/test/looponfail/remote.py	(original)
+++ py/trunk/py/test/looponfail/remote.py	Tue Jun 16 18:23:18 2009
@@ -137,10 +137,10 @@
     session.shouldclose = channel.isclosed 
    
     class Failures(list):
-        def pytest_itemtestreport(self, rep):
+        def pytest_runtest_logreport(self, rep):
             if rep.failed:
                 self.append(rep)
-        pytest_collectreport = pytest_itemtestreport
+        pytest_collectreport = pytest_runtest_logreport
         
     failreports = Failures()
     session.pluginmanager.register(failreports)
@@ -150,4 +150,4 @@
     session.config.hook.pytest_looponfailinfo(
         failreports=list(failreports), 
         rootdirs=[config.topdir])
-    channel.send([x.colitem._totrail() for x in failreports])
+    channel.send([rep.getnode()._totrail() for rep in failreports])

Modified: py/trunk/py/test/outcome.py
==============================================================================
--- py/trunk/py/test/outcome.py	(original)
+++ py/trunk/py/test/outcome.py	Tue Jun 16 18:23:18 2009
@@ -36,11 +36,11 @@
         self.expr = expr 
         self.expected = expected
 
-class Exit(Exception):
+class Exit(KeyboardInterrupt):
     """ for immediate program exits without tracebacks and reporter/summary. """
     def __init__(self, msg="unknown reason"):
         self.msg = msg 
-        Exception.__init__(self, msg)
+        KeyboardInterrupt.__init__(self, msg)
 
 # exposed helper methods 
 

Deleted: /py/trunk/py/test/plugin/api.py
==============================================================================
--- /py/trunk/py/test/plugin/api.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,142 +0,0 @@
-"""
-API definitions for pytest plugin hooks 
-"""
-
-class PluginHooks:
-    # ------------------------------------------------------------------------------
-    # Command line and configuration hooks 
-    # ------------------------------------------------------------------------------
-    def pytest_addoption(self, parser):
-        """ called before commandline parsing.  """
-
-    def pytest_configure(self, config):
-        """ called after command line options have been parsed. 
-            and all plugins and initial conftest files been loaded. 
-            ``config`` provides access to all such configuration values. 
-        """
-
-    def pytest_unconfigure(self, config):
-        """ called before test process is exited. 
-        """
-
-    # ------------------------------------------------------------------------------
-    # test Session related hooks 
-    # ------------------------------------------------------------------------------
-
-    def pytest_testrunstart(self):
-        """ whole test run starts. """
-
-    def pytest_testrunfinish(self, exitstatus, excrepr=None):
-        """ whole test run finishes. """
-
-    def pytest_deselected(self, items):
-        """ collected items that were deselected (by keyword). """
-
-
-
-    # ------------------------------------------------------------------------------
-    # collection hooks
-    # ------------------------------------------------------------------------------
-    def pytest_collect_file(self, path, parent):
-        """ return Collection node or None. """
-
-    def pytest_collect_recurse(self, path, parent):
-        """ return True/False to cause/prevent recursion into given directory. 
-            return None if you do not want to make the decision. 
-        """ 
-    pytest_collect_recurse.firstresult = True
-
-    def pytest_collect_directory(self, path, parent):
-        """ return Collection node or None. """
-
-    def pytest_pycollect_obj(self, collector, name, obj):
-        """ return custom item/collector for a python object in a module, or None.  """
-    pytest_pycollect_obj.firstresult = True
-
-    def pytest_genfuncruns(self, runspec):
-        """ generate (multiple) parametrized calls to a test function."""
-
-    def pytest_collectstart(self, collector):
-        """ collector starts collecting. """
-
-    def pytest_collectreport(self, rep):
-        """ collector finished collecting. """
-
-    # ------------------------------------------------------------------------------
-    # runtest related hooks 
-    # ------------------------------------------------------------------------------
-    #
-    def pytest_itemrun(self, item, pdb=None):
-        """ run given test item and return test report. """ 
-
-    def pytest_pyfunc_call(self, pyfuncitem, args, kwargs):
-        """ return True if we consumed/did the call to the python function item. """
-    pytest_pyfunc_call.firstresult = True
-
-    def pytest_item_makereport(self, item, excinfo, when, outerr):
-        """ return ItemTestReport for the given test outcome. """
-    pytest_item_makereport.firstresult = True
-
-    def pytest_itemstart(self, item, node=None):
-        """ test item gets collected. """
-
-    def pytest_itemtestreport(self, rep):
-        """ test has been run. """
-
-    def pytest_item_runtest_finished(self, item, excinfo, outerr):
-        """ test has been run. """
-
-    def pytest_itemsetupreport(self, rep):
-        """ a report on running a fixture function. """ 
-
-    # ------------------------------------------------------------------------------
-    # reporting hooks (invoked from pytest_terminal.py) 
-    # ------------------------------------------------------------------------------
-    def pytest_report_teststatus(self, rep):
-        """ return shortletter and verbose word. """
-    pytest_report_teststatus.firstresult = True
-
-    def pytest_terminal_summary(self, terminalreporter):
-        """ add additional section in terminal summary reporting. """
-
-    # ------------------------------------------------------------------------------
-    # doctest hooks 
-    # ------------------------------------------------------------------------------
-    def pytest_doctest_prepare_content(self, content):
-        """ return processed content for a given doctest"""
-    pytest_doctest_prepare_content.firstresult = True
-
-    # ------------------------------------------------------------------------------
-    # misc hooks 
-    # ------------------------------------------------------------------------------
-
-    def pytest_plugin_registered(self, plugin):
-        """ a new py lib plugin got registered. """
-
-    def pytest_plugin_unregistered(self, plugin):
-        """ a py lib plugin got unregistered. """
-
-    def pytest_internalerror(self, excrepr):
-        """ called for internal errors. """
-
-    def pytest_trace(self, category, msg):
-        """ called for debug info. """ 
-   
-
-
-    # ------------------------------------------------------------------------------
-    # distributed testing 
-    # ------------------------------------------------------------------------------
-
-    def pytest_testnodeready(self, node):
-        """ Test Node is ready to operate. """
-
-    def pytest_testnodedown(self, node, error):
-        """ Test Node is down. """
-
-    def pytest_rescheduleitems(self, items):
-        """ reschedule Items from a node that went down. """
-
-    def pytest_looponfailinfo(self, failreports, rootdirs):
-        """ info for repeating failing tests. """
-

Modified: py/trunk/py/test/plugin/conftest.py
==============================================================================
--- py/trunk/py/test/plugin/conftest.py	(original)
+++ py/trunk/py/test/plugin/conftest.py	Tue Jun 16 18:23:18 2009
@@ -1,10 +1,25 @@
 import py
 
-pytest_plugins = "pytester", "plugintester"
+pytest_plugins = "pytester"
 
-class ConftestPlugin: 
-    def pytest_collect_file(self, path, parent):
-        if path.basename.startswith("pytest_") and path.ext == ".py":
-            mod = parent.Module(path, parent=parent)
-            return mod
+def pytest_collect_file(path, parent):
+    if path.basename.startswith("pytest_") and path.ext == ".py":
+        mod = parent.Module(path, parent=parent)
+        return mod
+
+# decorate testdir to contain plugin under test 
+def pytest_funcarg__testdir(request):
+    testdir = request.getfuncargvalue("testdir")
+    #for obj in (request.cls, request.module):
+    #    if hasattr(obj, 'testplugin'): 
+    #        testdir.plugins.append(obj.testplugin)
+    #        break
+    #else:
+    basename = request.module.__name__.split(".")[-1] 
+    if basename.startswith("pytest_"):
+        testdir.plugins.append(vars(request.module))
+        testdir.plugins.append(basename) 
+    else:
+        pass # raise ValueError("need better support code")
+    return testdir
 

Added: py/trunk/py/test/plugin/hookspec.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/hookspec.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,150 @@
+"""
+py.test hooks / extension points 
+"""
+
+# ------------------------------------------------------------------------------
+# Command line and configuration hooks 
+# ------------------------------------------------------------------------------
+def pytest_addoption(parser):
+    """ called before commandline parsing.  """
+
+def pytest_configure(config):
+    """ called after command line options have been parsed. 
+        and all plugins and initial conftest files been loaded. 
+        ``config`` provides access to all such configuration values. 
+    """
+
+def pytest_unconfigure(config):
+    """ called before test process is exited.  """
+
+# ------------------------------------------------------------------------------
+# test Session related hooks 
+# ------------------------------------------------------------------------------
+
+def pytest_sessionstart(session):
+    """ before session.main() is called. """
+
+def pytest_sessionfinish(session, exitstatus, excrepr=None):
+    """ whole test run finishes. """
+
+def pytest_deselected(items):
+    """ repeatedly called for test items deselected by keyword. """
+
+# ------------------------------------------------------------------------------
+# collection hooks
+# ------------------------------------------------------------------------------
+
+def pytest_collect_directory(path, parent):
+    """ return Collection node or None for the given path. """
+
+def pytest_collect_file(path, parent):
+    """ return Collection node or None for the given path. """
+
+def pytest_collectstart(collector):
+    """ collector starts collecting. """
+
+def pytest_collectreport(rep):
+    """ collector finished collecting. """
+
+def pytest_make_collect_report(collector):
+    """ perform a collection and return a collection. """ 
+pytest_make_collect_report.firstresult = True
+
+# XXX rename to item_collected()?  meaning in distribution context? 
+def pytest_itemstart(item, node=None):
+    """ test item gets collected. """
+
+# ------------------------------------------------------------------------------
+# Python test function related hooks 
+# ------------------------------------------------------------------------------
+
+def pytest_pycollect_makeitem(collector, name, obj):
+    """ return custom item/collector for a python object in a module, or None.  """
+pytest_pycollect_makeitem.firstresult = True
+
+def pytest_pyfunc_call(pyfuncitem):
+    """ perform function call to the with the given function arguments. """ 
+pytest_pyfunc_call.firstresult = True
+
+def pytest_generate_tests(metafunc):
+    """ generate (multiple) parametrized calls to a test function."""
+
+# ------------------------------------------------------------------------------
+# generic runtest related hooks 
+# ------------------------------------------------------------------------------
+def pytest_runtest_setup(item):
+    """ called before pytest_runtest_call(). """ 
+
+def pytest_runtest_call(item):
+    """ execute test item. """ 
+
+def pytest_runtest_teardown(item):
+    """ called after pytest_runtest_call(). """ 
+
+def pytest_runtest_protocol(item):
+    """ run given test item and return test report. """ 
+pytest_runtest_protocol.firstresult = True
+
+def pytest_runtest_makereport(item, call):
+    """ make ItemTestReport for the specified test outcome. """
+pytest_runtest_makereport.firstresult = True
+
+def pytest_runtest_logreport(rep):
+    """ process item test report. """ 
+
+# ------------------------------------------------------------------------------
+# generic reporting hooks (invoked from pytest_terminal.py) 
+# ------------------------------------------------------------------------------
+def pytest_report_teststatus(rep):
+    """ return shortletter and verbose word. """
+pytest_report_teststatus.firstresult = True
+
+def pytest_terminal_summary(terminalreporter):
+    """ add additional section in terminal summary reporting. """
+
+def pytest_report_iteminfo(item):
+    """ return (fspath, lineno, name) for the item.
+        the information is used for result display and to sort tests
+    """
+pytest_report_iteminfo.firstresult = True
+
+# ------------------------------------------------------------------------------
+# doctest hooks 
+# ------------------------------------------------------------------------------
+def pytest_doctest_prepare_content(content):
+    """ return processed content for a given doctest"""
+pytest_doctest_prepare_content.firstresult = True
+
+
+# ------------------------------------------------------------------------------
+# misc hooks 
+# ------------------------------------------------------------------------------
+
+def pytest_plugin_registered(plugin):
+    """ a new py lib plugin got registered. """
+
+def pytest_plugin_unregistered(plugin):
+    """ a py lib plugin got unregistered. """
+
+def pytest_internalerror(excrepr):
+    """ called for internal errors. """
+
+def pytest_trace(category, msg):
+    """ called for debug info. """ 
+
+# ------------------------------------------------------------------------------
+# distributed testing 
+# ------------------------------------------------------------------------------
+
+def pytest_testnodeready(node):
+    """ Test Node is ready to operate. """
+
+def pytest_testnodedown(node, error):
+    """ Test Node is down. """
+
+def pytest_rescheduleitems(items):
+    """ reschedule Items from a node that went down. """
+
+def pytest_looponfailinfo(failreports, rootdirs):
+    """ info for repeating failing tests. """
+

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 Jun 16 18:23:18 2009
@@ -1,21 +1,23 @@
 import py
 
-class _pytestPlugin:
-    def pytest_funcarg___pytest(self, request):
-        return PytestArg(request)
+def pytest_funcarg___pytest(request):
+    return PytestArg(request)
 
 class PytestArg:
     def __init__(self, request):
         self.request = request 
-
-    def getcallrecorder(self, hookspecs, comregistry=None):
-        if comregistry is None:
-            comregistry = self.request.config.pluginmanager.comregistry
-        callrecorder = CallRecorder(comregistry)
-        callrecorder.start_recording(hookspecs)
-        self.request.addfinalizer(callrecorder.finalize)
-        return callrecorder 
-
+        self.monkeypatch = self.request.getfuncargvalue("monkeypatch")
+        self.comregistry = py._com.Registry()
+        self.monkeypatch.setattr(py._com, 'comregistry', self.comregistry)
+
+    def gethookrecorder(self, hookspecs, registry=None):
+        if registry is not None:
+            self.monkeypatch.setattr(py._com, 'comregistry', registry) 
+            self.comregistry = registry 
+        hookrecorder = HookRecorder(self.comregistry) 
+        hookrecorder.start_recording(hookspecs)
+        self.request.addfinalizer(hookrecorder.finish_recording)
+        return hookrecorder 
 
 class ParsedCall:
     def __init__(self, name, locals):
@@ -29,7 +31,7 @@
         del d['_name']
         return "<ParsedCall %r(**%r)>" %(self._name, d)
 
-class CallRecorder:
+class HookRecorder:
     def __init__(self, comregistry):
         self._comregistry = comregistry
         self.calls = []
@@ -41,26 +43,22 @@
             _recorder = self 
         for name, method in vars(hookspecs).items():
             if name[0] != "_":
-                setattr(RecordCalls, name, self._getcallparser(method))
+                setattr(RecordCalls, name, self._makecallparser(method))
         recorder = RecordCalls()
         self._recorders[hookspecs] = recorder
         self._comregistry.register(recorder)
         self.hook = py._com.Hooks(hookspecs, registry=self._comregistry)
 
-    def finalize(self):
+    def finish_recording(self):
         for recorder in self._recorders.values():
             self._comregistry.unregister(recorder)
         self._recorders.clear()
 
-    def recordsmethod(self, name):
-        for hookspecs in self._recorders:
-            if hasattr(hookspecs, name):
-                return True
-
-    def _getcallparser(self, method):
+    def _makecallparser(self, method):
         name = method.__name__
         args, varargs, varkw, default = py.std.inspect.getargspec(method)
-        assert args[0] == "self"
+        if args[0] != "self":
+            args.insert(0, 'self') 
         fspec = py.std.inspect.formatargspec(args, varargs, varkw, default)
         # we use exec because we want to have early type
         # errors on wrong input arguments, using
@@ -73,13 +71,6 @@
         """ % locals())
         return locals()[name]
 
-    def popcall(self, name):
-        for i, call in py.builtin.enumerate(self.calls):
-            if call._name == name:
-                del self.calls[i]
-                return call 
-        raise ValueError("could not find call %r in %r" %(name, self.calls))
-
     def getcalls(self, names):
         if isinstance(names, str):
             names = names.split()
@@ -96,12 +87,22 @@
                 l.append(call)
         return l
 
-def test_generic(plugintester):
-    plugintester.hookcheck(_pytestPlugin)
+    def popcall(self, name):
+        for i, call in py.builtin.enumerate(self.calls):
+            if call._name == name:
+                del self.calls[i]
+                return call 
+        raise ValueError("could not find call %r" %(name, ))
+
+    def getcall(self, name):
+        l = self.getcalls(name)
+        assert len(l) == 1, (name, l)
+        return l[0]
 
-def test_callrecorder_basic():
+
+def test_hookrecorder_basic():
     comregistry = py._com.Registry() 
-    rec = CallRecorder(comregistry)
+    rec = HookRecorder(comregistry)
     class ApiClass:
         def xyz(self, arg):
             pass
@@ -112,14 +113,19 @@
     assert call._name == "xyz"
     py.test.raises(ValueError, "rec.popcall('abc')")
 
+reg = py._com.comregistry
+def test_functional_default(testdir, _pytest):
+    assert _pytest.comregistry == py._com.comregistry 
+    assert _pytest.comregistry != reg
+
 def test_functional(testdir, linecomp):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         import py
         pytest_plugins="_pytest"
         def test_func(_pytest):
             class ApiClass:
                 def xyz(self, arg):  pass 
-            rec = _pytest.getcallrecorder(ApiClass)
+            rec = _pytest.gethookrecorder(ApiClass)
             class Plugin:
                 def xyz(self, arg):
                     return arg + 1
@@ -127,4 +133,4 @@
             res = rec.hook.xyz(arg=41)
             assert res == [42]
     """)
-    sorter.assertoutcome(passed=1)
+    reprec.assertoutcome(passed=1)

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 Jun 16 18:23:18 2009
@@ -1,176 +1,146 @@
-import py
+""" Plugin implementing defaults and general options. """ 
 
-class DefaultPlugin:
-    """ Plugin implementing defaults and general options. """ 
+import py
 
-    def pytest_itemrun(self, item, pdb=None):
-        from py.__.test.runner import basic_run_report, forked_run_report
-        if item.config.option.boxed:
-            runner = forked_run_report
+def pytest_pyfunc_call(__call__, pyfuncitem):
+    if not __call__.execute(firstresult=True):
+        testfunction = pyfuncitem.obj 
+        if pyfuncitem._isyieldedfunction():
+            testfunction(*pyfuncitem._args)
         else:
-            runner = basic_run_report
-        report = runner(item, pdb=pdb) 
-        item.config.hook.pytest_itemtestreport(rep=report)
-        return True
-
-    def pytest_item_makereport(self, item, excinfo, when, outerr):
-        from py.__.test import runner
-        return runner.ItemTestReport(item, excinfo, when, outerr)
-
-    def pytest_item_runtest_finished(self, item, excinfo, outerr):
-        from py.__.test import runner
-        rep = runner.ItemTestReport(item, excinfo, "execute", outerr)
-        item.config.hook.pytest_itemtestreport(rep=rep) 
-
-    def pytest_pyfunc_call(self, pyfuncitem, args, kwargs):
-        pyfuncitem.obj(*args, **kwargs)
-
-    def pytest_collect_file(self, path, parent):
-        ext = path.ext 
-        pb = path.purebasename
-        if pb.startswith("test_") or pb.endswith("_test") or \
-           path in parent.config.args:
-            if ext == ".py":
-                return parent.Module(path, parent=parent) 
-
-    def pytest_collect_recurse(self, path, parent):
-        #excludelist = parent._config.getvalue_pathlist('dir_exclude', path)
-        #if excludelist and path in excludelist:
-        #    return 
-        if not parent.recfilter(path):
-            # check if cmdline specified this dir or a subdir directly
-            for arg in parent.config.args:
-                if path == arg or arg.relto(path):
-                    break
-            else:
-                return False 
-        return True
-      
-    def pytest_collect_directory(self, path, parent):
-        # XXX reconsider the following comment 
-        # not use parent.Directory here as we generally 
-        # want dir/conftest.py to be able to 
-        # define Directory(dir) already 
-        Directory = parent.config.getvalue('Directory', path) 
-        return Directory(path, parent=parent)
-
-    def pytest_addoption(self, parser):
-        group = parser.addgroup("general", "test collection and failure interaction options")
-        group._addoption('-v', '--verbose', action="count", 
-                   dest="verbose", default=0, help="increase verbosity."),
-        group._addoption('-x', '--exitfirst',
-                   action="store_true", dest="exitfirst", default=False,
-                   help="exit instantly on first error or failed test."),
-        group._addoption('-k',
-            action="store", dest="keyword", default='',
-            help="only run test items matching the given "
-                 "space separated keywords.  precede a keyword with '-' to negate. "
-                 "Terminate the expression with ':' to treat a match as a signal "
-                 "to run all subsequent tests. ")
-        group._addoption('-l', '--showlocals',
-                   action="store_true", dest="showlocals", default=False,
-                   help="show locals in tracebacks (disabled by default).")
-        #group._addoption('--showskipsummary',
-        #           action="store_true", dest="showskipsummary", default=False,
-        #           help="always show summary of skipped tests") 
-        group._addoption('--pdb',
-                   action="store_true", dest="usepdb", default=False,
-                   help="start pdb (the Python debugger) on errors.")
-        group._addoption('--tb', metavar="style", 
-                   action="store", dest="tbstyle", default='long',
-                   type="choice", choices=['long', 'short', 'no'],
-                   help="traceback verboseness (long/short/no).")
-        group._addoption('-s', 
-                   action="store_true", dest="nocapture", default=False,
-                   help="disable catching of stdout/stderr during test run.")
-        group.addoption('--boxed',
-                   action="store_true", dest="boxed", default=False,
-                   help="box each test run in a separate process") 
-        group._addoption('-p', action="append", dest="plugin", default = [],
-                   help=("load the specified plugin after command line parsing. "
-                         "Example: '-p hello' will trigger 'import pytest_hello' "
-                         "and instantiate 'HelloPlugin' from the module."))
-        group._addoption('-f', '--looponfail',
-                   action="store_true", dest="looponfail", default=False,
-                   help="run tests, re-run failing test set until all pass.")
-
-        group = parser.addgroup("test process debugging")
-        group.addoption('--collectonly',
-            action="store_true", dest="collectonly",
-            help="only collect tests, don't execute them."),
-        group.addoption('--traceconfig',
-                   action="store_true", dest="traceconfig", default=False,
-                   help="trace considerations of conftest.py files."),
-        group._addoption('--nomagic',
-                   action="store_true", dest="nomagic", default=False,
-                   help="don't reinterpret asserts, no traceback cutting. ")
-        group._addoption('--fulltrace',
-                   action="store_true", dest="fulltrace", default=False,
-                   help="don't cut any tracebacks (default is to cut).")
-        group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
-                   help="base temporary directory for this test run.")
-        group._addoption('--iocapture', action="store", default="fd", metavar="method",
-                   type="choice", choices=['fd', 'sys', 'no'],
-                   help="set iocapturing method: fd|sys|no.")
-        group.addoption('--debug',
-                   action="store_true", dest="debug", default=False,
-                   help="generate and show debugging information.")
-
-        group = parser.addgroup("dist", "distributed testing") #  see http://pytest.org/help/dist")
-        group._addoption('--dist', metavar="distmode", 
-                   action="store", choices=['load', 'each', 'no'], 
-                   type="choice", dest="dist", default="no", 
-                   help=("set mode for distributing tests to exec environments.\n\n"
-                         "each: send each test to each available environment.\n\n"
-                         "load: send each test to available environment.\n\n"
-                         "(default) no: run tests inprocess, don't distribute."))
-        group._addoption('--tx', dest="tx", action="append", default=[], metavar="xspec",
-                   help=("add a test execution environment. some examples: "
-                         "--tx popen//python=python2.5 --tx socket=192.168.1.102:8888 "
-                         "--tx ssh=user at codespeak.net//chdir=testcache"))
-        group._addoption('-d', 
-                   action="store_true", dest="distload", default=False,
-                   help="load-balance tests.  shortcut for '--dist=load'")
-        group._addoption('-n', dest="numprocesses", metavar="numprocesses", 
-                   action="store", type="int", 
-                   help="shortcut for '--dist=load --tx=NUM*popen'")
-        group.addoption('--rsyncdir', action="append", default=[], metavar="dir1", 
-                   help="add directory for rsyncing to remote tx nodes.")
-
-    def pytest_configure(self, config):
-        self.fixoptions(config)
-        self.setsession(config)
-        self.loadplugins(config)
-
-    def fixoptions(self, config):
-        if config.option.numprocesses:
-            config.option.dist = "load"
-            config.option.tx = ['popen'] * int(config.option.numprocesses)
-        if config.option.distload:
-            config.option.dist = "load"
-        if config.getvalue("usepdb"):
-            if config.getvalue("looponfail"):
-                raise config.Error("--pdb incompatible with --looponfail.")
-            if config.option.dist != "no":
-                raise config.Error("--pdb incomptaible with distributing tests.")
-
-    def loadplugins(self, config):
-        for name in config.getvalue("plugin"):
-            print "importing", name
-            config.pluginmanager.import_plugin(name)
-
-    def setsession(self, config):
-        val = config.getvalue
-        if val("collectonly"):
-            from py.__.test.session import Session
-            config.setsessionclass(Session)
+            funcargs = pyfuncitem.funcargs
+            testfunction(**funcargs)
+
+def pytest_collect_file(path, parent):
+    ext = path.ext 
+    pb = path.purebasename
+    if pb.startswith("test_") or pb.endswith("_test") or \
+       path in parent.config.args:
+        if ext == ".py":
+            return parent.Module(path, parent=parent) 
+
+def pytest_collect_directory(path, parent):
+    # XXX reconsider the following comment 
+    # not use parent.Directory here as we generally 
+    # want dir/conftest.py to be able to 
+    # define Directory(dir) already 
+    if not parent.recfilter(path): # by default special ".cvs", ... 
+        # check if cmdline specified this dir or a subdir directly
+        for arg in parent.config.args:
+            if path == arg or arg.relto(path):
+                break
         else:
-            if val("looponfail"):
-                from py.__.test.looponfail.remote import LooponfailingSession
-                config.setsessionclass(LooponfailingSession)
-            elif val("dist") != "no":
-                from py.__.test.dist.dsession import  DSession
-                config.setsessionclass(DSession)
+            return 
+    Directory = parent.config.getvalue('Directory', path) 
+    return Directory(path, parent=parent)
+
+def pytest_report_iteminfo(item):
+    return item.reportinfo()
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general", "test collection and failure interaction options")
+    group._addoption('-v', '--verbose', action="count", 
+               dest="verbose", default=0, help="increase verbosity."),
+    group._addoption('-x', '--exitfirst',
+               action="store_true", dest="exitfirst", default=False,
+               help="exit instantly on first error or failed test."),
+    group._addoption('-k',
+        action="store", dest="keyword", default='',
+        help="only run test items matching the given "
+             "space separated keywords.  precede a keyword with '-' to negate. "
+             "Terminate the expression with ':' to treat a match as a signal "
+             "to run all subsequent tests. ")
+    group._addoption('-l', '--showlocals',
+               action="store_true", dest="showlocals", default=False,
+               help="show locals in tracebacks (disabled by default).")
+    #group._addoption('--showskipsummary',
+    #           action="store_true", dest="showskipsummary", default=False,
+    #           help="always show summary of skipped tests") 
+    group._addoption('--tb', metavar="style", 
+               action="store", dest="tbstyle", default='long',
+               type="choice", choices=['long', 'short', 'no'],
+               help="traceback verboseness (long/short/no).")
+    group._addoption('-s', 
+               action="store_true", dest="nocapture", default=False,
+               help="disable catching of stdout/stderr during test run.")
+    group._addoption('-p', action="append", dest="plugin", default = [],
+               help=("load the specified plugin after command line parsing. "))
+    group._addoption('-f', '--looponfail',
+               action="store_true", dest="looponfail", default=False,
+               help="run tests, re-run failing test set until all pass.")
+
+    group = parser.addgroup("test process debugging")
+    group.addoption('--collectonly',
+        action="store_true", dest="collectonly",
+        help="only collect tests, don't execute them."),
+    group.addoption('--traceconfig',
+               action="store_true", dest="traceconfig", default=False,
+               help="trace considerations of conftest.py files."),
+    group._addoption('--nomagic',
+               action="store_true", dest="nomagic", default=False,
+               help="don't reinterpret asserts, no traceback cutting. ")
+    group._addoption('--fulltrace',
+               action="store_true", dest="fulltrace", default=False,
+               help="don't cut any tracebacks (default is to cut).")
+    group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
+               help="base temporary directory for this test run.")
+    group._addoption('--iocapture', action="store", default="fd", metavar="method",
+               type="choice", choices=['fd', 'sys', 'no'],
+               help="set iocapturing method: fd|sys|no.")
+    group.addoption('--debug',
+               action="store_true", dest="debug", default=False,
+               help="generate and show debugging information.")
+
+    group = parser.addgroup("dist", "distributed testing") #  see http://pytest.org/help/dist")
+    group._addoption('--dist', metavar="distmode", 
+               action="store", choices=['load', 'each', 'no'], 
+               type="choice", dest="dist", default="no", 
+               help=("set mode for distributing tests to exec environments.\n\n"
+                     "each: send each test to each available environment.\n\n"
+                     "load: send each test to available environment.\n\n"
+                     "(default) no: run tests inprocess, don't distribute."))
+    group._addoption('--tx', dest="tx", action="append", default=[], metavar="xspec",
+               help=("add a test execution environment. some examples: "
+                     "--tx popen//python=python2.5 --tx socket=192.168.1.102:8888 "
+                     "--tx ssh=user at codespeak.net//chdir=testcache"))
+    group._addoption('-d', 
+               action="store_true", dest="distload", default=False,
+               help="load-balance tests.  shortcut for '--dist=load'")
+    group._addoption('-n', dest="numprocesses", metavar="numprocesses", 
+               action="store", type="int", 
+               help="shortcut for '--dist=load --tx=NUM*popen'")
+    group.addoption('--rsyncdir', action="append", default=[], metavar="dir1", 
+               help="add directory for rsyncing to remote tx nodes.")
+
+def pytest_configure(config):
+    fixoptions(config)
+    setsession(config)
+    loadplugins(config)
+
+def fixoptions(config):
+    if config.option.numprocesses:
+        config.option.dist = "load"
+        config.option.tx = ['popen'] * int(config.option.numprocesses)
+    if config.option.distload:
+        config.option.dist = "load"
+
+def loadplugins(config):
+    for name in config.getvalue("plugin"):
+        print "importing", name
+        config.pluginmanager.import_plugin(name)
+
+def setsession(config):
+    val = config.getvalue
+    if val("collectonly"):
+        from py.__.test.session import Session
+        config.setsessionclass(Session)
+    else:
+        if val("looponfail"):
+            from py.__.test.looponfail.remote import LooponfailingSession
+            config.setsessionclass(LooponfailingSession)
+        elif val("dist") != "no":
+            from py.__.test.dist.dsession import  DSession
+            config.setsessionclass(DSession)
 
 def test_implied_different_sessions(tmpdir):
     def x(*args):
@@ -186,9 +156,6 @@
     assert x('-n3') == 'DSession'
     assert x('-f') == 'LooponfailingSession'
 
-def test_generic(plugintester):
-    plugintester.hookcheck(DefaultPlugin)
-    
 def test_plugin_specify(testdir):
     testdir.chdir()
     config = py.test.raises(ImportError, """
@@ -239,12 +206,18 @@
         assert testdir.tmpdir.join('x') in roots 
 
 def test_dist_options(testdir):
-    py.test.raises(Exception, "testdir.parseconfigure('--pdb', '--looponfail')")
-    py.test.raises(Exception, "testdir.parseconfigure('--pdb', '-n 3')")
-    py.test.raises(Exception, "testdir.parseconfigure('--pdb', '-d')")
     config = testdir.parseconfigure("-n 2")
     assert config.option.dist == "load"
     assert config.option.tx == ['popen'] * 2
     
     config = testdir.parseconfigure("-d")
     assert config.option.dist == "load"
+
+def test_pytest_report_iteminfo():
+    class FakeItem(object):
+
+        def reportinfo(self):
+            return "-reportinfo-"
+
+    res = pytest_report_iteminfo(FakeItem())
+    assert res == "-reportinfo-"

Modified: py/trunk/py/test/plugin/pytest_doctest.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_doctest.py	(original)
+++ py/trunk/py/test/plugin/pytest_doctest.py	Tue Jun 16 18:23:18 2009
@@ -1,20 +1,22 @@
+"""
+automatically collect and execute doctests. 
+"""
+
 import py
+from py.__.code.excinfo import Repr, ReprFileLocation
 
-class DoctestPlugin:
-    def pytest_addoption(self, parser):
-        group = parser.addgroup("doctest options")
-        group.addoption("--doctest-modules", 
-            action="store_true", default=False,
-            dest="doctestmodules")
+def pytest_addoption(parser):
+    group = parser.addgroup("doctest options")
+    group.addoption("--doctest-modules", 
+        action="store_true", default=False,
+        dest="doctestmodules")
     
-    def pytest_collect_file(self, path, parent):
-        if path.ext == ".py":
-            if parent.config.getvalue("doctestmodules"):
-                return DoctestModule(path, parent)
-        if path.check(fnmatch="test_*.txt"):
-            return DoctestTextfile(path, parent)
-
-from py.__.code.excinfo import Repr, ReprFileLocation
+def pytest_collect_file(path, parent):
+    if path.ext == ".py":
+        if parent.config.getvalue("doctestmodules"):
+            return DoctestModule(path, parent)
+    if path.check(fnmatch="test_*.txt"):
+        return DoctestTextfile(path, parent)
 
 class ReprFailDoctest(Repr):
     def __init__(self, reprlocation, lines):
@@ -76,8 +78,8 @@
 #
 
 class TestDoctests:
+
     def test_collect_testtextfile(self, testdir):
-        testdir.plugins.append(DoctestPlugin())
         testdir.maketxtfile(whatever="")
         checkfile = testdir.maketxtfile(test_something="""
             alskdjalsdk
@@ -87,40 +89,37 @@
         """)
         for x in (testdir.tmpdir, checkfile): 
             #print "checking that %s returns custom items" % (x,) 
-            items, sorter = testdir.inline_genitems(x)
+            items, reprec = testdir.inline_genitems(x)
             assert len(items) == 1
             assert isinstance(items[0], DoctestTextfile)
 
     def test_collect_module(self, testdir):
-        testdir.plugins.append(DoctestPlugin())
         path = testdir.makepyfile(whatever="#")
         for p in (path, testdir.tmpdir): 
-            items, evrec = testdir.inline_genitems(p, '--doctest-modules')
+            items, reprec = testdir.inline_genitems(p, '--doctest-modules')
             assert len(items) == 1
             assert isinstance(items[0], DoctestModule)
 
     def test_simple_doctestfile(self, testdir):
-        testdir.plugins.append(DoctestPlugin())
         p = testdir.maketxtfile(test_doc="""
             >>> x = 1
             >>> x == 1
             False
         """)
-        sorter = testdir.inline_run(p)
-        sorter.assertoutcome(failed=1)
+        reprec = testdir.inline_run(p)
+        reprec.assertoutcome(failed=1)
 
     def test_doctest_unexpected_exception(self, testdir):
         from py.__.test.outcome import Failed 
 
-        testdir.plugins.append(DoctestPlugin())
         p = testdir.maketxtfile("""
             >>> i = 0
             >>> i = 1 
             >>> x
             2
         """)
-        sorter = testdir.inline_run(p)
-        call = sorter.getcall("pytest_itemtestreport")
+        reprec = testdir.inline_run(p)
+        call = reprec.getcall("pytest_runtest_logreport")
         assert call.rep.failed
         assert call.rep.longrepr 
         # XXX 
@@ -130,7 +129,6 @@
         #assert repr.reprlocation 
 
     def test_doctestmodule(self, testdir):
-        testdir.plugins.append(DoctestPlugin())
         p = testdir.makepyfile("""
             '''
                 >>> x = 1
@@ -139,11 +137,10 @@
 
             '''
         """)
-        sorter = testdir.inline_run(p, "--doctest-modules")
-        sorter.assertoutcome(failed=1) 
+        reprec = testdir.inline_run(p, "--doctest-modules")
+        reprec.assertoutcome(failed=1) 
 
     def test_txtfile_failing(self, testdir):
-        testdir.plugins.append('pytest_doctest')
         p = testdir.maketxtfile("""
             >>> i = 0
             >>> i + 1
@@ -159,8 +156,3 @@
             "    1",
             "*test_txtfile_failing.txt:2: DocTestFailure"
         ])
-
-
-def test_generic(plugintester):
-    plugintester.hookcheck(DoctestPlugin)
-

Deleted: /py/trunk/py/test/plugin/pytest_eventlog.py
==============================================================================
--- /py/trunk/py/test/plugin/pytest_eventlog.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,42 +0,0 @@
-import py
-
-class EventlogPlugin:
-    """ log pytest events to a file. """
-    def pytest_addoption(self, parser):
-        parser.addoption("--eventlog", dest="eventlog", 
-            help="write all pytest events to the given file.")
-
-    def pytest_configure(self, config):
-        eventlog = config.getvalue("eventlog")
-        if eventlog:
-            self.eventlogfile = open(eventlog, 'w')
-
-    def pytest_unconfigure(self, config):
-        if hasattr(self, 'eventlogfile'):
-            self.eventlogfile.close()
-            del self.eventlogfile
-
-    def pyevent(self, eventname, args, kwargs):
-        if hasattr(self, 'eventlogfile'):
-            f = self.eventlogfile
-            print >>f, eventname, args, kwargs
-            f.flush()
-
-# ===============================================================================
-# plugin tests 
-# ===============================================================================
-
- at py.test.mark.xfail
-def test_generic(plugintester):
-    plugintester.hookcheck(EventlogPlugin)
-
-    testdir = plugintester.testdir()
-    testdir.makepyfile("""
-        def test_pass():
-            pass
-    """)
-    testdir.runpytest("--eventlog=event.log")
-    s = testdir.tmpdir.join("event.log").read()
-    assert s.find("testrunstart") != -1
-    assert s.find("ItemTestReport") != -1
-    assert s.find("testrunfinish") != -1

Modified: py/trunk/py/test/plugin/pytest_execnetcleanup.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_execnetcleanup.py	(original)
+++ py/trunk/py/test/plugin/pytest_execnetcleanup.py	Tue Jun 16 18:23:18 2009
@@ -1,40 +1,36 @@
+"""
+cleanup gateways that were instantiated during a test function run. 
+"""
 import py
 
-class ExecnetcleanupPlugin:
-    _gateways = None
-    _debug = None
+def pytest_configure(config):
+    config.pluginmanager.register(Execnetcleanup())
 
-    def pytest_configure(self, config):
-        self._debug = config.option.debug
+class Execnetcleanup:
+    _gateways = None
+    def __init__(self, debug=False):
+        self._debug = debug 
 
-    def trace(self, msg, *args):
-        if self._debug:
-            print "[execnetcleanup %0x] %s %s" %(id(self), msg, args)
-        
     def pyexecnet_gateway_init(self, gateway):
-        self.trace("init", gateway)
         if self._gateways is not None:
             self._gateways.append(gateway)
         
     def pyexecnet_gateway_exit(self, gateway):
-        self.trace("exit", gateway)
         if self._gateways is not None:
             self._gateways.remove(gateway)
 
-    def pytest_testrunstart(self):
-        self.trace("testrunstart")
+    def pytest_sessionstart(self, session):
         self._gateways = []
 
-    def pytest_testrunfinish(self, exitstatus, excrepr=None):
-        self.trace("testrunfinish", exitstatus)
+    def pytest_sessionfinish(self, session, exitstatus, excrepr=None):
         l = []
         for gw in self._gateways:
             gw.exit()
             l.append(gw)
         #for gw in l:
         #    gw.join()
-        #
-    def pytest_pyfunc_call(self, __call__, pyfuncitem, args, kwargs):
+        
+    def pytest_pyfunc_call(self, __call__, pyfuncitem):
         if self._gateways is not None:
             gateways = self._gateways[:]
             res = __call__.execute(firstresult=True)
@@ -42,9 +38,6 @@
                 self._gateways[-1].exit()
             return res
    
-def test_generic(plugintester):
-    plugintester.hookcheck(ExecnetcleanupPlugin)
-
 @py.test.mark.xfail("clarify plugin registration/unregistration")
 def test_execnetplugin(testdir):
     p = ExecnetcleanupPlugin()

Modified: py/trunk/py/test/plugin/pytest_figleaf.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_figleaf.py	(original)
+++ py/trunk/py/test/plugin/pytest_figleaf.py	Tue Jun 16 18:23:18 2009
@@ -1,65 +1,58 @@
+"""
+write and report coverage data using the 'figleaf' module. 
+"""
 import py
 
-class FigleafPlugin:
-    def pytest_addoption(self, parser):
-        group = parser.addgroup('figleaf options')
-        group.addoption('-F', action='store_true', default=False,
-                dest = 'figleaf',
-                help=('trace coverage with figleaf and write HTML '
-                     'for files below the current working dir'))
-        group.addoption('--figleaf-data', action='store', default='.figleaf',
-                dest='figleafdata',
-                help='path coverage tracing file.')
-        group.addoption('--figleaf-html', action='store', default='html',
-                dest='figleafhtml', 
-                help='path to the coverage html dir.')
-
-    def pytest_configure(self, config):
-        if config.getvalue('figleaf'):
-            try:
-                import figleaf
-                import figleaf.annotate_html
-            except ImportError:
-                raise config.Error('Could not import figleaf module')
-            self.figleaf = figleaf
-            self.figleaf.start()
-
-    def pytest_terminal_summary(self, terminalreporter):
-        if hasattr(self, 'figleaf'):
-            config = terminalreporter.config
-            datafile = py.path.local(config.getvalue('figleafdata'))
-            tw = terminalreporter._tw
-            tw.sep('-', 'figleaf')
-            tw.line('Writing figleaf data to %s' % (datafile))
-            self.figleaf.stop()
-            self.figleaf.write_coverage(str(datafile))
-            coverage = self.get_coverage(datafile, config)
-
-            reportdir = py.path.local(config.getvalue('figleafhtml'))
-            tw.line('Writing figleaf html to file://%s' % (reportdir))
-            self.figleaf.annotate_html.prepare_reportdir(str(reportdir))
-            exclude = []
-            self.figleaf.annotate_html.report_as_html(coverage, 
-                    str(reportdir), exclude, {})
-
-    def get_coverage(self, datafile, config):
-        # basepath = config.topdir
-        basepath = py.path.local()
-        data = self.figleaf.read_coverage(str(datafile))
-        d = {}
-        coverage = self.figleaf.combine_coverage(d, data)
-        for path in coverage.keys():
-            if not py.path.local(path).relto(basepath):
-                del coverage[path]
-        return coverage
+figleaf = py.test.importorskip("figleaf")
+import figleaf.annotate_html
 
+def pytest_addoption(parser):
+    group = parser.addgroup('figleaf options')
+    group.addoption('-F', action='store_true', default=False,
+            dest = 'figleaf',
+            help=('trace coverage with figleaf and write HTML '
+                 'for files below the current working dir'))
+    group.addoption('--figleaf-data', action='store', default='.figleaf',
+            dest='figleafdata',
+            help='path coverage tracing file.')
+    group.addoption('--figleaf-html', action='store', default='html',
+            dest='figleafhtml', 
+            help='path to the coverage html dir.')
+
+def pytest_configure(config):
+    figleaf.start()
+
+def pytest_terminal_summary(terminalreporter):
+    config = terminalreporter.config
+    datafile = py.path.local(config.getvalue('figleafdata'))
+    tw = terminalreporter._tw
+    tw.sep('-', 'figleaf')
+    tw.line('Writing figleaf data to %s' % (datafile))
+    figleaf.stop()
+    figleaf.write_coverage(str(datafile))
+    coverage = get_coverage(datafile, config)
+    reportdir = py.path.local(config.getvalue('figleafhtml'))
+    tw.line('Writing figleaf html to file://%s' % (reportdir))
+    figleaf.annotate_html.prepare_reportdir(str(reportdir))
+    exclude = []
+    figleaf.annotate_html.report_as_html(coverage, 
+            str(reportdir), exclude, {})
+
+def get_coverage(datafile, config):
+    # basepath = config.topdir
+    basepath = py.path.local()
+    data = figleaf.read_coverage(str(datafile))
+    d = {}
+    coverage = figleaf.combine_coverage(d, data)
+    for path in coverage.keys():
+        if not py.path.local(path).relto(basepath):
+            del coverage[path]
+    return coverage
 
-def test_generic(plugintester):
-    plugintester.hookcheck(FigleafPlugin)
 
 def test_functional(testdir):
     py.test.importorskip("figleaf")
-    testdir.plugins.append('figleaf')
+    testdir.plugins.append("figleaf")
     testdir.makepyfile("""
         def f():    
             x = 42

Added: py/trunk/py/test/plugin/pytest_hooklog.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/pytest_hooklog.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,33 @@
+""" log calling of plugin hooks to a file. """ 
+import py
+
+def pytest_addoption(parser):
+    parser.addoption("--hooklog", dest="hooklog", default=None, 
+        help="write hook calls to the given file.")
+
+def pytest_configure(config):
+    hooklog = config.getvalue("hooklog")
+    if hooklog:
+        assert not config.pluginmanager.comregistry.logfile
+        config.pluginmanager.comregistry.logfile = open(hooklog, 'w')
+
+def pytest_unconfigure(config):
+    f = config.pluginmanager.comregistry.logfile
+    if f:
+        f.close()
+        config.pluginmanager.comregistry.logfile = None
+
+# ===============================================================================
+# plugin tests 
+# ===============================================================================
+
+def test_functional(testdir):
+    testdir.makepyfile("""
+        def test_pass():
+            pass
+    """)
+    testdir.runpytest("--hooklog=hook.log")
+    s = testdir.tmpdir.join("hook.log").read()
+    assert s.find("pytest_sessionstart") != -1
+    assert s.find("ItemTestReport") != -1
+    assert s.find("sessionfinish") != -1

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 Jun 16 18:23:18 2009
@@ -1,51 +1,64 @@
+"""
+'capsys' and 'capfd' funcargs for capturing stdout/stderror either
+by intercepting sys.stdout/stderr or File Descriptors 1/2. 
+
+Calling the reset() method of the capture funcargs gives 
+a out/err tuple of strings representing the captured streams. 
+You can call reset() multiple times each time getting
+the chunk of output that was captured between the invocations. 
+
+"""
 import py
 
-class IocapturePlugin:
-    """ capture sys.stdout/sys.stderr / fd1/fd2. """
-    def pytest_funcarg__stdcapture(self, request):
-        capture = Capture(py.io.StdCapture)
-        request.addfinalizer(capture.finalize)
-        return capture 
-
-    def pytest_funcarg__stdcapturefd(self, request):
-        capture = Capture(py.io.StdCaptureFD)
-        request.addfinalizer(capture.finalize)
-        return capture 
+def pytest_funcarg__capsys(request):
+    """ capture writes to sys.stdout/sys.stderr. """ 
+    capture = Capture(py.io.StdCapture)
+    request.addfinalizer(capture.finalize)
+    return capture 
+
+def pytest_funcarg__capfd(request):
+    """ capture writes to filedescriptors 1 and 2"""
+    capture = Capture(py.io.StdCaptureFD)
+    request.addfinalizer(capture.finalize)
+    return capture 
+
+def pytest_pyfunc_call(pyfuncitem):
+    for funcarg, value in pyfuncitem.funcargs.items():
+        if funcarg == "capsys" or funcarg == "capfd":
+            value.reset()
 
 class Capture:
+    _capture = None
     def __init__(self, captureclass):
         self._captureclass = captureclass
-        self._capture = self._captureclass()
 
     def finalize(self):
-        self._capture.reset()
+        if self._capture:
+            self._capture.reset()
 
     def reset(self):
-        res = self._capture.reset()
+        res = None
+        if self._capture:
+            res = self._capture.reset()
         self._capture = self._captureclass()
         return res 
 
-def test_generic(plugintester):
-    plugintester.hookcheck(IocapturePlugin)
-
 class TestCapture:
     def test_std_functional(self, testdir):        
-        testdir.plugins.append(IocapturePlugin())
-        evrec = testdir.inline_runsource("""
-            def test_hello(stdcapture):
+        reprec = testdir.inline_runsource("""
+            def test_hello(capsys):
                 print 42
-                out, err = stdcapture.reset()
+                out, err = capsys.reset()
                 assert out.startswith("42")
         """)
-        evrec.assertoutcome(passed=1)
+        reprec.assertoutcome(passed=1)
         
     def test_stdfd_functional(self, testdir):        
-        testdir.plugins.append(IocapturePlugin())
-        evrec = testdir.inline_runsource("""
-            def test_hello(stdcapturefd):
+        reprec = testdir.inline_runsource("""
+            def test_hello(capfd):
                 import os
                 os.write(1, "42")
-                out, err = stdcapturefd.reset()
+                out, err = capfd.reset()
                 assert out.startswith("42")
         """)
-        evrec.assertoutcome(passed=1)
+        reprec.assertoutcome(passed=1)

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 Jun 16 18:23:18 2009
@@ -1,11 +1,20 @@
+"""
+    "monkeypatch" funcarg for safely patching objects, 
+    dictionaries and environment variables during the execution of 
+    a test.  "monkeypatch" has three helper functions: 
+
+        monkeypatch.setattr(obj, name, value) 
+        monkeypatch.setitem(obj, name, value) 
+        monkeypatch.setenv(name, value) 
+
+    After the test has run modifications will be undone. 
+"""
 import os
 
-class MonkeypatchPlugin:
-    """ setattr-monkeypatching with automatical reversal after test. """
-    def pytest_funcarg__monkeypatch(self, request):
-        monkeypatch = MonkeyPatch()
-        request.addfinalizer(monkeypatch.finalize)
-        return monkeypatch
+def pytest_funcarg__monkeypatch(request):
+    monkeypatch = MonkeyPatch()
+    request.addfinalizer(monkeypatch.finalize)
+    return monkeypatch
 
 notset = object()
 
@@ -77,11 +86,11 @@
     assert 'XYZ123' not in os.environ
 
 def test_monkeypatch_plugin(testdir):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         pytest_plugins = 'pytest_monkeypatch', 
         def test_method(monkeypatch):
             assert monkeypatch.__class__.__name__ == "MonkeyPatch"
     """)
-    res = sorter.countoutcomes()
+    res = reprec.countoutcomes()
     assert tuple(res) == (1, 0, 0), res
         

Modified: py/trunk/py/test/plugin/pytest_pdb.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_pdb.py	(original)
+++ py/trunk/py/test/plugin/pytest_pdb.py	Tue Jun 16 18:23:18 2009
@@ -1,9 +1,155 @@
-from py.__.test.custompdb import post_mortem
+"""
+interactive debugging with a PDB prompt. 
 
-class PdbPlugin:
-    def pytest_item_runtest_finished(self, item, excinfo, outerr):
-        if excinfo and item.config.option.usepdb:
+"""
+import py
+import pdb, sys, linecache
+from py.__.test.outcome import Skipped
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general") 
+    group._addoption('--pdb',
+               action="store_true", dest="usepdb", default=False,
+               help="start pdb (the Python debugger) on errors.")
+
+
+def pytest_configure(config):
+    if config.option.usepdb:
+        if config.getvalue("looponfail"):
+            raise config.Error("--pdb incompatible with --looponfail.")
+        if config.option.dist != "no":
+            raise config.Error("--pdb incomptaible with distributing tests.")
+        config.pluginmanager.register(PdbInvoke())
+
+class PdbInvoke:
+    def pytest_runtest_makereport(self, item, call):
+        if call.excinfo and not call.excinfo.errisinstance(Skipped): 
             tw = py.io.TerminalWriter()
-            repr = excinfo.getrepr()
+            repr = call.excinfo.getrepr()
             repr.toterminal(tw) 
-            post_mortem(excinfo._excinfo[2])
+            post_mortem(call.excinfo._excinfo[2])
+
+class Pdb(py.std.pdb.Pdb):
+    def do_list(self, arg):
+        self.lastcmd = 'list'
+        last = None
+        if arg:
+            try:
+                x = eval(arg, {}, {})
+                if type(x) == type(()):
+                    first, last = x
+                    first = int(first)
+                    last = int(last)
+                    if last < first:
+                        # Assume it's a count
+                        last = first + last
+                else:
+                    first = max(1, int(x) - 5)
+            except:
+                print '*** Error in argument:', repr(arg)
+                return
+        elif self.lineno is None:
+            first = max(1, self.curframe.f_lineno - 5)
+        else:
+            first = self.lineno + 1
+        if last is None:
+            last = first + 10
+        filename = self.curframe.f_code.co_filename
+        breaklist = self.get_file_breaks(filename)
+        try:
+            for lineno in range(first, last+1):
+                # start difference from normal do_line
+                line = self._getline(filename, lineno)
+                # end difference from normal do_line
+                if not line:
+                    print '[EOF]'
+                    break
+                else:
+                    s = repr(lineno).rjust(3)
+                    if len(s) < 4: s = s + ' '
+                    if lineno in breaklist: s = s + 'B'
+                    else: s = s + ' '
+                    if lineno == self.curframe.f_lineno:
+                        s = s + '->'
+                    print s + '\t' + line,
+                    self.lineno = lineno
+        except KeyboardInterrupt:
+            pass
+    do_l = do_list
+
+    def _getline(self, filename, lineno):
+        if hasattr(filename, "__source__"):
+            try:
+                return filename.__source__.lines[lineno - 1] + "\n"
+            except IndexError:
+                return None
+        return linecache.getline(filename, lineno)
+
+    def get_stack(self, f, t):
+        # Modified from bdb.py to be able to walk the stack beyond generators,
+        # which does not work in the normal pdb :-(
+        stack, i = pdb.Pdb.get_stack(self, f, t)
+        if f is None:
+            i = max(0, len(stack) - 1)
+        return stack, i
+
+def post_mortem(t):
+    # modified from pdb.py for the new get_stack() implementation
+    p = Pdb()
+    p.reset()
+    p.interaction(None, t)
+
+def set_trace():
+    # again, a copy of the version in pdb.py
+    Pdb().set_trace(sys._getframe().f_back)
+
+
+class TestPDB: 
+    def pytest_funcarg__pdblist(self, request):
+        monkeypatch = request.getfuncargvalue("monkeypatch")
+        pdblist = []
+        def mypdb(*args):
+            pdblist.append(args)
+        monkeypatch.setitem(globals(), 'post_mortem', mypdb)
+        return pdblist 
+
+    def test_incompatibility_messages(self, testdir):
+        Error = py.test.config.Error
+        py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')")
+        py.test.raises(Error, "testdir.parseconfigure('--pdb', '-n 3')")
+        py.test.raises(Error, "testdir.parseconfigure('--pdb', '-d')")
+         
+    def test_pdb_on_fail(self, testdir, pdblist):
+        rep = testdir.inline_runsource1('--pdb', """
+            def test_func(): 
+                assert 0
+        """)
+        assert rep.failed
+        assert len(pdblist) == 1
+        tb = py.code.Traceback(pdblist[0][0])
+        assert tb[-1].name == "test_func"
+
+    def test_pdb_on_skip(self, testdir, pdblist):
+        rep = testdir.inline_runsource1('--pdb', """
+            import py
+            def test_func():
+                py.test.skip("hello")
+        """)
+        assert rep.skipped 
+        assert len(pdblist) == 0
+
+    def test_pdb_interaction(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_1():
+                i = 0
+                assert i == 1
+        """)
+        child = testdir.spawn_pytest("--pdb %s" % p1)
+        #child.expect(".*def test_1.*")
+        child.expect(".*i = 0.*")
+        child.expect("(Pdb)")
+        child.sendeof()
+        child.expect("1 failed")
+        if child.isalive(): 
+            child.wait()
+

Deleted: /py/trunk/py/test/plugin/pytest_plugintester.py
==============================================================================
--- /py/trunk/py/test/plugin/pytest_plugintester.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,87 +0,0 @@
-"""
-plugin with support classes and functions for testing pytest functionality 
-"""
-import py
-from py.__.test.plugin import api
-
-class PlugintesterPlugin:
-    """ test support code for testing pytest plugins. """
-    def pytest_funcarg__plugintester(self, request):
-        return PluginTester(request) 
-
-class PluginTester:
-    def __init__(self, request):
-        self.request = request
-
-    def testdir(self):
-        from pytest_pytester import TmpTestdir
-        crunner = TmpTestdir(self.request)
-        self.request.addfinalizer(crunner.finalize)
-        # 
-        #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 hookcheck(self, pluginclass):
-        print "loading and checking", pluginclass 
-        fail = False 
-        pm = py.test._PluginManager()
-        methods = collectattr(pluginclass)
-        hooks = collectattr(api.PluginHooks)
-        getargs = py.std.inspect.getargs
-
-        def isgenerichook(name):
-            return name.startswith("pytest_funcarg__")
-
-        while methods:
-            name, method = methods.popitem()
-            if isgenerichook(name):
-                continue
-            if name not in hooks:
-                print "found unknown hook: %s" % name 
-                fail = True
-            else:
-                hook = hooks[name]
-                if not hasattr(hook, 'func_code'):
-                    continue # XXX do some checks on attributes as well? 
-                method_args = getargs(method.func_code) 
-                if '__call__' in method_args[0]:
-                    method_args[0].remove('__call__')
-                hookargs = getargs(hook.func_code)
-                for arg, hookarg in zip(method_args[0], hookargs[0]):
-                    if arg != hookarg: 
-                        print "argument mismatch:" 
-                        print "actual  : %s.%s" %(pluginclass.__name__, formatdef(method))
-                        print "required:", formatdef(hook)
-                        fail = True
-                        break 
-                if not fail:
-                    print "matching hook:", formatdef(method)
-        if fail:
-            py.test.fail("Plugin API error")
-
-def collectattr(obj, prefixes=("pytest_",)):
-    methods = {}
-    for apiname in vars(obj): 
-        for prefix in prefixes:
-            if apiname.startswith(prefix):
-                methods[apiname] = getattr(obj, apiname) 
-    return methods 
-
-def formatdef(func):
-    formatargspec = py.std.inspect.formatargspec
-    getargspec = py.std.inspect.formatargspec
-    return "%s%s" %(
-        func.func_name, 
-        py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
-    )
-
-# ===============================================================================
-# plugin tests 
-# ===============================================================================
-
-def test_generic(plugintester):
-    plugintester.hookcheck(PlugintesterPlugin)

Modified: py/trunk/py/test/plugin/pytest_pocoo.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_pocoo.py	(original)
+++ py/trunk/py/test/plugin/pytest_pocoo.py	Tue Jun 16 18:23:18 2009
@@ -8,49 +8,43 @@
     xmlrpc = base + "/xmlrpc/"
     show = base + "/show/"
 
-class PocooPlugin:
-    """ report URLs from sending test failures to the pocoo paste service. """
+def pytest_addoption(parser):
+    group = parser.addgroup("pocoo plugin") 
+    group.addoption('-P', '--pocoo-sendfailures', 
+        action='store_true', dest="pocoo_sendfailures", 
+        help="send failures to %s paste service" %(url.base,))
+
+def getproxy():
+    return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes
+
+def pytest_terminal_summary(terminalreporter):
+    if terminalreporter.config.option.pocoo_sendfailures:
+        tr = terminalreporter
+        if 'failed' in tr.stats and tr.config.option.tbstyle != "no":
+            terminalreporter.write_sep("=", "Sending failures to %s" %(url.base,))
+            terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
+            #print self.__class__.getproxy
+            #print self.__class__, id(self.__class__)
+            serverproxy = getproxy()
+            for ev in terminalreporter.stats.get('failed'):
+                tw = py.io.TerminalWriter(stringio=True)
+                ev.toterminal(tw)
+                s = tw.stringio.getvalue()
+                # XXX add failure summary 
+                assert len(s)
+                terminalreporter.write_line("newpaste() ...")
+                proxyid = serverproxy.newPaste("python", s)
+                terminalreporter.write_line("%s%s\n" % (url.show, proxyid))
+                break
 
-    def pytest_addoption(self, parser):
-        parser.addoption('-P', '--pocoo-sendfailures', 
-            action='store_true', dest="pocoo_sendfailures", 
-            help="send failures to %s" %(url.base,))
-
-    def getproxy(self):
-        return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes
-
-    def pytest_terminal_summary(self, terminalreporter):
-        if terminalreporter.config.option.pocoo_sendfailures:
-            tr = terminalreporter
-            if 'failed' in tr.stats and tr.config.option.tbstyle != "no":
-                terminalreporter.write_sep("=", "Sending failures to %s" %(url.base,))
-                terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
-                print self.__class__.getproxy
-                print self.__class__, id(self.__class__)
-                serverproxy = self.getproxy()
-                for ev in terminalreporter.stats.get('failed'):
-                    tw = py.io.TerminalWriter(stringio=True)
-                    ev.toterminal(tw)
-                    s = tw.stringio.getvalue()
-                    # XXX add failure summary 
-                    assert len(s)
-                    terminalreporter.write_line("newpaste() ...")
-                    proxyid = serverproxy.newPaste("python", s)
-                    terminalreporter.write_line("%s%s\n" % (url.show, proxyid))
-                    break
-
-
-def test_apicheck(plugintester):
-    plugintester.hookcheck(PocooPlugin)
 
 def test_toproxy(testdir, monkeypatch):
     l = []
     class MockProxy:
         def newPaste(self, language, code):
             l.append((language, code))
-    monkeypatch.setattr(PocooPlugin, 'getproxy', MockProxy) 
-    testdir.plugins.insert(0, PocooPlugin())
-    testdir.chdir()
+    monkeypatch.setitem(globals(), 'getproxy', MockProxy) 
+    testdir.plugins.insert(0, globals())
     testpath = testdir.makepyfile("""
         import py
         def test_pass():
@@ -60,10 +54,10 @@
         def test_skip():
             py.test.skip("")
     """)
-    evrec = testdir.inline_run(testpath, "-P")
+    reprec = testdir.inline_run(testpath, "-P")
     assert len(l) == 1
     assert l[0][0] == "python"
     s = l[0][1]
     assert s.find("def test_fail") != -1
-    assert evrec.countoutcomes() == [1,1,1]
+    assert reprec.countoutcomes() == [1,1,1]
      

Modified: py/trunk/py/test/plugin/pytest_pylint.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_pylint.py	(original)
+++ py/trunk/py/test/plugin/pytest_pylint.py	Tue Jun 16 18:23:18 2009
@@ -1,36 +1,24 @@
 """pylint plugin
 
-
 XXX: Currently in progress, NOT IN WORKING STATE.
 """
 import py
 
-class PylintPlugin:
-    def pytest_addoption(self, parser):
-        group = parser.addgroup('pylint options')
-        group.addoption('--pylint', action='store_true',
-                        default=False, dest='pylint',
-                        help='Pylint coverate of test files.')
-
-    def pytest_configure(self, config):
-        if config.getvalue('pylint'):
-            try:
-                from pylint import lint
-                self.lint = lint
-            except ImportError:
-                raise config.Error('Could not import pylint module')
-            print "trying to configure pytest"
-
-    def pytest_collect_file(self, path, parent):
-        if path.ext == ".py":
-            if parent.config.getvalue('pylint'):
-                return PylintItem(path, parent, self.lint)
-
-    def pytest_terminal_summary(self, terminalreporter):
-        if hasattr(self, 'lint'):
-            print 'placeholder for pylint output'
+lint = py.test.importorskip("pylint") 
 
+def pytest_addoption(parser):
+    group = parser.addgroup('pylint options')
+    group.addoption('--pylint', action='store_true',
+                    default=False, dest='pylint',
+                    help='Pylint coverate of test files.')
+
+def pytest_collect_file(path, parent):
+    if path.ext == ".py":
+        if parent.config.getvalue('pylint'):
+            return PylintItem(path, parent, self.lint)
 
+def pytest_terminal_summary(terminalreporter):
+    print 'placeholder for pylint output'
 
 class PylintItem(py.test.collect.Item):
     def __init__(self, path, parent, lintlib):
@@ -50,8 +38,4 @@
         print ">>>",
         print rating
 
-def test_generic(plugintester):
-    plugintester.hookcheck(PylintPlugin)
-
-#def test_functional <pull from figleaf plugin>
 

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 Jun 16 18:23:18 2009
@@ -1,34 +1,29 @@
 """
-pytes plugin for easing testing of pytest runs themselves. 
+funcargs and support code for testing py.test functionality. 
 """
 
 import py
 import os
 import inspect
-from py.__.test import runner
 from py.__.test.config import Config as pytestConfig
-from pytest__pytest import CallRecorder
-import api
+import hookspec
 
+pytest_plugins = '_pytest'
 
-class PytesterPlugin:
-    def pytest_funcarg__linecomp(self, request):
-        return LineComp()
-
-    def pytest_funcarg__LineMatcher(self, request):
-        return LineMatcher
-
-    def pytest_funcarg__testdir(self, request):
-        tmptestdir = TmpTestdir(request)
-        return tmptestdir
- 
-    def pytest_funcarg__eventrecorder(self, request):
-        evrec = EventRecorder(py._com.comregistry)
-        request.addfinalizer(lambda: evrec.comregistry.unregister(evrec))
-        return evrec
+def pytest_funcarg__linecomp(request):
+    return LineComp()
 
-def test_generic(plugintester):
-    plugintester.hookcheck(PytesterPlugin)
+def pytest_funcarg__LineMatcher(request):
+    return LineMatcher
+
+def pytest_funcarg__testdir(request):
+    tmptestdir = TmpTestdir(request)
+    return tmptestdir
+
+def pytest_funcarg__reportrecorder(request):
+    reprec = ReportRecorder(py._com.comregistry)
+    request.addfinalizer(lambda: reprec.comregistry.unregister(reprec))
+    return reprec
 
 class RunResult:
     def __init__(self, ret, outlines, errlines):
@@ -41,6 +36,7 @@
 class TmpTestdir:
     def __init__(self, request):
         self.request = request
+        self._pytest = request.getfuncargvalue("_pytest")
         # XXX remove duplication with tmpdir plugin 
         basetmp = request.config.ensuretemp("testdir")
         name = request.function.__name__
@@ -74,13 +70,22 @@
         if hasattr(self, '_olddir'):
             self._olddir.chdir()
 
-    def geteventrecorder(self, registry):
-        sorter = EventRecorder(registry)
-        sorter.callrecorder = CallRecorder(registry)
-        sorter.callrecorder.start_recording(api.PluginHooks)
-        sorter.hook = sorter.callrecorder.hook
-        self.request.addfinalizer(sorter.callrecorder.finalize)
-        return sorter
+    def getreportrecorder(self, obj):
+        if isinstance(obj, py._com.Registry):
+            registry = obj
+        elif hasattr(obj, 'comregistry'):
+            registry = obj.comregistry
+        elif hasattr(obj, 'pluginmanager'):
+            registry = obj.pluginmanager.comregistry
+        elif hasattr(obj, 'config'):
+            registry = obj.config.pluginmanager.comregistry
+        else:
+            raise ValueError("obj %r provides no comregistry" %(obj,))
+        assert isinstance(registry, py._com.Registry)
+        reprec = ReportRecorder(registry)
+        reprec.hookrecorder = self._pytest.gethookrecorder(hookspec, registry)
+        reprec.hook = reprec.hookrecorder.hook
+        return reprec
 
     def chdir(self):
         old = self.tmpdir.chdir()
@@ -131,32 +136,42 @@
         #config = self.parseconfig(*args)
         config = self.parseconfig(*args)
         session = config.initsession()
-        rec = self.geteventrecorder(config.pluginmanager)
+        rec = self.getreportrecorder(config)
         colitems = [config.getfsnode(arg) for arg in config.args]
         items = list(session.genitems(colitems))
         return items, rec 
 
-    def runitem(self, source, **runnerargs):
+    def runitem(self, source):
         # used from runner functional tests 
         item = self.getitem(source)
         # the test class where we are called from wants to provide the runner 
         testclassinstance = self.request.function.im_self
         runner = testclassinstance.getrunner()
-        return runner(item, **runnerargs)
+        return runner(item)
 
     def inline_runsource(self, source, *cmdlineargs):
         p = self.makepyfile(source)
         l = list(cmdlineargs) + [p]
         return self.inline_run(*l)
 
+    def inline_runsource1(self, *args):
+        args = list(args)
+        source = args.pop()
+        p = self.makepyfile(source)
+        l = list(args) + [p]
+        reprec = self.inline_run(*l)
+        reports = reprec.getreports("pytest_runtest_logreport")
+        assert len(reports) == 1, reports 
+        return reports[0]
+
     def inline_run(self, *args):
         config = self.parseconfig(*args)
         config.pluginmanager.do_configure(config)
         session = config.initsession()
-        sorter = self.geteventrecorder(config.pluginmanager)
+        reprec = self.getreportrecorder(config)
         session.main()
         config.pluginmanager.do_unconfigure(config)
-        return sorter 
+        return reprec 
 
     def config_preparse(self):
         config = self.Config()
@@ -164,7 +179,11 @@
             if isinstance(plugin, str):
                 config.pluginmanager.import_plugin(plugin)
             else:
-                config.pluginmanager.register(plugin)
+                if isinstance(plugin, dict):
+                    plugin = PseudoPlugin(plugin) 
+                if not config.pluginmanager.isregistered(plugin):
+                    config.pluginmanager.register(plugin)
+        #print "config.pluginmanager.impname2plugin", config.pluginmanager.impname2plugin
         return config
 
     def parseconfig(self, *args):
@@ -207,6 +226,12 @@
             self.makepyfile(__init__ = "#")
         self.config = self.parseconfig(path, *configargs)
         self.session = self.config.initsession()
+        #self.config.pluginmanager.do_configure(config=self.config)
+        # XXX 
+        self.config.pluginmanager.import_plugin("runner") 
+        plugin = self.config.pluginmanager.getplugin("runner") 
+        plugin.pytest_configure(config=self.config)
+
         return self.config.getfsnode(path)
 
     def prepare(self):
@@ -244,8 +269,12 @@
         p1 = py.path.local("stdout")
         p2 = py.path.local("stderr")
         print "running", cmdargs, "curdir=", py.path.local()
-        popen = self.popen(cmdargs, stdout=p1.open("w"), stderr=p2.open("w"))
+        f1 = p1.open("w")
+        f2 = p2.open("w")
+        popen = self.popen(cmdargs, stdout=f1, stderr=f2, close_fds=True)
         ret = popen.wait()
+        f1.close()
+        f2.close()
         out, err = p1.readlines(cr=0), p2.readlines(cr=0)
         if err:
             for line in err: 
@@ -280,58 +309,36 @@
         child.timeout = expect_timeout
         return child
 
-class Event:
-    def __init__(self, name, args, kwargs):
-        self.name = name
-        self.args = args
-        self.kwargs = kwargs
-    def __repr__(self):
-        return "<Event %r %r>" %(self.name, self.args)
-
-class ParsedCall:
-    def __init__(self, locals):
-        self.__dict__ = locals.copy()
-
-    def __repr__(self):
-        return "<ParsedCall %r>" %(self.__dict__,)
+class PseudoPlugin:
+    def __init__(self, vars):
+        self.__dict__.update(vars) 
 
-class EventRecorder(object):
-    def __init__(self, comregistry, debug=False): # True):
+class ReportRecorder(object):
+    def __init__(self, comregistry):
         self.comregistry = comregistry
-        self.debug = debug
         comregistry.register(self)
 
     def getcall(self, name):
-        l = self.getcalls(name)
-        assert len(l) == 1, (name, l)
-        return l[0]
+        return self.hookrecorder.getcall(name)
+
+    def popcall(self, name):
+        return self.hookrecorder.popcall(name)
 
-    def getcalls(self, *names):
+    def getcalls(self, names):
         """ return list of ParsedCall instances matching the given eventname. """
-        if len(names) == 1 and isinstance(names, str):
-            names = names.split()
-        l = []
-        for name in names:
-            if self.callrecorder.recordsmethod("pytest_" + name):
-                name = "pytest_" + name
-            if self.callrecorder.recordsmethod(name):
-                l.extend(self.callrecorder.getcalls(name))
-        return l
+        return self.hookrecorder.getcalls(names)
 
     # functionality for test reports 
 
-    def getreports(self, names="itemtestreport collectreport"):
-        names = [("pytest_" + x) for x in names.split()]
-        l = []
-        for call in self.getcalls(*names):
-            l.append(call.rep)
-        return l 
+    def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
+        return [x.rep for x in self.getcalls(names)]
 
-    def matchreport(self, inamepart="", names="itemtestreport collectreport"):
+    def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport"):
         """ return a testreport whose dotted import path matches """
         l = []
         for rep in self.getreports(names=names):
-            if not inamepart or inamepart in rep.colitem.listnames():
+            colitem = rep.getnode()
+            if not inamepart or inamepart in colitem.listnames():
                 l.append(rep)
         if not l:
             raise ValueError("could not find test report matching %r: no test reports at all!" %
@@ -341,23 +348,20 @@
                              inamepart, l))
         return l[0]
 
-    def getfailures(self, names='itemtestreport collectreport'):
-        l = []
-        for call in self.getcalls(*names.split()):
-            if call.rep.failed:
-                l.append(call.rep)
-        return l
+    def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'):
+        return [rep for rep in self.getreports(names) if rep.failed]
 
     def getfailedcollections(self):
-        return self.getfailures('collectreport')
+        return self.getfailures('pytest_collectreport')
 
     def listoutcomes(self):
         passed = []
         skipped = []
         failed = []
-        for rep in self.getreports("itemtestreport"):
+        for rep in self.getreports("pytest_runtest_logreport"):
             if rep.passed: 
-                passed.append(rep) 
+                if rep.when == "call": 
+                    passed.append(rep) 
             elif rep.skipped: 
                 skipped.append(rep) 
             elif rep.failed:
@@ -374,34 +378,46 @@
         assert failed == len(realfailed)
 
     def clear(self):
-        self.callrecorder.calls[:] = []
+        self.hookrecorder.calls[:] = []
 
     def unregister(self):
         self.comregistry.unregister(self)
-        self.callrecorder.finalize()
+        self.hookrecorder.finish_recording()
 
-def test_eventrecorder(testdir):
+def test_reportrecorder(testdir):
     registry = py._com.Registry()
-    recorder = testdir.geteventrecorder(registry)
+    recorder = testdir.getreportrecorder(registry)
     assert not recorder.getfailures()
-    rep = runner.ItemTestReport(None, None)
-    rep.passed = False
-    rep.failed = True
-    recorder.hook.pytest_itemtestreport(rep=rep)
+    item = testdir.getitem("def test_func(): pass")
+    class rep:
+        excinfo = None
+        passed = False
+        failed = True 
+        skipped = False
+        when = "call" 
+
+    recorder.hook.pytest_runtest_logreport(rep=rep)
     failures = recorder.getfailures()
     assert failures == [rep]
     failures = recorder.getfailures()
     assert failures == [rep]
 
-    rep = runner.ItemTestReport(None, None)
+    class rep:
+        excinfo = None
+        passed = False
+        failed = False
+        skipped = True
+        when = "call" 
     rep.passed = False
     rep.skipped = True
-    recorder.hook.pytest_itemtestreport(rep=rep)
+    recorder.hook.pytest_runtest_logreport(rep=rep)
 
-    rep = runner.CollectReport(None, None)
+    modcol = testdir.getmodulecol("")
+    rep = modcol.config.hook.pytest_make_collect_report(collector=modcol)
     rep.passed = False
     rep.failed = True
-    recorder.hook.pytest_itemtestreport(rep=rep)
+    rep.skipped = False
+    recorder.hook.pytest_collectreport(rep=rep)
 
     passed, skipped, failed = recorder.listoutcomes()
     assert not passed and skipped and failed
@@ -409,13 +425,13 @@
     numpassed, numskipped, numfailed = recorder.countoutcomes()
     assert numpassed == 0
     assert numskipped == 1
-    assert numfailed == 2
+    assert numfailed == 1
+    assert len(recorder.getfailedcollections()) == 1
 
     recorder.unregister()
     recorder.clear() 
-    assert not recorder.getfailures()
-    recorder.hook.pytest_itemtestreport(rep=rep)
-    assert not recorder.getfailures()
+    recorder.hook.pytest_runtest_logreport(rep=rep)
+    py.test.raises(ValueError, "recorder.getfailures()")
 
 class LineComp:
     def __init__(self):
@@ -473,11 +489,19 @@
         extralines.extend(lines1)
         return extralines 
 
-
-
-
 def test_parseconfig(testdir):
     config1 = testdir.parseconfig()
     config2 = testdir.parseconfig()
     assert config2 != config1
     assert config1 != py.test.config
+
+def test_testdir_runs_with_plugin(testdir):
+    testdir.makepyfile("""
+        pytest_plugins = "pytest_pytester" 
+        def test_hello(testdir):
+            assert 1
+    """)
+    result = testdir.runpytest()
+    assert result.stdout.fnmatch_lines([
+        "*1 passed*"
+    ])

Modified: py/trunk/py/test/plugin/pytest_recwarn.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_recwarn.py	(original)
+++ py/trunk/py/test/plugin/pytest_recwarn.py	Tue Jun 16 18:23:18 2009
@@ -1,18 +1,14 @@
 """
-
-provides "recwarn" funcarg for asserting warnings to be shown 
-to a user.  See the test at the bottom for an example. 
-
+"recwarn" funcarg for asserting that warnings are shown to a user.  
 """
 import py
 import os
 
-class RecwarnPlugin:
-    def pytest_funcarg__recwarn(self, request):
-        """ check that warnings have been raised. """ 
-        warnings = WarningsRecorder()
-        request.addfinalizer(warnings.finalize)
-        return warnings
+def pytest_funcarg__recwarn(request):
+    """ check that warnings have been raised. """ 
+    warnings = WarningsRecorder()
+    request.addfinalizer(warnings.finalize)
+    return warnings
 
 class RecordedWarning:
     def __init__(self, message, category, filename, lineno, line):
@@ -77,7 +73,7 @@
     assert showwarning == py.std.warnings.showwarning
 
 def test_recwarn_functional(testdir):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         pytest_plugins = 'pytest_recwarn', 
         import warnings
         oldwarn = warnings.showwarning
@@ -89,6 +85,6 @@
         def test_finalized():
             assert warnings.showwarning == oldwarn
     """)
-    res = sorter.countoutcomes()
+    res = reprec.countoutcomes()
     assert tuple(res) == (2, 0, 0), res
         

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 Jun 16 18:23:18 2009
@@ -1,23 +1,26 @@
+"""
+perform ReST specific tests on .txt files, including
+linkchecks and remote URL checks. 
+"""
 import py
 
-class RestdocPlugin:
-    def pytest_addoption(self, parser):
-        group = parser.addgroup("ReST", "ReST documentation check options")
-        group.addoption('-R', '--urlcheck',
-               action="store_true", dest="urlcheck", default=False, 
-               help="urlopen() remote links found in ReST text files.") 
-        group.addoption('--urltimeout', action="store", metavar="secs",
-            type="int", dest="urlcheck_timeout", default=5,
-            help="timeout in seconds for remote urlchecks")
-        group.addoption('--forcegen',
-               action="store_true", dest="forcegen", default=False,
-               help="force generation of html files.")
-
-    def pytest_collect_file(self, path, parent):
-        if path.ext == ".txt":
-            project = getproject(path)
-            if project is not None:
-                return ReSTFile(path, parent=parent, project=project)
+def pytest_addoption(parser):
+    group = parser.addgroup("ReST", "ReST documentation check options")
+    group.addoption('-R', '--urlcheck',
+           action="store_true", dest="urlcheck", default=False, 
+           help="urlopen() remote links found in ReST text files.") 
+    group.addoption('--urltimeout', action="store", metavar="secs",
+        type="int", dest="urlcheck_timeout", default=5,
+        help="timeout in seconds for remote urlchecks")
+    group.addoption('--forcegen',
+           action="store_true", dest="forcegen", default=False,
+           help="force generation of html files.")
+
+def pytest_collect_file(path, parent):
+    if path.ext == ".txt":
+        project = getproject(path)
+        if project is not None:
+            return ReSTFile(path, parent=parent, project=project)
 
 def getproject(path):
     for parent in path.parts(reverse=True):
@@ -64,7 +67,7 @@
         super(ReSTSyntaxTest, self).__init__(*args, **kwargs)
         self.project = project
 
-    def metainfo(self):
+    def reportinfo(self):
         return self.fspath, None, "syntax check"
 
     def runtest(self):
@@ -196,7 +199,7 @@
                     #return [] # no need to rebuild 
 
 class DoctestText(py.test.collect.Item): 
-    def metainfo(self):
+    def reportinfo(self):
         return self.fspath, None, "doctest"
 
     def runtest(self): 
@@ -268,7 +271,7 @@
                 if tryfn.startswith('http:') or tryfn.startswith('https'): 
                     if self.config.getvalue("urlcheck"):
                         yield CheckLink(name, parent=self, 
-                            args=(tryfn, path, lineno, timeout), callobj=urlcheck)
+                            args=(tryfn, path, lineno, timeout), checkfunc=urlcheck)
                 elif tryfn.startswith('webcal:'):
                     continue
                 else: 
@@ -279,16 +282,19 @@
                         checkfn = tryfn 
                     if checkfn.strip() and (1 or checkfn.endswith('.html')): 
                         yield CheckLink(name, parent=self, 
-                            args=(tryfn, path, lineno), callobj=localrefcheck)
+                            args=(tryfn, path, lineno), checkfunc=localrefcheck)
         
-class CheckLink(py.test.collect.Function): 
-    def metainfo(self, basedir=None):
-        return (self.fspath, self._args[2], "checklink: %s" % self._args[0])
-
-    def setup(self): 
-        pass 
-    def teardown(self): 
-        pass 
+class CheckLink(py.test.collect.Item):
+    def __init__(self, name, parent, args, checkfunc):
+        super(CheckLink, self).__init__(name, parent)
+        self.args = args
+        self.checkfunc = checkfunc
+
+    def runtest(self):
+        return self.checkfunc(*self.args)
+
+    def reportinfo(self, basedir=None):
+        return (self.fspath, self.args[2], "checklink: %s" % self.args[0])
 
 def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN): 
     old = py.std.socket.getdefaulttimeout()
@@ -345,8 +351,6 @@
 #
 # PLUGIN tests
 #
-def test_generic(plugintester):
-    plugintester.hookcheck(RestdocPlugin)
 
 def test_deindent():
     assert deindent('foo') == 'foo'
@@ -388,26 +392,26 @@
                        "resolve_linkrole('source', 'py/foo/bar.py')")
 
 
-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:
+    def pytest_funcarg__testdir(self, request):
+        testdir = request.getfuncargvalue("testdir")
+        assert request.module.__name__ == __name__
+        testdir.makepyfile(confrest="from py.__.misc.rest import Project")
+        for p in testdir.plugins:
+            if p == globals():
+                break
+        else:
+            testdir.plugins.append(globals())
+        return testdir
+    
     def test_doctest_extra_exec(self, testdir):
         xtxt = testdir.maketxtfile(x="""
             hello::
                 .. >>> raise ValueError 
                    >>> None
         """)
-        sorter = testdir.inline_run(xtxt)
-        passed, skipped, failed = sorter.countoutcomes()
+        reprec = testdir.inline_run(xtxt)
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 1
 
     def test_doctest_basic(self, testdir): 
@@ -429,23 +433,23 @@
 
             end
         """)
-        sorter = testdir.inline_run(xtxt)
-        passed, skipped, failed = sorter.countoutcomes()
+        reprec = testdir.inline_run(xtxt)
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 0 
         assert passed + skipped == 2
 
     def test_doctest_eol(self, testdir): 
         ytxt = testdir.maketxtfile(y=".. >>> 1 + 1\r\n   2\r\n\r\n")
-        sorter = testdir.inline_run(ytxt)
-        passed, skipped, failed = sorter.countoutcomes()
+        reprec = testdir.inline_run(ytxt)
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 0 
         assert passed + skipped == 2
 
     def test_doctest_indentation(self, testdir):
         footxt = testdir.maketxtfile(foo=
             '..\n  >>> print "foo\\n  bar"\n  foo\n    bar\n')
-        sorter = testdir.inline_run(footxt)
-        passed, skipped, failed = sorter.countoutcomes()
+        reprec = testdir.inline_run(footxt)
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 0
         assert skipped + passed == 2 
 
@@ -455,8 +459,8 @@
 
             .. _`blah`: javascript:some_function()
         """)
-        sorter = testdir.inline_run(xtxt)
-        passed, skipped, failed = sorter.countoutcomes()
+        reprec = testdir.inline_run(xtxt)
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 0
         assert skipped + passed == 3 
 
@@ -476,9 +480,9 @@
                 False
 
         """)
-        sorter = testdir.inline_run(xtxt)
+        reprec = testdir.inline_run(xtxt)
         assert len(l) == 1
-        passed, skipped, failed = sorter.countoutcomes()
+        passed, skipped, failed = reprec.countoutcomes()
         assert passed >= 1
         assert not failed 
         assert skipped <= 1

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 Jun 16 18:23:18 2009
@@ -1,54 +1,51 @@
-import py
-from pytest_resultlog import ResultLog
+"""XXX in progress: resultdb plugin for database logging of test results. 
 
-class ResultdbPlugin:
-    """XXX in progress: resultdb plugin for database logging of test results. 
+Saves test results to a datastore.
 
-    Saves test results to a datastore.
+XXX this needs to be merged with resultlog plugin
 
-    XXX this needs to be merged with resultlog plugin
+Also mixes in some early ideas about an archive abstraction for test 
+results.
+""" 
+import py
 
-    Also mixes in some early ideas about an archive abstraction for test 
-    results.
-    """ 
-    def pytest_addoption(self, parser):
-        group = parser.addgroup("resultdb", "resultdb plugin options")
-        group.addoption('--resultdb', action="store", dest="resultdb", 
-                metavar="path",
-                help="path to the file to store test results.")
-        group.addoption('--resultdb_format', action="store", 
-                dest="resultdbformat", default='json',
-                help="data format (json, sqlite)")
-    
-    def pytest_configure(self, config):
-        if config.getvalue('resultdb'):
-            if config.option.resultdb:
-                # local import so missing module won't crash py.test
-                try:
-                    import sqlite3
-                except ImportError:
-                    raise config.Error('Could not import sqlite3 module')
-                try:
-                    import simplejson
-                except ImportError:
-                    raise config.Error('Could not import simplejson module')
-                if config.option.resultdbformat.lower() == 'json':
-                    self.resultdb = ResultDB(JSONResultArchive, 
-                            config.option.resultdb) 
-                elif config.option.resultdbformat.lower() == 'sqlite':
-                    self.resultdb = ResultDB(SQLiteResultArchive, 
-                            config.option.resultdb) 
-                else:
-                    raise config.Error('Unknown --resultdb_format: %s' % 
-                            config.option.resultdbformat) 
-
-                config.pluginmanager.register(self.resultdb)
-
-    def pytest_unconfigure(self, config):
-        if hasattr(self, 'resultdb'):
-            del self.resultdb 
-            #config.pluginmanager.unregister(self.resultdb)
+py.test.skip("XXX needs to be merged with resultlog")
+
+from pytest_resultlog import ResultLog
+
+def pytest_addoption(parser):
+    group = parser.addgroup("resultdb", "resultdb plugin options")
+    group.addoption('--resultdb', action="store", dest="resultdb", 
+            metavar="path",
+            help="path to the file to store test results.")
+    group.addoption('--resultdb_format', action="store", 
+            dest="resultdbformat", default='json',
+            help="data format (json, sqlite)")
+
+def pytest_configure(config):
+    # XXX using config.XYZ is not good 
+    if config.getvalue('resultdb'):
+        if config.option.resultdb:
+            # local import so missing module won't crash py.test
+            try:
+                import sqlite3
+            except ImportError:
+                raise config.Error('Could not import sqlite3 module')
+            try:
+                import simplejson
+            except ImportError:
+                raise config.Error('Could not import simplejson module')
+            if config.option.resultdbformat.lower() == 'json':
+                resultdb = ResultDB(JSONResultArchive, 
+                        config.option.resultdb) 
+            elif config.option.resultdbformat.lower() == 'sqlite':
+                resultdb = ResultDB(SQLiteResultArchive, 
+                        config.option.resultdb) 
+            else:
+                raise config.Error('Unknown --resultdb_format: %s' % 
+                        config.option.resultdbformat) 
 
+            config.pluginmanager.register(resultdb)
 
 class JSONResultArchive(object):
     def __init__(self, archive_path):
@@ -303,9 +300,8 @@
         archive.init_db()
         return archive
         
-    def test_collection_report(self, plugintester):
+    def test_collection_report(self, testdir):
         py.test.skip("Needs a rewrite for db version.")
-        testdir = plugintester.testdir()
         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")
@@ -328,8 +324,7 @@
             assert x.startswith(" ")
         assert "XXX" in "".join(lines[1:])
 
-    def test_log_test_outcomes(self, plugintester):
-        testdir = plugintester.testdir()
+    def test_log_test_outcomes(self, testdir):
         mod = testdir.makepyfile(test_mod="""
             import py 
             def test_pass(): pass
@@ -364,9 +359,7 @@
         assert entry_lines[-1][0] == ' '
         assert 'ValueError' in entry  
 
-def test_generic(plugintester):
-    plugintester.hookcheck(ResultdbPlugin)
-    testdir = plugintester.testdir()
+def test_generic(testdir):
     testdir.makepyfile("""
         import py
         def test_pass():

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 Jun 16 18:23:18 2009
@@ -1,26 +1,27 @@
+"""resultlog plugin for machine-readable logging of test results. 
+   Useful for buildbot integration code. 
+""" 
+
 import py
 
-class ResultlogPlugin:
-    """resultlog plugin for machine-readable logging of test results. 
-       Useful for buildbot integration code. 
-    """ 
-    def pytest_addoption(self, parser):
-        group = parser.addgroup("resultlog", "resultlog plugin options")
-        group.addoption('--resultlog', action="store", dest="resultlog", metavar="path",
-               help="path for machine-readable result log.")
-    
-    def pytest_configure(self, config):
-        resultlog = config.option.resultlog
-        if resultlog:
-            logfile = open(resultlog, 'w', 1) # line buffered
-            self.resultlog = ResultLog(logfile) 
-            config.pluginmanager.register(self.resultlog)
-
-    def pytest_unconfigure(self, config):
-        if hasattr(self, 'resultlog'):
-            self.resultlog.logfile.close()
-            del self.resultlog 
-            #config.pluginmanager.unregister(self.resultlog)
+def pytest_addoption(parser):
+    group = parser.addgroup("resultlog", "resultlog plugin options")
+    group.addoption('--resultlog', action="store", dest="resultlog", metavar="path",
+           help="path for machine-readable result log.")
+
+def pytest_configure(config):
+    resultlog = config.option.resultlog
+    if resultlog:
+        logfile = open(resultlog, 'w', 1) # line buffered
+        config._resultlog = ResultLog(logfile) 
+        config.pluginmanager.register(config._resultlog)
+
+def pytest_unconfigure(config):
+    resultlog = getattr(config, '_resultlog', None)
+    if resultlog:
+        resultlog.logfile.close()
+        del config._resultlog 
+        config.pluginmanager.unregister(resultlog)
 
 def generic_path(item):
     chain = item.listchain()
@@ -54,11 +55,11 @@
         for line in longrepr.splitlines():
             print >>self.logfile, " %s" % line
 
-    def log_outcome(self, event, shortrepr, longrepr):
-        testpath = generic_path(event.colitem)
+    def log_outcome(self, node, shortrepr, longrepr):
+        testpath = generic_path(node)
         self.write_log_entry(testpath, shortrepr, longrepr) 
 
-    def pytest_itemtestreport(self, rep):
+    def pytest_runtest_logreport(self, rep):
         code = rep.shortrepr 
         if rep.passed:
             longrepr = ""
@@ -66,7 +67,7 @@
             longrepr = str(rep.longrepr) 
         elif rep.skipped:
             longrepr = str(rep.longrepr.reprcrash.message)
-        self.log_outcome(rep, code, longrepr) 
+        self.log_outcome(rep.item, code, longrepr) 
 
     def pytest_collectreport(self, rep):
         if not rep.passed:
@@ -76,7 +77,7 @@
                 assert rep.skipped
                 code = "S"
             longrepr = str(rep.longrepr.reprcrash)
-            self.log_outcome(rep, code, longrepr)    
+            self.log_outcome(rep.collector, code, longrepr)    
 
     def pytest_internalerror(self, excrepr):
         path = excrepr.reprcrash.path 
@@ -93,7 +94,7 @@
 
 def test_generic_path():
     from py.__.test.collect import Node, Item, FSCollector
-    p1 = Node('a', config='dummy')
+    p1 = Node('a')
     assert p1.fspath is None
     p2 = Node('B', parent=p1)
     p3 = Node('()', parent = p2)
@@ -102,7 +103,7 @@
     res = generic_path(item)
     assert res == 'a.B().c'
 
-    p0 = FSCollector('proj/test', config='dummy')
+    p0 = FSCollector('proj/test')
     p1 = FSCollector('proj/test/a', parent=p0)
     p2 = Node('B', parent=p1)
     p3 = Node('()', parent = p2)
@@ -162,8 +163,7 @@
         testdir.runpytest(*args)
         return filter(None, resultlog.readlines(cr=0))
         
-    def test_collection_report(self, plugintester):
-        testdir = plugintester.testdir()
+    def test_collection_report(self, testdir):
         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")
@@ -185,8 +185,7 @@
             assert x.startswith(" ")
         assert "XXX" in "".join(lines[1:])
 
-    def test_log_test_outcomes(self, plugintester):
-        testdir = plugintester.testdir()
+    def test_log_test_outcomes(self, testdir):
         mod = testdir.makepyfile(test_mod="""
             import py 
             def test_pass(): pass
@@ -223,9 +222,7 @@
         assert entry_lines[-1][0] == ' '
         assert 'ValueError' in entry  
 
-def test_generic(plugintester, LineMatcher):
-    plugintester.hookcheck(ResultlogPlugin)
-    testdir = plugintester.testdir()
+def test_generic(testdir, LineMatcher):
     testdir.plugins.append("resultlog")
     testdir.makepyfile("""
         import py

Modified: py/trunk/py/test/plugin/pytest_runner.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_runner.py	(original)
+++ py/trunk/py/test/plugin/pytest_runner.py	Tue Jun 16 18:23:18 2009
@@ -1,31 +1,132 @@
+""" 
+    collect and run test items. 
+
+    * executing test items 
+    * running collectors 
+    * and generating report events about it 
+"""
+
 import py
+
 from py.__.test.outcome import Skipped
 
-class RunnerPlugin:
-    def pytest_configure(self, config):
-        config._setupstate = SetupState()
-
-    def pytest_unconfigure(self, config):
-        config._setupstate.teardown_all()
-
-    def pytest_item_setup_and_runtest(self, item):
-        setupstate = item.config._setupstate
-        call = item.config.guardedcall(lambda: setupstate.prepare(item))
-        if call.excinfo:
-            rep = ItemSetupReport(item, call.excinfo, call.outerr)
-            item.config.hook.pytest_itemsetupreport(rep=rep)
-        else:
-            call = item.config.guardedcall(lambda: item.runtest())
-            item.config.hook.pytest_item_runtest_finished(
-                item=item, excinfo=call.excinfo, outerr=call.outerr)
-            call = item.config.guardedcall(lambda: self.teardown_exact(item))
-            if call.excinfo:
-                rep = ItemSetupReport(item, call.excinfo, call.outerr)
-                item.config.hook.pytest_itemsetupreport(rep=rep)
-
-    def pytest_collector_collect(self, collector):
-        call = item.config.guardedcall(lambda x: collector._memocollect())
-        return CollectReport(collector, res, excinfo, outerr)
+#
+# pytest plugin hooks 
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general") 
+    group.addoption('--boxed',
+               action="store_true", dest="boxed", default=False,
+               help="box each test run in a separate process") 
+
+def pytest_configure(config):
+    config._setupstate = SetupState()
+
+def pytest_unconfigure(config):
+    config._setupstate.teardown_all()
+
+def pytest_make_collect_report(collector):
+    call = collector.config.guardedcall(
+        lambda: collector._memocollect()
+    )
+    result = None
+    if not call.excinfo:
+        result = call.result
+    return CollectReport(collector, result, call.excinfo, call.outerr)
+
+    return report 
+
+def pytest_runtest_protocol(item):
+    if item.config.getvalue("boxed"):
+        reports = forked_run_report(item) 
+        for rep in reports:
+            item.config.hook.pytest_runtest_logreport(rep=rep)
+    else:
+        runtestprotocol(item)
+    return True
+
+def runtestprotocol(item, log=True):
+    rep = call_and_report(item, "setup", log)
+    reports = [rep]
+    if rep.passed:
+        reports.append(call_and_report(item, "call", log))
+        reports.append(call_and_report(item, "teardown", log))
+    return reports
+
+def pytest_runtest_setup(item):
+    item.config._setupstate.prepare(item)
+
+def pytest_runtest_call(item):
+    if not item._deprecated_testexecution():
+        item.runtest()
+
+def pytest_runtest_makereport(item, call):
+    return ItemTestReport(item, call.excinfo, call.when, call.outerr)
+
+def pytest_runtest_teardown(item):
+    item.config._setupstate.teardown_exact(item)
+
+#
+# Implementation
+
+def call_and_report(item, when, log=True):
+    call = RuntestHookCall(item, when)
+    hook = item.config.hook
+    report = hook.pytest_runtest_makereport(item=item, call=call)
+    if log and (when == "call" or not report.passed):
+        hook.pytest_runtest_logreport(rep=report) 
+    return report
+
+
+class RuntestHookCall:
+    excinfo = None 
+    _prefix = "pytest_runtest_"
+    def __init__(self, item, when):
+        self.when = when 
+        hookname = self._prefix + when 
+        hook = getattr(item.config.hook, hookname)
+        capture = item.config._getcapture()
+        try:
+            try:
+                self.result = hook(item=item)
+            except KeyboardInterrupt:
+                raise
+            except:
+                self.excinfo = py.code.ExceptionInfo()
+        finally:
+            self.outerr = capture.reset()
+
+def forked_run_report(item):
+    # for now, we run setup/teardown in the subprocess 
+    # XXX optionally allow sharing of setup/teardown 
+    EXITSTATUS_TESTEXIT = 4
+    from py.__.test.dist.mypickle import ImmutablePickler
+    ipickle = ImmutablePickler(uneven=0)
+    ipickle.selfmemoize(item.config)
+    # XXX workaround the issue that 2.6 cannot pickle 
+    # instances of classes defined in global conftest.py files
+    ipickle.selfmemoize(item) 
+    def runforked():
+        try:
+            reports = runtestprotocol(item, log=False)
+        except KeyboardInterrupt: 
+            py.std.os._exit(EXITSTATUS_TESTEXIT)
+        return ipickle.dumps(reports)
+
+    ff = py.process.ForkedFunc(runforked)
+    result = ff.waitfinish()
+    if result.retval is not None:
+        return ipickle.loads(result.retval)
+    else:
+        if result.exitstatus == EXITSTATUS_TESTEXIT:
+            py.test.exit("forked test item %s raised Exit" %(item,))
+        return [report_process_crash(item, result)]
+
+def report_process_crash(item, result):
+    path, lineno = item._getfslineno()
+    info = "%s:%s: running the test CRASHED with signal %d" %(
+            path, lineno, result.signal)
+    return ItemTestReport(item, excinfo=info, when="???")
 
 class BaseReport(object):
     def __repr__(self):
@@ -43,10 +144,12 @@
 class ItemTestReport(BaseReport):
     failed = passed = skipped = False
 
-    def __init__(self, colitem, excinfo=None, when=None, outerr=None):
-        self.colitem = colitem 
-        if colitem and when != "setup":
-            self.keywords = colitem.readkeywords() 
+    def __init__(self, item, excinfo=None, when=None, outerr=None):
+        self.item = item 
+        self.when = when
+        self.outerr = outerr
+        if item and when != "setup":
+            self.keywords = item.readkeywords() 
         else:
             # if we fail during setup it might mean 
             # we are not able to access the underlying object
@@ -58,7 +161,6 @@
             self.passed = True
             self.shortrepr = "." 
         else:
-            self.when = when 
             if not isinstance(excinfo, py.code.ExceptionInfo):
                 self.failed = True
                 shortrepr = "?"
@@ -66,65 +168,79 @@
             elif excinfo.errisinstance(Skipped):
                 self.skipped = True 
                 shortrepr = "s"
-                longrepr = self.colitem._repr_failure_py(excinfo, outerr)
+                longrepr = self.item._repr_failure_py(excinfo, outerr)
             else:
                 self.failed = True
-                shortrepr = self.colitem.shortfailurerepr
-                if self.when == "execute":
-                    longrepr = self.colitem.repr_failure(excinfo, outerr)
+                shortrepr = self.item.shortfailurerepr
+                if self.when == "call":
+                    longrepr = self.item.repr_failure(excinfo, outerr)
                 else: # exception in setup or teardown 
-                    longrepr = self.colitem._repr_failure_py(excinfo, outerr)
+                    longrepr = self.item._repr_failure_py(excinfo, outerr)
                     shortrepr = shortrepr.lower()
             self.shortrepr = shortrepr 
             self.longrepr = longrepr 
-            
+
+    def getnode(self):
+        return self.item 
 
 class CollectReport(BaseReport):
     skipped = failed = passed = False 
 
-    def __init__(self, colitem, result, excinfo=None, outerr=None):
-        # XXX rename to collector 
-        self.colitem = colitem 
+    def __init__(self, collector, result, excinfo=None, outerr=None):
+        self.collector = collector 
         if not excinfo:
             self.passed = True
             self.result = result 
         else:
             self.outerr = outerr
-            self.longrepr = self.colitem._repr_failure_py(excinfo, outerr)
+            self.longrepr = self.collector._repr_failure_py(excinfo, outerr)
             if excinfo.errisinstance(Skipped):
                 self.skipped = True
                 self.reason = str(excinfo.value)
             else:
                 self.failed = True
 
-class ItemSetupReport(BaseReport):
-    failed = passed = skipped = False
-    def __init__(self, item, excinfo=None, outerr=None):
-        self.item = item 
-        self.outerr = outerr 
-        if not excinfo:
-            self.passed = True
-        else:
-            if excinfo.errisinstance(Skipped):
-                self.skipped = True 
-            else:
-                self.failed = True
-            self.excrepr = item._repr_failure_py(excinfo, [])
+    def getnode(self):
+        return self.collector 
 
 class SetupState(object):
     """ shared state for setting up/tearing down test items or collectors. """
     def __init__(self):
         self.stack = []
+        self._finalizers = {}
+
+    def addfinalizer(self, finalizer, colitem):
+        """ attach a finalizer to the given colitem. 
+        if colitem is None, this will add a finalizer that 
+        is called at the end of teardown_all(). 
+        """
+        assert callable(finalizer)
+        #assert colitem in self.stack
+        self._finalizers.setdefault(colitem, []).append(finalizer)
+
+    def _pop_and_teardown(self):
+        colitem = self.stack.pop()
+        self._teardown_with_finalization(colitem)
+
+    def _teardown_with_finalization(self, colitem): 
+        finalizers = self._finalizers.pop(colitem, None)
+        while finalizers:
+            fin = finalizers.pop()
+            fin()
+        if colitem: 
+            colitem.teardown()
+        for colitem in self._finalizers:
+            assert colitem is None or colitem in self.stack
 
     def teardown_all(self): 
         while self.stack: 
-            col = self.stack.pop() 
-            col.teardown() 
+            self._pop_and_teardown()
+        self._teardown_with_finalization(None)
+        assert not self._finalizers
 
     def teardown_exact(self, item):
-        if self.stack and self.stack[-1] == item:
-            col = self.stack.pop()
-            col.teardown()
+        assert self.stack and self.stack[-1] == item
+        self._pop_and_teardown()
      
     def prepare(self, colitem): 
         """ setup objects along the collector chain to the test-method
@@ -133,140 +249,7 @@
         while self.stack: 
             if self.stack == needed_collectors[:len(self.stack)]: 
                 break 
-            col = self.stack.pop() 
-            col.teardown()
+            self._pop_and_teardown()
         for col in needed_collectors[len(self.stack):]: 
             col.setup() 
             self.stack.append(col) 
-
-# ===============================================================================
-#
-# plugin tests 
-#
-# ===============================================================================
-
-def test_generic(plugintester):
-    plugintester.hookcheck(RunnerPlugin())
-
-class TestSetupState:
-    def test_setup_prepare(self, testdir):
-        modcol = testdir.getmodulecol("""
-            def setup_function(function):
-                function.myfunc = function
-            def test_func1():
-                pass
-            def test_func2():
-                pass
-            def teardown_function(function):
-                del function.myfunc 
-        """)
-        item1, item2 = modcol.collect()
-        setup = SetupState()
-        setup.prepare(item1)
-        assert item1.obj.myfunc == item1.obj 
-        assert not hasattr(item2, 'myfunc')
-
-        setup.prepare(item2)
-        assert item2.obj.myfunc == item2.obj 
-        assert not hasattr(item1, 'myfunc')
-
-    def test_setup_teardown_exact(self, testdir):
-        item = testdir.getitem("""
-            def test_func():
-                pass
-            def teardown_function(function):
-                function.tear = "down"
-        """)
-        setup = SetupState()
-        setup.prepare(item)
-        setup.teardown_exact(item)
-        assert item.obj.tear == "down"
-
-    def test_setup_teardown_exact(self, testdir):
-        item = testdir.getitem("""
-            def setup_module(mod):
-                mod.x = 1
-            def setup_function(function):
-                function.y = 2
-            def test_func():
-                pass
-            def teardown_function(function):
-                del function.y
-            def teardown_module(mod):
-                del mod.x
-        """)
-        setup = SetupState()
-        setup.prepare(item)
-        assert item.obj.y == 2
-        assert item.parent.obj.x == 1
-        setup.teardown_all()
-        assert not hasattr(item.obj, 'y')
-        assert not hasattr(item.parent.obj, 'x')
-
-class TestRunnerPlugin:
-    def test_pytest_item_setup_and_runtest(self, testdir):
-        item = testdir.getitem("""def test_func(): pass""")
-        plugin = RunnerPlugin()
-        plugin.pytest_configure(item.config)
-        sorter = testdir.geteventrecorder(item.config.pluginmanager)
-        plugin.pytest_item_setup_and_runtest(item)
-        rep = sorter.getcall("pytest_itemtestreport").rep
-        assert rep.passed 
-        
-class TestSetupEvents:
-    disabled = True
-
-    def test_setup_runtest_ok(self, testdir):
-        sorter = testdir.inline_runsource("""
-            def setup_module(mod):
-                pass 
-            def test_func():
-                pass
-        """)
-        assert not sorter.getcalls(pytest_itemsetupreport)
-
-    def test_setup_fails(self, testdir):
-        sorter = testdir.inline_runsource("""
-            def setup_module(mod):
-                print "world"
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        rep = sorter.popcall(pytest_itemsetupreport).rep
-        assert rep.failed
-        assert not rep.skipped
-        assert rep.excrepr 
-        assert "42" in str(rep.excrepr)
-        assert rep.outerr[0].find("world") != -1
-
-    def test_teardown_fails(self, testdir):
-        sorter = testdir.inline_runsource("""
-            def test_func():
-                pass
-            def teardown_function(func): 
-                print "13"
-                raise ValueError(25)
-        """)
-        rep = evrec.popcall(pytest_itemsetupreport).rep 
-        assert rep.failed 
-        assert rep.item == item 
-        assert not rep.passed
-        assert "13" in rep.outerr[0]
-        assert "25" in str(rep.excrepr)
-
-    def test_setup_skips(self, testdir):
-        sorter = testdir.inline_runsource("""
-            import py
-            def setup_module(mod):
-                py.test.skip("17")
-            def test_func():
-                pass
-        """)
-        rep = sorter.popcall(pytest_itemsetupreport)
-        assert not rep.failed
-        assert rep.skipped
-        assert rep.excrepr 
-        assert "17" in str(rep.excrepr)
-    
-

Modified: py/trunk/py/test/plugin/pytest_terminal.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_terminal.py	(original)
+++ py/trunk/py/test/plugin/pytest_terminal.py	Tue Jun 16 18:23:18 2009
@@ -1,21 +1,19 @@
 import py
 import sys
 
-class TerminalPlugin(object):
-    """ Report a test run to a terminal. """
-    def pytest_configure(self, config):
-        if config.option.collectonly:
-            self.reporter = CollectonlyReporter(config)
-        else:
-            self.reporter = TerminalReporter(config)
-        # XXX see remote.py's XXX 
-        for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
-            if hasattr(config, attr):
-                #print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
-                name = attr.split("_")[-1]
-                assert hasattr(self.reporter._tw, name), name
-                setattr(self.reporter._tw, name, getattr(config, attr))
-        config.pluginmanager.register(self.reporter)
+def pytest_configure(config):
+    if config.option.collectonly:
+        reporter = CollectonlyReporter(config)
+    else:
+        reporter = TerminalReporter(config)
+    # XXX see remote.py's XXX 
+    for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
+        if hasattr(config, attr):
+            #print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
+            name = attr.split("_")[-1]
+            assert hasattr(self.reporter._tw, name), name
+            setattr(reporter._tw, name, getattr(config, attr))
+    config.pluginmanager.register(reporter)
 
 class TerminalReporter:
     def __init__(self, config, file=None):
@@ -142,30 +140,31 @@
 
     def pytest_deselected(self, items):
         self.stats.setdefault('deselected', []).append(items)
-    
 
     def pytest_itemstart(self, item, node=None):
         if self.config.option.dist != "no":
             # for dist-testing situations itemstart means we 
             # queued the item for sending, not interesting (unless debugging) 
             if self.config.option.debug:
-                line = self._metainfoline(item)
+                line = self._reportinfoline(item)
                 extra = ""
                 if node:
                     extra = "-> " + str(node.gateway.id)
                 self.write_ensure_prefix(line, extra)
         else:
             if self.config.option.verbose:
-                line = self._metainfoline(item)
+                line = self._reportinfoline(item)
                 self.write_ensure_prefix(line, "") 
             else:
                 # ensure that the path is printed before the 
                 # 1st test of a module starts running
-                fspath, lineno, msg = item.metainfo()
+                fspath, lineno, msg = self._getreportinfo(item)
                 self.write_fspath_result(fspath, "")
 
-    def pytest_itemtestreport(self, rep):
-        fspath = rep.colitem.fspath 
+    def pytest_runtest_logreport(self, rep):
+        if rep.passed and rep.when in ("setup", "teardown"):
+            return 
+        fspath = rep.item.fspath 
         cat, letter, word = self.getcategoryletterword(rep)
         if isinstance(word, tuple):
             word, markup = word
@@ -173,10 +172,10 @@
             markup = {}
         self.stats.setdefault(cat, []).append(rep)
         if not self.config.option.verbose:
-            fspath, lineno, msg = rep.colitem.metainfo()
+            fspath, lineno, msg = self._getreportinfo(rep.item)
             self.write_fspath_result(fspath, letter)
         else:
-            line = self._metainfoline(rep.colitem)
+            line = self._reportinfoline(rep.item)
             if not hasattr(rep, 'node'):
                 self.write_ensure_prefix(line, word, **markup)
             else:
@@ -192,12 +191,12 @@
             if rep.failed:
                 self.stats.setdefault("failed", []).append(rep)
                 msg = rep.longrepr.reprcrash.message 
-                self.write_fspath_result(rep.colitem.fspath, "F")
+                self.write_fspath_result(rep.collector.fspath, "F")
             elif rep.skipped:
                 self.stats.setdefault("skipped", []).append(rep)
-                self.write_fspath_result(rep.colitem.fspath, "S")
+                self.write_fspath_result(rep.collector.fspath, "S")
 
-    def pytest_testrunstart(self):
+    def pytest_sessionstart(self, session):
         self.write_sep("=", "test session starts", bold=True)
         self._sessionstarttime = py.std.time.time()
 
@@ -228,7 +227,7 @@
         for i, testarg in py.builtin.enumerate(self.config.args):
             self.write_line("test object %d: %s" %(i+1, testarg))
 
-    def pytest_testrunfinish(self, exitstatus, excrepr=None):
+    def pytest_sessionfinish(self, session, exitstatus, excrepr=None):
         self._tw.line("")
         if exitstatus in (0, 1, 2):
             self.summary_failures()
@@ -254,8 +253,8 @@
         for rootdir in rootdirs:
             self.write_line("### Watching:   %s" %(rootdir,), bold=True)
 
-    def _metainfoline(self, item):
-        fspath, lineno, msg = item.metainfo()
+    def _reportinfoline(self, item):
+        fspath, lineno, msg = self._getreportinfo(item)
         if fspath:
             fspath = self.curdir.bestrelpath(fspath)
         if lineno is not None:
@@ -267,23 +266,41 @@
         elif fspath and lineno:
             line = "%(fspath)s:%(lineno)s"
         else:
-            line = "[nometainfo]"
+            line = "[noreportinfo]"
         return line % locals() + " "
+        
+    def _getfailureheadline(self, rep):
+        if hasattr(rep, "collector"):
+            return str(rep.collector.fspath)
+        else:
+            fspath, lineno, msg = self._getreportinfo(rep.item)
+            return msg
+
+    def _getreportinfo(self, item):
+        try:
+            return item.__reportinfo
+        except AttributeError:
+            pass
+        reportinfo = item.config.hook.pytest_report_iteminfo(item=item)
+        # cache on item
+        item.__reportinfo = reportinfo
+        return reportinfo
 
     #
-    # summaries for testrunfinish 
+    # summaries for sessionfinish 
     #
 
     def summary_failures(self):
         if 'failed' in self.stats and self.config.option.tbstyle != "no":
             self.write_sep("=", "FAILURES")
-            for ev in self.stats['failed']:
-                self.write_sep("_", "FAILURES")
-                if hasattr(ev, 'node'):
+            for rep in self.stats['failed']:
+                msg = self._getfailureheadline(rep)
+                self.write_sep("_", msg)
+                if hasattr(rep, 'node'):
                     self.write_line(self.gateway2info.get(
-                        ev.node.gateway, "node %r (platinfo not found? strange)")
+                        rep.node.gateway, "node %r (platinfo not found? strange)")
                             [:self._tw.fullwidth-1])
-                ev.toterminal(self._tw)
+                rep.toterminal(self._tw)
 
     def summary_stats(self):
         session_duration = py.std.time.time() - self._sessionstarttime
@@ -334,6 +351,10 @@
     def outindent(self, line):
         self.out.line(self.indent + str(line))
 
+    def pytest_internalerror(self, excrepr):
+        for line in str(excrepr).split("\n"):
+            self.out.line("INTERNALERROR> " + line)
+
     def pytest_collectstart(self, collector):
         self.outindent(collector)
         self.indent += self.INDENT 
@@ -347,7 +368,7 @@
             self._failed.append(rep)
         self.indent = self.indent[:-len(self.INDENT)]
 
-    def pytest_testrunfinish(self, exitstatus, excrepr=None):
+    def pytest_sessionfinish(self, session, exitstatus, excrepr=None):
         if self._failed:
             self.out.sep("!", "collection failures")
         for rep in self._failed:
@@ -378,10 +399,12 @@
 #
 # ===============================================================================
 
-from py.__.test import runner
+import pytest_runner as runner # XXX 
 
-class TestTerminal:
+def basic_run_report(item):
+    return runner.call_and_report(item, "call", log=False)
 
+class TestTerminal:
     def test_pass_skip_fail(self, testdir, linecomp):
         modcol = testdir.getmodulecol("""
             import py
@@ -394,15 +417,15 @@
         """)
         rep = TerminalReporter(modcol.config, file=linecomp.stringio)
         rep.config.pluginmanager.register(rep)
-        rep.config.hook.pytest_testrunstart()
-        
+        rep.config.hook.pytest_sessionstart(session=testdir.session)
+
         for item in testdir.genitems([modcol]):
-            ev = runner.basic_run_report(item) 
-            rep.config.hook.pytest_itemtestreport(rep=ev)
+            ev = basic_run_report(item) 
+            rep.config.hook.pytest_runtest_logreport(rep=ev)
         linecomp.assert_contains_lines([
                 "*test_pass_skip_fail.py .sF"
         ])
-        rep.config.hook.pytest_testrunfinish(exitstatus=1)
+        rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
         linecomp.assert_contains_lines([
             "    def test_func():",
             ">       assert 0",
@@ -421,21 +444,21 @@
         """, configargs=("-v",))
         rep = TerminalReporter(modcol.config, file=linecomp.stringio)
         rep.config.pluginmanager.register(rep)
-        rep.config.hook.pytest_testrunstart()
+        rep.config.hook.pytest_sessionstart(session=testdir.session)
         items = modcol.collect()
         rep.config.option.debug = True # 
         for item in items:
             rep.config.hook.pytest_itemstart(item=item, node=None)
             s = linecomp.stringio.getvalue().strip()
             assert s.endswith(item.name)
-            rep.config.hook.pytest_itemtestreport(rep=runner.basic_run_report(item))
+            rep.config.hook.pytest_runtest_logreport(rep=basic_run_report(item))
 
         linecomp.assert_contains_lines([
             "*test_pass_skip_fail_verbose.py:2: *test_ok*PASS*",
             "*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP*",
             "*test_pass_skip_fail_verbose.py:6: *test_func*FAIL*",
         ])
-        rep.config.hook.pytest_testrunfinish(exitstatus=1)
+        rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
         linecomp.assert_contains_lines([
             "    def test_func():",
             ">       assert 0",
@@ -446,13 +469,13 @@
         modcol = testdir.getmodulecol("import xyz")
         rep = TerminalReporter(modcol.config, file=linecomp.stringio)
         rep.config.pluginmanager.register(rep)
-        rep.config.hook.pytest_testrunstart()
+        rep.config.hook.pytest_sessionstart(session=testdir.session)
         l = list(testdir.genitems([modcol]))
         assert len(l) == 0
         linecomp.assert_contains_lines([
             "*test_collect_fail.py F*"
         ])
-        rep.config.hook.pytest_testrunfinish(exitstatus=1)
+        rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
         linecomp.assert_contains_lines([
             ">   import xyz",
             "E   ImportError: No module named xyz"
@@ -519,7 +542,7 @@
                 raise ValueError()
         """)
         rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        reports = [runner.basic_run_report(x) for x in modcol.collect()]
+        reports = [basic_run_report(x) for x in modcol.collect()]
         rep.pytest_looponfailinfo(reports, [modcol.config.topdir])
         linecomp.assert_contains_lines([
             "*test_looponfailreport.py:2: assert 0",
@@ -542,11 +565,11 @@
             """, configargs=("--tb=%s" % tbopt,))
             rep = TerminalReporter(modcol.config, file=linecomp.stringio)
             rep.config.pluginmanager.register(rep)
-            rep.config.hook.pytest_testrunstart()
+            rep.config.hook.pytest_sessionstart(session=testdir.session)
             for item in testdir.genitems([modcol]):
-                rep.config.hook.pytest_itemtestreport(
-                    rep=runner.basic_run_report(item))
-            rep.config.hook.pytest_testrunfinish(exitstatus=1)
+                rep.config.hook.pytest_runtest_logreport(
+                    rep=basic_run_report(item))
+            rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
             s = linecomp.stringio.getvalue()
             if tbopt == "long":
                 print s
@@ -571,11 +594,11 @@
             "*test_show_path_before_running_test.py*"
         ])
 
-    def test_itemreport_metainfo(self, testdir, linecomp):
+    def test_itemreport_reportinfo(self, testdir, linecomp):
         testdir.makeconftest("""
             import py
             class Function(py.test.collect.Function):
-                def metainfo(self):
+                def reportinfo(self):
                     return "ABCDE", 42, "custom"    
         """)
         item = testdir.getitem("def test_func(): pass")
@@ -591,6 +614,25 @@
             "*ABCDE:43: custom*"
         ])
 
+    def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
+        item = testdir.getitem("def test_func(): pass")
+        class Plugin:
+            def pytest_report_iteminfo(self, item):
+                return "FGHJ", 42, "custom"
+        item.config.pluginmanager.register(Plugin())             
+        tr = TerminalReporter(item.config, file=linecomp.stringio)
+        item.config.pluginmanager.register(tr)
+        tr.config.hook.pytest_itemstart(item=item)
+        linecomp.assert_contains_lines([
+            "*FGHJ "
+        ])
+        tr.config.option.verbose = True
+        tr.config.hook.pytest_itemstart(item=item)
+        linecomp.assert_contains_lines([
+            "*FGHJ:43: custom*"
+        ])
+
+
     def pseudo_keyboard_interrupt(self, testdir, linecomp, verbose=False):
         modcol = testdir.getmodulecol("""
             def test_foobar():
@@ -603,11 +645,11 @@
         #""", configargs=("--showskipsummary",) + ("-v",)*verbose)
         rep = TerminalReporter(modcol.config, file=linecomp.stringio)
         modcol.config.pluginmanager.register(rep)
-        modcol.config.hook.pytest_testrunstart()
+        modcol.config.hook.pytest_sessionstart(session=testdir.session)
         try:
             for item in testdir.genitems([modcol]):
-                modcol.config.hook.pytest_itemtestreport(
-                    rep=runner.basic_run_report(item))
+                modcol.config.hook.pytest_runtest_logreport(
+                    rep=basic_run_report(item))
         except KeyboardInterrupt:
             excinfo = py.code.ExceptionInfo()
         else:
@@ -615,7 +657,8 @@
         s = linecomp.stringio.getvalue()
         if not verbose:
             assert s.find("_keyboard_interrupt.py Fs") != -1
-        modcol.config.hook.pytest_testrunfinish(exitstatus=2, excrepr=excinfo.getrepr())
+        modcol.config.hook.pytest_sessionfinish(
+            session=testdir.session, exitstatus=2, excrepr=excinfo.getrepr())
         text = linecomp.stringio.getvalue()
         linecomp.assert_contains_lines([
             "    def test_foobar():",
@@ -705,13 +748,20 @@
               !!! ValueError: 0 !!!
         """)
 
+    def test_collectonly_fatal(self, testdir):
+        p1 = testdir.makeconftest("""
+            def pytest_collectstart(collector):
+                assert 0, "urgs" 
+        """)
+        result = testdir.runpytest("--collectonly") 
+        result.stdout.fnmatch_lines([
+            "*INTERNAL*args*"
+        ])
+        assert result.ret == 3
+
 def test_repr_python_version(monkeypatch):
     monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
     assert repr_pythonversion() == "2.5.1-final-0"
     py.std.sys.version_info = x = (2,3)
     assert repr_pythonversion() == str(x) 
 
-def test_generic(plugintester):
-    plugintester.hookcheck(TerminalPlugin)
-    plugintester.hookcheck(TerminalReporter)
-    plugintester.hookcheck(CollectonlyReporter)

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 Jun 16 18:23:18 2009
@@ -1,4 +1,6 @@
 """
+    provide temporary directories to test functions and methods. 
+
 example:
 
     pytest_plugins = "pytest_tmpdir" 
@@ -9,13 +11,9 @@
 """
 import py
 
-class TmpdirPlugin:
-    """ provide temporary directories to test functions and methods. 
-    """ 
-
-    def pytest_funcarg__tmpdir(self, request):
-        name = request.function.__name__ 
-        return request.config.mktemp(name, numbered=True)
+def pytest_funcarg__tmpdir(request):
+    name = request.function.__name__ 
+    return request.config.mktemp(name, numbered=True)
 
 # ===============================================================================
 #
@@ -23,14 +21,11 @@
 #
 # ===============================================================================
 #
-def test_generic(plugintester):
-    plugintester.hookcheck(TmpdirPlugin)
 
 def test_funcarg(testdir):
     from py.__.test.funcargs import FuncargRequest
     item = testdir.getitem("def test_func(tmpdir): pass")
-    plugin = TmpdirPlugin()
-    p = plugin.pytest_funcarg__tmpdir(FuncargRequest(item, "tmpdir"))
+    p = pytest_funcarg__tmpdir(FuncargRequest(item))
     assert p.check()
     bn = p.basename.strip("0123456789-")
     assert bn.endswith("test_func")

Modified: py/trunk/py/test/plugin/pytest_unittest.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_unittest.py	(original)
+++ py/trunk/py/test/plugin/pytest_unittest.py	Tue Jun 16 18:23:18 2009
@@ -1,5 +1,5 @@
 """
-automatically collect and run traditional "unittest.py" style tests. 
+automatically discover and run traditional "unittest.py" style tests. 
 
 you can mix unittest TestCase subclasses and 
 py.test style tests in one test module. 
@@ -10,17 +10,12 @@
 
     http://johnnydebris.net/svn/projects/py_unittest
 
-$HeadURL: https://codespeak.net/svn/py/branch/pytestplugin/contrib/py_unittest/conftest.py $
-$Id: conftest.py 60979 2009-01-14 22:29:32Z hpk $
 """
 import py
 
-class UnittestPlugin:
-    """ discover and integrate traditional ``unittest.py`` tests. 
-    """
-    def pytest_pycollect_obj(self, collector, name, obj):
-        if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
-            return UnitTestCase(name, parent=collector)
+def pytest_pycollect_makeitem(collector, name, obj):
+    if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
+        return UnitTestCase(name, parent=collector)
 
 class UnitTestCase(py.test.collect.Class):
     def collect(self):
@@ -70,9 +65,6 @@
         instance.tearDown()
 
 
-def test_generic(plugintester):
-    plugintester.hookcheck(UnittestPlugin)
-
 def test_simple_unittest(testdir):
     testpath = testdir.makepyfile("""
         import unittest
@@ -83,9 +75,9 @@
             def test_failing(self):
                 self.assertEquals('foo', 'bar')
     """)
-    sorter = testdir.inline_run(testpath)
-    assert sorter.matchreport("testpassing").passed
-    assert sorter.matchreport("test_failing").failed 
+    reprec = testdir.inline_run(testpath)
+    assert reprec.matchreport("testpassing").passed
+    assert reprec.matchreport("test_failing").failed 
 
 def test_setup(testdir):
     testpath = testdir.makepyfile(test_two="""
@@ -97,8 +89,8 @@
             def test_setUp(self):
                 self.assertEquals(1, self.foo)
     """)
-    sorter = testdir.inline_run(testpath)
-    rep = sorter.matchreport("test_setUp")
+    reprec = testdir.inline_run(testpath)
+    rep = reprec.matchreport("test_setUp")
     assert rep.passed
 
 def test_teardown(testdir):
@@ -115,8 +107,8 @@
             def test_check(self):
                 self.assertEquals(MyTestCase.l, [None])
     """)
-    sorter = testdir.inline_run(testpath)
-    passed, skipped, failed = sorter.countoutcomes()
+    reprec = testdir.inline_run(testpath)
+    passed, skipped, failed = reprec.countoutcomes()
     print "COUNTS", passed, skipped, failed
     assert failed == 0, failed
     assert passed == 2

Modified: py/trunk/py/test/plugin/pytest_xfail.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_xfail.py	(original)
+++ py/trunk/py/test/plugin/pytest_xfail.py	Tue Jun 16 18:23:18 2009
@@ -1,5 +1,8 @@
 """
-for marking and reporting "expected to fail" tests. 
+mark tests as expected-to-fail and report them separately. 
+
+example: 
+
     @py.test.mark.xfail("needs refactoring")
     def test_hello():
         ...
@@ -7,48 +10,47 @@
 """
 import py
 
-class XfailPlugin(object):
-    """ mark and report specially about "expected to fail" tests. """
-
-    def pytest_item_makereport(self, __call__, item, excinfo, when, outerr):
-        if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'):
-            if 'xfail' in item.obj.func_dict:
-                res = __call__.execute(firstresult=True)
-                if excinfo:
-                    res.skipped = True
-                    res.failed = res.passed = False
-                else:
-                    res.skipped = res.passed = False
-                    res.failed = True
-                return res 
-
-    def pytest_report_teststatus(self, rep):
-        """ return shortletter and verbose word. """
-        if 'xfail' in rep.keywords: 
-            if rep.skipped:
-                return "xfailed", "x", "xfail"
-            elif rep.failed:
-                return "xpassed", "P", "xpass" 
-
-    # a hook implemented called by the terminalreporter instance/plugin
-    def pytest_terminal_summary(self, terminalreporter):
-        tr = terminalreporter
-        xfailed = tr.stats.get("xfailed")
-        if xfailed:
-            tr.write_sep("_", "expected failures")
-            for event in xfailed:
-                entry = event.longrepr.reprcrash 
-                key = entry.path, entry.lineno, entry.message
-                reason = event.longrepr.reprcrash.message
-                modpath = event.colitem.getmodpath(includemodule=True)
-                #tr._tw.line("%s %s:%d: %s" %(modpath, entry.path, entry.lineno, entry.message))
-                tr._tw.line("%s %s:%d: " %(modpath, entry.path, entry.lineno))
-
-        xpassed = terminalreporter.stats.get("xpassed")
-        if xpassed:
-            tr.write_sep("_", "UNEXPECTEDLY PASSING TESTS")
-            for event in xpassed:
-                tr._tw.line("%s: xpassed" %(event.colitem,))
+def pytest_runtest_makereport(__call__, item, call):
+    if call.when != "call":
+        return
+    if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'):
+        if 'xfail' in item.obj.func_dict:
+            res = __call__.execute(firstresult=True)
+            if call.excinfo:
+                res.skipped = True
+                res.failed = res.passed = False
+            else:
+                res.skipped = res.passed = False
+                res.failed = True
+            return res 
+
+def pytest_report_teststatus(rep):
+    """ return shortletter and verbose word. """
+    if 'xfail' in rep.keywords: 
+        if rep.skipped:
+            return "xfailed", "x", "xfail"
+        elif rep.failed:
+            return "xpassed", "P", "xpass" 
+
+# called by the terminalreporter instance/plugin
+def pytest_terminal_summary(terminalreporter):
+    tr = terminalreporter
+    xfailed = tr.stats.get("xfailed")
+    if xfailed:
+        tr.write_sep("_", "expected failures")
+        for event in xfailed:
+            entry = event.longrepr.reprcrash 
+            key = entry.path, entry.lineno, entry.message
+            reason = event.longrepr.reprcrash.message
+            modpath = event.item.getmodpath(includemodule=True)
+            #tr._tw.line("%s %s:%d: %s" %(modpath, entry.path, entry.lineno, entry.message))
+            tr._tw.line("%s %s:%d: " %(modpath, entry.path, entry.lineno))
+
+    xpassed = terminalreporter.stats.get("xpassed")
+    if xpassed:
+        tr.write_sep("_", "UNEXPECTEDLY PASSING TESTS")
+        for event in xpassed:
+            tr._tw.line("%s: xpassed" %(event.item,))
 
 # ===============================================================================
 #
@@ -56,11 +58,8 @@
 #
 # ===============================================================================
 
-def test_generic(plugintester):
-    plugintester.hookcheck(XfailPlugin) 
                
-def test_xfail(plugintester, linecomp):
-    testdir = plugintester.testdir()
+def test_xfail(testdir, linecomp):
     p = testdir.makepyfile(test_one="""
         import py
         pytest_plugins="pytest_xfail",

Added: py/trunk/py/test/plugin/test_pytest_runner.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/test_pytest_runner.py	Tue Jun 16 18:23:18 2009
@@ -0,0 +1,297 @@
+import py
+from py.__.test.plugin import pytest_runner as runner
+from py.__.code.excinfo import ReprExceptionInfo
+
+class TestSetupState:
+    def test_setup(self, testdir):
+        ss = runner.SetupState()
+        item = testdir.getitem("def test_func(): pass")
+        l = [1]
+        ss.prepare(item)
+        ss.addfinalizer(l.pop, colitem=item)
+        assert l
+        ss._pop_and_teardown()
+        assert not l 
+
+    def test_setup_scope_None(self, testdir):
+        item = testdir.getitem("def test_func(): pass")
+        ss = runner.SetupState()
+        l = [1]
+        ss.prepare(item)
+        ss.addfinalizer(l.pop, colitem=None)
+        assert l
+        ss._pop_and_teardown()
+        assert l 
+        ss._pop_and_teardown()
+        assert l 
+        ss.teardown_all()
+        assert not l 
+
+
+class BaseFunctionalTests:
+    def test_funcattr(self, testdir):
+        reports = testdir.runitem("""
+            import py
+            @py.test.mark(xfail="needs refactoring")
+            def test_func():
+                raise Exit()
+        """)
+        rep = reports[1]
+        assert rep.keywords['xfail'] == "needs refactoring" 
+
+    def test_passfunction(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                pass
+        """)
+        rep = reports[1]
+        assert rep.passed 
+        assert not rep.failed
+        assert rep.shortrepr == "."
+        assert not hasattr(rep, 'longrepr')
+                
+    def test_failfunction(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                assert 0
+        """)
+        rep = reports[1]
+        assert not rep.passed 
+        assert not rep.skipped 
+        assert rep.failed 
+        assert rep.when == "call"
+        assert isinstance(rep.longrepr, ReprExceptionInfo)
+        assert str(rep.shortrepr) == "F"
+
+    def test_skipfunction(self, testdir):
+        reports = testdir.runitem("""
+            import py
+            def test_func():
+                py.test.skip("hello")
+        """)
+        rep = reports[1]
+        assert not rep.failed 
+        assert not rep.passed 
+        assert rep.skipped 
+        #assert rep.skipped.when == "call"
+        #assert rep.skipped.when == "call"
+        #assert rep.skipped == "%sreason == "hello"
+        #assert rep.skipped.location.lineno == 3
+        #assert rep.skipped.location.path
+        #assert not rep.skipped.failurerepr 
+
+    def test_skip_in_setup_function(self, testdir):
+        reports = testdir.runitem("""
+            import py
+            def setup_function(func):
+                py.test.skip("hello")
+            def test_func():
+                pass
+        """)
+        print reports
+        rep = reports[0]
+        assert not rep.failed 
+        assert not rep.passed 
+        assert rep.skipped 
+        #assert rep.skipped.reason == "hello"
+        #assert rep.skipped.location.lineno == 3
+        #assert rep.skipped.location.lineno == 3
+        assert len(reports) == 1
+
+    def test_failure_in_setup_function(self, testdir):
+        reports = testdir.runitem("""
+            import py
+            def setup_function(func):
+                raise ValueError(42)
+            def test_func():
+                pass
+        """)
+        rep = reports[0]
+        assert not rep.skipped 
+        assert not rep.passed 
+        assert rep.failed 
+        assert rep.when == "setup"
+        assert len(reports) == 1
+
+    def test_failure_in_teardown_function(self, testdir):
+        reports = testdir.runitem("""
+            import py
+            def teardown_function(func):
+                raise ValueError(42)
+            def test_func():
+                pass
+        """)
+        print reports
+        assert len(reports) == 3
+        rep = reports[2]
+        assert not rep.skipped 
+        assert not rep.passed 
+        assert rep.failed 
+        assert rep.when == "teardown" 
+        assert rep.longrepr.reprcrash.lineno == 3
+        assert rep.longrepr.reprtraceback.reprentries 
+
+    def test_custom_failure_repr(self, testdir):
+        testdir.makepyfile(conftest="""
+            import py
+            class Function(py.test.collect.Function):
+                def repr_failure(self, excinfo, outerr):
+                    return "hello" 
+        """)
+        reports = testdir.runitem("""
+            import py
+            def test_func():
+                assert 0
+        """)
+        rep = reports[1]
+        assert not rep.skipped 
+        assert not rep.passed 
+        assert rep.failed 
+        #assert rep.outcome.when == "call"
+        #assert rep.failed.where.lineno == 3
+        #assert rep.failed.where.path.basename == "test_func.py" 
+        #assert rep.failed.failurerepr == "hello"
+
+    def test_failure_in_setup_function_ignores_custom_failure_repr(self, testdir):
+        testdir.makepyfile(conftest="""
+            import py
+            class Function(py.test.collect.Function):
+                def repr_failure(self, excinfo):
+                    assert 0
+        """)
+        reports = testdir.runitem("""
+            import py
+            def setup_function(func):
+                raise ValueError(42)
+            def test_func():
+                pass
+        """)
+        assert len(reports) == 1
+        rep = reports[0]
+        print rep
+        assert not rep.skipped 
+        assert not rep.passed 
+        assert rep.failed 
+        #assert rep.outcome.when == "setup"
+        #assert rep.outcome.where.lineno == 3
+        #assert rep.outcome.where.path.basename == "test_func.py" 
+        #assert instanace(rep.failed.failurerepr, PythonFailureRepr)
+
+    def test_capture_in_func(self, testdir):
+        reports = testdir.runitem("""
+            import sys
+            def setup_function(func):
+                print "in setup"
+            def test_func():
+                print "in function"
+                assert 0
+            def teardown_function(func):
+                print "in teardown"
+        """)
+        assert reports[0].outerr[0] == "in setup\n"
+        assert reports[1].outerr[0] == "in function\n"
+        assert reports[2].outerr[0] == "in teardown\n"
+        
+    def test_systemexit_does_not_bail_out(self, testdir):
+        try:
+            reports = testdir.runitem("""
+                def test_func():
+                    raise SystemExit(42)
+            """)
+        except SystemExit:
+            py.test.fail("runner did not catch SystemExit")
+        rep = reports[1]
+        assert rep.failed
+        assert rep.when == "call"
+
+    def test_exit_propagates(self, testdir):
+        from py.__.test.outcome import Exit
+        try:
+            testdir.runitem("""
+                from py.__.test.outcome import Exit
+                def test_func():
+                    raise Exit()
+            """)
+        except Exit:
+            pass
+        else: 
+            py.test.fail("did not raise")
+
+
+class TestExecutionNonForked(BaseFunctionalTests):
+    def getrunner(self):
+        def f(item):
+            return runner.runtestprotocol(item, log=False)
+        return f
+
+    def test_keyboardinterrupt_propagates(self, testdir):
+        from py.__.test.outcome import Exit
+        try:
+            testdir.runitem("""
+                def test_func():
+                    raise KeyboardInterrupt("fake")
+            """)
+        except KeyboardInterrupt, e:
+            pass
+        else: 
+            py.test.fail("did not raise")
+
+class TestExecutionForked(BaseFunctionalTests): 
+    def getrunner(self):
+        if not hasattr(py.std.os, 'fork'):
+            py.test.skip("no os.fork available")
+        return runner.forked_run_report
+
+    def test_suicide(self, testdir):
+        reports = testdir.runitem("""
+            def test_func():
+                import os
+                os.kill(os.getpid(), 15)
+        """)
+        rep = reports[0]
+        assert rep.failed
+        assert rep.when == "???"
+
+class TestCollectionReports:
+    def test_collect_result(self, testdir):
+        col = testdir.getmodulecol("""
+            def test_func1():
+                pass
+            class TestClass:
+                pass
+        """)
+        rep = runner.pytest_make_collect_report(col)
+        assert not rep.failed
+        assert not rep.skipped
+        assert rep.passed 
+        res = rep.result 
+        assert len(res) == 2
+        assert res[0].name == "test_func1" 
+        assert res[1].name == "TestClass" 
+
+    def test_skip_at_module_scope(self, testdir):
+        col = testdir.getmodulecol("""
+            import py
+            py.test.skip("hello")
+            def test_func():
+                pass
+        """)
+        rep = runner.pytest_make_collect_report(col)
+        assert not rep.failed 
+        assert not rep.passed 
+        assert rep.skipped 
+
+
+def test_functional_boxed(testdir):
+    if not hasattr(py.std.os, 'fork'):
+        py.test.skip("needs os.fork")
+    p1 = testdir.makepyfile("""
+        import os
+        def test_function():
+            os.kill(os.getpid(), 15)
+    """)
+    result = testdir.runpytest(p1, "--boxed")
+    assert result.stdout.fnmatch_lines([
+        "*CRASHED*",
+        "*1 failed*"
+    ])

Modified: py/trunk/py/test/pluginmanager.py
==============================================================================
--- py/trunk/py/test/pluginmanager.py	(original)
+++ py/trunk/py/test/pluginmanager.py	Tue Jun 16 18:23:18 2009
@@ -2,9 +2,15 @@
 managing loading and interacting with pytest plugins. 
 """
 import py
-from py.__.test.plugin import api
+from py.__.test.plugin import hookspec
+
+def check_old_use(mod, modname):
+    clsname = modname[len('pytest_'):].capitalize() + "Plugin" 
+    assert not hasattr(mod, clsname), (mod, clsname)
 
 class PluginManager(object):
+    class Error(Exception):
+        """signals a plugin specific error."""
     def __init__(self, comregistry=None):
         if comregistry is None: 
             comregistry = py._com.Registry()
@@ -13,30 +19,46 @@
         self.impname2plugin = {}
 
         self.hook = py._com.Hooks(
-            hookspecs=api.PluginHooks, 
+            hookspecs=hookspec, 
             registry=self.comregistry) 
 
-    def register(self, plugin):
+    def _getpluginname(self, plugin, name):
+        if name is None:
+            if hasattr(plugin, '__name__'):
+                name = plugin.__name__.split(".")[-1]
+            else:
+                name = id(plugin) 
+        return name 
+
+    def register(self, plugin, name=None):
+        assert not self.isregistered(plugin)
+        name = self._getpluginname(plugin, name)
+        assert name not in self.impname2plugin
+        self.impname2plugin[name] = plugin
         self.hook.pytest_plugin_registered(plugin=plugin)
-        import types
+        self._checkplugin(plugin)
         self.comregistry.register(plugin)
+        return True
 
     def unregister(self, plugin):
         self.hook.pytest_plugin_unregistered(plugin=plugin)
         self.comregistry.unregister(plugin)
+        for name, value in self.impname2plugin.items():
+            if value == plugin:
+                del self.impname2plugin[name]
 
-    def isregistered(self, plugin):
-        return self.comregistry.isregistered(plugin)
+    def isregistered(self, plugin, name=None):
+        return self._getpluginname(plugin, name) in self.impname2plugin
 
     def getplugins(self):
         return list(self.comregistry)
 
-    # API for bootstrapping 
-    #
     def getplugin(self, importname):
-        impname, clsname = canonical_names(importname)
+        impname = canonical_importname(importname)
         return self.impname2plugin[impname]
 
+    # API for bootstrapping 
+    #
     def _envlist(self, varname):
         val = py.std.os.environ.get(varname, None)
         if val is not None:
@@ -54,10 +76,11 @@
 
     def consider_conftest(self, conftestmodule):
         cls = getattr(conftestmodule, 'ConftestPlugin', None)
-        if cls is not None and cls not in self.impname2plugin:
-            self.impname2plugin[cls] = True
-            self.register(cls())
-        self.consider_module(conftestmodule)
+        if cls is not None:
+            raise ValueError("%r: 'ConftestPlugins' only existed till 1.0.0b1, "
+                "were removed in 1.0.0b2" % (cls,))
+        if self.register(conftestmodule, name=conftestmodule.__file__):
+            self.consider_module(conftestmodule)
 
     def consider_module(self, mod):
         attr = getattr(mod, "pytest_plugins", ())
@@ -69,28 +92,61 @@
 
     def import_plugin(self, spec):
         assert isinstance(spec, str)
-        modname, clsname = canonical_names(spec)
+        modname = canonical_importname(spec)
         if modname in self.impname2plugin:
             return
         mod = importplugin(modname)
-        plugin = registerplugin(self.register, mod, clsname)
-        self.impname2plugin[modname] = plugin
+        check_old_use(mod, modname) 
+        self.register(mod)
         self.consider_module(mod)
+
+    def _checkplugin(self, plugin):
+        # =====================================================
+        # check plugin hooks 
+        # =====================================================
+        methods = collectattr(plugin)
+        hooks = collectattr(hookspec)
+        stringio = py.std.StringIO.StringIO()
+        def Print(*args):
+            if args:
+                stringio.write(" ".join(map(str, args)))
+            stringio.write("\n")
+
+        fail = False
+        while methods:
+            name, method = methods.popitem()
+            #print "checking", name
+            if isgenerichook(name):
+                continue
+            if name not in hooks: 
+                Print("found unknown hook:", name)
+                fail = True
+            else:
+                method_args = getargs(method)
+                if '__call__' in method_args:
+                    method_args.remove('__call__')
+                hook = hooks[name]
+                hookargs = getargs(hook)
+                for arg, hookarg in zip(method_args, hookargs):
+                    if arg != hookarg: 
+                        Print("argument mismatch: %r != %r"  %(arg, hookarg))
+                        Print("actual  : %s" %(formatdef(method)))
+                        Print("required:", formatdef(hook))
+                        fail = True
+                        break 
+                #if not fail:
+                #    print "matching hook:", formatdef(method)
+            if fail:
+                name = getattr(plugin, '__name__', plugin)
+                raise self.Error("%s:\n%s" %(name, stringio.getvalue()))
     # 
     #
     # API for interacting with registered and instantiated plugin objects 
     #
     # 
-    def getfirst(self, attrname):
-        for x in self.comregistry.listattr(attrname):
-            return x
-
     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)
-
     def notify_exception(self, excinfo=None):
         if excinfo is None:
             excinfo = py.code.ExceptionInfo()
@@ -123,27 +179,15 @@
         config.hook.pytest_unconfigure(config=config)
         config.pluginmanager.unregister(self)
 
-    def do_itemrun(self, item, pdb=None):
-        res = self.comregistry.call_firstresult("pytest_itemrun", item=item, pdb=pdb)
-        if res is None:
-            raise ValueError("could not run %r" %(item,))
-
 # 
 #  XXX old code to automatically load classes
 #
-def canonical_names(importspec):
-    importspec = importspec.lower()
+def canonical_importname(name):
+    name = name.lower()
     modprefix = "pytest_"
-    if not importspec.startswith(modprefix):
-        importspec = modprefix + importspec
-    clsname = importspec[len(modprefix):].capitalize() + "Plugin"
-    return importspec, clsname
-
-def registerplugin(registerfunc, mod, clsname):
-    pluginclass = getattr(mod, clsname) 
-    plugin = pluginclass()
-    registerfunc(plugin)
-    return plugin
+    if not name.startswith(modprefix):
+        name = modprefix + name 
+    return name 
 
 def importplugin(importspec):
     try:
@@ -159,3 +203,30 @@
             #print "syspath:", py.std.sys.path
             #print "curdir:", py.std.os.getcwd()
             return __import__(importspec)  # show the original exception
+
+
+
+def isgenerichook(name):
+    return name == "pytest_plugins" or \
+           name.startswith("pytest_funcarg__") or \
+           name.startswith("pytest_option_")
+
+def getargs(func):
+    args = py.std.inspect.getargs(func.func_code)[0]
+    startindex = hasattr(func, 'im_self') and 1 or 0
+    return args[startindex:]
+
+def collectattr(obj, prefixes=("pytest_",)):
+    methods = {}
+    for apiname in dir(obj):
+        for prefix in prefixes:
+            if apiname.startswith(prefix):
+                methods[apiname] = getattr(obj, apiname) 
+    return methods 
+
+def formatdef(func):
+    return "%s%s" %(
+        func.func_name, 
+        py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
+    )
+

Modified: py/trunk/py/test/pycollect.py
==============================================================================
--- py/trunk/py/test/pycollect.py	(original)
+++ py/trunk/py/test/pycollect.py	Tue Jun 16 18:23:18 2009
@@ -18,7 +18,6 @@
 """ 
 import py
 from py.__.test.collect import configproperty, warnoldcollect
-from py.__.code.source import findsource
 pydir = py.path.local(py.__file__).dirpath()
 from py.__.test import funcargs
 
@@ -38,16 +37,6 @@
     def _getobj(self):
         return getattr(self.parent.obj, self.name)
 
-    def getmodulecollector(self):
-        return self._getparent(Module)
-    def getclasscollector(self):
-        return self._getparent(Class)
-    def _getparent(self, cls):
-        current = self
-        while current and not isinstance(current, cls):
-            current = current.parent
-        return current 
-
     def getmodpath(self, stopatmodule=True, includemodule=False):
         """ return python path relative to the containing module. """
         chain = self.listchain()
@@ -69,37 +58,21 @@
         s = ".".join(parts)
         return s.replace(".[", "[")
 
-    def getfslineno(self):
+    def _getfslineno(self):
         try:
             return self._fslineno
         except AttributeError:
             pass
         obj = self.obj
-        # let decorators etc specify a sane ordering
+        # xxx let decorators etc specify a sane ordering
         if hasattr(obj, 'place_as'):
             obj = obj.place_as
-        try:
-            code = py.code.Code(obj)
-        except TypeError:
-            # fallback to 
-            fn = (py.std.inspect.getsourcefile(obj) or
-                  py.std.inspect.getfile(obj))
-            fspath = fn and py.path.local(fn) or None
-            if fspath:
-                try:
-                    _, lineno = findsource(obj)
-                except IOError:
-                    lineno = None
-            else:
-                lineno = None
-        else:
-            fspath = code.path
-            lineno = code.firstlineno 
-        self._fslineno = fspath, lineno
-        return fspath, lineno
 
-    def metainfo(self):
-        fspath, lineno = self.getfslineno()
+        self._fslineno = py.code.getfslineno(obj)
+        return self._fslineno
+
+    def reportinfo(self):
+        fspath, lineno = self._getfslineno()
         modpath = self.getmodpath()
         return fspath, lineno, modpath 
 
@@ -120,7 +93,7 @@
             return l
         name2items = self._buildname2items()
         colitems = name2items.values()
-        colitems.sort()
+        colitems.sort(key=lambda item: item.reportinfo()[:2])
         return colitems
 
     def _buildname2items(self): 
@@ -146,9 +119,9 @@
             return self.join(name)
 
     def makeitem(self, name, obj):
-        res = self.config.hook.pytest_pycollect_obj(
+        res = self.config.hook.pytest_pycollect_makeitem(
             collector=self, name=name, obj=obj)
-        if res:
+        if res is not None:
             return res
         if (self.classnamefilter(name)) and \
             py.std.inspect.isclass(obj):
@@ -167,18 +140,18 @@
                 return self._genfunctions(name, obj)
 
     def _genfunctions(self, name, funcobj):
-        module = self.getmodulecollector().obj
+        module = self.getparent(Module).obj
         # due to _buildname2items funcobj is the raw function, we need
         # to work to get at the class 
-        clscol = self.getclasscollector()
+        clscol = self.getparent(Class)
         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:
+        metafunc = funcargs.Metafunc(funcobj, config=self.config, cls=cls, module=module)
+        gentesthook = self.config.hook.pytest_generate_tests.clone(extralookup=module)
+        gentesthook(metafunc=metafunc)
+        if not metafunc._calls:
             return self.Function(name, parent=self)
         return funcargs.FunctionCollector(name=name, 
-            parent=self, combinations=runspec._combinations)
+            parent=self, calls=metafunc._calls)
         
 class Module(py.test.collect.File, PyCollectorMixin):
     def _getobj(self):
@@ -232,9 +205,6 @@
             teardown_class = getattr(teardown_class, 'im_func', teardown_class) 
             teardown_class(self.obj) 
 
-    def _getsortvalue(self):  
-        return self.getfslineno()
-
 class Instance(PyCollectorMixin, py.test.collect.Collector): 
     def _getobj(self): 
         return self.parent.obj()  
@@ -257,9 +227,6 @@
     """ mixin for the code common to Function and Generator.
     """
 
-    def _getsortvalue(self):  
-        return self.getfslineno()
-
     def setup(self): 
         """ perform setup for this test function. """
         if hasattr(self.obj, 'im_self'):
@@ -346,25 +313,26 @@
     """ a Function Item is responsible for setting up  
         and executing a Python callable test object.
     """
-    def __init__(self, name, parent=None, config=None, args=(), funcargs=None, callobj=_dummy):
-        super(Function, self).__init__(name, parent, config=config) 
-        self._finalizers = []
-        self._args = args
-        if funcargs is None:
-            funcargs = {}
-        self.funcargs = funcargs 
+    _genid = None
+    def __init__(self, name, parent=None, args=None, 
+                 callspec=None, callobj=_dummy):
+        super(Function, self).__init__(name, parent)
+        self._args = args 
+        if self._isyieldedfunction():
+            assert not callspec, "yielded functions (deprecated) cannot have funcargs" 
+        else:
+            if callspec is not None:
+                self.funcargs = callspec.funcargs or {}
+                self._genid = callspec.id 
+                if hasattr(callspec, "param"):
+                    self._requestparam = callspec.param
+            else:
+                self.funcargs = {}
         if callobj is not _dummy: 
             self._obj = callobj 
 
-    def addfinalizer(self, func):
-        self._finalizers.append(func)
-        
-    def teardown(self):
-        finalizers = self._finalizers
-        while finalizers:
-            call = finalizers.pop()
-            call()
-        super(Function, self).teardown()
+    def _isyieldedfunction(self):
+        return self._args is not None
 
     def readkeywords(self):
         d = super(Function, self).readkeywords()
@@ -372,20 +340,23 @@
         return d
 
     def runtest(self):
-        """ execute the given test function. """
-        self.config.hook.pytest_pyfunc_call(pyfuncitem=self, 
-            args=self._args, kwargs=self.funcargs)
+        """ execute the underlying test function. """
+        self.config.hook.pytest_pyfunc_call(pyfuncitem=self)
 
     def setup(self):
         super(Function, self).setup()
-        funcargs.fillfuncargs(self)
+        if hasattr(self, 'funcargs'): 
+            funcargs.fillfuncargs(self)
 
     def __eq__(self, other):
         try:
             return (self.name == other.name and 
                     self._args == other._args and
                     self.parent == other.parent and
-                    self.obj == other.obj)
+                    self.obj == other.obj and 
+                    getattr(self, '_genid', None) == 
+                    getattr(other, '_genid', None) 
+            )
         except AttributeError:
             pass
         return False

Deleted: /py/trunk/py/test/runner.py
==============================================================================
--- /py/trunk/py/test/runner.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,197 +0,0 @@
-""" 
-    internal classes for
-
-    * executing test items 
-    * running collectors 
-    * and generating report events about it 
-"""
-
-import py
-
-from py.__.test.outcome import Exit, Skipped
-
-def basic_run_report(item, pdb=None):
-    """ return report about setting up and running a test item. """ 
-    excinfo = None
-    capture = item.config._getcapture()
-    try:
-        try:
-            when = "setup"
-            item.config._setupstate.prepare(item)
-            try:
-                when = "execute"
-                if not item._deprecated_testexecution():
-                    item.runtest()
-            finally:
-                when = "teardown"
-                item.config._setupstate.teardown_exact(item)
-                when = "execute"
-        finally:
-            outerr = capture.reset()
-    except (Exit, KeyboardInterrupt):
-        raise
-    except: 
-        excinfo = py.code.ExceptionInfo()
-    testrep = item.config.hook.pytest_item_makereport(
-        item=item, excinfo=excinfo, when=when, outerr=outerr)
-    if pdb and testrep.failed:
-        tw = py.io.TerminalWriter()
-        testrep.toterminal(tw)
-        pdb(excinfo)
-    return testrep
-
-def basic_collect_report(collector):
-    excinfo = res = None
-    try:
-        capture = collector.config._getcapture()
-        try:
-            res = collector._memocollect()
-        finally:
-            outerr = capture.reset()
-    except (Exit, KeyboardInterrupt):
-        raise
-    except: 
-        excinfo = py.code.ExceptionInfo()
-    return CollectReport(collector, res, excinfo, outerr)
-
-def forked_run_report(item, pdb=None):
-    EXITSTATUS_TESTEXIT = 4
-    from py.__.test.dist.mypickle import ImmutablePickler
-    ipickle = ImmutablePickler(uneven=0)
-    ipickle.selfmemoize(item.config)
-    def runforked():
-        try:
-            testrep = basic_run_report(item)
-        except (KeyboardInterrupt, Exit): 
-            py.std.os._exit(EXITSTATUS_TESTEXIT)
-        return ipickle.dumps(testrep)
-
-    ff = py.process.ForkedFunc(runforked)
-    result = ff.waitfinish()
-    if result.retval is not None:
-        return ipickle.loads(result.retval)
-    else:
-        if result.exitstatus == EXITSTATUS_TESTEXIT:
-            raise Exit("forked test item %s raised Exit" %(item,))
-        return report_process_crash(item, result)
-
-def report_process_crash(item, result):
-    path, lineno = item.getfslineno()
-    longrepr = [
-        ("X", "CRASHED"), 
-        ("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)),
-    ]
-    return ItemTestReport(item, excinfo=longrepr, when="???")
-
-class BaseReport(object):
-    def __repr__(self):
-        l = ["%s=%s" %(key, value)
-           for key, value in self.__dict__.items()]
-        return "<%s %s>" %(self.__class__.__name__, " ".join(l),)
-
-    def toterminal(self, out):
-        longrepr = self.longrepr 
-        if hasattr(longrepr, 'toterminal'):
-            longrepr.toterminal(out)
-        else:
-            out.line(str(longrepr))
-   
-class ItemTestReport(BaseReport):
-    failed = passed = skipped = False
-
-    # XXX rename colitem to item here
-    def __init__(self, colitem, excinfo=None, when=None, outerr=None):
-        self.colitem = colitem 
-        if colitem and when != "setup":
-            self.keywords = colitem.readkeywords() 
-        else:
-            # if we fail during setup it might mean 
-            # we are not able to access the underlying object
-            # this might e.g. happen if we are unpickled 
-            # and our parent collector did not collect us 
-            # (because it e.g. skipped for platform reasons)
-            self.keywords = {}  
-        if not excinfo:
-            self.passed = True
-            self.shortrepr = "." 
-        else:
-            self.when = when 
-            if not isinstance(excinfo, py.code.ExceptionInfo):
-                self.failed = True
-                shortrepr = "?"
-                longrepr = excinfo 
-            elif excinfo.errisinstance(Skipped):
-                self.skipped = True 
-                shortrepr = "s"
-                longrepr = self.colitem._repr_failure_py(excinfo, outerr)
-            else:
-                self.failed = True
-                shortrepr = self.colitem.shortfailurerepr
-                if self.when == "execute":
-                    longrepr = self.colitem.repr_failure(excinfo, outerr)
-                else: # exception in setup or teardown 
-                    longrepr = self.colitem._repr_failure_py(excinfo, outerr)
-                    shortrepr = shortrepr.lower()
-            self.shortrepr = shortrepr 
-            self.longrepr = longrepr 
-            
-
-class CollectReport(BaseReport):
-    skipped = failed = passed = False 
-
-    def __init__(self, colitem, result, excinfo=None, outerr=None):
-        # XXX rename to collector 
-        self.colitem = colitem 
-        if not excinfo:
-            self.passed = True
-            self.result = result 
-        else:
-            self.outerr = outerr
-            self.longrepr = self.colitem._repr_failure_py(excinfo, outerr)
-            if excinfo.errisinstance(Skipped):
-                self.skipped = True
-                self.reason = str(excinfo.value)
-            else:
-                self.failed = True
-
-class ItemSetupReport(BaseReport):
-    failed = passed = skipped = False
-    def __init__(self, item, excinfo=None, outerr=None):
-        self.item = item 
-        self.outerr = outerr 
-        if not excinfo:
-            self.passed = True
-        else:
-            if excinfo.errisinstance(Skipped):
-                self.skipped = True 
-            else:
-                self.failed = True
-            self.excrepr = item._repr_failure_py(excinfo, [])
-
-class SetupState(object):
-    """ shared state for setting up/tearing down test items or collectors. """
-    def __init__(self):
-        self.stack = []
-
-    def teardown_all(self): 
-        while self.stack: 
-            col = self.stack.pop() 
-            col.teardown() 
-
-    def teardown_exact(self, item):
-        if self.stack and self.stack[-1] == item:
-            col = self.stack.pop()
-            col.teardown()
-     
-    def prepare(self, colitem): 
-        """ setup objects along the collector chain to the test-method
-            Teardown any unneccessary previously setup objects."""
-        needed_collectors = colitem.listchain() 
-        while self.stack: 
-            if self.stack == needed_collectors[:len(self.stack)]: 
-                break 
-            col = self.stack.pop() 
-            col.teardown()
-        for col in needed_collectors[len(self.stack):]: 
-            col.setup() 
-            self.stack.append(col) 

Modified: py/trunk/py/test/session.py
==============================================================================
--- py/trunk/py/test/session.py	(original)
+++ py/trunk/py/test/session.py	Tue Jun 16 18:23:18 2009
@@ -11,7 +11,6 @@
 # imports used for genitems()
 Item = py.test.collect.Item
 Collector = py.test.collect.Collector
-from runner import basic_collect_report
 
 class Session(object): 
     """ 
@@ -42,7 +41,7 @@
             else:
                 assert isinstance(next, Collector)
                 self.config.hook.pytest_collectstart(collector=next)
-                rep = basic_collect_report(next)
+                rep = self.config.hook.pytest_make_collect_report(collector=next)
                 if rep.passed:
                     for x in self.genitems(rep.result, keywordexpr):
                         yield x 
@@ -78,18 +77,19 @@
 
     def sessionstarts(self):
         """ setup any neccessary resources ahead of the test run. """
-        self.config.hook.pytest_testrunstart()
+        self.config.hook.pytest_sessionstart(session=self)
         
-    def pytest_itemtestreport(self, rep):
+    def pytest_runtest_logreport(self, rep):
         if rep.failed:
             self._testsfailed = True
             if self.config.option.exitfirst:
                 self.shouldstop = True
-    pytest_collectreport = pytest_itemtestreport
+    pytest_collectreport = pytest_runtest_logreport
 
     def sessionfinishes(self, exitstatus=0, excinfo=None):
         """ teardown any resources after a test run. """ 
-        self.config.hook.pytest_testrunfinish(
+        self.config.hook.pytest_sessionfinish(
+            session=self, 
             exitstatus=exitstatus, 
             excrepr=excinfo and excinfo.getrepr() or None
         )
@@ -112,9 +112,7 @@
                 if self.shouldstop: 
                     break 
                 if not self.config.option.collectonly: 
-                    self.runtest(item)
-
-            self.config._setupstate.teardown_all()
+                    item.config.hook.pytest_runtest_protocol(item=item)
         except KeyboardInterrupt:
             captured_excinfo = py.code.ExceptionInfo()
             exitstatus = outcome.EXIT_INTERRUPTED
@@ -126,11 +124,3 @@
             exitstatus = outcome.EXIT_TESTSFAILED
         self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo)
         return exitstatus
-
-    def runpdb(self, excinfo):
-        from py.__.test.custompdb import post_mortem
-        post_mortem(excinfo._excinfo[2])
-
-    def runtest(self, item):
-        pdb = self.config.option.usepdb and self.runpdb or None
-        item.config.pluginmanager.do_itemrun(item, pdb=pdb)

Modified: py/trunk/py/test/testing/acceptance_test.py
==============================================================================
--- py/trunk/py/test/testing/acceptance_test.py	(original)
+++ py/trunk/py/test/testing/acceptance_test.py	Tue Jun 16 18:23:18 2009
@@ -5,9 +5,8 @@
 class TestGeneralUsage:
     def test_config_error(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_configure(self, config):
-                    raise config.Error("hello")
+            def pytest_configure(config):
+                raise config.Error("hello")
         """)
         result = testdir.runpytest(testdir.tmpdir)
         assert result.ret != 0
@@ -17,9 +16,8 @@
 
     def test_config_preparse_plugin_option(self, testdir):
         testdir.makepyfile(pytest_xyz="""
-            class XyzPlugin:
-                def pytest_addoption(self, parser):
-                    parser.addoption("--xyz", dest="xyz", action="store")
+            def pytest_addoption(parser):
+                parser.addoption("--xyz", dest="xyz", action="store")
         """)
         testdir.makepyfile(test_one="""
             import py
@@ -443,21 +441,6 @@
 
 
 class TestInteractive:
-    def test_pdb_interaction(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_1():
-                i = 0
-                assert i == 1
-        """)
-        child = testdir.spawn_pytest("--pdb %s" % p1)
-        #child.expect(".*def test_1.*")
-        child.expect(".*i = 0.*")
-        child.expect("(Pdb)")
-        child.sendeof()
-        child.expect("1 failed")
-        if child.isalive(): 
-            child.wait()
-
     def test_simple_looponfail_interaction(self, testdir):
         p1 = testdir.makepyfile("""
             def test_1():

Deleted: /py/trunk/py/test/testing/test_api.py
==============================================================================
--- /py/trunk/py/test/testing/test_api.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,14 +0,0 @@
-
-class TestPyfuncHooks:
-    def test_pyfunc_call(self, testdir):
-        item = testdir.getitem("def test_func(): raise ValueError")
-        config = item.config
-        class MyPlugin1:
-            def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs):
-                raise ValueError
-        class MyPlugin2:
-            def pytest_pyfunc_call(self, pyfuncitem, *args, **kwargs):
-                return True
-        config.pluginmanager.register(MyPlugin1())
-        config.pluginmanager.register(MyPlugin2())
-        config.hook.pytest_pyfunc_call(pyfuncitem=item)

Modified: py/trunk/py/test/testing/test_collect.py
==============================================================================
--- py/trunk/py/test/testing/test_collect.py	(original)
+++ py/trunk/py/test/testing/test_collect.py	Tue Jun 16 18:23:18 2009
@@ -6,7 +6,7 @@
         assert not issubclass(Collector, Item)
         assert not issubclass(Item, Collector)
 
-    def test_check_equality_and_cmp_basic(self, testdir):
+    def test_check_equality(self, testdir):
         modcol = testdir.getmodulecol("""
             def test_pass(): pass
             def test_fail(): assert 0
@@ -25,11 +25,7 @@
         assert isinstance(fn3, py.test.collect.Function)
         assert not (fn1 == fn3) 
         assert fn1 != fn3
-        assert cmp(fn1, fn3) == -1
 
-        assert cmp(fn1, 10) == -1 
-        assert cmp(fn2, 10) == -1 
-        assert cmp(fn3, 10) == -1 
         for fn in fn1,fn2,fn3:
             assert fn != 3
             assert fn != modcol
@@ -37,6 +33,24 @@
             assert [1,2,3] != fn
             assert modcol != fn
 
+    def test_getparent(self, testdir):
+        modcol = testdir.getmodulecol("""
+            class TestClass:
+                 def test_foo():
+                     pass
+        """)
+        cls = modcol.collect_by_name("TestClass")
+        fn = cls.collect_by_name("()").collect_by_name("test_foo")
+        
+        parent = fn.getparent(py.test.collect.Module)
+        assert parent is modcol
+
+        parent = fn.getparent(py.test.collect.Function)
+        assert parent is fn
+
+        parent = fn.getparent(py.test.collect.Class)
+        assert parent is cls     
+
     def test_totrail_and_back(self, tmpdir):
         a = tmpdir.ensure("a", dir=1)
         tmpdir.ensure("a", "__init__.py")
@@ -157,12 +171,12 @@
         testdir.plugins.append(Plugin())
         testdir.mkdir("hello")
         testdir.mkdir("world")
-        sorter = testdir.inline_run()
+        reprec = testdir.inline_run()
         assert "hello" in wascalled
         assert "world" in wascalled
         # make sure the directories do not get double-appended 
-        colreports = sorter.getreports("collectreport")
-        names = [rep.colitem.name for rep in colreports]
+        colreports = reprec.getreports("pytest_collectreport")
+        names = [rep.collector.name for rep in colreports]
         assert names.count("hello") == 1
 
 class TestCustomConftests:
@@ -191,18 +205,22 @@
         assert item.name == "hello.xxx"
         assert item.__class__.__name__ == "CustomItem"
 
-    def test_avoid_directory_on_option(self, testdir):
+    def test_collectignore_exclude_on_option(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_addoption(self, parser):
-                    parser.addoption("--XX", action="store_true", default=False)
-                def pytest_collect_recurse(self, path, parent):
-                    return parent.config.getvalue("XX")
+            collect_ignore = ['hello', 'test_world.py']
+            def pytest_addoption(parser):
+                parser.addoption("--XX", action="store_true", default=False)
+            def pytest_configure(config):
+                if config.getvalue("XX"):
+                    collect_ignore[:] = []
         """)
         testdir.mkdir("hello")
-        sorter = testdir.inline_run(testdir.tmpdir)
-        names = [rep.colitem.name for rep in sorter.getreports("collectreport")]
+        testdir.makepyfile(test_world="#")
+        reprec = testdir.inline_run(testdir.tmpdir)
+        names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")]
         assert 'hello' not in names 
-        evrec = testdir.inline_run(testdir.tmpdir, "--XX")
-        names = [rep.colitem.name for rep in evrec.getreports("collectreport")]
+        assert 'test_world.py' not in names 
+        reprec = testdir.inline_run(testdir.tmpdir, "--XX")
+        names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")]
         assert 'hello' in names 
+        assert 'test_world.py' in names 

Modified: py/trunk/py/test/testing/test_config.py
==============================================================================
--- py/trunk/py/test/testing/test_config.py	(original)
+++ py/trunk/py/test/testing/test_config.py	Tue Jun 16 18:23:18 2009
@@ -303,8 +303,8 @@
 
 def test_options_on_small_file_do_not_blow_up(testdir):
     def runfiletest(opts):
-        sorter = testdir.inline_run(*opts)
-        passed, skipped, failed = sorter.countoutcomes()
+        reprec = testdir.inline_run(*opts)
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 2 
         assert skipped == passed == 0
     path = testdir.makepyfile("""

Modified: py/trunk/py/test/testing/test_conftesthandle.py
==============================================================================
--- py/trunk/py/test/testing/test_conftesthandle.py	(original)
+++ py/trunk/py/test/testing/test_conftesthandle.py	Tue Jun 16 18:23:18 2009
@@ -1,98 +1,92 @@
 import py
 from py.__.test.conftesthandle import Conftest
 
-class TestConftestValueAccessGlobal:
-    def setup_class(cls):
-        # if we have "global" conftests (i.e. no __init__.py
-        # and thus no further import scope) it should still all work 
-        # because "global" conftests are imported with a
-        # mangled module name (related to their actual path) 
-        cls.basedir = d = py.test.ensuretemp(cls.__name__)
+def pytest_generate_tests(metafunc):
+    if "basedir" in metafunc.funcargnames:
+        metafunc.addcall(param="global") 
+        metafunc.addcall(param="inpackage")
+
+def pytest_funcarg__basedir(request): 
+    def basedirmaker(request):
+        basedir = d = request.config.ensuretemp(request.param) 
         d.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
         d.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
+        if request.param == "inpackage": 
+            d.ensure("adir/__init__.py")
+            d.ensure("adir/b/__init__.py")
+        return d 
+    return request.cached_setup(lambda: basedirmaker(request), extrakey=request.param)
 
-    def test_basic_init(self):
+class TestConftestValueAccessGlobal:
+    def test_basic_init(self, basedir):
         conftest = Conftest()
-        conftest.setinitial([self.basedir.join("adir")])
+        conftest.setinitial([basedir.join("adir")])
         assert conftest.rget("a") == 1
 
-    def test_onimport(self):
+    def test_onimport(self, basedir):
         l = []
         conftest = Conftest(onimport=l.append)
-        conftest.setinitial([self.basedir.join("adir")])
+        conftest.setinitial([basedir.join("adir")])
         assert len(l) == 2 # default + the one 
         assert conftest.rget("a") == 1
-        assert conftest.rget("b", self.basedir.join("adir", "b")) == 2
+        assert conftest.rget("b", basedir.join("adir", "b")) == 2
         assert len(l) == 3
 
-    def test_immediate_initialiation_and_incremental_are_the_same(self):
+    def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
         conftest = Conftest()
         snap0 = len(conftest._path2confmods)
-        conftest.getconftestmodules(self.basedir)
+        conftest.getconftestmodules(basedir)
         snap1 = len(conftest._path2confmods)
         #assert len(conftest._path2confmods) == snap1 + 1
-        conftest.getconftestmodules(self.basedir.join('adir'))
+        conftest.getconftestmodules(basedir.join('adir'))
         assert len(conftest._path2confmods) == snap1 + 1
-        conftest.getconftestmodules(self.basedir.join('b'))
+        conftest.getconftestmodules(basedir.join('b'))
         assert len(conftest._path2confmods) == snap1 + 2
 
-    def test_default_Module_setting_is_visible_always(self):
-        for path in self.basedir.parts():
+    def test_default_Module_setting_is_visible_always(self, basedir):
+        for path in basedir.parts():
             conftest = Conftest(path) 
             #assert conftest.lget("Module") == py.test.collect.Module
             assert conftest.rget("Module") == py.test.collect.Module
 
-    def test_default_has_lower_prio(self):
-        conftest = Conftest(self.basedir.join("adir"))
+    def test_default_has_lower_prio(self, basedir):
+        conftest = Conftest(basedir.join("adir"))
         assert conftest.rget('Directory') == 3
         #assert conftest.lget('Directory') == py.test.collect.Directory 
         
-    def test_value_access_not_existing(self):
-        conftest = Conftest(self.basedir)
+    def test_value_access_not_existing(self, basedir):
+        conftest = Conftest(basedir)
         py.test.raises(KeyError, "conftest.rget('a')")
         #py.test.raises(KeyError, "conftest.lget('a')")
 
-    def test_value_access_by_path(self):
-        conftest = Conftest(self.basedir)
-        assert conftest.rget("a", self.basedir.join('adir')) == 1
-        #assert conftest.lget("a", self.basedir.join('adir')) == 1
-        assert conftest.rget("a", self.basedir.join('adir', 'b')) == 1.5 
-        #assert conftest.lget("a", self.basedir.join('adir', 'b')) == 1
-        #assert conftest.lget("b", self.basedir.join('adir', 'b')) == 2
+    def test_value_access_by_path(self, basedir):
+        conftest = Conftest(basedir)
+        assert conftest.rget("a", basedir.join('adir')) == 1
+        #assert conftest.lget("a", basedir.join('adir')) == 1
+        assert conftest.rget("a", basedir.join('adir', 'b')) == 1.5 
+        #assert conftest.lget("a", basedir.join('adir', 'b')) == 1
+        #assert conftest.lget("b", basedir.join('adir', 'b')) == 2
         #assert py.test.raises(KeyError, 
-        #    'conftest.lget("b", self.basedir.join("a"))'
+        #    'conftest.lget("b", basedir.join("a"))'
         #)
 
-    def test_value_access_with_init_one_conftest(self):
-        conftest = Conftest(self.basedir.join('adir'))
+    def test_value_access_with_init_one_conftest(self, basedir):
+        conftest = Conftest(basedir.join('adir'))
         assert conftest.rget("a") == 1
         #assert conftest.lget("a") == 1
 
-    def test_value_access_with_init_two_conftests(self):
-        conftest = Conftest(self.basedir.join("adir", "b"))
+    def test_value_access_with_init_two_conftests(self, basedir):
+        conftest = Conftest(basedir.join("adir", "b"))
         conftest.rget("a") == 1.5
         #conftest.lget("a") == 1
         #conftest.lget("b") == 1
 
-    def test_value_access_with_confmod(self):
-        topdir = self.basedir.join("adir", "b")
+    def test_value_access_with_confmod(self, basedir):
+        topdir = basedir.join("adir", "b")
         topdir.ensure("xx", dir=True)
         conftest = Conftest(topdir)
         mod, value = conftest.rget_with_confmod("a", topdir)
         assert  value == 1.5
         path = py.path.local(mod.__file__)
-        assert path.dirpath() == self.basedir.join("adir", "b")
+        assert path.dirpath() == basedir.join("adir", "b")
         assert path.purebasename == "conftest"
-
-class TestConftestValueAccessInPackage(TestConftestValueAccessGlobal):
-    def setup_class(cls):
-        TestConftestValueAccessGlobal.__dict__['setup_class'](cls)
-        d = cls.basedir 
-        d.ensure("adir/__init__.py")
-        d.ensure("adir/b/__init__.py")
-        
-
-        
-        
-        
-

Modified: py/trunk/py/test/testing/test_funcargs.py
==============================================================================
--- py/trunk/py/test/testing/test_funcargs.py	(original)
+++ py/trunk/py/test/testing/test_funcargs.py	Tue Jun 16 18:23:18 2009
@@ -19,9 +19,8 @@
 class TestFillFuncArgs:
     def test_funcarg_lookupfails(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_funcarg__xyzsomething(self, request):
-                    return 42
+            def pytest_funcarg__xyzsomething(request):
+                return 42
         """)
         item = testdir.getitem("def test_func(some): pass")
         exc = py.test.raises(LookupError, "funcargs.fillfuncargs(item)")
@@ -67,14 +66,37 @@
         funcargs.fillfuncargs(item2)
         assert item2.funcargs['something'] ==  "test_func"
 
+    def test_funcarg_lookup_classlevel(self, testdir):
+        p = testdir.makepyfile("""
+            class TestClass:
+                def pytest_funcarg__something(self, request):
+                    return request.instance 
+                def test_method(self, something):
+                    assert something is self 
+        """)
+        result = testdir.runpytest(p)
+        assert result.stdout.fnmatch_lines([
+            "*1 passed*"
+        ])
+
+    def test_fillfuncargs_exposed(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())
+        if hasattr(item, '_args'):
+            del item._args
+        py.test.collect._fillfuncargs(item)
+        assert len(item.funcargs) == 1
+
 class TestRequest:
     def test_request_attributes(self, testdir):
         item = testdir.getitem("""
             def pytest_funcarg__something(request): pass
             def test_func(something): pass
         """)
-        req = funcargs.FuncargRequest(item, argname="other")
-        assert req.argname == "other"
+        req = funcargs.FuncargRequest(item)
         assert req.function == item.obj 
         assert hasattr(req.module, 'test_func')
         assert req.cls is None
@@ -88,144 +110,292 @@
                 def test_func(self, something): 
                     pass
         """)
-        req = funcargs.FuncargRequest(item, argname="something")
+        req = funcargs.FuncargRequest(item)
         assert req.cls.__name__ == "TestB"
-        
-    def test_request_contains_funcargs_provider(self, testdir):
+        assert req.instance.__class__ == req.cls
+
+    def XXXtest_request_contains_funcargs_provider(self, testdir):
         modcol = testdir.getmodulecol("""
             def pytest_funcarg__something(request):
                 pass
             class TestClass:
                 def test_method(self, something):
-                    pass 
+                    pass
         """)
         item1, = testdir.genitems([modcol])
         assert item1.name == "test_method"
-        provider = funcargs.FuncargRequest(item1, "something")._provider 
+        provider = funcargs.FuncargRequest(item1)._provider
         assert len(provider) == 1
         assert provider[0].__name__ == "pytest_funcarg__something"
 
-    def test_request_call_next_provider(self, testdir):
+    def test_getfuncargvalue_recursive(self, testdir):
+        testdir.makeconftest("""
+            def pytest_funcarg__something(request):
+                return 1
+        """)
         item = testdir.getitem("""
-            def pytest_funcarg__something(request): pass
+            def pytest_funcarg__something(request):
+                return request.getfuncargvalue("something") + 1
+            def test_func(something): 
+                assert something == 2
+        """)
+        req = funcargs.FuncargRequest(item)
+        val = req.getfuncargvalue("something") 
+        assert val == 2
+
+    def test_getfuncargvalue(self, testdir):
+        item = testdir.getitem("""
+            l = [2]
+            def pytest_funcarg__something(request): return 1
+            def pytest_funcarg__other(request): 
+                return l.pop()
             def test_func(something): pass
         """)
-        req = funcargs.FuncargRequest(item, "something")
-        val = req.call_next_provider()
-        assert val is None
-        py.test.raises(req.Error, "req.call_next_provider()")
+        req = funcargs.FuncargRequest(item)
+        val = req.getfuncargvalue("something") 
+        assert val == 1
+        val = req.getfuncargvalue("something") 
+        assert val == 1
+        val2 = req.getfuncargvalue("other")
+        assert val2 == 2 
+        val2 = req.getfuncargvalue("other")  # see about caching
+        assert val2 == 2
+        req._fillfuncargs()
+        assert item.funcargs == {'something': 1}
 
-    def test_request_addfinalizer(self, testdir):
+    def test_request_addfinalizer_scopes(self, testdir):
         item = testdir.getitem("""
-            def pytest_funcarg__something(request): pass
+            teardownlist = []
+            def pytest_funcarg__something(request): 
+                for scope in ("function", "module", "session"):
+                    request.addfinalizer(
+                        lambda x=scope: teardownlist.append(x), 
+                        scope=scope)
+                
             def test_func(something): pass
         """)
-        req = funcargs.FuncargRequest(item, "something")
-        l = [1]
-        req.addfinalizer(l.pop)
-        item.teardown()
+        req = funcargs.FuncargRequest(item)
+        req.config._setupstate.prepare(item) # XXX 
+        req._fillfuncargs()
+        # successively check finalization calls 
+        teardownlist = item.getparent(py.test.collect.Module).obj.teardownlist 
+        ss = item.config._setupstate
+        assert not teardownlist 
+        ss.teardown_exact(item) 
+        print ss.stack
+        assert teardownlist == ['function']
+        ss.teardown_exact(item.parent) 
+        assert teardownlist == ['function', 'module']
+        ss.teardown_all()
+        assert teardownlist == ['function', 'module', 'session']
+
+    def test_request_addfinalizer_unknown_scope(self, testdir):
+        item = testdir.getitem("def test_func(): pass") 
+        req = funcargs.FuncargRequest(item)
+        py.test.raises(ValueError, "req.addfinalizer(None, scope='xyz')")
 
     def test_request_getmodulepath(self, testdir):
         modcol = testdir.getmodulecol("def test_somefunc(): pass")
         item, = testdir.genitems([modcol])
-        req = funcargs.FuncargRequest(item, "xxx")
+        req = funcargs.FuncargRequest(item)
         assert req.fspath == modcol.fspath 
 
-class TestRunSpecs:
+class TestRequestProtocol:
+    @py.test.mark.xfail
+    def test_protocol(self, testdir):
+        item = testdir.getitem("""
+            def pytest_funcarg_arg1(request): return 1
+            def pytest_funcarg_arg2(request): return 2
+            def test_func(arg1, arg2): pass
+        """)
+        req = funcargs.FuncargRequest(item)
+        req._fillargs()
+        #assert item.funcreq.
+         
+
+class TestRequestCachedSetup:
+    def test_request_cachedsetup(self, testdir):
+        item1,item2 = testdir.getitems("""
+            class TestClass:
+                def test_func1(self, something): 
+                    pass
+                def test_func2(self, something): 
+                    pass
+        """)
+        req1 = funcargs.FuncargRequest(item1)
+        l = ["hello"]
+        def setup():
+            return l.pop()
+        ret1 = req1.cached_setup(setup)
+        assert ret1 == "hello"
+        ret1b = req1.cached_setup(setup)
+        assert ret1 == ret1b
+        req2 = funcargs.FuncargRequest(item2)
+        ret2 = req2.cached_setup(setup)
+        assert ret2 == ret1
+
+    def test_request_cachedsetup_extrakey(self, testdir):
+        item1 = testdir.getitem("def test_func(): pass")
+        req1 = funcargs.FuncargRequest(item1)
+        l = ["hello", "world"]
+        def setup():
+            return l.pop()
+        ret1 = req1.cached_setup(setup, extrakey=1)
+        ret2 = req1.cached_setup(setup, extrakey=2)
+        assert ret2 == "hello"
+        assert ret1 == "world"
+        ret1b = req1.cached_setup(setup, extrakey=1)
+        ret2b = req1.cached_setup(setup, extrakey=2)
+        assert ret1 == ret1b
+        assert ret2 == ret2b
+
+    def test_request_cached_setup_functional(self, testdir):
+        testdir.makepyfile(test_0="""
+            l = []
+            def pytest_funcarg__something(request):
+                val = request.cached_setup(setup, teardown)
+                return val 
+            def setup(mycache=[1]):
+                l.append(mycache.pop())
+                return l 
+            def teardown(something):
+                l.remove(something[0])
+                l.append(2)
+            def test_list_once(something):
+                assert something == [1]
+            def test_list_twice(something):
+                assert something == [1]
+        """)
+        testdir.makepyfile(test_1="""
+            import test_0 # should have run already 
+            def test_check_test0_has_teardown_correct():
+                assert test_0.l == [2]
+        """)
+        result = testdir.runpytest("-v")
+        result.stdout.fnmatch_lines([
+            "*3 passed*"
+        ])
+
+
+class TestMetafunc:
     def test_no_funcargs(self, testdir):
         def function(): pass
-        runspec = funcargs.RunSpecs(function)
-        assert not runspec.funcargnames
+        metafunc = funcargs.Metafunc(function)
+        assert not metafunc.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
+        metafunc = funcargs.Metafunc(func)
+        assert len(metafunc.funcargnames) == 1
+        assert 'arg1' in metafunc.funcargnames
+        assert metafunc.function is func 
+        assert metafunc.cls is None
+
+    def test_addcall_no_args(self):
+        def func(arg1): pass
+        metafunc = funcargs.Metafunc(func)
+        metafunc.addcall()
+        assert len(metafunc._calls) == 1
+        call = metafunc._calls[0]
+        assert call.id == "0"
+        assert not hasattr(call, 'param')
 
-    def test_addfuncarg_basic(self):
+    def test_addcall_id(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}
+        metafunc = funcargs.Metafunc(func)
+        py.test.raises(ValueError, "metafunc.addcall(id=None)")
 
-    def test_addfuncarg_two(self):
+        metafunc.addcall(id=1)
+        py.test.raises(ValueError, "metafunc.addcall(id=1)")
+        py.test.raises(ValueError, "metafunc.addcall(id='1')")
+        metafunc.addcall(id=2)
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].id == "1"
+        assert metafunc._calls[1].id == "2"
+
+    def test_addcall_param(self):
+        def func(arg1): pass
+        metafunc = funcargs.Metafunc(func)
+        class obj: pass 
+        metafunc.addcall(param=obj) 
+        metafunc.addcall(param=obj) 
+        metafunc.addcall(param=1) 
+        assert len(metafunc._calls) == 3
+        assert metafunc._calls[0].param == obj 
+        assert metafunc._calls[1].param == obj 
+        assert metafunc._calls[2].param == 1
+
+    def test_addcall_funcargs(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}
+        metafunc = funcargs.Metafunc(func)
+        class obj: pass 
+        metafunc.addcall(funcargs={"x": 2})
+        metafunc.addcall(funcargs={"x": 3})
+        assert len(metafunc._calls) == 2
+        assert metafunc._calls[0].funcargs == {'x': 2}
+        assert metafunc._calls[1].funcargs == {'x': 3}
+        assert not hasattr(metafunc._calls[1], 'param')
 
 class TestGenfuncFunctional:
     def test_attributes(self, testdir):
         p = testdir.makepyfile("""
+            # assumes that generate/provide runs in the same process
             import py
-            def pytest_genfuncruns(runspec):
-                runspec.addfuncarg("runspec", runspec)
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(param=metafunc) 
+
+            def pytest_funcarg__metafunc(request):
+                assert request._pyfuncitem._genid == "0"
+                return request.param 
+
+            def test_function(metafunc):
+                assert metafunc.config == py.test.config
+                assert metafunc.module.__name__ == __name__
+                assert metafunc.function == test_function
+                assert metafunc.cls is None
 
-            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
+            class TestClass:
+                def test_method(self, metafunc):
+                    assert metafunc.config == py.test.config
+                    assert metafunc.module.__name__ == __name__
+                    # XXX actually have an unbound test function here?
+                    assert metafunc.function == TestClass.test_method.im_func
+                    assert metafunc.cls == TestClass
         """)
         result = testdir.runpytest(p, "-v")
         result.stdout.fnmatch_lines([
             "*2 passed in*",
         ])
 
-    def test_arg_twice(self, testdir):
+    def test_addcall_with_funcargs_two(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_genfuncruns(self, runspec):
-                    assert "arg" in runspec.funcargnames 
-                    runspec.addfuncarg("arg", 10)
-                    runspec.addfuncarg("arg", 20)
+            def pytest_generate_tests(metafunc):
+                assert "arg1" in metafunc.funcargnames 
+                metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
         """)
         p = testdir.makepyfile("""
-            def test_myfunc(arg):
-                assert arg == 10
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
+
+            class TestClass:
+                def test_myfunc(self, arg1, arg2):
+                    assert arg1 == arg2 
         """)
         result = testdir.runpytest("-v", p)
         assert result.stdout.fnmatch_lines([
-            "*test_myfunc*PASS*", # case for 10
-            "*test_myfunc*FAIL*", # case for 20
+            "*test_myfunc*0*PASS*", 
+            "*test_myfunc*1*FAIL*", 
             "*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 pytest_generate_tests(metafunc):
+                metafunc.addcall(param=10)
+                metafunc.addcall(param=20) 
+
+            def pytest_funcarg__arg1(request):
+                return request.param
 
             def test_func1(arg1):
                 assert arg1 == 10
@@ -240,27 +410,28 @@
             "*1 failed, 3 passed*"
         ])
 
-    def test_genfuncarg_inmodule(self, testdir):
+    def test_generate_plugin_and_module(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_genfuncruns(self, runspec):
-                    assert "arg" in runspec.funcargnames 
-                    runspec.addfuncarg("arg", 10)
+            def pytest_generate_tests(metafunc):
+                assert "arg1" in metafunc.funcargnames 
+                metafunc.addcall(id="world", param=(2,100))
         """)
         p = testdir.makepyfile("""
-            def pytest_genfuncruns(runspec):
-                runspec.addfuncarg("arg2", 10)
-                runspec.addfuncarg("arg2", 20)
-                runspec.addfuncarg("classarg", 17)
+            def pytest_generate_tests(metafunc):
+                metafunc.addcall(param=(1,1), id="hello")
+
+            def pytest_funcarg__arg1(request):
+                return request.param[0]
+            def pytest_funcarg__arg2(request):
+                return request.param[1]
 
             class TestClass:
-                def test_myfunc(self, arg, arg2, classarg):
-                    assert classarg == 17
-                    assert arg == arg2
+                def test_myfunc(self, arg1, arg2):
+                    assert arg1 == arg2 
         """)
         result = testdir.runpytest("-v", p)
         assert result.stdout.fnmatch_lines([
-            "*test_myfunc*0*PASS*", 
-            "*test_myfunc*1*FAIL*", 
+            "*test_myfunc*hello*PASS*", 
+            "*test_myfunc*world*FAIL*", 
             "*1 failed, 1 passed*"
         ])

Modified: py/trunk/py/test/testing/test_genitems.py
==============================================================================
--- py/trunk/py/test/testing/test_genitems.py	(original)
+++ py/trunk/py/test/testing/test_genitems.py	Tue Jun 16 18:23:18 2009
@@ -10,7 +10,7 @@
                 pass
         """)
         p.copy(p.dirpath(p.purebasename + "2" + ".py"))
-        items, sorter = testdir.inline_genitems(p.dirpath())
+        items, reprec = testdir.inline_genitems(p.dirpath())
         assert len(items) == 4
         for numi, i in enumerate(items):
             for numj, j in enumerate(items):
@@ -27,8 +27,8 @@
     def test_subdir_conftest_error(self, testdir):
         tmp = testdir.tmpdir
         tmp.ensure("sub", "conftest.py").write("raise SyntaxError\n")
-        items, sorter = testdir.inline_genitems(tmp)
-        collectionfailures = sorter.getfailedcollections()
+        items, reprec = testdir.inline_genitems(tmp)
+        collectionfailures = reprec.getfailedcollections()
         assert len(collectionfailures) == 1
         ev = collectionfailures[0] 
         assert ev.longrepr.reprcrash.message.startswith("SyntaxError")
@@ -45,7 +45,7 @@
             class TestY(TestX):
                 pass
         ''')
-        items, sorter = testdir.inline_genitems(p)
+        items, reprec = testdir.inline_genitems(p)
         assert len(items) == 3
         assert items[0].name == 'testone'
         assert items[1].name == 'testmethod_one'
@@ -70,11 +70,11 @@
                     assert 42 == 43 
         """)
         def check(keyword, name):
-            sorter = testdir.inline_run("-s", "-k", keyword, file_test)
-            passed, skipped, failed = sorter.listoutcomes()
+            reprec = testdir.inline_run("-s", "-k", keyword, file_test)
+            passed, skipped, failed = reprec.listoutcomes()
             assert len(failed) == 1
-            assert failed[0].colitem.name == name 
-            assert len(sorter.getcalls('deselected')) == 1
+            assert failed[0].item.name == name 
+            assert len(reprec.getcalls('pytest_deselected')) == 1
 
         for keyword in ['test_one', 'est_on']:
             #yield check, keyword, 'test_one'
@@ -97,12 +97,12 @@
         """)
         for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1', 
                         'TestClass test_2', 'xxx TestClass test_2',): 
-            sorter = testdir.inline_run(p.dirpath(), '-s', '-k', keyword)
+            reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword)
             print "keyword", repr(keyword)
-            passed, skipped, failed = sorter.listoutcomes()
+            passed, skipped, failed = reprec.listoutcomes()
             assert len(passed) == 1
-            assert passed[0].colitem.name == "test_2"
-            dlist = sorter.getcalls("pytest_deselected")
+            assert passed[0].item.name == "test_2"
+            dlist = reprec.getcalls("pytest_deselected")
             assert len(dlist) == 1
             assert dlist[0].items[0].name == 'test_1'
 
@@ -112,11 +112,11 @@
             def test_two(): assert 1
             def test_three(): assert 1
         """)
-        sorter = testdir.inline_run("-k", "test_two:", threepass)
-        passed, skipped, failed = sorter.listoutcomes()
+        reprec = testdir.inline_run("-k", "test_two:", threepass)
+        passed, skipped, failed = reprec.listoutcomes()
         assert len(passed) == 2
         assert not failed 
-        dlist = sorter.getcalls("pytest_deselected")
+        dlist = reprec.getcalls("pytest_deselected")
         assert len(dlist) == 1
         item = dlist[0].items[0]
         assert item.name == "test_one" 

Modified: py/trunk/py/test/testing/test_outcome.py
==============================================================================
--- py/trunk/py/test/testing/test_outcome.py	(original)
+++ py/trunk/py/test/testing/test_outcome.py	Tue Jun 16 18:23:18 2009
@@ -76,6 +76,13 @@
         print py.code.ExceptionInfo()
         py.test.fail("spurious skip")
 
+def test_pytest_exit():
+    try:
+        py.test.exit("hello")
+    except:
+        excinfo = py.code.ExceptionInfo()
+        assert excinfo.errisinstance(KeyboardInterrupt)
+
 def test_pytest_mark_getattr():
     from py.__.test.outcome import mark
     def f(): pass

Modified: py/trunk/py/test/testing/test_parseopt.py
==============================================================================
--- py/trunk/py/test/testing/test_parseopt.py	(original)
+++ py/trunk/py/test/testing/test_parseopt.py	Tue Jun 16 18:23:18 2009
@@ -4,10 +4,10 @@
 pytest_plugins = 'pytest_iocapture'
 
 class TestParser:
-    def test_init(self, stdcapture):
+    def test_init(self, capsys):
         parser = parseopt.Parser(usage="xyz")
         py.test.raises(SystemExit, 'parser.parse(["-h"])')
-        out, err = stdcapture.reset()
+        out, err = capsys.reset()
         assert out.find("xyz") != -1
 
     def test_group_add_and_get(self):

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 Jun 16 18:23:18 2009
@@ -14,7 +14,7 @@
 
 def pytest_funcarg__testdir(request):
     setglobals(request)
-    return request.call_next_provider()
+    return request.getfuncargvalue("testdir")
 
 class ImmutablePickleTransport:
     def __init__(self, request):
@@ -87,11 +87,10 @@
 
     def test_config_pickling_customoption(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_addoption(self, parser):
-                    group = parser.addgroup("testing group")
-                    group.addoption('-G', '--glong', action="store", default=42, 
-                        type="int", dest="gdest", help="g value.")
+            def pytest_addoption(parser):
+                group = parser.addgroup("testing group")
+                group.addoption('-G', '--glong', action="store", default=42, 
+                    type="int", dest="gdest", help="g value.")
         """)
         config = testdir.parseconfig("-G", "11")
         assert config.option.gdest == 11
@@ -108,11 +107,10 @@
         tmp = testdir.tmpdir.ensure("w1", "w2", dir=1)
         tmp.ensure("__init__.py")
         tmp.join("conftest.py").write(py.code.Source("""
-            class ConftestPlugin:
-                def pytest_addoption(self, parser):
-                    group = parser.addgroup("testing group")
-                    group.addoption('-G', '--glong', action="store", default=42, 
-                        type="int", dest="gdest", help="g value.")
+            def pytest_addoption(parser):
+                group = parser.addgroup("testing group")
+                group.addoption('-G', '--glong', action="store", default=42, 
+                    type="int", dest="gdest", help="g value.")
         """))
         config = testdir.parseconfig(tmp, "-G", "11")
         assert config.option.gdest == 11

Modified: py/trunk/py/test/testing/test_pluginmanager.py
==============================================================================
--- py/trunk/py/test/testing/test_pluginmanager.py	(original)
+++ py/trunk/py/test/testing/test_pluginmanager.py	Tue Jun 16 18:23:18 2009
@@ -1,6 +1,5 @@
 import py, os
-from py.__.test.pluginmanager import PluginManager, canonical_names
-from py.__.test.pluginmanager import registerplugin, importplugin
+from py.__.test.pluginmanager import PluginManager, canonical_importname, collectattr
 
 class TestBootstrapping:
     def test_consider_env_fails_to_import(self, monkeypatch):
@@ -17,7 +16,7 @@
     def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
         pluginmanager = PluginManager()
         testdir.syspathinsert()
-        testdir.makepyfile(pytest_xy123="class Xy123Plugin: pass")
+        testdir.makepyfile(pytest_xy123="#")
         monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
         l1 = len(pluginmanager.getplugins())
         pluginmanager.consider_env()
@@ -29,7 +28,7 @@
         assert l2 == l3
 
     def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
-        x500 = testdir.makepyfile(pytest_x500="class X500Plugin: pass")
+        x500 = testdir.makepyfile(pytest_x500="#")
         p = testdir.makepyfile("""
             import py
             def test_hello():
@@ -48,59 +47,49 @@
 
         reset = testdir.syspathinsert()
         pluginname = "pytest_hello"
-        testdir.makepyfile(**{pluginname: """
-            class HelloPlugin:
-                pass
-        """})
+        testdir.makepyfile(**{pluginname: ""})
         pluginmanager.import_plugin("hello")
         len1 = len(pluginmanager.getplugins())
         pluginmanager.import_plugin("pytest_hello")
         len2 = len(pluginmanager.getplugins())
         assert len1 == len2
         plugin1 = pluginmanager.getplugin("pytest_hello")
-        assert plugin1.__class__.__name__ == 'HelloPlugin'
+        assert plugin1.__name__.endswith('pytest_hello')
         plugin2 = pluginmanager.getplugin("hello")
         assert plugin2 is plugin1
 
     def test_consider_module(self, testdir):
         pluginmanager = PluginManager()
         testdir.syspathinsert()
-        testdir.makepyfile(pytest_plug1="class Plug1Plugin: pass")
-        testdir.makepyfile(pytest_plug2="class Plug2Plugin: pass")
+        testdir.makepyfile(pytest_plug1="#")
+        testdir.makepyfile(pytest_plug2="#")
         mod = py.std.new.module("temp")
         mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"]
         pluginmanager.consider_module(mod)
-        assert pluginmanager.getplugin("plug1").__class__.__name__ == "Plug1Plugin"
-        assert pluginmanager.getplugin("plug2").__class__.__name__ == "Plug2Plugin"
+        assert pluginmanager.getplugin("plug1").__name__ == "pytest_plug1"
+        assert pluginmanager.getplugin("plug2").__name__ == "pytest_plug2"
 
     def test_consider_module_import_module(self, testdir):
         mod = py.std.new.module("x")
         mod.pytest_plugins = "pytest_a"
-        aplugin = testdir.makepyfile(pytest_a="""class APlugin: pass""")
+        aplugin = testdir.makepyfile(pytest_a="#")
         pluginmanager = PluginManager() 
-        sorter = testdir.geteventrecorder(pluginmanager)
+        reprec = testdir.getreportrecorder(pluginmanager)
         #syspath.prepend(aplugin.dirpath())
         py.std.sys.path.insert(0, str(aplugin.dirpath()))
         pluginmanager.consider_module(mod)
-        call = sorter.getcall(pluginmanager.hook.pytest_plugin_registered.name)
-        assert call.plugin.__class__.__name__ == "APlugin"
+        call = reprec.getcall(pluginmanager.hook.pytest_plugin_registered.name)
+        assert call.plugin.__name__ == "pytest_a"
 
         # check that it is not registered twice 
         pluginmanager.consider_module(mod)
-        l = sorter.getcalls("plugin_registered")
+        l = reprec.getcalls("pytest_plugin_registered")
         assert len(l) == 1
 
-    def test_consider_conftest(self, testdir):
+    def test_consider_conftest_deprecated(self, testdir):
         pp = PluginManager()
-        mod = testdir.makepyfile("class ConftestPlugin: hello = 1").pyimport()
-        pp.consider_conftest(mod)
-        l = [x for x in pp.getplugins() if isinstance(x, mod.ConftestPlugin)]
-        assert len(l) == 1
-        assert l[0].hello == 1
-
-        pp.consider_conftest(mod)
-        l = [x for x in pp.getplugins() if isinstance(x, mod.ConftestPlugin)]
-        assert len(l) == 1
+        mod = testdir.makepyfile("class ConftestPlugin: pass").pyimport()
+        call = py.test.raises(ValueError, pp.consider_conftest, mod)
 
     def test_config_sets_conftesthandle_onimport(self, testdir):
         config = testdir.parseconfig([])
@@ -113,7 +102,8 @@
 
     def test_registry(self):
         pp = PluginManager()
-        a1, a2 = object(), object()
+        class A: pass 
+        a1, a2 = A(), A()
         pp.register(a1)
         assert pp.isregistered(a1)
         pp.register(a2)
@@ -124,33 +114,43 @@
         pp.unregister(a2)
         assert not pp.isregistered(a2)
 
-    def test_canonical_names(self):
-        for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
-            impname, clsname = canonical_names(name)
-            assert impname == "pytest_xyz"
-            assert clsname == "XyzPlugin"
+    def test_register_imported_modules(self):
+        pp = PluginManager()
+        mod = py.std.new.module("x.y.pytest_hello")
+        pp.register(mod)
+        assert pp.isregistered(mod)
+        assert pp.getplugins() == [mod]
+        py.test.raises(AssertionError, "pp.register(mod)")
+        mod2 = py.std.new.module("pytest_hello")
+        #pp.register(mod2) # double registry 
+        py.test.raises(AssertionError, "pp.register(mod)")
+        #assert not pp.isregistered(mod2)
+        assert pp.getplugins() == [mod] # does not actually modify plugins 
 
-    def test_registerplugin(self):
-        l = []
-        registerfunc = l.append
-        registerplugin(registerfunc, py.io, "TerminalWriter")
-        assert len(l) == 1
-        assert isinstance(l[0], py.io.TerminalWriter)
+    def test_register_mismatch_method(self):
+        pp = PluginManager()
+        class hello:
+            def pytest_gurgel(self):
+                pass
+        py.test.raises(pp.Error, "pp.register(hello())")
 
-    def test_importplugin(self):
-        assert importplugin("py") == py 
-        py.test.raises(ImportError, "importplugin('laksjd.qwe')")
-        mod = importplugin("pytest_terminal")
-        assert mod is py.__.test.plugin.pytest_terminal 
+    def test_register_mismatch_arg(self):
+        pp = PluginManager()
+        class hello:
+            def pytest_configure(self, asd):
+                pass
+        excinfo = py.test.raises(pp.Error, "pp.register(hello())")
 
+    def test_canonical_importname(self):
+        for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
+            impname = canonical_importname(name)
 
 class TestPytestPluginInteractions:
     def test_do_option_conftestplugin(self, testdir):
         from py.__.test.config import Config 
         p = testdir.makepyfile("""
-            class ConftestPlugin:
-                def pytest_addoption(self, parser):
-                    parser.addoption('--test123', action="store_true")
+            def pytest_addoption(parser):
+                parser.addoption('--test123', action="store_true")
         """)
         config = Config() 
         config._conftest.importconftest(p)
@@ -165,10 +165,9 @@
         config.pluginmanager.do_configure(config=config)
         assert not hasattr(config.option, 'test123')
         p = testdir.makepyfile("""
-            class ConftestPlugin:
-                def pytest_addoption(self, parser):
-                    parser.addoption('--test123', action="store_true", 
-                        default=True)
+            def pytest_addoption(parser):
+                parser.addoption('--test123', action="store_true", 
+                    default=True)
         """)
         config._conftest.importconftest(p)
         assert config.option.test123
@@ -198,35 +197,6 @@
 
     # lower level API
 
-    def test_getfirst(self):
-        pluginmanager = PluginManager()
-        class My1:
-            x = 1
-        assert pluginmanager.getfirst("x") is None
-        pluginmanager.register(My1())
-        assert pluginmanager.getfirst("x") == 1
-
-
-    def test_call_firstresult(self):
-        pluginmanager = PluginManager()
-        class My1:
-            def method(self):
-                pass
-        class My2:
-            def method(self):
-                return True 
-        class My3:
-            def method(self):
-                return None
-        assert pluginmanager.call_firstresult("method") is None
-        assert pluginmanager.call_firstresult("methodnotexists") is None
-        pluginmanager.register(My1())
-        assert pluginmanager.call_firstresult("method") is None
-        pluginmanager.register(My2())
-        assert pluginmanager.call_firstresult("method") == True
-        pluginmanager.register(My3())
-        assert pluginmanager.call_firstresult("method") == True
-
     def test_listattr(self):
         pluginmanager = PluginManager()
         class My2:
@@ -259,3 +229,14 @@
         results = call.execute()
         assert results == [1,2,2]
 
+def test_collectattr():
+    class A:
+        def pytest_hello(self):
+            pass
+    class B(A):
+        def pytest_world(self):
+            pass
+    methods = py.builtin.sorted(collectattr(B))
+    assert list(methods) == ['pytest_hello', 'pytest_world']
+    methods = py.builtin.sorted(collectattr(B()))
+    assert list(methods) == ['pytest_hello', 'pytest_world']

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 Jun 16 18:23:18 2009
@@ -6,7 +6,8 @@
     def test_module_file_not_found(self, testdir):
         tmpdir = testdir.tmpdir
         fn = tmpdir.join('nada','no')
-        col = py.test.collect.Module(fn, config=testdir.parseconfig(tmpdir))
+        col = py.test.collect.Module(fn)
+        col.config = testdir.parseconfig(tmpdir)
         py.test.raises(py.error.ENOENT, col.collect) 
 
     def test_failing_import(self, testdir):
@@ -179,8 +180,8 @@
                     yield list_append, i
                 yield assert_order_of_execution
         """)
-        sorter = testdir.inline_run(o)
-        passed, skipped, failed = sorter.countoutcomes() 
+        reprec = testdir.inline_run(o)
+        passed, skipped, failed = reprec.countoutcomes() 
         assert passed == 7
         assert not skipped and not failed 
 
@@ -209,27 +210,27 @@
                 yield list_append_2
                 yield assert_order_of_execution   
         """)
-        sorter = testdir.inline_run(o) 
-        passed, skipped, failed = sorter.countoutcomes() 
+        reprec = testdir.inline_run(o) 
+        passed, skipped, failed = reprec.countoutcomes() 
         assert passed == 4
         assert not skipped and not failed 
 
 class TestFunction:
     def test_getmodulecollector(self, testdir):
         item = testdir.getitem("def test_func(): pass")
-        modcol = item.getmodulecollector()
+        modcol = item.getparent(py.test.collect.Module)
         assert isinstance(modcol, py.test.collect.Module)
         assert hasattr(modcol.obj, 'test_func')
         
     def test_function_equality(self, tmpdir):
         config = py.test.config._reparse([tmpdir])
-        f1 = py.test.collect.Function(name="name", config=config, 
+        f1 = py.test.collect.Function(name="name", 
                                       args=(1,), callobj=isinstance)
-        f2 = py.test.collect.Function(name="name", config=config, 
+        f2 = py.test.collect.Function(name="name",
                                       args=(1,), callobj=callable)
         assert not f1 == f2
         assert f1 != f2
-        f3 = py.test.collect.Function(name="name", config=config, 
+        f3 = py.test.collect.Function(name="name", 
                                       args=(1,2), callobj=callable)
         assert not f3 == f2
         assert f3 != f2
@@ -237,13 +238,43 @@
         assert not f3 == f1
         assert f3 != f1
 
-        f1_b = py.test.collect.Function(name="name", config=config, 
+        f1_b = py.test.collect.Function(name="name", 
                                       args=(1,), callobj=isinstance)
         assert f1 == f1_b
         assert not f1 != f1_b
 
+    def test_function_equality_with_callspec(self, tmpdir):
+        config = py.test.config._reparse([tmpdir])
+        class callspec1:
+            param = 1
+            funcargs = {}
+            id = "hello"
+        class callspec2:
+            param = 1
+            funcargs = {}
+            id = "world"
+        f5 = py.test.collect.Function(name="name", 
+                                      callspec=callspec1, callobj=isinstance)
+        f5b = py.test.collect.Function(name="name", 
+                                      callspec=callspec2, callobj=isinstance)
+        assert f5 != f5b
+        assert not (f5 == f5b)
+
+    def test_pyfunc_call(self, testdir):
+        item = testdir.getitem("def test_func(): raise ValueError")
+        config = item.config
+        class MyPlugin1:
+            def pytest_pyfunc_call(self, pyfuncitem):
+                raise ValueError
+        class MyPlugin2:
+            def pytest_pyfunc_call(self, pyfuncitem):
+                return True
+        config.pluginmanager.register(MyPlugin1())
+        config.pluginmanager.register(MyPlugin2())
+        config.hook.pytest_pyfunc_call(pyfuncitem=item)
+
 class TestSorting:
-    def test_check_equality_and_cmp_basic(self, testdir):
+    def test_check_equality(self, testdir):
         modcol = testdir.getmodulecol("""
             def test_pass(): pass
             def test_fail(): assert 0
@@ -262,11 +293,7 @@
         assert isinstance(fn3, py.test.collect.Function)
         assert not (fn1 == fn3) 
         assert fn1 != fn3
-        assert cmp(fn1, fn3) == -1
 
-        assert cmp(fn1, 10) == -1 
-        assert cmp(fn2, 10) == -1 
-        assert cmp(fn3, 10) == -1 
         for fn in fn1,fn2,fn3:
             assert fn != 3
             assert fn != modcol
@@ -282,18 +309,18 @@
                 return g
 
 
-            def test_a(y):
-                pass
-            test_a = dec(test_a)
-
             def test_b(y):
                 pass
             test_b = dec(test_b)
+
+            def test_a(y):
+                pass
+            test_a = dec(test_a)
         """)
         colitems = modcol.collect()
         assert len(colitems) == 2
-        f1, f2 = colitems
-        assert cmp(f2, f1) > 0
+        assert [item.name for item in colitems] == ['test_b', 'test_a']
+
 
 class TestConftestCustomization:
     def test_extra_python_files_and_functions(self, testdir):
@@ -346,28 +373,28 @@
         assert colitems[0].name == "check_method"
 
 
-class TestMetaInfo:
+class TestReportinfo:
         
-    def test_func_metainfo(self, testdir):
+    def test_func_reportinfo(self, testdir):
         item = testdir.getitem("def test_func(): pass")
-        fspath, lineno, modpath = item.metainfo()
+        fspath, lineno, modpath = item.reportinfo()
         assert fspath == item.fspath 
         assert lineno == 0
         assert modpath == "test_func"
 
-    def test_class_metainfo(self, testdir):
+    def test_class_reportinfo(self, testdir):
         modcol = testdir.getmodulecol("""
             # lineno 0
             class TestClass:
                 def test_hello(self): pass
         """)
         classcol = modcol.collect_by_name("TestClass")
-        fspath, lineno, msg = classcol.metainfo()
+        fspath, lineno, msg = classcol.reportinfo()
         assert fspath == modcol.fspath 
         assert lineno == 1
         assert msg == "TestClass"
 
-    def test_generator_metainfo(self, testdir):
+    def test_generator_reportinfo(self, testdir):
         modcol = testdir.getmodulecol("""
             # lineno 0
             def test_gen(): 
@@ -376,13 +403,13 @@
                 yield check, 3
         """)
         gencol = modcol.collect_by_name("test_gen")
-        fspath, lineno, modpath = gencol.metainfo()
+        fspath, lineno, modpath = gencol.reportinfo()
         assert fspath == modcol.fspath
         assert lineno == 1
         assert modpath == "test_gen"
 
         genitem = gencol.collect()[0]
-        fspath, lineno, modpath = genitem.metainfo()
+        fspath, lineno, modpath = genitem.reportinfo()
         assert fspath == modcol.fspath
         assert lineno == 2
         assert modpath == "test_gen[0]"

Deleted: /py/trunk/py/test/testing/test_runner.py
==============================================================================
--- /py/trunk/py/test/testing/test_runner.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,92 +0,0 @@
-
-from py.__.test.config import SetupState
-
-class TestSetupState:
-    disabled = True
-    def test_setup_ok(self, testdir):
-        item = testdir.getitem("""
-            def setup_module(mod):
-                pass 
-            def test_func():
-                pass
-        """)
-        evrec = testdir.geteventrecorder(item.config)
-        setup = SetupState()
-        res = setup.do_setup(item)
-        assert res
-
-    def test_setup_fails(self, testdir):
-        item = testdir.getitem("""
-            def setup_module(mod):
-                print "world"
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        evrec = testdir.geteventrecorder(item.config)
-        setup = SetupState()
-        res = setup.do_setup(item)
-        assert not res
-        rep = evrec.popcall(pytest_itemsetupreport).rep
-        assert rep.failed
-        assert not rep.skipped
-        assert rep.excrepr 
-        assert "42" in str(rep.excrepr)
-        assert rep.outerr[0].find("world") != -1
-
-    def test_teardown_fails(self, testdir):
-        item = testdir.getitem("""
-            def test_func():
-                pass
-            def teardown_function(func): 
-                print "13"
-                raise ValueError(25)
-        """)
-        evrec = testdir.geteventrecorder(item.config)
-        setup = SetupState()
-        res = setup.do_setup(item)
-        assert res 
-        rep = evrec.popcall(pytest_itemsetupreport).rep
-        assert rep.passed
-        setup.do_teardown(item)
-        rep = evrec.popcall(pytest_itemsetupreport).rep
-        assert rep.item == item 
-        assert rep.failed 
-        assert not rep.passed
-        assert "13" in rep.outerr[0]
-        assert "25" in str(rep.excrepr)
-
-    def test_setupitem_skips(self, testdir):
-        item = testdir.getitem("""
-            import py
-            def setup_module(mod):
-                py.test.skip("17")
-            def test_func():
-                pass
-        """)
-        evrec = testdir.geteventrecorder(item.config)
-        setup = SetupState()
-        setup.do_setup(item)
-        rep = evrec.popcall(pytest_itemsetupreport).rep
-        assert not rep.failed
-        assert rep.skipped
-        assert rep.excrepr 
-        assert "17" in str(rep.excrepr)
-
-    def test_runtest_ok(self, testdir):
-        item = testdir.getitem("def test_func(): pass")
-        evrec = testdir.geteventrecorder(item.config)
-        setup = SetupState()
-        setup.do_fixture_and_runtest(item)
-        rep = evrec.popcall(pytest_itemtestreport).rep 
-        assert rep.passed 
-
-    def test_runtest_fails(self, testdir):
-        item = testdir.getitem("def test_func(): assert 0")
-        evrec = testdir.geteventrecorder(item.config)
-        setup = SetupState()
-        setup.do_fixture_and_runtest(item)
-        event = evrec.popcall(pytest_item_runtest_finished)
-        assert event.excinfo 
-        
-    

Deleted: /py/trunk/py/test/testing/test_runner_functional.py
==============================================================================
--- /py/trunk/py/test/testing/test_runner_functional.py	Tue Jun 16 18:23:18 2009
+++ (empty file)
@@ -1,263 +0,0 @@
-import py
-from py.__.test.runner import basic_run_report, forked_run_report, basic_collect_report
-from py.__.code.excinfo import ReprExceptionInfo
-
-class BaseTests:
-    def test_funcattr(self, testdir):
-        ev = testdir.runitem("""
-            import py
-            @py.test.mark(xfail="needs refactoring")
-            def test_func():
-                raise Exit()
-        """)
-        assert ev.keywords['xfail'] == "needs refactoring" 
-
-    def test_passfunction(self, testdir):
-        ev = testdir.runitem("""
-            def test_func():
-                pass
-        """)
-        assert ev.passed 
-        assert not ev.failed
-        assert ev.shortrepr == "."
-        assert not hasattr(ev, 'longrepr')
-                
-    def test_failfunction(self, testdir):
-        ev = testdir.runitem("""
-            def test_func():
-                assert 0
-        """)
-        assert not ev.passed 
-        assert not ev.skipped 
-        assert ev.failed 
-        assert ev.when == "execute"
-        assert isinstance(ev.longrepr, ReprExceptionInfo)
-        assert str(ev.shortrepr) == "F"
-
-    def test_skipfunction(self, testdir):
-        ev = testdir.runitem("""
-            import py
-            def test_func():
-                py.test.skip("hello")
-        """)
-        assert not ev.failed 
-        assert not ev.passed 
-        assert ev.skipped 
-        #assert ev.skipped.when == "execute"
-        #assert ev.skipped.when == "execute"
-        #assert ev.skipped == "%sreason == "hello"
-        #assert ev.skipped.location.lineno == 3
-        #assert ev.skipped.location.path
-        #assert not ev.skipped.failurerepr 
-
-    def test_skip_in_setup_function(self, testdir):
-        ev = testdir.runitem("""
-            import py
-            def setup_function(func):
-                py.test.skip("hello")
-            def test_func():
-                pass
-        """)
-        print ev
-        assert not ev.failed 
-        assert not ev.passed 
-        assert ev.skipped 
-        #assert ev.skipped.reason == "hello"
-        #assert ev.skipped.location.lineno == 3
-        #assert ev.skipped.location.lineno == 3
-
-    def test_failure_in_setup_function(self, testdir):
-        ev = testdir.runitem("""
-            import py
-            def setup_function(func):
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        print ev
-        assert not ev.skipped 
-        assert not ev.passed 
-        assert ev.failed 
-        assert ev.when == "setup"
-
-    def test_failure_in_teardown_function(self, testdir):
-        ev = testdir.runitem("""
-            import py
-            def teardown_function(func):
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        print ev
-        assert not ev.skipped 
-        assert not ev.passed 
-        assert ev.failed 
-        assert ev.when == "teardown" 
-        assert ev.longrepr.reprcrash.lineno == 3
-        assert ev.longrepr.reprtraceback.reprentries 
-
-    def test_custom_failure_repr(self, testdir):
-        testdir.makepyfile(conftest="""
-            import py
-            class Function(py.test.collect.Function):
-                def repr_failure(self, excinfo, outerr):
-                    return "hello" 
-        """)
-        ev = testdir.runitem("""
-            import py
-            def test_func():
-                assert 0
-        """)
-        assert not ev.skipped 
-        assert not ev.passed 
-        assert ev.failed 
-        #assert ev.outcome.when == "execute"
-        #assert ev.failed.where.lineno == 3
-        #assert ev.failed.where.path.basename == "test_func.py" 
-        #assert ev.failed.failurerepr == "hello"
-
-    def test_failure_in_setup_function_ignores_custom_failure_repr(self, testdir):
-        testdir.makepyfile(conftest="""
-            import py
-            class Function(py.test.collect.Function):
-                def repr_failure(self, excinfo):
-                    assert 0
-        """)
-        ev = testdir.runitem("""
-            import py
-            def setup_function(func):
-                raise ValueError(42)
-            def test_func():
-                pass
-        """)
-        print ev
-        assert not ev.skipped 
-        assert not ev.passed 
-        assert ev.failed 
-        #assert ev.outcome.when == "setup"
-        #assert ev.outcome.where.lineno == 3
-        #assert ev.outcome.where.path.basename == "test_func.py" 
-        #assert instanace(ev.failed.failurerepr, PythonFailureRepr)
-
-    def test_capture_in_func(self, testdir):
-        ev = testdir.runitem("""
-            import py
-            def setup_function(func):
-                print >>py.std.sys.stderr, "in setup"
-            def test_func():
-                print "in function"
-                assert 0
-            def teardown_func(func):
-                print "in teardown"
-        """)
-        assert ev.failed 
-        # out, err = ev.failed.outerr
-        # assert out == ['in function\nin teardown\n']
-        # assert err == ['in setup\n']
-        
-    def test_systemexit_does_not_bail_out(self, testdir):
-        try:
-            ev = testdir.runitem("""
-                def test_func():
-                    raise SystemExit(42)
-            """)
-        except SystemExit:
-            py.test.fail("runner did not catch SystemExit")
-        assert ev.failed
-        assert ev.when == "execute"
-
-    def test_exit_propagates(self, testdir):
-        from py.__.test.outcome import Exit
-        try:
-            testdir.runitem("""
-                from py.__.test.outcome import Exit
-                def test_func():
-                    raise Exit()
-            """)
-        except Exit:
-            pass
-        else: 
-            py.test.fail("did not raise")
-
-
-class TestExecutionNonForked(BaseTests):
-    def getrunner(self):
-        return basic_run_report 
-
-    def test_keyboardinterrupt_propagates(self, testdir):
-        from py.__.test.outcome import Exit
-        try:
-            testdir.runitem("""
-                def test_func():
-                    raise KeyboardInterrupt("fake")
-            """)
-        except KeyboardInterrupt, e:
-            pass
-        else: 
-            py.test.fail("did not raise")
-
-    def test_pdb_on_fail(self, testdir):
-        l = []
-        ev = testdir.runitem("""
-            def test_func():
-                assert 0
-        """, pdb=l.append)
-        assert ev.failed
-        assert ev.when == "execute"
-        assert len(l) == 1
-
-    def test_pdb_on_skip(self, testdir):
-        l = []
-        ev = testdir.runitem("""
-            import py
-            def test_func():
-                py.test.skip("hello")
-        """, pdb=l.append)
-        assert len(l) == 0
-        assert ev.skipped 
-
-class TestExecutionForked(BaseTests): 
-    def getrunner(self):
-        if not hasattr(py.std.os, 'fork'):
-            py.test.skip("no os.fork available")
-        return forked_run_report
-
-    def test_suicide(self, testdir):
-        ev = testdir.runitem("""
-            def test_func():
-                import os
-                os.kill(os.getpid(), 15)
-        """)
-        assert ev.failed
-        assert ev.when == "???"
-
-class TestCollectionEvent:
-    def test_collect_result(self, testdir):
-        col = testdir.getmodulecol("""
-            def test_func1():
-                pass
-            class TestClass:
-                pass
-        """)
-        ev = basic_collect_report(col)
-        assert not ev.failed
-        assert not ev.skipped
-        assert ev.passed 
-        res = ev.result 
-        assert len(res) == 2
-        assert res[0].name == "test_func1" 
-        assert res[1].name == "TestClass" 
-
-    def test_skip_at_module_scope(self, testdir):
-        col = testdir.getmodulecol("""
-            import py
-            py.test.skip("hello")
-            def test_func():
-                pass
-        """)
-        ev = basic_collect_report(col)
-        assert not ev.failed 
-        assert not ev.passed 
-        assert ev.skipped 
-
-

Modified: py/trunk/py/test/testing/test_session.py
==============================================================================
--- py/trunk/py/test/testing/test_session.py	(original)
+++ py/trunk/py/test/testing/test_session.py	Tue Jun 16 18:23:18 2009
@@ -17,17 +17,17 @@
             def test_two(someargs):
                 pass
         """)
-        sorter = testdir.inline_run(tfile)
-        passed, skipped, failed = sorter.listoutcomes()
+        reprec = testdir.inline_run(tfile)
+        passed, skipped, failed = reprec.listoutcomes()
         assert len(skipped) == 0
         assert len(passed) == 1
         assert len(failed) == 3  
-        assert failed[0].colitem.name == "test_one_one"
-        assert failed[1].colitem.name == "test_other"
-        assert failed[2].colitem.name == "test_two"
-        itemstarted = sorter.getcalls("itemstart")
+        assert failed[0].item.name == "test_one_one"
+        assert failed[1].item.name == "test_other"
+        assert failed[2].item.name == "test_two"
+        itemstarted = reprec.getcalls("pytest_itemstart")
         assert len(itemstarted) == 4
-        colstarted = sorter.getcalls("collectstart")
+        colstarted = reprec.getcalls("pytest_collectstart")
         assert len(colstarted) == 1
         col = colstarted[0].collector
         assert isinstance(col, py.test.collect.Module)
@@ -41,19 +41,19 @@
             import does_not_work 
             a = 1
         """)
-        sorter = testdir.inline_run(tfile)
-        l = sorter.getfailedcollections()
+        reprec = testdir.inline_run(tfile)
+        l = reprec.getfailedcollections()
         assert len(l) == 1 
         out = l[0].longrepr.reprcrash.message
         assert out.find('does_not_work') != -1 
 
     def test_raises_output(self, testdir): 
-        sorter = testdir.inline_runsource("""
+        reprec = testdir.inline_runsource("""
             import py
             def test_raises_doesnt():
                 py.test.raises(ValueError, int, "3")
         """)
-        passed, skipped, failed = sorter.listoutcomes()
+        passed, skipped, failed = reprec.listoutcomes()
         assert len(failed) == 1
         out = failed[0].longrepr.reprcrash.message
         if not out.find("DID NOT RAISE") != -1: 
@@ -61,28 +61,28 @@
             py.test.fail("incorrect raises() output") 
 
     def test_generator_yields_None(self, testdir): 
-        sorter = testdir.inline_runsource("""
+        reprec = testdir.inline_runsource("""
             def test_1():
                 yield None 
         """)
-        failures = sorter.getfailedcollections()
+        failures = reprec.getfailedcollections()
         out = failures[0].longrepr.reprcrash.message
         i = out.find('TypeError') 
         assert i != -1 
 
     def test_syntax_error_module(self, testdir): 
-        sorter = testdir.inline_runsource("this is really not python")
-        l = sorter.getfailedcollections()
+        reprec = testdir.inline_runsource("this is really not python")
+        l = reprec.getfailedcollections()
         assert len(l) == 1
         out = l[0].longrepr.reprcrash.message
         assert out.find(str('not python')) != -1
 
     def test_exit_first_problem(self, testdir): 
-        sorter = testdir.inline_runsource("""
+        reprec = testdir.inline_runsource("""
             def test_one(): assert 0
             def test_two(): assert 0
         """, '--exitfirst')
-        passed, skipped, failed = sorter.countoutcomes()
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == 1
         assert passed == skipped == 0
 
@@ -111,8 +111,8 @@
                     t = BrokenRepr2()
                     assert t.foo == 1
         """)
-        sorter = testdir.inline_run(p)
-        passed, skipped, failed = sorter.listoutcomes()
+        reprec = testdir.inline_run(p)
+        passed, skipped, failed = reprec.listoutcomes()
         assert len(failed) == 2
         out = failed[0].longrepr.reprcrash.message
         assert out.find("""[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]""") != -1 #'
@@ -129,31 +129,15 @@
         """, test_file="""
             def test_one(): pass
         """)
-        sorter = testdir.inline_run(testdir.tmpdir)
-        reports = sorter.getreports("collectreport")
+        reprec = testdir.inline_run(testdir.tmpdir)
+        reports = reprec.getreports("pytest_collectreport")
         assert len(reports) == 1
         assert reports[0].skipped 
 
 class TestNewSession(SessionTests):
-    def test_pdb_run(self, testdir, monkeypatch):
-        import py.__.test.custompdb
-        tfile = testdir.makepyfile("""
-            def test_usepdb(): 
-                assert 0
-        """)
-        l = []
-        def mypdb(*args):
-            l.append(args)
-        monkeypatch.setattr(py.__.test.custompdb, 'post_mortem', mypdb)
-        sorter = testdir.inline_run('--pdb', tfile)
-        rep = sorter.matchreport("test_usepdb")
-        assert rep.failed
-        assert len(l) == 1
-        tb = py.code.Traceback(l[0][0])
-        assert tb[-1].name == "test_usepdb" 
 
     def test_order_of_execution(self, testdir): 
-        sorter = testdir.inline_runsource("""
+        reprec = testdir.inline_runsource("""
             l = []
             def test_1():
                 l.append(1)
@@ -172,7 +156,7 @@
                 def test_4(self):
                     assert self.reslist == [1,2,1,2,3]
         """)
-        passed, skipped, failed = sorter.countoutcomes()
+        passed, skipped, failed = reprec.countoutcomes()
         assert failed == skipped == 0
         assert passed == 7
         # also test listnames() here ... 
@@ -197,13 +181,13 @@
             test_three="xxxdsadsadsadsa",
             __init__=""
         )
-        sorter = testdir.inline_run('--collectonly', p.dirpath())
+        reprec = testdir.inline_run('--collectonly', p.dirpath())
        
-        itemstarted = sorter.getcalls("pytest_itemstart")
+        itemstarted = reprec.getcalls("pytest_itemstart")
         assert len(itemstarted) == 3
-        assert not sorter.getreports("itemtestreport") 
-        started = sorter.getcalls("pytest_collectstart")
-        finished = sorter.getreports("collectreport")
+        assert not reprec.getreports("pytest_runtest_logreport") 
+        started = reprec.getcalls("pytest_collectstart")
+        finished = reprec.getreports("pytest_collectreport")
         assert len(started) == len(finished) 
         assert len(started) == 8 
         colfail = [x for x in finished if x.failed]
@@ -214,8 +198,8 @@
     def test_minus_x_import_error(self, testdir):
         testdir.makepyfile(__init__="")
         testdir.makepyfile(test_one="xxxx", test_two="yyyy")
-        sorter = testdir.inline_run("-x", testdir.tmpdir)
-        finished = sorter.getreports("collectreport")
+        reprec = testdir.inline_run("-x", testdir.tmpdir)
+        finished = reprec.getreports("pytest_collectreport")
         colfail = [x for x in finished if x.failed]
         assert len(colfail) == 1
 

Modified: py/trunk/py/test/testing/test_setup_functional.py
==============================================================================
--- py/trunk/py/test/testing/test_setup_functional.py	(original)
+++ py/trunk/py/test/testing/test_setup_functional.py	Tue Jun 16 18:23:18 2009
@@ -3,7 +3,7 @@
 # module, class, and instance level
 
 def test_module_and_function_setup(testdir):
-    sorter = testdir.inline_runsource(""" 
+    reprec = testdir.inline_runsource(""" 
         modlevel = []
         def setup_module(module):
             assert not modlevel
@@ -27,13 +27,13 @@
                 assert modlevel[0] == 42
                 assert not hasattr(test_modlevel, 'answer') 
     """)
-    rep = sorter.matchreport("test_modlevel") 
+    rep = reprec.matchreport("test_modlevel") 
     assert rep.passed 
-    rep = sorter.matchreport("test_module") 
+    rep = reprec.matchreport("test_module") 
     assert rep.passed 
 
 def test_class_setup(testdir):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         class TestSimpleClassSetup:
             clslevel = []
             def setup_class(cls):
@@ -53,10 +53,10 @@
             assert not TestSimpleClassSetup.clslevel 
             assert not TestInheritedClassSetupStillWorks.clslevel
     """)
-    sorter.assertoutcome(passed=1+2+1)
+    reprec.assertoutcome(passed=1+2+1)
 
 def test_method_setup(testdir):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         class TestSetupMethod:
             def setup_method(self, meth):
                 self.methsetup = meth 
@@ -69,10 +69,10 @@
             def test_other(self):
                 assert self.methsetup == self.test_other
     """)
-    sorter.assertoutcome(passed=2)
+    reprec.assertoutcome(passed=2)
        
 def test_method_generator_setup(testdir):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         class TestSetupTeardownOnInstance: 
             def setup_class(cls):
                 cls.classsetup = True 
@@ -91,10 +91,10 @@
                 assert self.methsetup == self.test_generate 
                 assert value == 5
     """)
-    sorter.assertoutcome(passed=1, failed=1)
+    reprec.assertoutcome(passed=1, failed=1)
 
 def test_func_generator_setup(testdir):
-    sorter = testdir.inline_runsource(""" 
+    reprec = testdir.inline_runsource(""" 
         import sys
 
         def setup_module(mod):
@@ -118,11 +118,11 @@
             yield check 
             assert x == [1]
     """)
-    rep = sorter.matchreport("test_one", names="itemtestreport") 
+    rep = reprec.matchreport("test_one", names="pytest_runtest_logreport") 
     assert rep.passed 
         
 def test_method_setup_uses_fresh_instances(testdir):
-    sorter = testdir.inline_runsource("""
+    reprec = testdir.inline_runsource("""
         class TestSelfState1: 
             def __init__(self):
                 self.hello = 42
@@ -137,5 +137,5 @@
             def test_world(self):
                 assert not hasattr(self, 'world')
     """)
-    sorter.assertoutcome(passed=4, failed=0)
+    reprec.assertoutcome(passed=4, failed=0)
 

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 Jun 16 18:23:18 2009
@@ -9,21 +9,20 @@
 
     def test_traceback_argsetup(self, testdir):
         testdir.makeconftest("""
-            class ConftestPlugin:
-                def pytest_funcarg__hello(self, request):
-                    raise ValueError("xyz")
+            def pytest_funcarg__hello(request):
+                raise ValueError("xyz")
         """)
         p = testdir.makepyfile("def test(hello): pass")
         result = testdir.runpytest(p)
         assert result.ret != 0
         out = result.stdout.str()
         assert out.find("xyz") != -1
-        assert out.find("conftest.py:3: ValueError") != -1
+        assert out.find("conftest.py:2: ValueError") != -1
         numentries = out.count("_ _ _") # separator for traceback entries
         assert numentries == 0
 
         result = testdir.runpytest("--fulltrace", p)
         out = result.stdout.str()
-        assert out.find("conftest.py:3: ValueError") != -1
+        assert out.find("conftest.py:2: ValueError") != -1
         numentries = out.count("_ _ _ _") # separator for traceback entries
         assert numentries >3

Modified: py/trunk/py/thread/testing/test_io.py
==============================================================================
--- py/trunk/py/thread/testing/test_io.py	(original)
+++ py/trunk/py/thread/testing/test_io.py	Tue Jun 16 18:23:18 2009
@@ -12,55 +12,61 @@
     assert old == sys.stdout
 
 class TestThreadOut:
-    def setup_method(self, method):
-        self.out = ThreadOut(sys, 'stdout')
-    def teardown_method(self, method):
-        self.out.deinstall()
-
     def test_threadout_one(self):
-        l = []
-        self.out.setwritefunc(l.append)
-        print 42,13,
-        x = l.pop(0)
-        assert x == '42'
-        x = l.pop(0)
-        assert x == ' '
-        x = l.pop(0)
-        assert x == '13'
-
+        out = ThreadOut(sys, 'stdout')
+        try:
+            l = []
+            out.setwritefunc(l.append)
+            print 42,13,
+            x = l.pop(0)
+            assert x == '42'
+            x = l.pop(0)
+            assert x == ' '
+            x = l.pop(0)
+            assert x == '13'
+        finally:
+            out.deinstall()
 
     def test_threadout_multi_and_default(self):
-        num = 3
-        defaults = []
-        def f(l):
-            self.out.setwritefunc(l.append)
-            print id(l),
-            self.out.delwritefunc()
-            print 1
-        self.out.setdefaultwriter(defaults.append)
-        pool = WorkerPool()
-        listlist = []
-        for x in range(num):
-            l = []
-            listlist.append(l)
-            pool.dispatch(f, l)
-        pool.shutdown()
-        for name, value in self.out.__dict__.items():
-            print >>sys.stderr, "%s: %s" %(name, value)
-        pool.join(2.0)
-        for i in range(num):
-            item = listlist[i]
-            assert item ==[str(id(item))]
-        assert not self.out._tid2out
-        assert defaults
-        expect = ['1' for x in range(num)]
-        defaults = [x for x in defaults if x.strip()]
-        assert defaults == expect
+        out = ThreadOut(sys, 'stdout')
+        try:
+            num = 3
+            defaults = []
+            def f(l):
+                out.setwritefunc(l.append)
+                print id(l),
+                out.delwritefunc()
+                print 1
+            out.setdefaultwriter(defaults.append)
+            pool = WorkerPool()
+            listlist = []
+            for x in range(num):
+                l = []
+                listlist.append(l)
+                pool.dispatch(f, l)
+            pool.shutdown()
+            for name, value in out.__dict__.items():
+                print >>sys.stderr, "%s: %s" %(name, value)
+            pool.join(2.0)
+            for i in range(num):
+                item = listlist[i]
+                assert item ==[str(id(item))]
+            assert not out._tid2out
+            assert defaults
+            expect = ['1' for x in range(num)]
+            defaults = [x for x in defaults if x.strip()]
+            assert defaults == expect
+        finally:
+            out.deinstall()
 
     def test_threadout_nested(self):
-        # we want ThreadOuts to coexist
-        last = sys.stdout
-        out = ThreadOut(sys, 'stdout')
-        assert last == sys.stdout
-        out.deinstall()
-        assert last == sys.stdout
+        out1 = ThreadOut(sys, 'stdout')
+        try:
+            # we want ThreadOuts to coexist
+            last = sys.stdout
+            out = ThreadOut(sys, 'stdout')
+            assert last == sys.stdout
+            out.deinstall()
+            assert last == sys.stdout
+        finally:
+            out1.deinstall()

Modified: py/trunk/setup.py
==============================================================================
--- py/trunk/setup.py	(original)
+++ py/trunk/setup.py	Tue Jun 16 18:23:18 2009
@@ -1,27 +1,25 @@
 """
     setup file for 'py' package based on:
 
-        https://codespeak.net/svn/py/trunk, revision=64088
+        https://codespeak.net/svn/py/trunk, revision=65224
 
     autogenerated by gensetup.py
 """
 import os, sys
         
-from distutils.core import setup, Extension
+from distutils.core import setup
             
 long_description = """
 
-The py lib is an extensible library for testing, distributed processing and 
-interacting with filesystems. 
+advanced testing and development support library: 
 
 - `py.test`_: cross-project testing tool with many advanced features
 - `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes
 - `py.path`_: path abstractions over local and subversion files 
 - `py.code`_: dynamic code compile and traceback printing support
 
-The py lib and its tools should work well on Linux, Win32, 
-OSX, Python versions 2.3-2.6.  For questions please go to
-http://pylib.org/contact.html
+Compatibility: Linux, Win32, OSX, Python versions 2.3-2.6. 
+For questions please check out http://pylib.org/contact.html
 
 .. _`py.test`: http://pylib.org/test.html
 .. _`py.execnet`: http://pylib.org/execnet.html
@@ -33,11 +31,10 @@
 def main():
     setup(
         name='py',
-        description='pylib and py.test: agile development and test support library',
+        description='py.test and pylib: advanced testing tool and networking lib',
         long_description = long_description, 
         version='1.0.0b2', 
         url='http://pylib.org', 
-        download_url='http://codespeak.net/py/1.0.0b2/download.html', 
         license='MIT license',
         platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], 
         author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
@@ -148,6 +145,45 @@
                              'rest/testing/data/tocdepth.rst2pdfconfig']},
     )
 
+def getscripts():
+    if sys.platform == "win32":
+        base = "py/bin/win32/"
+        ext = ".cmd"
+    else:
+        base = "py/bin/"
+        ext = ""
+    l = []
+    for name in ['py.countloc', 'py.svnwcrevert', 'py.rest', 'py.test', 'py.cleanup', 'py.lookup', 'py.which']:
+        l.append(base + name + ext)
+    return l 
+        
+# scripts for windows: turn "py.SCRIPT" into "py_SCRIPT" and create
+# "py.SCRIPT.cmd" files invoking "py_SCRIPT"
+from distutils.command.install_scripts import install_scripts
+class my_install_scripts(install_scripts):
+    def run(self):
+        install_scripts.run(self)
+        #print self.outfiles
+        for fn in self.outfiles:
+            basename = os.path.basename(fn)
+            if basename.startswith("py.") and not basename.endswith(".cmd"):
+                newbasename = basename.replace(".", "_")
+                newfn = os.path.join(os.path.dirname(fn), newbasename)
+                if os.path.exists(newfn):
+                    os.remove(newfn)
+                os.rename(fn, newfn)
+                fncmd = fn + ".cmd"
+                if os.path.exists(fncmd):
+                   os.remove(fncmd)
+                f = open(fncmd, 'w')
+                f.write("@echo off\n")
+                f.write('python "%%~dp0\%s" %%*' %(newbasename))
+                f.close()
+if sys.platform == "win32":
+    cmdclass = {'install_scripts': my_install_scripts}
+else:
+    cmdclass = {}
+        
 if __name__ == '__main__':
     main()
         
\ No newline at end of file



More information about the pytest-commit mailing list