[py-svn] r66740 - in py/trunk: . doc doc/announce doc/test doc/test/plugin example/assertion example/execnet example/funcarg example/funcarg/costlysetup/sub1 example/funcarg/costlysetup/sub2 py py/code py/code/testing py/execnet py/execnet/testing py/io py/io/testing py/magic py/magic/testing py/path py/path/svn py/process/testing py/test py/test/dist py/test/dist/testing py/test/looponfail py/test/plugin py/test/testing

hpk at codespeak.net hpk at codespeak.net
Tue Aug 4 19:45:35 CEST 2009


Author: hpk
Date: Tue Aug  4 19:45:30 2009
New Revision: 66740

Added:
   py/trunk/doc/test/plugin/
   py/trunk/doc/test/plugin/capture.txt
   py/trunk/doc/test/plugin/doctest.txt
   py/trunk/doc/test/plugin/figleaf.txt
   py/trunk/doc/test/plugin/hooklog.txt
   py/trunk/doc/test/plugin/hookspec.txt
   py/trunk/doc/test/plugin/index.txt
   py/trunk/doc/test/plugin/keyword.txt
   py/trunk/doc/test/plugin/links.txt
   py/trunk/doc/test/plugin/monkeypatch.txt
   py/trunk/doc/test/plugin/oejskit.txt
   py/trunk/doc/test/plugin/pastebin.txt
   py/trunk/doc/test/plugin/pdb.txt
   py/trunk/doc/test/plugin/recwarn.txt
   py/trunk/doc/test/plugin/restdoc.txt
   py/trunk/doc/test/plugin/resultlog.txt
   py/trunk/doc/test/plugin/terminal.txt
   py/trunk/doc/test/plugin/unittest.txt
   py/trunk/doc/test/plugin/xfail.txt
   py/trunk/example/assertion/
   py/trunk/example/assertion/failure_demo.py
   py/trunk/example/assertion/test_failures.py
   py/trunk/example/assertion/test_setup_flow_example.py
   py/trunk/example/execnet/redirect_remote_output.py
   py/trunk/example/execnet/svn-sync-repo.py
   py/trunk/example/execnet/sysinfo.py
   py/trunk/example/funcarg/conftest.py
   py/trunk/example/funcarg/costlysetup/sub1/__init__.py
   py/trunk/example/funcarg/costlysetup/sub2/__init__.py
   py/trunk/py/test/dist/testing/acceptance_test.py
   py/trunk/py/test/plugin/pytest_capture.py
   py/trunk/py/test/plugin/pytest_keyword.py
   py/trunk/py/test/plugin/pytest_pastebin.py
   py/trunk/py/test/plugin/test_pytest_capture.py
   py/trunk/py/test/plugin/test_pytest_runner_xunit.py
   py/trunk/py/test/plugin/test_pytest_terminal.py
   py/trunk/py/test/testing/test_install.py
Removed:
   py/trunk/ez_setup.py
   py/trunk/py/test/plugin/pytest_iocapture.py
   py/trunk/py/test/plugin/pytest_pocoo.py
   py/trunk/py/test/plugin/pytest_resultdb.py
   py/trunk/py/test/testing/test_setup_functional.py
Modified:
   py/trunk/.hgignore
   py/trunk/CHANGELOG
   py/trunk/MANIFEST
   py/trunk/doc/announce/release-1.0.0.txt
   py/trunk/doc/code.txt
   py/trunk/doc/confrest.py
   py/trunk/doc/contact.txt
   py/trunk/doc/download.txt
   py/trunk/doc/execnet.txt
   py/trunk/doc/index.txt
   py/trunk/doc/style.css
   py/trunk/doc/test/config.txt
   py/trunk/doc/test/extend.txt
   py/trunk/doc/test/features.txt
   py/trunk/doc/test/funcargs.txt
   py/trunk/doc/test/quickstart.txt
   py/trunk/doc/test/test.txt
   py/trunk/py/__init__.py
   py/trunk/py/code/excinfo.py
   py/trunk/py/code/testing/test_excinfo.py
   py/trunk/py/conftest.py
   py/trunk/py/execnet/register.py
   py/trunk/py/execnet/testing/test_gateway.py
   py/trunk/py/io/stdcapture.py
   py/trunk/py/io/terminalwriter.py
   py/trunk/py/io/testing/test_stdcapture.py
   py/trunk/py/magic/exprinfo.py
   py/trunk/py/magic/testing/test_exprinfo.py
   py/trunk/py/path/common.py
   py/trunk/py/path/svn/wccommand.py
   py/trunk/py/process/testing/test_forkedfunc.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/mypickle.py
   py/trunk/py/test/dist/testing/test_dsession.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/plugin/hookspec.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_hooklog.py
   py/trunk/py/test/plugin/pytest_monkeypatch.py
   py/trunk/py/test/plugin/pytest_pdb.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_resultlog.py
   py/trunk/py/test/plugin/pytest_runner.py
   py/trunk/py/test/plugin/pytest_terminal.py
   py/trunk/py/test/plugin/pytest_unittest.py
   py/trunk/py/test/plugin/pytest_xfail.py
   py/trunk/py/test/plugin/test_pytest_runner.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_config.py
   py/trunk/py/test/testing/test_funcargs.py
   py/trunk/py/test/testing/test_outcome.py
   py/trunk/py/test/testing/test_parseopt.py
   py/trunk/py/test/testing/test_pluginmanager.py
   py/trunk/py/test/testing/test_pycollect.py
   py/trunk/setup.py
Log:
snapshotting mercurial py-trunk (1.0.0) to py/trunk 


Modified: py/trunk/.hgignore
==============================================================================
--- py/trunk/.hgignore	(original)
+++ py/trunk/.hgignore	Tue Aug  4 19:45:30 2009
@@ -11,3 +11,6 @@
 *.pyo
 *.swp
 *.html
+
+build/
+py.egg-info

Modified: py/trunk/CHANGELOG
==============================================================================
--- py/trunk/CHANGELOG	(original)
+++ py/trunk/CHANGELOG	Tue Aug  4 19:45:30 2009
@@ -1,6 +1,81 @@
-Changes between 1.0.0b3 and 1.0.0
+Changes between 1.0.0b9 and 1.0.0
+=====================================
+
+* more terse reporting try to show filesystem path relatively to current dir 
+* improve xfail output a bit
+
+Changes between 1.0.0b8 and 1.0.0b9
+=====================================
+
+* cleanly handle and report final teardown of test setup
+
+* fix svn-1.6 compat issue with py.path.svnwc().versioned()
+  (thanks Wouter Vanden Hove)
+
+* setup/teardown or collection problems now show as ERRORs
+  or with big "E"'s in the progress lines.  they are reported
+  and counted separately. 
+ 
+* dist-testing: properly handle test items that get locally 
+  collected but cannot be collected on the remote side - often 
+  due to platform/dependency reasons
+
+* simplified py.test.mark API - see keyword plugin documentation
+
+* integrate better with logging: capturing now by default captures
+  test functions and their immediate setup/teardown in a single stream 
+
+* capsys and capfd funcargs now have a readouterr() and a close() method
+  (underlyingly py.io.StdCapture/FD objects are used which grew a 
+  readouterr() method as well to return snapshots of captured out/err)
+
+* make assert-reinterpretation work better with comparisons not 
+  returning bools (reported with numpy from thanks maciej fijalkowski)
+
+* reworked per-test output capturing into the pytest_iocapture.py plugin 
+  and thus removed capturing code from config object 
+
+* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
+
+
+Changes between 1.0.0b7 and 1.0.0b8
+=====================================
+
+* pytest_unittest-plugin is now enabled by default
+
+* introduced pytest_keyboardinterrupt hook and 
+  refined pytest_sessionfinish hooked, added tests. 
+
+* workaround a buggy logging module interaction ("closing already closed
+  files").  Thanks to Sridhar Ratnakumar for triggering. 
+
+* if plugins use "py.test.importorskip" for importing 
+  a dependency only a warning will be issued instead 
+  of exiting the testing process. 
+
+* many improvements to docs: 
+  - refined funcargs doc , use the term "factory" instead of "provider"
+  - added a new talk/tutorial doc page 
+  - better download page
+  - better plugin docstrings
+  - added new plugins page and automatic doc generation script
+
+* fixed teardown problem related to partially failing funcarg setups 
+  (thanks MrTopf for reporting), "pytest_runtest_teardown" is now 
+  always invoked even if the "pytest_runtest_setup" failed. 
+
+* tweaked doctest output for docstrings in py modules, 
+  thanks Radomir. 
+
+Changes between 1.0.0b3 and 1.0.0b7
 =============================================
 
+* renamed py.test.xfail back to py.test.mark.xfail to avoid 
+  two ways to decorate for xfail
+
+* re-added py.test.mark decorator for setting keywords on functions 
+  (it was actually documented so removing it was not nice) 
+
 * remove scope-argument from request.addfinalizer() because 
   request.cached_setup has the scope arg. TOOWTDI. 
 

Modified: py/trunk/MANIFEST
==============================================================================
--- py/trunk/MANIFEST	(original)
+++ py/trunk/MANIFEST	Tue Aug  4 19:45:30 2009
@@ -1,10 +1,6 @@
-MANIFEST
-py/__init__.py
-setup.py
-.hgignore
-.hgtags
 CHANGELOG
 LICENSE
+MANIFEST
 README.txt
 _findpy.py
 doc/announce/release-0.9.0.txt
@@ -32,13 +28,41 @@
 doc/test/extend.txt
 doc/test/features.txt
 doc/test/funcargs.txt
+doc/test/plugin/capture.txt
+doc/test/plugin/doctest.txt
+doc/test/plugin/figleaf.txt
+doc/test/plugin/hooklog.txt
+doc/test/plugin/hookspec.txt
+doc/test/plugin/index.txt
+doc/test/plugin/keyword.txt
+doc/test/plugin/links.txt
+doc/test/plugin/monkeypatch.txt
+doc/test/plugin/oejskit.txt
+doc/test/plugin/pastebin.txt
+doc/test/plugin/pdb.txt
+doc/test/plugin/recwarn.txt
+doc/test/plugin/restdoc.txt
+doc/test/plugin/resultlog.txt
+doc/test/plugin/terminal.txt
+doc/test/plugin/unittest.txt
+doc/test/plugin/xfail.txt
 doc/test/quickstart.txt
+doc/test/talks.txt
 doc/test/test.txt
 doc/test/xunit_setup.txt
 doc/xml.txt
+example/assertion/failure_demo.py
+example/assertion/test_failures.py
+example/assertion/test_setup_flow_example.py
 example/execnet/popen_read_multiple.py
+example/execnet/redirect_remote_output.py
+example/execnet/svn-sync-repo.py
+example/execnet/sysinfo.py
+example/funcarg/conftest.py
 example/funcarg/costlysetup/conftest.py
+example/funcarg/costlysetup/sub1/__init__.py
 example/funcarg/costlysetup/sub1/test_quick.py
+example/funcarg/costlysetup/sub2/__init__.py
 example/funcarg/costlysetup/sub2/test_two.py
 example/funcarg/mysetup/__init__.py
 example/funcarg/mysetup/conftest.py
@@ -56,11 +80,9 @@
 example/genhtml.py
 example/genhtmlcss.py
 example/genxml.py
-example/pytest/failure_demo.py
-example/pytest/test_failures.py
-example/pytest/test_setup_flow_example.py
-ez_setup.py
+makepluginlist.py
 py/LICENSE
+py/__init__.py
 py/_com.py
 py/bin/_findpy.py
 py/bin/_genscripts.py
@@ -296,6 +318,7 @@
 py/test/dist/mypickle.py
 py/test/dist/nodemanage.py
 py/test/dist/testing/__init__.py
+py/test/dist/testing/acceptance_test.py
 py/test/dist/testing/test_dsession.py
 py/test/dist/testing/test_mypickle.py
 py/test/dist/testing/test_nodemanage.py
@@ -314,27 +337,30 @@
 py/test/plugin/conftest.py
 py/test/plugin/hookspec.py
 py/test/plugin/pytest__pytest.py
+py/test/plugin/pytest_capture.py
 py/test/plugin/pytest_default.py
 py/test/plugin/pytest_doctest.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_keyword.py
 py/test/plugin/pytest_monkeypatch.py
+py/test/plugin/pytest_pastebin.py
 py/test/plugin/pytest_pdb.py
-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
 py/test/plugin/pytest_runner.py
 py/test/plugin/pytest_terminal.py
 py/test/plugin/pytest_tmpdir.py
 py/test/plugin/pytest_unittest.py
 py/test/plugin/pytest_xfail.py
+py/test/plugin/test_pytest_capture.py
 py/test/plugin/test_pytest_runner.py
+py/test/plugin/test_pytest_runner_xunit.py
+py/test/plugin/test_pytest_terminal.py
 py/test/pluginmanager.py
 py/test/pycollect.py
 py/test/session.py
@@ -353,6 +379,7 @@
 py/test/testing/test_deprecated_api.py
 py/test/testing/test_funcargs.py
 py/test/testing/test_genitems.py
+py/test/testing/test_install.py
 py/test/testing/test_outcome.py
 py/test/testing/test_parseopt.py
 py/test/testing/test_pickling.py
@@ -360,7 +387,6 @@
 py/test/testing/test_pycollect.py
 py/test/testing/test_recording.py
 py/test/testing/test_session.py
-py/test/testing/test_setup_functional.py
 py/test/testing/test_traceback.py
 py/test/web/__init__.py
 py/test/web/exception.py
@@ -383,4 +409,5 @@
 py/xmlobj/testing/test_html.py
 py/xmlobj/testing/test_xml.py
 py/xmlobj/visit.py
-py/xmlobj/xml.py
\ No newline at end of file
+py/xmlobj/xml.py
+setup.py
\ No newline at end of file

Modified: py/trunk/doc/announce/release-1.0.0.txt
==============================================================================
--- py/trunk/doc/announce/release-1.0.0.txt	(original)
+++ py/trunk/doc/announce/release-1.0.0.txt	Tue Aug  4 19:45:30 2009
@@ -1,54 +1,63 @@
-py.test / py lib 1.0.0: new test plugins, funcargs and cleanups 
-============================================================================
 
-Welcome to the 1.0 release bringing new flexibility and 
-power to testing with Python.  Main news: 
+pylib 1.0.0 released: testing-with-python innovations continue
+--------------------------------------------------------------------
 
-* improved test architecture, featuring super-simple project 
-  specific or cross-project single-file plugins, e.g:
+Took a few betas but finally i uploaded a `1.0.0 py lib release`_,
+featuring the mature and powerful py.test tool and "execnet-style"
+*elastic* distributed programming.  With the new release, there are
+many new advanced automated testing features - here is a quick summary:
 
-  * pytest_unittest.py: run traditional unittest.py tests 
-  * pytest_xfail.py: mark tests as "expected to fail" 
-  * pytest_pocoo.py: automatically send tracebacks to pocoo paste service 
-  * pytest_monkeypatch.py: safely patch parts of your environment in a test function
-  * pytest_figleaf.py: generate html coverage reports 
-  * pytest_resultlog.py: generate buildbot-friendly output 
+* funcargs_ - pythonic zero-boilerplate fixtures for Python test functions :
 
-  and many more!
-
-* funcargs - bringing new flexibilty and zero-boilerplate to Python testing: 
-
-  - cleanly separated test code and test configuration and test value setup 
+  - totally separates test code, test configuration and test setup 
   - ideal for integration and functional tests
-  - new generative tests -> deprecation of yield-generated tests
+  - allows for flexible and natural test parametrization schemes
 
-* distributed testing and distributed execution (py.execnet): 
+* new `plugin architecture`_, allowing easy-to-write project-specific and cross-project single-file plugins.   The most notable new external plugin is `oejskit`_ which naturally enables **running and reporting of javascript-unittests in real-life browsers**. 
 
-  - new unified "TX" URL scheme for specifying remote resources 
-  - new sync/async ways to handle multiple remote processes 
-  - much improved documentation 
+* many new features done in easy-to-improve `default plugins`_, highlights:
 
+  * xfail: mark tests as "expected to fail" and report separately.
+  * pastebin: automatically send tracebacks to pocoo paste service 
+  * capture: flexibly capture stdout/stderr of subprocesses, per-test ...
+  * monkeypatch: safely monkeypatch modules/classes from within tests
+  * unittest: run and integrate traditional unittest.py tests 
+  * figleaf: generate html coverage reports with the figleaf module
+  * resultlog: generate buildbot-friendly reporting output 
+  * ...
 
-See the py.test documentation for more info: 
+* `distributed testing`_ and `elastic distributed execution`_: 
 
-    http://pytest.org
+  - new unified "TX" URL scheme for specifying remote processes 
+  - new distribution modes "--dist=each" and "--dist=load" 
+  - new sync/async ways to handle 1:N communication 
+  - improved documentation 
 
-The py lib also got smaller and focuses on offering much of the 
-well-tested py.test code in independent namespaces: 
+The py lib continues to offer most of the functionality used by
+the testing tool in `independent namespaces`_.  
 
-* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes
-* py.code: higher-level introspection and dynamic generation of python code 
-* py.path: path abstractions over local and subversion files 
+Some non-test related code, notably greenlets/co-routines and
+api-generation now live as their own projects which simplifies the
+installation procedure because no C-Extensions are required anymore. 
 
-Some non-strictly-test related code, notably greenlets/co-routines 
-and apigen now live on their own and have been removed, also simplifying
-the installation procedures. 
+The whole package should work well with Linux, Win32 and OSX, on Python
+2.3, 2.4, 2.5 and 2.6.  (Expect Python3 compatibility soon!) 
 
-The whole package works well with Linux, OSX and Win32, on 
-Python 2.3, 2.4, 2.5 and 2.6.  (Expect Python3 compatibility soon!) 
+For more info, see the py.test and py lib documentation: 
 
-Download/Install:   http://codespeak.net/py/dist/download.html 
+    http://pytest.org
+
+    http://pylib.org
 
-best, 
+have fun, 
 holger
 
+.. _`independent namespaces`: http://pylib.org
+.. _`funcargs`: http://codespeak.net/py/dist/test/funcargs.html
+.. _`plugin architecture`: http://codespeak.net/py/dist/test/extend.html
+.. _`default plugins`: http://codespeak.net/py/dist/test/plugin/index.html
+.. _`distributed testing`: http://codespeak.net/py/dist/test/dist.html
+.. _`elastic distributed execution`: http://codespeak.net/py/dist/execnet.html
+.. _`1.0.0 py lib release`: http://pypi.python.org/pypi/py 
+.. _`oejskit`: http://codespeak.net/py/dist/test/plugin/oejskit.html
+

Modified: py/trunk/doc/code.txt
==============================================================================
--- py/trunk/doc/code.txt	(original)
+++ py/trunk/doc/code.txt	Tue Aug  4 19:45:30 2009
@@ -1,8 +1,8 @@
-=======
-py.code
-=======
+================================================================================
+py.code: higher level python code and introspection objects
+================================================================================
 
-The :api:`py.code` part of the 'py lib' contains some functionality to help
+The :api:`py.code` part of the pylib contains some functionality to help
 dealing with Python code objects. Even though working with Python's internal
 code objects (as found on frames and callables) can be very powerful, it's
 usually also quite cumbersome, because the API provided by core Python is

Modified: py/trunk/doc/confrest.py
==============================================================================
--- py/trunk/doc/confrest.py	(original)
+++ py/trunk/doc/confrest.py	Tue Aug  4 19:45:30 2009
@@ -4,9 +4,27 @@
 
 html = py.xml.html 
 
+class css:
+    #pagetitle = "pagetitle"
+    contentspace = "contentspace"
+    menubar = "menubar"
+    navspace = "navspace"
+    versioninfo = "versioninfo"
+
 class Page(object): 
     doctype = ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
                ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
+    googlefragment = """
+<script type="text/javascript">
+var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+</script>
+<script type="text/javascript">
+try {
+var pageTracker = _gat._getTracker("UA-7597274-3");
+pageTracker._trackPageview();
+} catch(err) {}</script>
+"""
 
     def __init__(self, project, title, targetpath, stylesheeturl=None,
                  type="text/html", encoding="ISO-8859-1"): 
@@ -22,8 +40,8 @@
         self._root = html.html(self.head, self.body) 
         self.fill() 
 
-    def a_href(self, name, url):
-        return html.a(name, class_="menu", href=url)
+    def a_href(self, name, url, **kwargs):
+        return html.a(name, class_="menu", href=url, **kwargs)
 
     def a_docref(self, name, relhtmlpath):
         docpath = self.project.docpath
@@ -39,20 +57,29 @@
         
     def fill_menubar(self):
         items = [
-            self.a_docref("index", "index.html"),
+            self.a_docref("pylib index", "index.html"),
+            self.a_docref("test doc-index", "test/test.html"),
+            self.a_docref("test quickstart", "test/quickstart.html"),
+            self.a_docref("test features", "test/features.html"),
+            self.a_docref("test plugins", "test/plugin/index.html"),
+            self.a_docref("py.execnet", "execnet.html"),
+            #self.a_docref("py.code", "code.html"),
             #self.a_apigenref("api", "api/index.html"),
             #self.a_apigenref("source", "source/index.html"),
             #self.a_href("source", "http://bitbucket.org/hpk42/py-trunk/src/"),
             self.a_href("issues", "http://bitbucket.org/hpk42/py-trunk/issues/"),
             self.a_docref("contact", "contact.html"),
-            self.a_docref("download", "download.html"),
+            self.a_docref("install", "download.html"),
         ]
-        items2 = [items.pop(0)]
-        sep = " "
-        for item in items:
-            items2.append(sep)
-            items2.append(item)
-        self.menubar = html.div(id="menubar", *items2)
+        self.menubar = html.div(id=css.menubar, *[
+            html.div(item) for item in items])
+        version = py.version
+        self.menubar.insert(0, 
+            html.div("%s" % (py.version), style="font-style: italic;")
+        )
+            #self.a_href("%s-%s" % (self.title, py.version), 
+            #    "http://pypi.python.org/pypi/py/%s" % version, 
+            #id="versioninfo",
 
     def fill(self):
         content_type = "%s;charset=%s" %(self.type, self.encoding)
@@ -65,18 +92,19 @@
                               type="text/css"))
         self.fill_menubar()
 
-        self.metaspace = html.div(
-                html.div(self.title, class_="project_title"),
-                self.menubar,
-                id='metaspace')
-
-        self.body.append(self.project.logo)
-        self.body.append(self.metaspace)
-        self.contentspace = html.div(id="contentspace")
+        self.body.append(html.div(
+            self.project.logo,
+            self.menubar,
+            id=css.navspace, 
+        ))
+            
+        #self.body.append(html.div(self.title, id=css.pagetitle))
+        self.contentspace = html.div(id=css.contentspace)
         self.body.append(self.contentspace)
 
     def unicode(self, doctype=True): 
         page = self._root.unicode() 
+        page = page.replace("</body>", self.googlefragment + "</body>")
         if doctype: 
             return self.doctype + page 
         else: 
@@ -115,9 +143,9 @@
     encoding = 'latin1' 
     logo = html.div(
         html.a(
-            html.img(alt="py lib", id='pyimg', height=114, width=154, 
+            html.img(alt="py lib", id='pyimg', height=114/2, width=154/2, 
                               src="http://codespeak.net/img/pylib.png"), 
-                            href="http://codespeak.net"))
+                            href="http://pylib.org"))
     Page = PyPage 
 
     def __init__(self, sourcepath=None):
@@ -173,21 +201,21 @@
                                     stylesheet=stylesheet, encoding=encoding)
         content = strip_html_header(content, encoding=encoding)
 
-        page = self.Page(self, "[%s] " % txtpath.purebasename,
+        title = txtpath.purebasename 
+        if txtpath.dirpath().basename == "test":
+            title = "py.test " + title
+        # title = "[%s] %s" % (txtpath.purebasename, py.version)
+        page = self.Page(self, title, 
                          outputpath, stylesheeturl=stylesheet)
 
         try:
-            svninfo = txtpath.info() 
-            modified = " modified %s by %s" % (worded_time(svninfo.mtime),
-                                               getrealname(svninfo.last_author))
-        except (KeyboardInterrupt, SystemExit): 
-            raise
-        except:
+            modified = py.process.cmdexec(
+                "hg tip --template 'modified {date|shortdate}'" 
+            )
+        except py.process.cmdexec.Error:
             modified = " "
 
-        page.contentspace.append(
-            html.div(html.div(modified, style="float: right; font-style: italic;"), 
-                     id = 'docinfoline'))
+        #page.body.append(html.div(modified, id="docinfoline"))
 
         page.contentspace.append(py.xml.raw(content))
         outputpath.ensure().write(page.unicode().encode(encoding)) 

Modified: py/trunk/doc/contact.txt
==============================================================================
--- py/trunk/doc/contact.txt	(original)
+++ py/trunk/doc/contact.txt	Tue Aug  4 19:45:30 2009
@@ -1,18 +1,20 @@
-Contact and communication 
+Contact and Communication points 
 ===================================
 
-- **#pylib on irc.freenode.net**: you are welcome to lurk or ask questions! 
+- `py-dev developers list`_ announcements and discussions. 
 
-- `py-dev developers list`_ development mailing list.  
+- #pylib on irc.freenode.net IRC channel for random questions. 
 
 - `tetamap`_: Holger Krekel's blog, often about testing and py.test related news. 
 
-- `py-svn general commit mailing list`_ to follow all development commits. 
+- `py-svn general commit mailing list`_ to follow development commits, 
 
-- `development bug/feature tracker`_ this roundup instance serves to file bugs and track issues.  
-  (soon to be substitued by a google-code or other hosted one). 
+- `bitbucket issue tracker`_ use this bitbucket issue tracker to report
+  bugs or request features. 
 
-- `merlinux.eu`_ offers teaching and consulting services. 
+- `merlinux.eu`_ offers on-site teaching and consulting services. 
+
+.. _`bitbucket issue tracker`: http://bitbucket.org/hpk42/py-trunk/issues/ 
 
 .. _`merlinux.eu`: http://merlinux.eu
 

Modified: py/trunk/doc/download.txt
==============================================================================
--- py/trunk/doc/download.txt	(original)
+++ py/trunk/doc/download.txt	Tue Aug  4 19:45:30 2009
@@ -1,15 +1,16 @@
-==============
-Downloading
-==============
+.. 
+    ==============
+    Downloading
+    ==============
 
-.. _`PyPI project page`: http://pypi.python.org/pypi/py/
+    .. _`PyPI project page`: http://pypi.python.org/pypi/py/
 
-Latest Release, see `PyPI project page`_
+    Latest Release, see `PyPI project page`_
 
-"easy_install py"
+using setuptools / easy_install 
 ===================================================
 
-If you have a working `setuptools installation`_ you can install from the command line::
+With a working `setuptools installation`_ you can type::
 
     easy_install -U py 
 
@@ -25,95 +26,103 @@
 you can still install the py lib but without greenlets - look 
 below for the ``install_lib`` target. 
 
-**IMPORTANT NOTE**: if you are using Windows and have previous 
-installations of the py lib on your system, please download 
+**IMPORTANT NOTE**: if you are using Windows and have 
+0.8 versions of the py lib on your system, please download 
 and execute http://codespeak.net/svn/py/build/winpathclean.py
 This will check that no previous files are getting in the way. 
-(Unfortunately we don't know about a way to execute this 
-code automatically during the above install). 
+You can find out the py lib version with::
 
-Installing on Debian or Fedora 
-===================================
+    import py
+    print py.version
 
-On Debian systems look for ``python-codespeak-lib``.
-*This package is probably outdated - if somebody 
-can help with bringing this up to date, 
-that would be very much appreciated.*
 
-Dwayne Bailey has thankfully put together a Fedora `RPM`_. 
+.. _`checkout`:
 
-.. _`RPM`: http://translate.sourceforge.net/releases/testing/fedora/pylib-0.9.2-1.fc9.noarch.rpm
+Installing from version control / develop mode
+=================================================
 
-.. _`setuptools installation`: http://pypi.python.org/pypi/setuptools
+To follow development or help with fixing things
+for the next release, checkout the complete code
+and documentation source with mercurial_::
 
+    hg clone https://bitbucket.org/hpk42/py-trunk/
 
-Downloading a tar/zip archive and installing that
-===================================================
+With a working `setuptools installation`_ you can then issue::
 
-Go to the python package index (pypi) and download a tar or zip file:
+    python setup.py develop 
 
-    http://pypi.python.org/pypi/py/
+in order to work with your checkout version. 
 
-and unpack it to a directory, where you then type::
+For enhancing one of the plugins you may go to
+the ``py/test/plugin/`` sub directory. 
 
-    python setup.py install
+.. _mercurial: http://mercurial.selenic.com/wiki/
 
-If you don't have a working C-compiler you can do::
+.. _`no-setuptools`:
 
-    python setup.py install_lib
+Working without setuptools / from source
+==========================================
 
-You will then not be able to use greenlets but otherwise
-``py.test`` and all tools and APIs are fine to use. 
+If you have a checkout_ or a tarball_ it is actually not neccessary to issue
+``setup.py`` commands in order to use py lib and its tools.  You can
+simply add the root directory to ``PYTHONPATH`` and ``py/bin`` or
+``py\bin\win32`` to your ``PATH`` settings. 
 
-Installing from subversion / develop mode
-============================================
+There are also helper scripts to set the environment
+on windows::
 
-To follow development or help with fixing things
-for the next release, checkout the complete code
-and documentation source::
+    c:\\path\to\checkout\py\env.cmd
 
-    svn co http://codespeak.net/svn/py/release/0.9.x py-0.9.x
+and on linux/osx you can add something like this to 
+your shell initialization::
 
-You can then issue::
+    eval `python ~/path/to/checkout/py/env.py`
 
-    python setup.py develop 
+both of which which will get you good settings 
+for ``PYTHONPATH`` and ``PATH``.
 
-in order to work with your checkout version. 
+Note also that the command line scripts will look 
+for "nearby" py libs, so if you have a layout like this::
 
-other interesting svn checkout points::
+    mypkg/
+        subpkg1/
+            tests/
+        tests/
+    py/ 
 
-    http://codespeak.net/
-        svn/py/release      # release tags and branches
-        svn/py/dist         # latest stable (may or may not be a release) 
-        svn/py/trunk        # head development / merge point
+then issuing ``py.test subpkg1`` will use the py lib
+from that projects root directory. 
 
+Debian and RPM packages 
+===================================
 
-Working with multiple py lib versions / svn externals
-=======================================================
+As of July 2009 pytest/pylib 1.0 RPMs and Debian packages 
+are not yet available. So you will only find older
+versions. 
 
-If you happen to have multiple versions of the py lib 
-around or you ship the py lib as an svn-external to 
-then you might want to use py lib scripts more directly.
-For example if you have a project layout like this::
+On Debian systems look for ``python-codespeak-lib``.
+*But this package is probably outdated - if somebody 
+can help with bringing this up to date, 
+that would be very much appreciated.*
 
-    mypkg/
-        subpkg1/
-            tests/
-        tests/
-    py/ # as svn-external, could be specific tag/version
+Dwayne Bailey has thankfully put together a Fedora `RPM`_. 
 
-then you want to make sure that the actual local py lib is used
-and not another system-wide version.  For this you need to add
-``py/bin`` or ``py\bin\win32`` respectively to your system's PATH settings. 
+.. _`RPM`: http://translate.sourceforge.net/releases/testing/fedora/pylib-0.9.2-1.fc9.noarch.rpm
 
-You can do this by executing (on windows) a script to set the environment::
+.. _`setuptools installation`: http://pypi.python.org/pypi/setuptools
 
-    c:\\path\to\checkout\py\env.cmd
+.. _tarball:
 
-or on linux/osx you can add something like this to your shell
-initialization::
+Installing from a TAR archive 
+===================================================
 
-    eval `python ~/path/to/checkout/py/env.py`
+You need a working `setuptools installation`_. 
+
+Go to the python package index (pypi) and download a tar file:
 
-to get good settings for PYTHONPATH and PATH.
+    http://pypi.python.org/pypi/py/
+
+and unpack it to a directory, where you then type::
+
+    python setup.py install
 

Modified: py/trunk/doc/execnet.txt
==============================================================================
--- py/trunk/doc/execnet.txt	(original)
+++ py/trunk/doc/execnet.txt	Tue Aug  4 19:45:30 2009
@@ -1,13 +1,26 @@
-==========
-py.execnet
-==========
+==============================================================================
+py.execnet: *elastic* distributed programming 
+==============================================================================
 
-``py.execnet`` allows to:
+``execnet`` helps you to: 
 
-* instantiate local or remote Python Processes
+* ad-hoc instantiate local or remote Python Processes
 * send code for execution in one or many processes 
-* asynchronously send and receive data between processes through channels 
-* completely avoid manual installation steps on remote places
+* send and receive data between processes through channels
+
+One of it's unique features is that it uses a **zero-install** 
+technique: no manual installation steps are required on 
+remote places, only a basic working Python interpreter 
+and some input/output connection to it. 
+
+There is a `EuroPython2009 talk`_ from July 2009 with
+examples and some pictures. 
+
+.. contents:: 
+    :local:
+    :depth: 2
+
+.. _`EuroPython2009 talk`: http://codespeak.net/download/py/ep2009-execnet.pdf 
 
 Gateways: immediately spawn local or remote process
 ===================================================

Modified: py/trunk/doc/index.txt
==============================================================================
--- py/trunk/doc/index.txt	(original)
+++ py/trunk/doc/index.txt	Tue Aug  4 19:45:30 2009
@@ -1,19 +1,19 @@
-py lib: Main tools and APIs
-===================================
-
-.. _`PyPI project page`: http://pypi.python.org/pypi?%3Aaction=pkg_edit&name=py
+py lib: testing and distributed programming library 
+====================================================
 
-Latest Release, see `PyPI project page`_
+The ``py`` lib has several namespaces which help with testing,
+generating and distributing code across machines.  Here is 
+documentation on the most interesting ones:
 
 `py.test`_ write and deploy unit- and functional tests to multiple machines. 
 
-`py.execnet`_ rapidly deploy local or remote processes from your program. 
+`py.execnet`_ elastic distributed programming.
 
-`py.path`_: use path objects to transparently access local and svn filesystems.
+`py.code`_: generate code and use advanced introspection/traceback support. 
 
-`py.code`_: generate python code and use advanced introspection/traceback support. 
+`py.path`_: use path objects to transparently access local and svn filesystems.
 
-Minor support functionality 
+Other (minor) support functionality 
 ===================================
 
 `py lib scripts`_ to make python development easier. 
@@ -27,6 +27,10 @@
 `miscellaneous features`_ describes some small but nice py lib features.
 
 
+.. _`PyPI project page`: http://pypi.python.org/pypi/py/
+
+For the latest Release, see `PyPI project page`_
+
 .. _`download and installation`: download.html
 .. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev 
 .. _`py.execnet`: execnet.html 
@@ -39,27 +43,3 @@
 .. _`py.xml`: xml.html 
 .. _`miscellaneous features`: misc.html 
 
-Full Contents
-===================================
-
-.. toctree::
-   :maxdepth: 2
-
-   test
-   execnet
-   path
-   code
-   bin
-   xml
-   io
-   log
-   misc
-   coding-style
-   contact
-   download
-   releases
-
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`

Modified: py/trunk/doc/style.css
==============================================================================
--- py/trunk/doc/style.css	(original)
+++ py/trunk/doc/style.css	Tue Aug  4 19:45:30 2009
@@ -67,10 +67,6 @@
 dl {
 }
 
-dt {
-    font-weight: bold;    
-}
-
 dd {
     line-height: 1.5em;
     margin-bottom: 1em;
@@ -85,15 +81,18 @@
 code {
     color: Black;
     /*background-color: #dee7ec;*/
-    background-color: #cccccc;
+    /*background-color: #cccccc;*/
 }
 
 pre {
     padding: 1em;
-    border: 1px solid #8cacbb;
+    border: 1px dotted #8cacbb;
     color: Black;
+    /*
     background-color: #dee7ec;
     background-color: #cccccc;
+    background-color: #dee7ec;
+    */
     overflow: auto;
 }
 
@@ -111,7 +110,6 @@
 
 span.menu_selected {
 	color: black;
-  	font: 140% Verdana, Helvetica, Arial, sans-serif;
 	text-decoration: none;
     padding-right: 0.3em;
     background-color: #cccccc;
@@ -120,14 +118,13 @@
 
 a.menu {
   	/*color: #3ba6ec; */
-  	font: 140% Verdana, Helvetica, Arial, sans-serif;
+  	font: 120% Verdana, Helvetica, Arial, sans-serif;
 	text-decoration: none;
     padding-right: 0.3em;
 }
 
 a.menu[href]:visited, a.menu[href]:link{
   	/*color: #3ba6ec; */
-  	font: 140% Verdana, Helvetica, Arial, sans-serif;
 	text-decoration: none;
 }
 
@@ -135,11 +132,12 @@
   	/*color: black;*/
 }
 
-div.project_title{
+div#pagetitle{
   /*border-spacing: 20px;*/
   font: 160% Verdana, Helvetica, Arial, sans-serif;
   color: #3ba6ec; 
   vertical-align: middle;
+  left: 80 px;
   padding-bottom: 0.3em;
 }
 
@@ -566,7 +564,11 @@
     padding: 0px;
 }
 
-div.pagename {
+a#versioninfo {
+    color: blue;
+}
+
+div#pagename {
     font-size: 140%;
     color: blue;
     text-align: center;
@@ -593,37 +595,6 @@
 	/*background-color: #dddddd; */
 }
 
-span.wikiuserpref {
-    padding-top: 1em;
-    font-size: 120%;
-}
-
-div.wikitrail {
-    vertical-align: bottom;
-    /*font-size: -1;*/
-    padding-top: 1em;
-    display: none;
-}
-
-div.wikiaction {
-    vertical-align: middle;
-    /*border-bottom: 1px solid #8cacbb;*/
-    padding-bottom:1em;
-    text-align: left;
-    width: 100%;
-}
-
-div.wikieditmenu {
-    text-align: right;
-}
-
-form.wikiedit {
-    border: 1px solid #8cacbb;
-    background-color: #f0f0f0;
-    background-color: #fabf00;
-    padding: 1em;
-    padding-right: 0em;
-}
 
 div.legenditem {
     padding-top: 0.5em;
@@ -662,6 +633,10 @@
     border-bottom: 1px solid #8CACBB;
 }
 
+h2 {
+    border-bottom: 1px dotted #8CACBB;
+}
+
 
 h1, h2, h3, h4, h5, h6 {
     color: Black;
@@ -677,11 +652,10 @@
 
 
 h1 { font-size: 145%; }
-h2 { font-size: 135%; }
-h3 { font-size: 125%; }
-h4 { font-size: 120%; }
-h5 { font-size: 110%; }
-h6 { font-size: 80%; }
+h2 { font-size: 115%; }
+h3 { font-size: 105%; }
+h4 { font-size: 100%; }
+h5 { font-size: 100%; }
 
 h1 a { text-decoration: None;}
 
@@ -769,25 +743,16 @@
 }
 
 img#pyimg {
-    position: absolute;
-    top: 4px;
-    left: 4px;
+    float: left;
 }
     
 div#navspace {
     position: absolute;
-    top: 100px;
-    left: 11px;
     font-size: 100%;
     width: 150px;
     overflow: hidden; /* scroll;  */
 }
 
-div#metaspace {
-    position: absolute;
-    top: 10px;
-    left: 170px;
-}
 
 div#errorline {
     position: relative;
@@ -799,7 +764,6 @@
     position: absolute;
   	/* font: 120% "Times New Roman", serif;*/
     font: 110% Verdana, Helvetica, Arial, sans-serif;
-    top: 100px;
     left: 170px;
     margin-right: 5px;
 }
@@ -810,16 +774,17 @@
 }
 
 /* for the documentation page */
-div#docinfoline {
-  position: relative;
-  top: 5px; 
-  left: 0px;
-
-  /*background-color: #dee7ec; */
-  padding: 5pt; 
-  padding-bottom: 1em; 
+div#title{
+    
+  font-size: 110%;
   color: black;
-  /*border-width: 1pt;
+    
+    
+  /*background-color: #dee7ec; 
+  #padding: 5pt; 
+  #padding-bottom: 1em; 
+  #color: black;
+  border-width: 1pt;
   border-style: solid;*/
 
 }

Modified: py/trunk/doc/test/config.txt
==============================================================================
--- py/trunk/doc/test/config.txt	(original)
+++ py/trunk/doc/test/config.txt	Tue Aug  4 19:45:30 2009
@@ -1,5 +1,6 @@
-Test configuration
-========================
+.. contents:: 
+    :local:
+    :depth: 2
 
 available test options 
 -----------------------------
@@ -68,25 +69,28 @@
 
 .. _`basetemp`: 
 
-per-testrun temporary directories 
+Temporary directories 
 -------------------------------------------
 
 ``py.test`` runs provide means to create per-test session
 temporary (sub) directories through the config object.  
-You can create directories like this:
+You can create directories by calling a method
+on the config object: 
 
-.. XXX use a more local example, just with "config" 
+- ``config.mktemp(basename)``: create and returns a new tempdir 
 
-.. sourcecode: python
+- ``config.ensuretemp(basename)``: create or return a new tempdir 
 
-    import py
-    basetemp = py.test.config.ensuretemp() 
-    basetemp_subdir = py.test.config.ensuretemp("subdir") 
-
-By default, ``py.test`` creates a ``pytest-NUMBER`` directory
+tempdirs are created as sub directories of a per-session testdir 
 and will keep around the directories of the last three 
 test runs.  You can also set the base temporary directory 
 with the `--basetemp`` option.  When distributing 
 tests on the same machine, ``py.test`` takes care to 
 pass around the basetemp directory such that all temporary
 files land below the same basetemp directory.  
+
+The config object is available when implementing `function arguments`_ 
+or `extensions`_ and can otherwise be globally accessed as ``py.test.config``. 
+
+.. _`function arguments`: funcargs.html
+.. _`extensions`: extend.html

Modified: py/trunk/doc/test/extend.txt
==============================================================================
--- py/trunk/doc/test/extend.txt	(original)
+++ py/trunk/doc/test/extend.txt	Tue Aug  4 19:45:30 2009
@@ -4,15 +4,18 @@
 
 .. _`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 conftest plugins. They are useful for keeping test extensions close
-to the application package.  Named plugins are normal python modules or packages 
-that can be distributed separately.   Named plugins need to follow a naming pattern;
-they have an all lowercase ``pytest_`` prefixed name.  While conftest plugins are
-discovered automatically, named plugins must be explicitely specified. 
+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 conftest plugins. They are useful for keeping test
+extensions close to your application.  Named plugins are normal python
+modules or packages that can be distributed separately.   Named plugins
+need to follow a naming pattern; they have an all lowercase ``pytest_``
+prefixed name.  While conftest plugins are discovered automatically,
+named plugins must be explicitely specified. 
+
+.. _`named plugins`: plugin/index.html
 
 .. _`tool startup`:
 .. _`test tool starts up`: 
@@ -95,7 +98,7 @@
 information on particular hooks.  It's sensible to look at existing 
 plugins so see example usages and start off with your own plugin. 
 
-.. _`hook definition specification`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/hookspec.py
+.. _`hook definition specification`: plugin/hookspec.html
 
 .. _`configuration hooks`: 
 
@@ -132,8 +135,8 @@
 If you want to make global helper functions or objects available 
 to your test code you can implement: 
 
-    def pytest_namespace(config):
-        """ return dictionary with items to be made available on py.test. """
+    def pytest_namespace():
+        """ return dictionary with items to be made available on py.test. namespace """
 
 All such returned items will be made available directly on 
 the ``py.test`` namespace.  

Modified: py/trunk/doc/test/features.txt
==============================================================================
--- py/trunk/doc/test/features.txt	(original)
+++ py/trunk/doc/test/features.txt	Tue Aug  4 19:45:30 2009
@@ -1,32 +1,29 @@
 ==================================================
-py.test Features
+py.test features
 ==================================================
 
 py.test is an extensible tool for running all kinds
-of tests one one or more machines.  It supports a variety
-of testing methods for your Python application and modules,
-including unit, functional, integration and doc-testing. 
-
-It is used in projects that run more than 10000 tests 
-daily as well as single-python-module projects. 
+of tests on one or more machines.  It supports a variety
+of testing methods including unit, functional, integration 
+and doc-testing.  It is used in projects that run more 
+than 10 thousand tests regularly as well as in single-file projects. 
 
 py.test presents a clean and powerful command line interface
-and strives to generally make testing a fun effort. 
-
-py.test 1.0 works across linux, windows and osx 
-and on Python 2.3 - Python 2.6.
+and strives to generally make testing a fun no-boilerplate effort. 
+It works and is tested against linux, windows and osx 
+on CPython 2.3 - CPython 2.6. 
 
-More detailed feature list: 
-
-.. contents::
+.. contents:: List of Contents
     :depth: 1
 
+.. _`autocollect`:
+
 automatically collects and executes tests 
 ===============================================
 
-py.test discovers tests automatically by inspect specified
+py.test discovers tests automatically by inspecting specified
 directories or files.  By default, it collects all python
-modules a leading ``test_`` or trailing ``_test`` filename.
+modules with a leading ``test_`` or trailing ``_test`` filename.
 From each test module every function with a leading ``test_``
 or class with a leading ``Test`` name is collected.  
 
@@ -237,13 +234,19 @@
 
 By default, all filename parts and
 class/function names of a test function are put into the set
-of keywords for a given test.  You may specify additional 
+of keywords for a given test.  You can specify additional 
 kewords like this::
 
-    @py.test.mark(webtest=True)
+    @py.test.mark.webtest 
     def test_send_http():
         ... 
 
+and then use those keywords to select tests.  See the `pytest_keyword`_
+plugin for more information. 
+
+.. _`pytest_keyword`: plugin/keyword.html
+
+
 disabling a test class
 ---------------------- 
 
@@ -290,7 +293,8 @@
 easy to extend 
 =========================================
 
-Since 1.0 py.test has advanced `extension mechanisms`_. 
+Since 1.0 py.test has advanced `extension mechanisms`_
+and a growing `list of plugins`_. 
 One can can easily modify or add aspects for for 
 purposes such as: 
 
@@ -299,6 +303,7 @@
 * running non-python tests
 * managing custom test state setup 
 
+.. _`list of plugins`: plugin/index.html
 .. _`extension mechanisms`: extend.html
 
 .. _`reStructured Text`: http://docutils.sourceforge.net

Modified: py/trunk/doc/test/funcargs.txt
==============================================================================
--- py/trunk/doc/test/funcargs.txt	(original)
+++ py/trunk/doc/test/funcargs.txt	Tue Aug  4 19:45:30 2009
@@ -1,45 +1,36 @@
-======================================================
-**funcargs**: test setup and parametrization
-======================================================
-
-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. 
+==========================================================
+**funcargs**: test function arguments FTW
+==========================================================
+
+Since version 1.0 py.test features the "funcarg" mechanism which
+allows a test function to take arguments independently provided 
+by factory functions.  Factory functions allow to encapsulate 
+all setup and fixture glue code into nicely separated objects
+and provide a natural way for writing python test functions.  
+Compared to `xUnit style`_ the new mechanism is meant to:
+
+* make test functions easier to write and to read 
+* isolate test fixture creation to a single place 
+* bring new flexibility and power to test state management 
+* naturally extend towards parametrizing test functions
+  with multiple argument sets 
+  (superseding `old-style generative tests`_)
+* enable creation of zero-boilerplate test helper objects that 
+  interact with the execution of a test function, see the 
+  `blog post about the monkeypatch funcarg`_. 
 
+If you find issues or have further suggestions for improving
+the mechanism you are welcome to checkout `contact possibilities`_ page. 
+
+.. _`contact possibilities`: ../contact.html
 
 .. _`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 factory`:
  
-funcarg providers: instantiating test function arguments 
+funcarg factories: setting up test function arguments 
 ==============================================================
 
 Test functions can specify one ore more arguments ("funcargs") 
@@ -49,22 +40,22 @@
 
 .. sourcecode:: python
 
-    # ./test_simpleprovider.py 
+    # ./test_simplefactory.py 
     def pytest_funcarg__myfuncarg(request):
         return 42
 
     def test_function(myfuncarg):
         assert myfuncarg == 17
 
-If you run this with ``py.test test_simpleprovider.py`` you see something like this:
+If you run this with ``py.test test_simplefactory.py`` you see something like this:
 
 .. 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
+    test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py
 
-    test_simpleprovider.py F
+    test_simplefactory.py F
 
     ================================= FAILURES ==================================
     _______________________________ test_function _______________________________
@@ -75,7 +66,7 @@
     >       assert myfuncarg == 17
     E       assert 42 == 17
 
-    test_simpleprovider.py:6: AssertionError
+    test_simplefactory.py:6: AssertionError
     ========================= 1 failed in 0.11 seconds ==========================
 
 
@@ -84,7 +75,7 @@
 
 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 
+   A matching factory function is discovered by looking for the special 
    name ``pytest_funcarg__myfuncarg``. 
 
 2. ``pytest_funcarg__myfuncarg(request)`` is called and 
@@ -96,18 +87,17 @@
 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 
+For more interesting factory functions that make good use of the 
 `request object`_ please see the `application setup tutorial example`_. 
 
 .. _`request object`: 
 
-funcarg request objects
+funcarg factory request objects
 ------------------------------------------
 
-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 objects are passed to funcarg factories and allow 
+to access test configuration, test context and `useful caching
+and finalization helpers`_. Here is a list of attributes: 
 
 ``request.function``: python function object requesting the argument
 
@@ -119,9 +109,11 @@
 
 ``request.param``: if exists was passed by a `parametrizing test generator`_ 
 
+.. _`useful caching and finalization helpers`: 
+
 
-teardown/cleanup after test function execution
-------------------------------------------------
+registering funcarg related finalizers/cleanup 
+----------------------------------------------------
 
 .. sourcecode:: python 
 
@@ -130,7 +122,8 @@
      
 Calling ``request.addfinalizer()`` is useful for scheduling teardown 
 functions.  Here is an example for providing a ``myfile`` 
-object that is to be closed when the test function finishes. 
+object that is to be closed when the execution of a 
+test function finishes. 
 
 .. sourcecode:: python
 
@@ -140,54 +133,52 @@
         return myfile
 
 
-perform scope-specific setup and cleanup 
----------------------------------------------
+managing fixtures across test modules and test runs
+----------------------------------------------------------
 
 .. sourcecode:: python 
 
-    def cached_setup(setup, teardown=None, scope="module", keyextra=None):
+    def cached_setup(setup, teardown=None, scope="module", extrakey=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: 
+        The scope and the ``extrakey`` determine the cache key. 
+        The scope also determines when teardown(result) 
+        will be called.  valid scopes are: 
         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: 
+Calling ``request.cached_setup()`` helps you to manage fixture 
+objects across several scopes.  For example, for creating a Database object 
+that is to be setup only once during a test session you can use the helper
+like this:
 
 .. sourcecode:: python 
   
-    def pytest_funcarg__db(request):
+    def pytest_funcarg__database(request):
         return request.cached_setup(
-                 lambda: ExpensiveSetup(request.config.option.db), 
-                 lambda val: val.close(), 
-                 scope="run"
+                 setup=lambda: Database("..."), 
+                 teardown=lambda val: val.close(), 
+                 scope="session"
         )
 
 
 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. 
+        """ Lookup and call function argument factory for the given name. 
+        Each function argument is only created 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. 
-
+``request.getfuncargvalue(name)`` calls another funcarg factory function. 
+You can use this function if you want to `decorate a funcarg`_, i.e. 
+you want to provide the "normal" value but add something
+extra.  If a factory cannot be found a ``request.Error`` 
+exception will be raised. 
 
 .. _`test generators`: 
 .. _`parametrizing test generator`: 
@@ -195,7 +186,7 @@
 generating parametrized tests with funcargs 
 ===========================================================
 
-You can directly parametrize multiple runs of the same test 
+You can 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: 
@@ -280,7 +271,7 @@
 invocations for a given test function. 
 
 ``param`` if specified will be seen by any 
-`funcarg provider`_ as a ``request.param`` attribute. 
+`funcarg factory`_ as a ``request.param`` attribute. 
 Setting it is called *indirect parametrization*.  
 
 Indirect parametrization is preferable if test values are 
@@ -322,12 +313,12 @@
         answer = app.question()
         assert answer == 42
 
-To run this test py.test needs to find and call a provider to 
+To run this test py.test needs to find and call a factory to 
 obtain the required ``mysetup`` function argument.  The test 
 function interacts with the provided application specific setup. 
 
 To provide the ``mysetup`` function argument we write down
-a provider method in a `local plugin`_ by putting the
+a factory method in a `local plugin`_ by putting the
 following code into a local ``conftest.py``:
 
 .. sourcecode:: python
@@ -448,7 +439,7 @@
     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. 
+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 factories 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. 
 
@@ -503,13 +494,14 @@
 
 For larger scale setups it's sometimes useful to decorare 
 a funcarg just for a particular test module.  We can 
-extend the `accept example`_ by putting this in our test class:
+extend the `accept example`_ by putting this in our test module:
 
 .. sourcecode:: python
 
-    def pytest_funcarg__accept(self, request):
-        arg = request.getfuncargvalue("accept") # call the next provider
-        # create a special layout in our tempdir
+    # method of class 
+    def pytest_funcarg__accept(request):
+        # call the next factory in the conftest.py file 
+        arg = request.getfuncargvalue("accept") 
         arg.tmpdir.mkdir("special")
         return arg 
 
@@ -517,8 +509,8 @@
         def test_sometest(self, accept):
             assert accept.tmpdir.join("special").check()
 
-Our module level provider will be invoked first and it can 
-ask its request object to call the next provider and then 
+Our module-level "accept" factory is invoked first and here 
+it asks its request object to call the next factory 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`_. 
@@ -543,7 +535,7 @@
 considered an explicit registration mechanism, i.e. calling a register
 method on the config object.  But lacking a good use case for this
 indirection and flexibility we decided to go for `Convention over
-Configuration`_ and allow to directly specify the provider.  It has the
+Configuration`_ and allow to directly specify the factory.  It has the
 positive implication that you should be able to "grep" for
 ``pytest_funcarg__MYARG`` and will find all providing sites (usually
 exactly one).  

Added: py/trunk/doc/test/plugin/capture.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/capture.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,131 @@
+
+pytest_capture plugin
+=====================
+
+configurable per-test stdout/stderr capturing mechanisms.
+
+.. contents::
+  :local:
+
+This plugin captures stdout/stderr output for each test separately. 
+In case of test failures this captured output is shown grouped 
+togtther with the test. 
+
+The plugin also provides test function arguments that help to
+assert stdout/stderr output from within your tests, see the 
+`funcarg example`_. 
+
+
+Capturing of input/output streams during tests 
+---------------------------------------------------
+
+By default ``sys.stdout`` and ``sys.stderr`` are substituted with
+temporary streams during the execution of tests and setup/teardown code.  
+During the whole testing process it will re-use the same temporary 
+streams allowing to play well with the logging module which easily
+takes ownership on these streams. 
+
+Also, 'sys.stdin' is substituted with a file-like "null" object that 
+does not return any values.  This is to immediately error out
+on tests that wait on reading something from stdin. 
+
+You can influence output capturing mechanisms from the command line::
+
+    py.test -s            # disable all capturing
+    py.test --capture=sys # set StringIO() to each of sys.stdout/stderr 
+    py.test --capture=fd  # capture stdout/stderr on Filedescriptors 1/2 
+
+If you set capturing values in a conftest file like this::
+
+    # conftest.py
+    conf_capture = 'fd'
+
+then all tests in that directory will execute with "fd" style capturing. 
+
+sys-level capturing 
+------------------------------------------
+
+Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr`` 
+will be replaced with StringIO() objects.   
+
+FD-level capturing and subprocesses
+------------------------------------------
+
+The ``fd`` based method means that writes going to system level files
+based on the standard file descriptors will be captured, for example 
+writes such as ``os.write(1, 'hello')`` will be captured properly. 
+Capturing on fd-level will include output generated from 
+any subprocesses created during a test. 
+
+.. _`funcarg example`:
+
+Example Usage of the capturing Function arguments
+---------------------------------------------------
+
+You can use the `capsys funcarg`_ and `capfd funcarg`_ to 
+capture writes to stdout and stderr streams.  Using the
+funcargs frees your test from having to care about setting/resetting 
+the old streams and also interacts well with py.test's own 
+per-test capturing.  Here is an example test function:
+
+.. sourcecode:: python
+
+    def test_myoutput(capsys):
+        print "hello" 
+        print >>sys.stderr, "world"
+        out, err = capsys.readouterr()
+        assert out == "hello\n"
+        assert err == "world\n"
+        print "next"
+        out, err = capsys.readouterr()
+        assert out == "next\n" 
+
+The ``readouterr()`` call snapshots the output so far - 
+and capturing will be continued.  After the test 
+function finishes the original streams will 
+be restored.  If you want to capture on 
+the filedescriptor level you can use the ``capfd`` function
+argument which offers the same interface.
+
+.. _`capsys funcarg`:
+
+
+the 'capsys' test function argument
+-----------------------------------
+
+captures writes to sys.stdout/sys.stderr and makes 
+them available successively via a ``capsys.readouterr()`` method 
+which returns a ``(out, err)`` tuple of captured snapshot strings. 
+
+.. _`capfd funcarg`:
+
+
+the 'capfd' test function argument
+----------------------------------
+
+captures writes to file descriptors 1 and 2 and makes 
+snapshotted ``(out, err)`` string tuples available 
+via the ``capsys.readouterr()`` method. 
+
+command line options
+--------------------
+
+
+``-s``
+    shortcut for --capture=no.
+``--capture=method``
+    set capturing method during tests: fd (default)|sys|no.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_capture.py`_ plugin source code 
+2. put it somewhere as ``pytest_capture.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/doctest.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/doctest.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,40 @@
+
+pytest_doctest plugin
+=====================
+
+collect and execute doctests from modules and test files.
+
+.. contents::
+  :local:
+
+Usage
+-------------
+
+By default all files matching the ``test_*.txt`` pattern will 
+be run with the ``doctest`` module.  If you issue::
+
+    py.test --doctest-modules
+
+all python files in your projects will be doctest-run 
+as well.
+
+command line options
+--------------------
+
+
+``--doctest-modules``
+    search all python files for doctests
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_doctest.py`_ plugin source code 
+2. put it somewhere as ``pytest_doctest.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/figleaf.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/figleaf.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,35 @@
+
+pytest_figleaf plugin
+=====================
+
+write and report coverage data with 'figleaf'.
+
+.. contents::
+  :local:
+
+
+
+command line options
+--------------------
+
+
+``-F``
+    trace python coverage with figleaf and write HTML for files below the current working dir
+``--figleaf-data=FIGLEAFDATA``
+    path to coverage tracing file.
+``--figleaf-html=FIGLEAFHTML``
+    path to the coverage html dir.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_figleaf.py`_ plugin source code 
+2. put it somewhere as ``pytest_figleaf.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/hooklog.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/hooklog.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,31 @@
+
+pytest_hooklog plugin
+=====================
+
+log invocations of extension hooks to a file.
+
+.. contents::
+  :local:
+
+
+
+command line options
+--------------------
+
+
+``--hooklog=HOOKLOG``
+    write hook calls to the given file.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_hooklog.py`_ plugin source code 
+2. put it somewhere as ``pytest_hooklog.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/hookspec.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/hookspec.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,174 @@
+
+hook specification sourcecode
+=============================
+
+.. sourcecode:: python
+
+    """
+    hook specifications for py.test plugins 
+    """
+    
+    # -------------------------------------------------------------------------
+    # Command line and configuration 
+    # -------------------------------------------------------------------------
+    
+    def pytest_addoption(parser):
+        """ called before commandline parsing.  """
+    
+    def pytest_namespace():
+        """ return dict of name->object which will get stored at py.test. namespace"""
+    
+    def pytest_configure(config):
+        """ called after command line options have been parsed. 
+            and all plugins and initial conftest files been loaded. 
+        """
+    
+    def pytest_unconfigure(config):
+        """ called before test process is exited.  """
+    
+    # -------------------------------------------------------------------------
+    # 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(report):
+        """ collector finished collecting. """
+    
+    def pytest_deselected(items):
+        """ called for test items deselected by keyword. """
+    
+    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_protocol(item):
+        """ implement fixture, run and report protocol. """
+    pytest_runtest_protocol.firstresult = True
+    
+    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_makereport(item, call):
+        """ make ItemTestReport for the given item and call outcome. """ 
+    pytest_runtest_makereport.firstresult = True
+    
+    def pytest_runtest_logreport(report):
+        """ process item test report. """ 
+    
+    # special handling for final teardown - somewhat internal for now
+    def pytest__teardown_final(session):
+        """ called before test session finishes. """
+    pytest__teardown_final.firstresult = True
+    
+    def pytest__teardown_final_logerror(rep):
+        """ called if runtest_teardown_final failed. """ 
+    
+    # -------------------------------------------------------------------------
+    # test session related hooks 
+    # -------------------------------------------------------------------------
+    
+    def pytest_sessionstart(session):
+        """ before session.main() is called. """
+    
+    def pytest_sessionfinish(session, exitstatus):
+        """ whole test run finishes. """
+    
+    # -------------------------------------------------------------------------
+    # hooks for influencing reporting (invoked from pytest_terminal)
+    # -------------------------------------------------------------------------
+    
+    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
+    
+    # -------------------------------------------------------------------------
+    # 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. """
+    
+    
+    # -------------------------------------------------------------------------
+    # error handling and internal debugging 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_keyboard_interrupt(excinfo):
+        """ called for keyboard interrupt. """
+    
+    def pytest_trace(category, msg):
+        """ called for debug info. """ 
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/index.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/index.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,48 @@
+
+Plugins related to Python test functions and programs
+=====================================================
+
+xfail_ mark python test functions as expected-to-fail and report them separately.
+
+figleaf_ write and report coverage data with 'figleaf'.
+
+monkeypatch_ safely patch object attributes, dicts and environment variables.
+
+capture_ configurable per-test stdout/stderr capturing mechanisms.
+
+recwarn_ helpers for asserting deprecation and other warnings.
+
+
+Plugins for other testing styles and languages
+==============================================
+
+unittest_ automatically discover and run traditional "unittest.py" style tests.
+
+doctest_ collect and execute doctests from modules and test files.
+
+oejskit_ run javascript tests in real life browsers
+
+restdoc_ perform ReST syntax, local and remote reference tests on .rst/.txt files.
+
+
+Plugins for generic reporting and failure logging
+=================================================
+
+pastebin_ submit failure or test session information to a pastebin service.
+
+resultlog_ resultlog plugin for machine-readable logging of test results.
+
+terminal_ Implements terminal reporting of the full testing process.
+
+
+internal plugins / core functionality
+=====================================
+
+pdb_ interactive debugging with the Python Debugger.
+
+keyword_ mark test functions with keywords that may hold values.
+
+hooklog_ log invocations of extension hooks to a file.
+
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/keyword.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/keyword.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,46 @@
+
+pytest_keyword plugin
+=====================
+
+mark test functions with keywords that may hold values.
+
+.. contents::
+  :local:
+
+Marking functions and setting rich attributes
+----------------------------------------------------
+
+By default, all filename parts and class/function names of a test
+function are put into the set of keywords for a given test.  You can
+specify additional kewords like this::
+
+    @py.test.mark.webtest 
+    def test_send_http():
+        ... 
+
+This will set an attribute 'webtest' on the given test function
+and by default all such attributes signal keywords.  You can 
+also set values in this attribute which you could read from
+a hook in order to do something special with respect to
+the test function::
+
+    @py.test.mark.timeout(seconds=5)
+    def test_receive():
+        ...
+
+This will set the "timeout" attribute with a Marker object 
+that has a 'seconds' attribute.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_keyword.py`_ plugin source code 
+2. put it somewhere as ``pytest_keyword.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/links.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/links.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,33 @@
+.. _`terminal`: terminal.html
+.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_recwarn.py
+.. _`unittest`: unittest.html
+.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_monkeypatch.py
+.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_keyword.py
+.. _`pastebin`: pastebin.html
+.. _`plugins`: index.html
+.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_capture.py
+.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_doctest.py
+.. _`capture`: capture.html
+.. _`hooklog`: hooklog.html
+.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_restdoc.py
+.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_hooklog.py
+.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_pastebin.py
+.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_figleaf.py
+.. _`xfail`: xfail.html
+.. _`contact`: ../../contact.html
+.. _`checkout the py.test development version`: ../../download.html#checkout
+.. _`oejskit`: oejskit.html
+.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_xfail.py
+.. _`figleaf`: figleaf.html
+.. _`extend`: ../extend.html
+.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_terminal.py
+.. _`recwarn`: recwarn.html
+.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_pdb.py
+.. _`monkeypatch`: monkeypatch.html
+.. _`resultlog`: resultlog.html
+.. _`keyword`: keyword.html
+.. _`restdoc`: restdoc.html
+.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_unittest.py
+.. _`doctest`: doctest.html
+.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_resultlog.py
+.. _`pdb`: pdb.html

Added: py/trunk/doc/test/plugin/monkeypatch.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/monkeypatch.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,61 @@
+
+pytest_monkeypatch plugin
+=========================
+
+safely patch object attributes, dicts and environment variables.
+
+.. contents::
+  :local:
+
+Usage
+----------------
+
+Use the `monkeypatch funcarg`_ to safely patch the environment
+variables, object attributes or dictionaries.  For example, if you want
+to set the environment variable ``ENV1`` and patch the
+``os.path.abspath`` function to return a particular value during a test
+function execution you can write it down like this:
+
+.. sourcecode:: python 
+
+    def test_mytest(monkeypatch):
+        monkeypatch.setenv('ENV1', 'myval')
+        monkeypatch.setattr(os.path, 'abspath', lambda x: '/')
+        ... # your test code 
+
+The function argument will do the modifications and memorize the 
+old state.  After the test function finished execution all 
+modifications will be reverted.  See the `monkeypatch blog post`_ 
+for an extensive discussion. 
+
+.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
+
+.. _`monkeypatch funcarg`:
+
+
+the 'monkeypatch' test function argument
+----------------------------------------
+
+The returned ``monkeypatch`` funcarg provides three 
+helper methods to modify objects, dictionaries or os.environ::
+
+    monkeypatch.setattr(obj, name, value)  
+    monkeypatch.setitem(mapping, name, value) 
+    monkeypatch.setenv(name, value) 
+
+All such modifications will be undone when the requesting 
+test function finished its execution. 
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_monkeypatch.py`_ plugin source code 
+2. put it somewhere as ``pytest_monkeypatch.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/oejskit.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/oejskit.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,12 @@
+pytest_oejskit plugin (EXTERNAL)
+==========================================
+
+The `oejskit`_ offers a py.test plugin for running Javascript tests in life browers.   Running inside the browsers comes with some speed cost, on the other hand it means for example the code is tested against the real-word DOM implementations.
+The approach enables to write integration tests such that the JavaScript code is tested against server-side Python code mocked as necessary. Any server-side framework that can already be exposed through WSGI (or for which a subset of WSGI can be written to accommodate the jskit own needs) can play along.
+
+For more info and download please visit the `oejskit PyPI`_ page. 
+
+.. _`oejskit`: 
+.. _`oejskit PyPI`: http://pypi.python.org/pypi/oejskit
+
+.. source link 'http://bitbucket.org/pedronis/js-infrastructure/src/tip/pytest_jstests.py',

Added: py/trunk/doc/test/plugin/pastebin.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/pastebin.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,46 @@
+
+pytest_pastebin plugin
+======================
+
+submit failure or test session information to a pastebin service.
+
+.. contents::
+  :local:
+
+Usage
+----------
+
+**Creating a URL for each test failure**::
+
+    py.test --pastebin=failed 
+
+This will submit full failure information to a remote Paste service and
+provide a URL for each failure.  You may select tests as usual or add
+for example ``-x`` if you only want to send one particular failure. 
+
+**Creating a URL for a whole test session log**::
+
+    py.test --pastebin=all 
+
+Currently only pasting to the http://paste.pocoo.org service is implemented.
+
+command line options
+--------------------
+
+
+``--pastebin=mode``
+    send failed|all info to Pocoo pastebin service.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_pastebin.py`_ plugin source code 
+2. put it somewhere as ``pytest_pastebin.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/pdb.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/pdb.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,31 @@
+
+pytest_pdb plugin
+=================
+
+interactive debugging with the Python Debugger.
+
+.. contents::
+  :local:
+
+
+
+command line options
+--------------------
+
+
+``--pdb``
+    start pdb (the Python debugger) on errors.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_pdb.py`_ plugin source code 
+2. put it somewhere as ``pytest_pdb.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/recwarn.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/recwarn.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,61 @@
+
+pytest_recwarn plugin
+=====================
+
+helpers for asserting deprecation and other warnings.
+
+.. contents::
+  :local:
+
+Example usage 
+---------------------
+
+You can use the ``recwarn`` funcarg to track 
+warnings within a test function:
+
+.. sourcecode:: python
+
+    def test_hello(recwarn):
+        from warnings import warn
+        warn("hello", DeprecationWarning)
+        w = recwarn.pop(DeprecationWarning)
+        assert issubclass(w.category, DeprecationWarning)
+        assert 'hello' in str(w.message)
+        assert w.filename
+        assert w.lineno
+
+You can also call a global helper for checking
+taht a certain function call yields a Deprecation
+warning:
+
+.. sourcecode:: python
+
+    import py
+            
+    def test_global():
+        py.test.deprecated_call(myfunction, 17)
+
+.. _`recwarn funcarg`:
+
+
+the 'recwarn' test function argument
+------------------------------------
+
+Return a WarningsRecorder instance that provides these methods:
+
+* ``pop(category=None)``: return last warning matching the category.
+* ``clear()``: clear list of warnings 
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_recwarn.py`_ plugin source code 
+2. put it somewhere as ``pytest_recwarn.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/restdoc.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/restdoc.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,35 @@
+
+pytest_restdoc plugin
+=====================
+
+perform ReST syntax, local and remote reference tests on .rst/.txt files.
+
+.. contents::
+  :local:
+
+
+
+command line options
+--------------------
+
+
+``-R, --urlcheck``
+    urlopen() remote links found in ReST text files.
+``--urltimeout=secs``
+    timeout in seconds for remote urlchecks
+``--forcegen``
+    force generation of html files.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_restdoc.py`_ plugin source code 
+2. put it somewhere as ``pytest_restdoc.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/resultlog.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/resultlog.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,31 @@
+
+pytest_resultlog plugin
+=======================
+
+resultlog plugin for machine-readable logging of test results.
+
+.. contents::
+  :local:
+
+Useful for buildbot integration code.
+
+command line options
+--------------------
+
+
+``--resultlog=path``
+    path for machine-readable result log.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_resultlog.py`_ plugin source code 
+2. put it somewhere as ``pytest_resultlog.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/terminal.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/terminal.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,39 @@
+
+pytest_terminal plugin
+======================
+
+Implements terminal reporting of the full testing process.
+
+.. contents::
+  :local:
+
+This is a good source for looking at the various reporting hooks.
+
+command line options
+--------------------
+
+
+``--collectonly``
+    only collect tests, don't execute them.
+``--traceconfig``
+    trace considerations of conftest.py files.
+``--nomagic``
+    don't reinterpret asserts, no traceback cutting. 
+``--fulltrace``
+    don't cut any tracebacks (default is to cut).
+``--debug``
+    generate and show debugging information.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_terminal.py`_ plugin source code 
+2. put it somewhere as ``pytest_terminal.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/unittest.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/unittest.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,34 @@
+
+pytest_unittest plugin
+======================
+
+automatically discover and run traditional "unittest.py" style tests.
+
+.. contents::
+  :local:
+
+Usage
+----------------
+
+This plugin collects and runs Python `unittest.py style`_ tests. 
+It will automatically collect ``unittest.TestCase`` subclasses 
+and their ``test`` methods from the test modules of a project
+(usually following the ``test_*.py`` pattern). 
+
+This plugin is enabled by default. 
+
+.. _`unittest.py style`: http://docs.python.org/library/unittest.html
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_unittest.py`_ plugin source code 
+2. put it somewhere as ``pytest_unittest.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Added: py/trunk/doc/test/plugin/xfail.txt
==============================================================================
--- (empty file)
+++ py/trunk/doc/test/plugin/xfail.txt	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,36 @@
+
+pytest_xfail plugin
+===================
+
+mark python test functions as expected-to-fail and report them separately.
+
+.. contents::
+  :local:
+
+usage
+------------
+
+Use the generic mark decorator to mark your test functions as 
+'expected to fail':: 
+
+    @py.test.mark.xfail
+    def test_hello():
+        ...
+
+This test will be executed but no traceback will be reported 
+when it fails. Instead terminal reporting will list it in the 
+"expected to fail" section or "unexpectedly passing" section.
+
+Start improving this plugin in 30 seconds
+=========================================
+
+
+Do you find the above documentation or the plugin itself lacking? 
+
+1. Download `pytest_xfail.py`_ plugin source code 
+2. put it somewhere as ``pytest_xfail.py`` into your import path 
+3. a subsequent ``py.test`` run will use your local version
+
+Further information: extend_ documentation, other plugins_ or contact_.  
+
+.. include:: links.txt

Modified: py/trunk/doc/test/quickstart.txt
==============================================================================
--- py/trunk/doc/test/quickstart.txt	(original)
+++ py/trunk/doc/test/quickstart.txt	Tue Aug  4 19:45:30 2009
@@ -5,55 +5,33 @@
 Quickstart
 ==================
 
-This document assumes basic python knowledge.  If you have a
-`setuptools installation`_, install ``py.test`` by typing::
+.. _here: ../download.html#no-setuptools
 
-    easy_install -U py 
-
-For alternative installation methods please see the download_ page.  
-
-You should now have a ``py.test`` command line tool and can
-look at its documented cmdline options via this command::
+This document assumes basic python knowledge and a working `setuptools
+installation`_ (otherwise see here_). You can install
+the py lib and py.test by typing::
 
-    py.test -h  
-
-Writing and running a test
-==========================
+    easy_install -U py 
 
-``py.test`` is the command line tool to run tests.  
-Let's write a first test module by putting the following
-test function into a ``test_sample.py`` file::
+Now open a file ``test_sample.py`` file and put the following
+example content into it::
 
     # content of test_sample.py 
     def test_answer():
         assert 42 == 43 
 
-Now you can run the test by passing it as an argument::
+You can now run the test file like this::
 
   py.test test_sample.py
 
-What does happen here?  ``py.test`` looks for functions and
-methods in the module that start with ``test_``.  It then
-executes those tests.  Assertions about test outcomes are
-done via the standard ``assert`` statement.
-
-You can also use ``py.test`` to run all tests in a directory structure by
-invoking it without any arguments::
-
-  py.test
-
-This will automatically collect and run any Python module whose filenames 
-start with ``test_`` or ends with ``_test`` from the directory and any
-subdirectories, starting with the current directory, and run them. Each 
-Python test module is inspected for test methods starting with ``test_``. 
-
-.. Organising your tests 
-.. ---------------------------
-
-Please refer to `features`_ for a walk through the basic features. 
-
+and will see an error report on the failing assert statement. 
+For further information please refer to `features`_ 
+or checkout the `tutorials`_ page for more introduction material. 
 
+.. _`automatically collected`: features.html#autocollect
 .. _download: ../download.html
 .. _features: features.html
+.. _tutorials: talks.html
+
 
 

Modified: py/trunk/doc/test/test.txt
==============================================================================
--- py/trunk/doc/test/test.txt	(original)
+++ py/trunk/doc/test/test.txt	Tue Aug  4 19:45:30 2009
@@ -1,24 +1,32 @@
-=======
-py.test
-=======
+=======================================
+py.test documentation index 
+=======================================
+
+the project independent ``py.test`` command line tool helps you to: 
 
 * rapidly collect and run tests 
-* use unit- or doctests, functional or integration tests 
+* run unit- or doctests, functional or integration tests 
 * distribute tests to multiple environments
-* local or global plugins for custom test scenarios and types
+* use local or global plugins for custom test types and setup 
 
 quickstart_: for getting started immediately.
 
 features_: a walk through basic features and usage. 
 
+`available plugins`_: list of py.test plugins 
+
 funcargs_: powerful parametrized test function setup  
 
 `distributed testing`_: distribute test runs to other machines and platforms. 
 
 extend_: intro to extend and customize py.test runs 
 
-config_: ``conftest.py`` files and general configuration 
+config_: ``conftest.py`` files and the config object 
+
+talks_: talk and tutorial slides 
 
+.. _`available plugins`: plugin/index.html
+.. _talks: talks.html
 .. _quickstart: quickstart.html
 .. _features: features.html
 .. _funcargs: funcargs.html

Added: py/trunk/example/assertion/failure_demo.py
==============================================================================
--- (empty file)
+++ py/trunk/example/assertion/failure_demo.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,120 @@
+from py.test import raises
+import py
+
+def otherfunc(a,b):
+    assert a==b
+
+def somefunc(x,y):
+    otherfunc(x,y)
+
+def otherfunc_multi(a,b): 
+    assert (a == 
+            b) 
+
+class TestFailing(object):
+    def test_simple(self):
+        def f():
+            return 42
+        def g():
+            return 43
+
+        assert f() == g()
+
+    def test_simple_multiline(self):
+        otherfunc_multi(
+                  42,
+                  6*9)
+
+    def test_not(self):
+        def f():
+            return 42
+        assert not f()
+
+    def test_complex_error(self):
+        def f():
+            return 44
+        def g():
+            return 43
+        somefunc(f(), g())
+
+    def test_z1_unpack_error(self):
+        l = []
+        a,b  = l
+
+    def test_z2_type_error(self):
+        l = 3
+        a,b  = l
+
+    def test_startswith(self):
+        s = "123"
+        g = "456"
+        assert s.startswith(g)
+
+    def test_startswith_nested(self):
+        def f():
+            return "123"
+        def g():
+            return "456"
+        assert f().startswith(g())
+
+    def test_global_func(self):
+        assert isinstance(globf(42), float)
+
+    def test_instance(self):
+        self.x = 6*7
+        assert self.x != 42
+
+    def test_compare(self):
+        assert globf(10) < 5
+
+    def test_try_finally(self):
+        x = 1
+        try:
+            assert x == 0
+        finally:
+            x = 0
+
+    def test_raises(self):
+        s = 'qwe'
+        raises(TypeError, "int(s)")
+
+    def test_raises_doesnt(self):
+        raises(IOError, "int('3')")
+
+    def test_raise(self):
+        raise ValueError("demo error")
+
+    def test_tupleerror(self):
+        a,b = [1]
+
+    def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
+        l = [1,2,3]
+        print "l is", l
+        a,b = l.pop()
+
+    def test_some_error(self):
+        if namenotexi:
+            pass
+
+    def test_generator(self):
+        yield None
+
+    def func1(self):
+        assert 41 == 42
+
+    def test_generator2(self):
+        yield self.func1
+
+# thanks to Matthew Scott for this test
+def test_dynamic_compile_shows_nicely():
+    src = 'def foo():\n assert 1 == 0\n'
+    name = 'abc-123'
+    module = py.std.imp.new_module(name)
+    code = py.code.compile(src, name, 'exec')
+    exec code in module.__dict__
+    py.std.sys.modules[name] = module
+    module.foo()
+
+
+def globf(x):
+    return x+1

Added: py/trunk/example/assertion/test_failures.py
==============================================================================
--- (empty file)
+++ py/trunk/example/assertion/test_failures.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,14 @@
+
+import py
+failure_demo = py.magic.autopath().dirpath('failure_demo.py')
+
+pytest_plugins = "pytest_pytester"
+
+def test_failure_demo_fails_properly(testdir): 
+    reprec = testdir.inline_run(failure_demo)
+    passed, skipped, failed = reprec.countoutcomes() 
+    assert passed == 0 
+    assert failed == 20, failed
+    colreports = reprec.getreports("pytest_collectreport")
+    failed = len([x.failed for x in colreports])
+    assert failed == 5

Added: py/trunk/example/assertion/test_setup_flow_example.py
==============================================================================
--- (empty file)
+++ py/trunk/example/assertion/test_setup_flow_example.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,42 @@
+def setup_module(module):
+    module.TestStateFullThing.classcount = 0
+
+class TestStateFullThing:
+    def setup_class(cls):
+        cls.classcount += 1
+
+    def teardown_class(cls):
+        cls.classcount -= 1
+
+    def setup_method(self, method):
+        self.id = eval(method.func_name[5:])
+
+    def test_42(self):
+        assert self.classcount == 1
+        assert self.id == 42
+
+    def test_23(self):
+        assert self.classcount == 1
+        assert self.id == 23
+
+def teardown_module(module):
+    assert module.TestStateFullThing.classcount == 0
+
+""" For this example the control flow happens as follows::
+    import test_setup_flow_example
+    setup_module(test_setup_flow_example)
+       setup_class(TestStateFullThing)
+           instance = TestStateFullThing()
+           setup_method(instance, instance.test_42)
+              instance.test_42()
+           setup_method(instance, instance.test_23)
+              instance.test_23()
+       teardown_class(TestStateFullThing)
+    teardown_module(test_setup_flow_example)
+
+Note that ``setup_class(TestStateFullThing)`` is called and not
+``TestStateFullThing.setup_class()`` which would require you
+to insert ``setup_class = classmethod(setup_class)`` to make
+your setup function callable.
+"""
+

Added: py/trunk/example/execnet/redirect_remote_output.py
==============================================================================
--- (empty file)
+++ py/trunk/example/execnet/redirect_remote_output.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,31 @@
+"""
+redirect output from remote to a local function 
+showcasing features of the channel object:
+
+- sending a channel over a channel 
+- adapting a channel to a file object 
+- setting a callback for receiving channel data 
+
+"""
+
+import py
+
+gw = py.execnet.PopenGateway()
+
+outchan = gw.remote_exec("""
+    import sys
+    outchan = channel.gateway.newchannel()
+    sys.stdout = outchan.makefile("w")
+    channel.send(outchan) 
+""").receive()
+
+# note: callbacks execute in receiver thread! 
+def write(data):
+    print "received:", repr(data)
+outchan.setcallback(write)
+
+gw.remote_exec("""
+    print 'hello world'
+    print 'remote execution ends'
+""").waitclose()
+

Added: py/trunk/example/execnet/svn-sync-repo.py
==============================================================================
--- (empty file)
+++ py/trunk/example/execnet/svn-sync-repo.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,93 @@
+#!/usr/bin/env python 
+
+import py
+import sys, os
+
+def usage():
+    arg0 = sys.argv[0]
+    print """%s [user@]remote-host:/repo/location localrepo [identity keyfile]""" % (arg0,)
+
+
+def main(args):
+    remote = args[0]
+    localrepo = py.path.local(args[1])
+    if not localrepo.check(dir=1):
+        raise SystemExit("localrepo %s does not exist" %(localrepo,))
+    if len(args) == 3:
+        keyfile = py.path.local(args[2])
+    else:
+        keyfile = None
+    remote_host, path = remote.split(':', 1)
+    print "ssh-connecting to", remote_host 
+    gw = getgateway(remote_host, keyfile)
+
+    local_rev = get_svn_youngest(localrepo)
+
+    # local protocol 
+    # 1. client sends rev/repo -> server 
+    # 2. server checks for newer revisions and sends dumps 
+    # 3. client receives dumps, updates local repo 
+    # 4. client goes back to step 1
+    c = gw.remote_exec("""
+        import py
+        import os
+        remote_rev, repopath = channel.receive()
+        while 1: 
+            rev = py.process.cmdexec('svnlook youngest "%s"' % repopath) 
+            rev = int(rev)
+            if rev > remote_rev:
+                revrange = (remote_rev+1, rev)
+                dumpchannel = channel.gateway.newchannel()
+                channel.send(revrange)
+                channel.send(dumpchannel)
+
+                f = os.popen(
+                        "svnadmin dump -q --incremental -r %s:%s %s" 
+                         % (revrange[0], revrange[1], repopath), 'r')
+                try:
+                    while 1:
+                        s = f.read(8192)
+                        if not s:
+                            raise EOFError
+                        dumpchannel.send(s)
+                except EOFError:
+                    dumpchannel.close()
+                remote_rev = rev 
+            else:
+                # using svn-hook instead would be nice here
+                py.std.time.sleep(30)
+    """)
+
+    c.send((local_rev, path))
+    print "checking revisions from %d in %s" %(local_rev, remote)
+    while 1: 
+        revstart, revend = c.receive()
+        dumpchannel = c.receive() 
+
+        print "receiving revisions", revstart, "-", revend, "replaying..."
+        svn_load(localrepo, dumpchannel)
+        print "current revision", revend 
+
+def svn_load(repo, dumpchannel):
+    f = os.popen("svnadmin load -q %s" %(repo, ), "w")
+    for x in dumpchannel:
+        sys.stdout.write(".")
+        sys.stdout.flush()
+        f.write(x)
+    print >>sys.stdout
+    f.close() 
+
+def get_svn_youngest(repo):
+    rev = py.process.cmdexec('svnlook youngest "%s"' % repo) 
+    return int(rev)
+
+def getgateway(host, keyfile=None):
+    return py.execnet.SshGateway(host, identity=keyfile)
+
+if __name__ == '__main__':
+    if len(sys.argv) < 3:
+        usage()
+        raise SystemExit(1)
+
+    main(sys.argv[1:])
+

Added: py/trunk/example/execnet/sysinfo.py
==============================================================================
--- (empty file)
+++ py/trunk/example/execnet/sysinfo.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,140 @@
+"""
+sysinfo.py [host1] [host2] [options]
+
+obtain system info from remote machine. 
+"""
+
+import py
+import sys
+
+optparse = py.compat.optparse
+
+parser = optparse.OptionParser(usage=__doc__)
+parser.add_option("-f", "--sshconfig", action="store", dest="ssh_config", default=None,
+                  help="use given ssh config file, and add info all contained hosts for getting info")
+parser.add_option("-i", "--ignore", action="store", dest="ignores", default=None,
+                  help="ignore hosts (useful if the list of hostnames come from a file list)")
+
+def parsehosts(path):
+    path = py.path.local(path)
+    l = []
+    rex = py.std.re.compile(r'Host\s*(\S+)')
+    for line in path.readlines():
+        m = rex.match(line)
+        if m is not None:
+            sshname, = m.groups()
+            l.append(sshname)
+    return l
+
+class RemoteInfo:
+    def __init__(self, gateway):
+        self.gw = gateway
+        self._cache = {}
+
+    def exreceive(self, execstring):
+        if execstring not in self._cache:
+            channel = self.gw.remote_exec(execstring)
+            self._cache[execstring] = channel.receive()
+        return self._cache[execstring]
+
+    def getmodattr(self, modpath):
+        module = modpath.split(".")[0]
+        return self.exreceive("""
+            import %s
+            channel.send(%s)
+        """ %(module, modpath))
+
+    def islinux(self):
+        return self.getmodattr('sys.platform').find("linux") != -1
+
+    def getfqdn(self):
+        return self.exreceive("""
+            import socket
+            channel.send(socket.getfqdn())
+        """)
+
+    def getmemswap(self):
+        if self.islinux():
+            return self.exreceive(""" 
+            import commands, re
+            out = commands.getoutput("free")
+            mem = re.search(r"Mem:\s+(\S*)", out).group(1)
+            swap = re.search(r"Swap:\s+(\S*)", out).group(1)
+            channel.send((mem, swap))
+            """)
+
+    def getcpuinfo(self):
+        if self.islinux():
+            return self.exreceive("""
+                # a hyperthreaded cpu core only counts as 1, although it
+                # is present as 2 in /proc/cpuinfo.  Counting it as 2 is
+                # misleading because it is *by far* not as efficient as
+                # two independent cores.
+                cpus = {}
+                cpuinfo = {}
+                f = open("/proc/cpuinfo")
+                lines = f.readlines()
+                f.close()
+                for line in lines + ['']:
+                    if line.strip():
+                        key, value = line.split(":", 1)
+                        cpuinfo[key.strip()] = value.strip()
+                    else:
+                        corekey = (cpuinfo.get("physical id"),
+                                   cpuinfo.get("core id"))
+                        cpus[corekey] = 1
+                numcpus = len(cpus)
+                model = cpuinfo.get("model name")
+                channel.send((numcpus, model))
+            """)
+
+def debug(*args):
+    print >>sys.stderr, " ".join(map(str, args))
+def error(*args):
+    debug("ERROR", args[0] + ":", *args[1:])
+
+def getinfo(sshname, ssh_config=None, loginfo=sys.stdout):
+    debug("connecting to", sshname)
+    try:
+        gw = py.execnet.SshGateway(sshname, ssh_config=ssh_config)
+    except IOError:
+        error("could not get sshagteway", sshname)
+    else:
+        ri = RemoteInfo(gw)
+        #print "%s info:" % sshname
+        prefix = sshname.upper() + " "
+        print >>loginfo, prefix, "fqdn:", ri.getfqdn()
+        for attr in (
+            "sys.platform", 
+            "sys.version_info", 
+        ):
+            loginfo.write("%s %s: " %(prefix, attr,))
+            loginfo.flush()
+            value = ri.getmodattr(attr)
+            loginfo.write(str(value))
+            loginfo.write("\n")
+            loginfo.flush()
+        memswap = ri.getmemswap()
+        if memswap:
+            mem,swap = memswap
+            print >>loginfo, prefix, "Memory:", mem, "Swap:", swap 
+        cpuinfo = ri.getcpuinfo()
+        if cpuinfo:
+            numcpu, model = cpuinfo
+            print >>loginfo, prefix, "number of cpus:",  numcpu
+            print >>loginfo, prefix, "cpu model", model 
+        return ri
+            
+if __name__ == '__main__':
+    options, args = parser.parse_args()
+    hosts = list(args)
+    ssh_config = options.ssh_config
+    if ssh_config:
+        hosts.extend(parsehosts(ssh_config))
+    ignores = options.ignores or ()
+    if ignores:
+        ignores = ignores.split(",")
+    for host in hosts:
+        if host not in ignores:
+            getinfo(host, ssh_config=ssh_config)
+        

Added: py/trunk/example/funcarg/conftest.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/conftest.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,3 @@
+import py
+
+collect_ignore = 'mysetup', 'mysetup2', 'test_simpleprovider.py', 'parametrize'

Added: py/trunk/example/funcarg/costlysetup/sub1/__init__.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/costlysetup/sub1/__init__.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1 @@
+#

Added: py/trunk/example/funcarg/costlysetup/sub2/__init__.py
==============================================================================
--- (empty file)
+++ py/trunk/example/funcarg/costlysetup/sub2/__init__.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1 @@
+#

Deleted: /py/trunk/ez_setup.py
==============================================================================
--- /py/trunk/ez_setup.py	Tue Aug  4 19:45:30 2009
+++ (empty file)
@@ -1,276 +0,0 @@
-#!python
-"""Bootstrap setuptools installation
-
-If you want to use setuptools in your package's setup.py, just include this
-file in the same directory with it, and add this to the top of your setup.py::
-
-    from ez_setup import use_setuptools
-    use_setuptools()
-
-If you want to require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, you can do so by supplying
-the appropriate options to ``use_setuptools()``.
-
-This file can also be run as a script to install or upgrade setuptools.
-"""
-import sys
-DEFAULT_VERSION = "0.6c9"
-DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
-
-md5_data = {
-    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
-    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
-    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
-    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
-    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
-    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
-    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
-    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
-    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
-    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
-    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
-    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
-    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
-    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
-    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
-    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
-    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
-    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
-    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
-    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
-    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
-    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
-    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
-    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
-    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
-    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
-    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
-    'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
-    'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
-    'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
-    'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
-    'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
-    'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
-    'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
-}
-
-import sys, os
-try: from hashlib import md5
-except ImportError: from md5 import md5
-
-def _validate_md5(egg_name, data):
-    if egg_name in md5_data:
-        digest = md5(data).hexdigest()
-        if digest != md5_data[egg_name]:
-            print >>sys.stderr, (
-                "md5 validation of %s failed!  (Possible download problem?)"
-                % egg_name
-            )
-            sys.exit(2)
-    return data
-
-def use_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    download_delay=15
-):
-    """Automatically find/download setuptools and make it available on sys.path
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end with
-    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
-    it is not already available.  If `download_delay` is specified, it should
-    be the number of seconds that will be paused before initiating a download,
-    should one be required.  If an older version of setuptools is installed,
-    this routine will print a message to ``sys.stderr`` and raise SystemExit in
-    an attempt to abort the calling script.
-    """
-    was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
-    def do_download():
-        egg = download_setuptools(version, download_base, to_dir, download_delay)
-        sys.path.insert(0, egg)
-        import setuptools; setuptools.bootstrap_install_from = egg
-    try:
-        import pkg_resources
-    except ImportError:
-        return do_download()       
-    try:
-        pkg_resources.require("setuptools>="+version); return
-    except pkg_resources.VersionConflict, e:
-        if was_imported:
-            print >>sys.stderr, (
-            "The required version of setuptools (>=%s) is not available, and\n"
-            "can't be installed while this script is running. Please install\n"
-            " a more recent version first, using 'easy_install -U setuptools'."
-            "\n\n(Currently using %r)"
-            ) % (version, e.args[0])
-            sys.exit(2)
-        else:
-            del pkg_resources, sys.modules['pkg_resources']    # reload ok
-            return do_download()
-    except pkg_resources.DistributionNotFound:
-        return do_download()
-
-def download_setuptools(
-    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
-    delay = 15
-):
-    """Download setuptools from a specified location and return its filename
-
-    `version` should be a valid setuptools version number that is available
-    as an egg for download under the `download_base` URL (which should end
-    with a '/'). `to_dir` is the directory where the egg will be downloaded.
-    `delay` is the number of seconds to pause before an actual download attempt.
-    """
-    import urllib2, shutil
-    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
-    url = download_base + egg_name
-    saveto = os.path.join(to_dir, egg_name)
-    src = dst = None
-    if not os.path.exists(saveto):  # Avoid repeated downloads
-        try:
-            from distutils import log
-            if delay:
-                log.warn("""
----------------------------------------------------------------------------
-This script requires setuptools version %s to run (even to display
-help).  I will attempt to download it for you (from
-%s), but
-you may need to enable firewall access for this script first.
-I will start the download in %d seconds.
-
-(Note: if this machine does not have network access, please obtain the file
-
-   %s
-
-and place it in this directory before rerunning this script.)
----------------------------------------------------------------------------""",
-                    version, download_base, delay, url
-                ); from time import sleep; sleep(delay)
-            log.warn("Downloading %s", url)
-            src = urllib2.urlopen(url)
-            # Read/write all in one block, so we don't create a corrupt file
-            # if the download is interrupted.
-            data = _validate_md5(egg_name, src.read())
-            dst = open(saveto,"wb"); dst.write(data)
-        finally:
-            if src: src.close()
-            if dst: dst.close()
-    return os.path.realpath(saveto)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-def main(argv, version=DEFAULT_VERSION):
-    """Install or upgrade setuptools and EasyInstall"""
-    try:
-        import setuptools
-    except ImportError:
-        egg = None
-        try:
-            egg = download_setuptools(version, delay=0)
-            sys.path.insert(0,egg)
-            from setuptools.command.easy_install import main
-            return main(list(argv)+[egg])   # we're done here
-        finally:
-            if egg and os.path.exists(egg):
-                os.unlink(egg)
-    else:
-        if setuptools.__version__ == '0.0.1':
-            print >>sys.stderr, (
-            "You have an obsolete version of setuptools installed.  Please\n"
-            "remove it from your system entirely before rerunning this script."
-            )
-            sys.exit(2)
-
-    req = "setuptools>="+version
-    import pkg_resources
-    try:
-        pkg_resources.require(req)
-    except pkg_resources.VersionConflict:
-        try:
-            from setuptools.command.easy_install import main
-        except ImportError:
-            from easy_install import main
-        main(list(argv)+[download_setuptools(delay=0)])
-        sys.exit(0) # try to force an exit
-    else:
-        if argv:
-            from setuptools.command.easy_install import main
-            main(argv)
-        else:
-            print "Setuptools version",version,"or greater has been installed."
-            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
-
-def update_md5(filenames):
-    """Update our built-in md5 registry"""
-
-    import re
-
-    for name in filenames:
-        base = os.path.basename(name)
-        f = open(name,'rb')
-        md5_data[base] = md5(f.read()).hexdigest()
-        f.close()
-
-    data = ["    %r: %r,\n" % it for it in md5_data.items()]
-    data.sort()
-    repl = "".join(data)
-
-    import inspect
-    srcfile = inspect.getsourcefile(sys.modules[__name__])
-    f = open(srcfile, 'rb'); src = f.read(); f.close()
-
-    match = re.search("\nmd5_data = {\n([^}]+)}", src)
-    if not match:
-        print >>sys.stderr, "Internal error!"
-        sys.exit(2)
-
-    src = src[:match.start(1)] + repl + src[match.end(1):]
-    f = open(srcfile,'w')
-    f.write(src)
-    f.close()
-
-
-if __name__=='__main__':
-    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
-        update_md5(sys.argv[2:])
-    else:
-        main(sys.argv[1:])
-
-
-
-
-
-

Modified: py/trunk/py/__init__.py
==============================================================================
--- py/trunk/py/__init__.py	(original)
+++ py/trunk/py/__init__.py	Tue Aug  4 19:45:30 2009
@@ -18,8 +18,9 @@
  
 """
 from initpkg import initpkg
+trunk = None
 
-version = "1.0.0b6"
+version = trunk or "1.0.0"
 
 initpkg(__name__,
     description = "py.test and pylib: advanced testing tool and networking lib", 
@@ -31,7 +32,7 @@
     author_email = "holger at merlinux.eu, py-dev at codespeak.net",
     long_description = globals()['__doc__'],
     classifiers = [
-        "Development Status :: 4 - Beta", 
+        "Development Status :: 5 - Production/Stable", 
         "Intended Audience :: Developers", 
         "License :: OSI Approved :: MIT License",
         "Operating System :: POSIX",

Modified: py/trunk/py/code/excinfo.py
==============================================================================
--- py/trunk/py/code/excinfo.py	(original)
+++ py/trunk/py/code/excinfo.py	Tue Aug  4 19:45:30 2009
@@ -57,14 +57,15 @@
         reprcrash = ReprFileLocation(path, lineno+1, exconly)
         return reprcrash
 
-    def getrepr(self, showlocals=False, style="long", tbfilter=True, funcargs=False):
+    def getrepr(self, showlocals=False, style="long", 
+            abspath=False, tbfilter=True, funcargs=False):
         """ return str()able representation of this exception info.
             showlocals: show locals per traceback entry 
             style: long|short|no traceback style 
             tbfilter: hide entries (where __tracebackhide__ is true)
         """
         fmt = FormattedExcinfo(showlocals=showlocals, style=style, 
-                               tbfilter=tbfilter, funcargs=funcargs)
+            abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
         return fmt.repr_excinfo(self)
 
     def __str__(self):
@@ -78,11 +79,12 @@
     flow_marker = ">"    
     fail_marker = "E"
     
-    def __init__(self, showlocals=False, style="long", tbfilter=True, funcargs=False):
+    def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
         self.showlocals = showlocals
         self.style = style
         self.tbfilter = tbfilter
         self.funcargs = funcargs
+        self.abspath = abspath 
 
     def _getindent(self, source):
         # figure out indent for given source 
@@ -154,8 +156,9 @@
                 if name == '__builtins__': 
                     lines.append("__builtins__ = <builtins>")
                 else:
-                    # This formatting could all be handled by the _repr() function, which is 
-                    # only repr.Repr in disguise, so is very configurable.
+                    # This formatting could all be handled by the
+                    # _repr() function, which is only repr.Repr in
+                    # disguise, so is very configurable.
                     str_repr = self._saferepr(value)
                     #if len(str_repr) < 70 or not isinstance(value,
                     #                            (list, tuple, dict)):
@@ -180,7 +183,8 @@
             reprargs = self.repr_args(entry) 
             lines.extend(self.get_source(source, line_index, excinfo))
             message = excinfo and excinfo.typename or ""
-            filelocrepr = ReprFileLocation(entry.path, entry.lineno+1, message)
+            path = self._makepath(entry.path)
+            filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
             localsrepr =  self.repr_locals(entry.locals)
             return ReprEntry(lines, reprargs, localsrepr, filelocrepr)
         else: 
@@ -193,6 +197,13 @@
                 lines.extend(self.get_exconly(excinfo, indent=4))
             return ReprEntry(lines, None, None, None)
 
+    def _makepath(self, path):
+        if not self.abspath:
+            np = py.path.local().bestrelpath(path)
+            if len(np) < len(str(path)):
+                path = np
+        return path
+
     def repr_traceback(self, excinfo): 
         traceback = excinfo.traceback 
         if self.tbfilter:

Modified: py/trunk/py/code/testing/test_excinfo.py
==============================================================================
--- py/trunk/py/code/testing/test_excinfo.py	(original)
+++ py/trunk/py/code/testing/test_excinfo.py	Tue Aug  4 19:45:30 2009
@@ -625,6 +625,29 @@
         assert tw.lines[9] == ""
         assert tw.lines[10].endswith("mod.py:3: ValueError")
 
+    def test_toterminal_long_filenames(self):
+        mod = self.importasmod("""
+            def f():
+                raise ValueError()
+        """)
+        excinfo = py.test.raises(ValueError, mod.f)
+        tw = TWMock()
+        path = py.path.local(mod.__file__)
+        old = path.dirpath().chdir()
+        try: 
+            repr = excinfo.getrepr(abspath=False)
+            repr.toterminal(tw)
+            line = tw.lines[-1]
+            x = py.path.local().bestrelpath(path) 
+            if len(x) < len(str(path)):
+                assert line == "mod.py:3: ValueError" 
+
+            repr = excinfo.getrepr(abspath=True)
+            repr.toterminal(tw)
+            line = tw.lines[-1]
+            assert line == "%s:3: ValueError" %(path,)
+        finally:
+            old.chdir()
 
     def test_format_excinfo(self):
         mod = self.importasmod("""

Modified: py/trunk/py/conftest.py
==============================================================================
--- py/trunk/py/conftest.py	(original)
+++ py/trunk/py/conftest.py	Tue Aug  4 19:45:30 2009
@@ -43,3 +43,13 @@
         if spec.socket:
             return spec
     py.test.skip("need '--gx socket=...'")
+
+
+def pytest_generate_tests(metafunc):
+    multi = getattr(metafunc.function, 'multi', None)
+    if multi is None:
+        return
+    assert len(multi.__dict__) == 1
+    for name, l in multi.__dict__.items():
+        for val in l:
+            metafunc.addcall(funcargs={name: val})

Modified: py/trunk/py/execnet/register.py
==============================================================================
--- py/trunk/py/execnet/register.py	(original)
+++ py/trunk/py/execnet/register.py	Tue Aug  4 19:45:30 2009
@@ -63,12 +63,15 @@
     def __init__(self, cmd):
         # on win close_fds=True does not work, not sure it'd needed
         #p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True)
-        p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE) 
+        self._popen = p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE) 
         infile, outfile = p.stdin, p.stdout
         self._cmd = cmd
         io = inputoutput.Popen2IO(infile, outfile)
         super(PopenCmdGateway, self).__init__(io=io)
 
+    def exit(self):
+        super(PopenCmdGateway, self).exit()
+        self._popen.poll()
 
 class PopenGateway(PopenCmdGateway):
     """ This Gateway provides interaction with a newly started

Modified: py/trunk/py/execnet/testing/test_gateway.py
==============================================================================
--- py/trunk/py/execnet/testing/test_gateway.py	(original)
+++ py/trunk/py/execnet/testing/test_gateway.py	Tue Aug  4 19:45:30 2009
@@ -548,7 +548,7 @@
             ret = channel.receive()
             assert ret == 42
             
-    @py.test.xfail # "fix needed: dying remote process does not cause waitclose() to fail"
+    @py.test.mark.xfail # "fix needed: dying remote process does not cause waitclose() to fail"
     def test_waitclose_on_remote_killed(self):
         gw = py.execnet.PopenGateway()
         channel = gw.remote_exec("""
@@ -621,12 +621,12 @@
     def test_sshaddress(self):
         assert self.gw.remoteaddress == self.sshhost
 
-    @py.test.xfail # XXX ssh-gateway error handling
+    @py.test.mark.xfail # XXX ssh-gateway error handling
     def test_connexion_failes_on_non_existing_hosts(self):
         py.test.raises(IOError, 
             "py.execnet.SshGateway('nowhere.codespeak.net')")
 
-    @py.test.xfail # "XXX ssh-gateway error handling"
+    @py.test.mark.xfail # "XXX ssh-gateway error handling"
     def test_deprecated_identity(self):
         py.test.deprecated_call(
             py.test.raises, IOError, 

Modified: py/trunk/py/io/stdcapture.py
==============================================================================
--- py/trunk/py/io/stdcapture.py	(original)
+++ py/trunk/py/io/stdcapture.py	Tue Aug  4 19:45:30 2009
@@ -21,22 +21,53 @@
         return res, out, err 
     call = classmethod(call) 
 
-    def reset(self): 
-        """ reset sys.stdout and sys.stderr and return captured output 
-            as strings and restore sys.stdout/err.
-        """
-        x, y = self.done() 
-        outerr = x.read(), y.read() 
-        x.close()
-        y.close()
+    def reset(self):
+        """ reset sys.stdout/stderr and return captured output as strings. """
+        if hasattr(self, '_suspended'):
+            outfile = self._kwargs['out']
+            errfile = self._kwargs['err']
+            del self._kwargs
+        else:
+            outfile, errfile = self.done() 
+        out, err = "", ""
+        if outfile:
+            out = outfile.read()
+            outfile.close()
+        if errfile and errfile != outfile:
+            err = errfile.read()
+            errfile.close()
+        return out, err
+
+    def suspend(self):
+        """ return current snapshot captures, memorize tempfiles. """
+        assert not hasattr(self, '_suspended')
+        self._suspended = True
+        outerr = self.readouterr()
+        outfile, errfile = self.done()
+        self._kwargs['out'] = outfile
+        self._kwargs['err'] = errfile
         return outerr
 
+    def resume(self):
+        """ resume capturing with original temp files. """
+        assert self._suspended
+        self._initialize(**self._kwargs)
+        del self._suspended
+
+
 class StdCaptureFD(Capture): 
     """ This class allows to capture writes to FD1 and FD2 
         and may connect a NULL file to FD0 (and prevent
         reads from sys.stdin)
     """
-    def __init__(self, out=True, err=True, mixed=False, in_=True, patchsys=True): 
+    def __init__(self, out=True, err=True, 
+                 mixed=False, in_=True, patchsys=True): 
+        self._kwargs = locals().copy()
+        del self._kwargs['self']
+        self._initialize(**self._kwargs)
+
+    def _initialize(self, out=True, err=True, 
+                    mixed=False, in_=True, patchsys=True): 
         if in_:
             self._oldin = (sys.stdin, os.dup(0))
             sys.stdin  = DontReadFromInput()
@@ -44,14 +75,19 @@
             os.dup2(fd, 0)
             os.close(fd)
         if out: 
-            self.out = py.io.FDCapture(1) 
+            tmpfile = None
+            if isinstance(out, file):
+                tmpfile = out
+            self.out = py.io.FDCapture(1, tmpfile=tmpfile)
             if patchsys: 
                 self.out.setasfile('stdout')
         if err: 
             if mixed and out:
                 tmpfile = self.out.tmpfile 
+            elif isinstance(err, file):
+                tmpfile = err
             else:
-                tmpfile = None    
+                tmpfile = None
             self.err = py.io.FDCapture(2, tmpfile=tmpfile) 
             if patchsys: 
                 self.err.setasfile('stderr')
@@ -61,11 +97,11 @@
         if hasattr(self, 'out'): 
             outfile = self.out.done() 
         else:
-            outfile = StringIO()
+            outfile = None
         if hasattr(self, 'err'): 
             errfile = self.err.done() 
         else:
-            errfile = StringIO()
+            errfile = None 
         if hasattr(self, '_oldin'):
             oldsys, oldfd = self._oldin 
             os.dup2(oldfd, 0)
@@ -73,6 +109,20 @@
             sys.stdin = oldsys 
         return outfile, errfile 
 
+    def readouterr(self):
+        """ return snapshot value of stdout/stderr capturings. """
+        l = []
+        for name in ('out', 'err'):
+            res = ""
+            if hasattr(self, name):
+                f = getattr(self, name).tmpfile
+                f.seek(0)
+                res = f.read()
+                f.truncate(0)
+                f.seek(0)
+            l.append(res)
+        return l 
+
 class StdCapture(Capture):
     """ This class allows to capture writes to sys.stdout|stderr "in-memory"
         and will raise errors on tries to read from sys.stdin. It only
@@ -80,21 +130,28 @@
         touch underlying File Descriptors (use StdCaptureFD for that). 
     """
     def __init__(self, out=True, err=True, in_=True, mixed=False):
+        self._kwargs = locals().copy()
+        del self._kwargs['self']
+        self._initialize(**self._kwargs)
+
+    def _initialize(self, out, err, in_, mixed):
         self._out = out
         self._err = err 
         self._in = in_
         if out: 
-            self.oldout = sys.stdout
-            sys.stdout = self.newout = StringIO()
+            self._oldout = sys.stdout
+            if not hasattr(out, 'write'):
+                out = StringIO()
+            sys.stdout = self.out = out
         if err: 
-            self.olderr = sys.stderr
+            self._olderr = sys.stderr
             if out and mixed: 
-                newerr = self.newout 
-            else:
-                newerr = StringIO()
-            sys.stderr = self.newerr = newerr
+                err = self.out 
+            elif not hasattr(err, 'write'):
+                err = StringIO()
+            sys.stderr = self.err = err
         if in_:
-            self.oldin  = sys.stdin
+            self._oldin  = sys.stdin
             sys.stdin  = self.newin  = DontReadFromInput()
 
     def done(self): 
@@ -102,28 +159,39 @@
         o,e = sys.stdout, sys.stderr
         if self._out: 
             try:
-                sys.stdout = self.oldout 
+                sys.stdout = self._oldout 
             except AttributeError:
                 raise IOError("stdout capturing already reset")
-            del self.oldout
-            outfile = self.newout
+            del self._oldout
+            outfile = self.out
             outfile.seek(0)
         else:
-            outfile = StringIO()
+            outfile = None
         if self._err: 
             try:
-                sys.stderr = self.olderr 
+                sys.stderr = self._olderr 
             except AttributeError:
                 raise IOError("stderr capturing already reset")
-            del self.olderr 
-            errfile = self.newerr 
+            del self._olderr 
+            errfile = self.err 
             errfile.seek(0)
         else:
-            errfile = StringIO()
+            errfile = None
         if self._in:
-            sys.stdin = self.oldin 
+            sys.stdin = self._oldin 
         return outfile, errfile
 
+    def readouterr(self):
+        """ return snapshot value of stdout/stderr capturings. """
+        out = err = ""
+        if self._out:
+            out = sys.stdout.getvalue()
+            sys.stdout.truncate(0)
+        if self._err:
+            err = sys.stderr.getvalue()
+            sys.stderr.truncate(0)
+        return out, err 
+
 class DontReadFromInput:
     """Temporary stub class.  Ideally when stdin is accessed, the
     capturing should be turned off, with possibly all data captured

Modified: py/trunk/py/io/terminalwriter.py
==============================================================================
--- py/trunk/py/io/terminalwriter.py	(original)
+++ py/trunk/py/io/terminalwriter.py	Tue Aug  4 19:45:30 2009
@@ -201,13 +201,8 @@
         self._file.flush()
 
     def line(self, s='', **kw):
-        if s:
-            s = self.markup(s, **kw)
-            self._file.write(s + '\n')
-        else:
-            self._file.write('\n')
-        self._file.flush()
-
+        self.write(s, **kw)
+        self.write('\n')
 
 class Win32ConsoleWriter(object):
 

Modified: py/trunk/py/io/testing/test_stdcapture.py
==============================================================================
--- py/trunk/py/io/testing/test_stdcapture.py	(original)
+++ py/trunk/py/io/testing/test_stdcapture.py	Tue Aug  4 19:45:30 2009
@@ -30,6 +30,19 @@
         assert out == "hello world\n"
         assert err == "hello error\n"
 
+    def test_capturing_readouterr(self):
+        cap = self.getcapture()
+        try:
+            print "hello world"
+            print >>sys.stderr, "hello error"
+            out, err = cap.readouterr()
+            assert out == "hello world\n"
+            assert err == "hello error\n"
+            print >>sys.stderr, "error2"
+        finally:
+            out, err = cap.reset()
+        assert err == "error2\n"
+
     def test_capturing_mixed(self):
         cap = self.getcapture(mixed=True)
         print "hello",
@@ -43,7 +56,7 @@
         cap = self.getcapture() 
         print "hello"
         cap.reset()
-        py.test.raises(EnvironmentError, "cap.reset()")
+        py.test.raises(Exception, "cap.reset()")
 
     def test_capturing_modify_sysouterr_in_between(self):
         oldout = sys.stdout 
@@ -67,7 +80,7 @@
         cap2 = self.getcapture() 
         print "cap2"
         out2, err2 = cap2.reset()
-        py.test.raises(EnvironmentError, "cap2.reset()")
+        py.test.raises(Exception, "cap2.reset()")
         out1, err1 = cap1.reset() 
         assert out1 == "cap1\n"
         assert out2 == "cap2\n"
@@ -104,6 +117,24 @@
         py.test.raises(IOError, "sys.stdin.read()")
         out, err = cap.reset()
 
+    def test_suspend_resume(self):
+        cap = self.getcapture(out=True, err=False, in_=False)
+        try:
+            print "hello"
+            sys.stderr.write("error\n")
+            out, err = cap.suspend()
+            assert out == "hello\n"
+            assert not err 
+            print "in between"
+            sys.stderr.write("in between\n")
+            cap.resume()
+            print "after"
+            sys.stderr.write("error_after\n")
+        finally:
+            out, err = cap.reset()
+        assert out == "after\n"
+        assert not err 
+
 class TestStdCaptureFD(TestStdCapture): 
     def getcapture(self, **kw): 
         return py.io.StdCaptureFD(**kw)
@@ -150,10 +181,14 @@
         os.write(1, "hello")
         os.write(2, "hello")
         print x
-        print >>py.std.sys.stderr, y
+        print >>sys.stderr, y
         return 42
    
-    res, out, err = py.io.StdCapture.call(func, 3, y=4) 
+    capfd = py.io.StdCaptureFD(patchsys=False)
+    try:
+        res, out, err = py.io.StdCapture.call(func, 3, y=4) 
+    finally:
+        capfd.reset()
     assert res == 42 
     assert out.startswith("3") 
     assert err.startswith("4") 

Modified: py/trunk/py/magic/exprinfo.py
==============================================================================
--- py/trunk/py/magic/exprinfo.py	(original)
+++ py/trunk/py/magic/exprinfo.py	Tue Aug  4 19:45:30 2009
@@ -122,6 +122,10 @@
         expr = Interpretable(self.expr)
         expr.eval(frame)
         for operation, expr2 in self.ops:
+            if hasattr(self, 'result'):
+                # shortcutting in chained expressions
+                if not frame.is_true(self.result):
+                    break
             expr2 = Interpretable(expr2)
             expr2.eval(frame)
             self.explanation = "%s %s %s" % (
@@ -135,8 +139,6 @@
                 raise
             except:
                 raise Failure(self)
-            if not frame.is_true(self.result):
-                break
             expr = expr2
 
 class And(Interpretable):

Modified: py/trunk/py/magic/testing/test_exprinfo.py
==============================================================================
--- py/trunk/py/magic/testing/test_exprinfo.py	(original)
+++ py/trunk/py/magic/testing/test_exprinfo.py	Tue Aug  4 19:45:30 2009
@@ -131,3 +131,26 @@
     s = result.stdout.str()
     assert s.find("re-run") != -1
 
+def test_twoarg_comparison_does_not_call_nonzero():
+    # this arises e.g. in numpy array comparisons 
+    class X(object):
+        def __eq__(self, other):
+            return self
+
+        def __nonzero__(self):
+            raise ValueError
+
+        def all(self):
+            return False
+
+    def f():
+        a = X()
+        b = X()
+        assert (a == b).all()
+
+    excinfo = getexcinfo(AssertionError, f)
+    msg = getmsg(excinfo)
+    print msg
+    assert "re-run" not in msg
+    assert "ValueError" not in msg
+

Modified: py/trunk/py/path/common.py
==============================================================================
--- py/trunk/py/path/common.py	(original)
+++ py/trunk/py/path/common.py	Tue Aug  4 19:45:30 2009
@@ -152,14 +152,14 @@
         return ""
 
     def bestrelpath(self, dest): 
-        """ return relative path from self to dest
-            such that self.join(bestrelpath) == dest. 
+        """ return a string which is a relative path from self 
+            to dest such that self.join(bestrelpath) == dest and 
             if not such path can be determined return dest. 
         """ 
         try:
             base = self.common(dest)
             if not base:  # can be the case on windows
-                return dest
+                return str(dest)
             self2base = self.relto(base)
             reldest = dest.relto(base)
             if self2base:
@@ -172,7 +172,7 @@
             target = dest.sep.join(l)
             return target 
         except AttributeError:
-            return dest
+            return str(dest)
 
 
     def parts(self, reverse=False):

Modified: py/trunk/py/path/svn/wccommand.py
==============================================================================
--- py/trunk/py/path/svn/wccommand.py	(original)
+++ py/trunk/py/path/svn/wccommand.py	Tue Aug  4 19:45:30 2009
@@ -454,6 +454,8 @@
             except py.process.cmdexec.Error, e:
                 if e.err.find('is not a working copy')!=-1:
                     return False
+                if e.err.lower().find('not a versioned resource') != -1:
+                    return False
                 raise
             else:
                 return True 

Modified: py/trunk/py/process/testing/test_forkedfunc.py
==============================================================================
--- py/trunk/py/process/testing/test_forkedfunc.py	(original)
+++ py/trunk/py/process/testing/test_forkedfunc.py	Tue Aug  4 19:45:30 2009
@@ -11,12 +11,12 @@
     ff.waitfinish()
     assert not ff.tempdir.check()
 
-def test_tempdir_gets_gc_collected():
+def test_tempdir_gets_gc_collected(monkeypatch):
+    monkeypatch.setattr(os, 'fork', lambda: os.getpid())
     ff = py.process.ForkedFunc(boxf1)
     assert ff.tempdir.check()
     ff.__del__()
     assert not ff.tempdir.check()
-    os.waitpid(ff.pid, 0)
 
 def test_basic_forkedfunc():
     result = py.process.ForkedFunc(boxf1).waitfinish()

Modified: py/trunk/py/test/collect.py
==============================================================================
--- py/trunk/py/test/collect.py	(original)
+++ py/trunk/py/test/collect.py	Tue Aug  4 19:45:30 2009
@@ -4,7 +4,6 @@
 that is usually built iteratively.  
 """ 
 import py
-from py.__.test.outcome import Skipped
 
 def configproperty(name):
     def fget(self):
@@ -31,6 +30,10 @@
         self.config = getattr(parent, 'config', None)
         self.fspath = getattr(parent, 'fspath', None) 
 
+    def _checkcollectable(self):
+        if not hasattr(self, 'fspath'):
+            self.parent._memocollect() # to reraise exception
+            
     # 
     # note to myself: Pickling is uh.
     # 
@@ -44,6 +47,7 @@
         except Exception:
             # seems our parent can't collect us 
             # so let's be somewhat operable 
+            # _checkcollectable() is to tell outsiders about the fact
             self.name = name 
             self.parent = parent 
             self.config = parent.config
@@ -247,7 +251,8 @@
         return col._getitembynames(names)
     _fromtrail = staticmethod(_fromtrail)
 
-    def _repr_failure_py(self, excinfo, outerr):
+    def _repr_failure_py(self, excinfo, outerr=None):
+        assert outerr is None, "XXX deprecated"
         excinfo.traceback = self._prunetraceback(excinfo.traceback)
         # XXX temporary hack: getrepr() should not take a 'style' argument
         # at all; it should record all data in all cases, and the style
@@ -256,13 +261,9 @@
             style = "short"
         else:
             style = "long"
-        repr = excinfo.getrepr(funcargs=True, 
+        return excinfo.getrepr(funcargs=True, 
                                showlocals=self.config.option.showlocals,
                                style=style)
-        for secname, content in zip(["out", "err"], outerr):
-            if content:
-                repr.addsection("Captured std%s" % secname, content.rstrip())
-        return repr 
 
     repr_failure = _repr_failure_py
     shortfailurerepr = "F"
@@ -291,9 +292,10 @@
             if colitem.name == name:
                 return colitem
 
-    def repr_failure(self, excinfo, outerr):
+    def repr_failure(self, excinfo, outerr=None):
         """ represent a failure. """
-        return self._repr_failure_py(excinfo, outerr)
+        assert outerr is None, "XXX deprecated"
+        return self._repr_failure_py(excinfo)
 
     def _memocollect(self):
         """ internal helper method to cache results of calling collect(). """

Modified: py/trunk/py/test/config.py
==============================================================================
--- py/trunk/py/test/config.py	(original)
+++ py/trunk/py/test/config.py	Tue Aug  4 19:45:30 2009
@@ -240,20 +240,6 @@
         finally: 
             config_per_process = py.test.config = oldconfig 
 
-    def _getcapture(self, path=None):
-        if self.option.nocapture:
-            iocapture = "no" 
-        else:
-            iocapture = self.getvalue("iocapture", path=path)
-        if iocapture == "fd": 
-            return py.io.StdCaptureFD()
-        elif iocapture == "sys":
-            return py.io.StdCapture()
-        elif iocapture == "no": 
-            return py.io.StdCapture(out=False, err=False, in_=False)
-        else:
-            raise self.Error("unknown io capturing: " + iocapture)
-
     def getxspecs(self):
         xspeclist = []
         for xspec in self.getvalue("tx"):
@@ -286,29 +272,6 @@
         if pydir is not None:
             roots.append(pydir)
         return roots 
-
-    def guardedcall(self, func):
-        excinfo = result = None
-        capture = self._getcapture()
-        try:
-            try:
-                result = func()
-            except KeyboardInterrupt:
-                raise
-            except:
-                excinfo = py.code.ExceptionInfo()
-        finally:
-            stdout, stderr = capture.reset()
-        return CallResult(result, excinfo, stdout, stderr)
-
-class CallResult:
-    def __init__(self, result, excinfo, stdout, stderr):
-        self.stdout = stdout 
-        self.stderr = stderr
-        self.outerr = (self.stdout, self.stderr)
-        self.excinfo = excinfo
-        if excinfo is None:
-            self.result = result 
     
 #
 # helpers

Modified: py/trunk/py/test/defaultconftest.py
==============================================================================
--- py/trunk/py/test/defaultconftest.py	(original)
+++ py/trunk/py/test/defaultconftest.py	Tue Aug  4 19:45:30 2009
@@ -10,5 +10,6 @@
 Function = py.test.collect.Function
 Instance = py.test.collect.Instance
 
-pytest_plugins = "default runner terminal xfail tmpdir execnetcleanup monkeypatch recwarn pdb".split()
+pytest_plugins = "default runner capture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb pastebin unittest".split()
 
+conf_capture = "fd"

Modified: py/trunk/py/test/dist/dsession.py
==============================================================================
--- py/trunk/py/test/dist/dsession.py	(original)
+++ py/trunk/py/test/dist/dsession.py	Tue Aug  4 19:45:30 2009
@@ -11,6 +11,13 @@
 
 import Queue 
 
+debug_file = None # open('/tmp/loop.log', 'w')
+def debug(*args):
+    if debug_file is not None:
+        s = " ".join(map(str, args))
+        debug_file.write(s+"\n")
+        debug_file.flush()
+
 class LoopState(object):
     def __init__(self, dsession, colitems):
         self.dsession = dsession
@@ -23,15 +30,20 @@
         self.shuttingdown = False
         self.testsfailed = False
 
-    def pytest_runtest_logreport(self, rep):
-        if rep.item in self.dsession.item2nodes:
-            self.dsession.removeitem(rep.item, rep.node)
-        if rep.failed:
+    def __repr__(self):
+        return "<LoopState exitstatus=%r shuttingdown=%r len(colitems)=%d>" % (
+            self.exitstatus, self.shuttingdown, len(self.colitems))
+
+    def pytest_runtest_logreport(self, report):
+        if report.item in self.dsession.item2nodes:
+            if report.when != "teardown": # otherwise we already managed it
+                self.dsession.removeitem(report.item, report.node)
+        if report.failed:
             self.testsfailed = True
 
-    def pytest_collectreport(self, rep):
-        if rep.passed:
-            self.colitems.extend(rep.result)
+    def pytest_collectreport(self, report):
+        if report.passed:
+            self.colitems.extend(report.result)
 
     def pytest_testnodeready(self, node):
         self.dsession.addnode(node)
@@ -39,9 +51,14 @@
     def pytest_testnodedown(self, node, error=None):
         pending = self.dsession.removenode(node)
         if pending:
-            crashitem = pending[0]
-            self.dsession.handle_crashitem(crashitem, node)
-            self.colitems.extend(pending[1:])
+            if error:
+                crashitem = pending[0]
+                debug("determined crashitem", crashitem)
+                self.dsession.handle_crashitem(crashitem, node)
+                # XXX recovery handling for "each"? 
+                # currently pending items are not retried 
+                if self.dsession.config.option.dist == "load":
+                    self.colitems.extend(pending[1:])
 
     def pytest_rescheduleitems(self, items):
         self.colitems.extend(items)
@@ -75,7 +92,7 @@
         self.setup()
         exitstatus = self.loop(colitems)
         self.teardown()
-        self.sessionfinishes() 
+        self.sessionfinishes(exitstatus=exitstatus) 
         return exitstatus
 
     def loop_once(self, loopstate):
@@ -115,6 +132,9 @@
         if eventname == "pytest_testnodedown":
             self.config.hook.pytest_testnodedown(**kwargs)
             self.removenode(kwargs['node'])
+        elif eventname == "pytest_runtest_logreport":
+            # might be some teardown report
+            self.config.hook.pytest_runtest_logreport(**kwargs)
         if not self.node2pending:
             # finished
             if loopstate.testsfailed:
@@ -138,6 +158,8 @@
                     exitstatus = loopstate.exitstatus
                     break 
         except KeyboardInterrupt:
+            excinfo = py.code.ExceptionInfo()
+            self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
             exitstatus = outcome.EXIT_INTERRUPTED
         except:
             self.config.pluginmanager.notify_exception()
@@ -177,7 +199,7 @@
             else:
                 self.config.hook.pytest_collectstart(collector=next)
                 colrep = self.config.hook.pytest_make_collect_report(collector=next)
-                self.queueevent("pytest_collectreport", rep=colrep)
+                self.queueevent("pytest_collectreport", report=colrep)
         if self.config.option.dist == "each":
             self.senditems_each(senditems)
         else:
@@ -198,7 +220,9 @@
             node.sendlist(sending)
             pending.extend(sending)
             for item in sending:
-                self.item2nodes.setdefault(item, []).append(node)
+                nodes = self.item2nodes.setdefault(item, [])
+                assert node not in nodes
+                nodes.append(node)
                 self.config.hook.pytest_itemstart(item=item, node=node)
         tosend[:] = tosend[room:]  # update inplace
         if tosend:
@@ -235,14 +259,15 @@
             nodes.remove(node)
         if not nodes:
             del self.item2nodes[item]
-        self.node2pending[node].remove(item)
+        pending = self.node2pending[node]
+        pending.remove(item)
 
     def handle_crashitem(self, item, node):
         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_runtest_logreport(rep=rep)
+        self.config.hook.pytest_runtest_logreport(report=rep)
 
     def setup(self):
         """ setup any neccessary resources ahead of the test run. """

Modified: py/trunk/py/test/dist/mypickle.py
==============================================================================
--- py/trunk/py/test/dist/mypickle.py	(original)
+++ py/trunk/py/test/dist/mypickle.py	Tue Aug  4 19:45:30 2009
@@ -69,7 +69,8 @@
         pickler = MyPickler(f, self._protocol, uneven=self.uneven)
         pickler.memo = self._picklememo
         pickler.dump(obj)
-        self._updateunpicklememo()
+        if obj is not None:
+            self._updateunpicklememo()
         #print >>debug, "dumped", obj 
         #print >>debug, "picklememo", self._picklememo
         return f.getvalue()

Added: py/trunk/py/test/dist/testing/acceptance_test.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/dist/testing/acceptance_test.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,148 @@
+import py
+
+class TestDistribution:
+    def test_dist_conftest_options(self, testdir):
+        p1 = testdir.tmpdir.ensure("dir", 'p1.py')
+        p1.dirpath("__init__.py").write("")
+        p1.dirpath("conftest.py").write(py.code.Source("""
+            print "importing conftest", __file__
+            import py
+            Option = py.test.config.Option 
+            option = py.test.config.addoptions("someopt", 
+                Option('--someopt', action="store_true", dest="someopt", default=False))
+            dist_rsync_roots = ['../dir']
+            print "added options", option
+            print "config file seen from conftest", py.test.config
+        """))
+        p1.write(py.code.Source("""
+            import py, conftest
+            def test_1(): 
+                print "config from test_1", py.test.config
+                print "conftest from test_1", conftest.__file__
+                print "test_1: py.test.config.option.someopt", py.test.config.option.someopt
+                print "test_1: conftest", conftest
+                print "test_1: conftest.option.someopt", conftest.option.someopt
+                assert conftest.option.someopt 
+        """))
+        result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt')
+        assert result.ret == 0
+        extra = result.stdout.fnmatch_lines([
+            "*1 passed*", 
+        ])
+
+    def test_manytests_to_one_popen(self, testdir):
+        p1 = testdir.makepyfile("""
+                import py
+                def test_fail0():
+                    assert 0
+                def test_fail1():
+                    raise ValueError()
+                def test_ok():
+                    pass
+                def test_skip():
+                    py.test.skip("hello")
+            """, 
+        )
+        result = testdir.runpytest(p1, '-d', '--tx=popen', '--tx=popen')
+        result.stdout.fnmatch_lines([
+            "*1*popen*Python*",
+            "*2*popen*Python*",
+            "*2 failed, 1 passed, 1 skipped*",
+        ])
+        assert result.ret == 1
+
+    def test_dist_conftest_specified(self, testdir):
+        p1 = testdir.makepyfile("""
+                import py
+                def test_fail0():
+                    assert 0
+                def test_fail1():
+                    raise ValueError()
+                def test_ok():
+                    pass
+                def test_skip():
+                    py.test.skip("hello")
+            """, 
+        )
+        testdir.makeconftest("""
+            pytest_option_tx = 'popen popen popen'.split()
+        """)
+        result = testdir.runpytest(p1, '-d')
+        result.stdout.fnmatch_lines([
+            "*1*popen*Python*",
+            "*2*popen*Python*",
+            "*3*popen*Python*",
+            "*2 failed, 1 passed, 1 skipped*",
+        ])
+        assert result.ret == 1
+
+    def test_dist_tests_with_crash(self, testdir):
+        if not hasattr(py.std.os, 'kill'):
+            py.test.skip("no os.kill")
+        
+        p1 = testdir.makepyfile("""
+                import py
+                def test_fail0():
+                    assert 0
+                def test_fail1():
+                    raise ValueError()
+                def test_ok():
+                    pass
+                def test_skip():
+                    py.test.skip("hello")
+                def test_crash():
+                    import time
+                    import os
+                    time.sleep(0.5)
+                    os.kill(os.getpid(), 15)
+            """
+        )
+        result = testdir.runpytest(p1, '-d', '--tx=3*popen')
+        result.stdout.fnmatch_lines([
+            "*popen*Python*",
+            "*popen*Python*",
+            "*popen*Python*",
+            "*node down*",
+            "*3 failed, 1 passed, 1 skipped*"
+        ])
+        assert result.ret == 1
+
+    def test_distribution_rsyncdirs_example(self, testdir):
+        source = testdir.mkdir("source")
+        dest = testdir.mkdir("dest")
+        subdir = source.mkdir("example_pkg")
+        subdir.ensure("__init__.py")
+        p = subdir.join("test_one.py")
+        p.write("def test_5(): assert not __file__.startswith(%r)" % str(p))
+        result = testdir.runpytest("-d", "--rsyncdir=%(subdir)s" % locals(), 
+            "--tx=popen//chdir=%(dest)s" % locals(), p)
+        assert result.ret == 0
+        result.stdout.fnmatch_lines([
+            "*1* *popen*platform*",
+            #"RSyncStart: [G1]",
+            #"RSyncFinished: [G1]",
+            "*1 passed*"
+        ])
+        assert dest.join(subdir.basename).check(dir=1)
+
+    def test_dist_each(self, testdir):
+        interpreters = []
+        for name in ("python2.4", "python2.5"):
+            interp = py.path.local.sysfind(name)
+            if interp is None:
+                py.test.skip("%s not found" % name)
+            interpreters.append(interp)
+
+        testdir.makepyfile(__init__="", test_one="""
+            import sys
+            def test_hello():
+                print "%s...%s" % sys.version_info[:2]
+                assert 0
+        """)
+        args = ["--dist=each"]
+        args += ["--tx", "popen//python=%s" % interpreters[0]]
+        args += ["--tx", "popen//python=%s" % interpreters[1]]
+        result = testdir.runpytest(*args)
+        result.stdout.fnmatch_lines(["2...4"])
+        result.stdout.fnmatch_lines(["2...5"])
+

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 Aug  4 19:45:30 2009
@@ -7,7 +7,7 @@
 def run(item, node, excinfo=None):
     runner = item.config.pluginmanager.getplugin("runner")
     rep = runner.ItemTestReport(item=item, 
-        excinfo=excinfo, when="call", outerr=("", ""))
+        excinfo=excinfo, when="call")
     rep.node = node
     return rep 
 
@@ -81,8 +81,8 @@
         session.triggertesting([modcol])
         name, args, kwargs = session.queue.get(block=False)
         assert name == 'pytest_collectreport'
-        rep = kwargs['rep']  
-        assert len(rep.result) == 1
+        report = kwargs['report']  
+        assert len(report.result) == 1
 
     def test_triggertesting_item(self, testdir):
         item = testdir.getitem("def test_func(): pass")
@@ -134,7 +134,7 @@
         session.queueevent(None)
         session.loop_once(loopstate)
         assert node.sent == [[item]]
-        session.queueevent("pytest_runtest_logreport", rep=run(item, node))
+        session.queueevent("pytest_runtest_logreport", report=run(item, node))
         session.loop_once(loopstate)
         assert loopstate.shuttingdown 
         assert not loopstate.testsfailed 
@@ -155,6 +155,45 @@
         dumpqueue(session.queue)
         assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
 
+    def test_removeitem_from_failing_teardown(self, testdir):
+        # teardown reports only come in when they signal a failure
+        # internal session-management should basically ignore them 
+        # XXX probably it'S best to invent a new error hook for 
+        # teardown/setup related failures
+        modcol = testdir.getmodulecol("""
+            def test_one(): 
+                pass 
+            def teardown_function(function):
+                assert 0
+        """)
+        item1, = modcol.collect()
+
+        # setup a session with two nodes
+        session = DSession(item1.config)
+        node1, node2 = MockNode(), MockNode()
+        session.addnode(node1)
+        session.addnode(node2)
+      
+        # have one test pending for a node that goes down 
+        session.senditems_each([item1])
+        nodes = session.item2nodes[item1]
+        class rep:
+            failed = True
+            item = item1
+            node = nodes[0]
+            when = "call"
+        session.queueevent("pytest_runtest_logreport", report=rep)
+        reprec = testdir.getreportrecorder(session)
+        print session.item2nodes
+        loopstate = session._initloopstate([])
+        assert len(session.item2nodes[item1]) == 2
+        session.loop_once(loopstate)
+        assert len(session.item2nodes[item1]) == 1
+        rep.when = "teardown"
+        session.queueevent("pytest_runtest_logreport", report=rep)
+        session.loop_once(loopstate)
+        assert len(session.item2nodes[item1]) == 1
+
     def test_testnodedown_causes_reschedule_pending(self, testdir):
         modcol = testdir.getmodulecol("""
             def test_crash(): 
@@ -173,7 +212,8 @@
         # have one test pending for a node that goes down 
         session.senditems_load([item1, item2])
         node = session.item2nodes[item1] [0]
-        session.queueevent("pytest_testnodedown", node=node, error=None)
+        item1.config.option.dist = "load"
+        session.queueevent("pytest_testnodedown", node=node, error="xyz")
         reprec = testdir.getreportrecorder(session)
         print session.item2nodes
         loopstate = session._initloopstate([])
@@ -209,7 +249,7 @@
 
         assert node.sent == [[item]]
         ev = run(item, node, excinfo=excinfo) 
-        session.queueevent("pytest_runtest_logreport", rep=ev)
+        session.queueevent("pytest_runtest_logreport", report=ev)
         session.loop_once(loopstate)
         assert loopstate.shuttingdown  
         session.queueevent("pytest_testnodedown", node=node, error=None)
@@ -246,8 +286,8 @@
         # run tests ourselves and produce reports 
         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)
+        session.queueevent("pytest_runtest_logreport", report=ev1) # a failing one
+        session.queueevent("pytest_runtest_logreport", report=ev2)
         # now call the loop
         loopstate = session._initloopstate(items)
         session.loop_once(loopstate)
@@ -262,7 +302,7 @@
         loopstate = session._initloopstate([])
         loopstate.shuttingdown = True
         reprec = testdir.getreportrecorder(session)
-        session.queueevent("pytest_runtest_logreport", rep=run(item, node))
+        session.queueevent("pytest_runtest_logreport", report=run(item, node))
         session.loop_once(loopstate)
         assert not reprec.getcalls("pytest_testnodedown")
         session.queueevent("pytest_testnodedown", node=node, error=None)
@@ -303,7 +343,7 @@
         node = MockNode()
         session.addnode(node)
         session.senditems_load([item])
-        session.queueevent("pytest_runtest_logreport", rep=run(item, node))
+        session.queueevent("pytest_runtest_logreport", report=run(item, node))
         loopstate = session._initloopstate([])
         session.loop_once(loopstate)
         assert node._shutdown is True
@@ -329,10 +369,10 @@
         session.senditems_load([item1])
         # node2pending will become empty when the loop sees the report 
         rep = run(item1, node)
-        session.queueevent("pytest_runtest_logreport", rep=run(item1, node)) 
+        session.queueevent("pytest_runtest_logreport", report=run(item1, node)) 
 
         # but we have a collection pending
-        session.queueevent("pytest_collectreport", rep=colreport) 
+        session.queueevent("pytest_collectreport", report=colreport) 
 
         loopstate = session._initloopstate([])
         session.loop_once(loopstate)
@@ -356,24 +396,47 @@
         dsession = DSession(config)
         hookrecorder = testdir.getreportrecorder(config).hookrecorder
         dsession.main([config.getfsnode(p1)])
-        rep = hookrecorder.popcall("pytest_runtest_logreport").rep 
+        rep = hookrecorder.popcall("pytest_runtest_logreport").report
         assert rep.passed
-        rep = hookrecorder.popcall("pytest_runtest_logreport").rep
+        rep = hookrecorder.popcall("pytest_runtest_logreport").report
         assert rep.skipped
-        rep = hookrecorder.popcall("pytest_runtest_logreport").rep
+        rep = hookrecorder.popcall("pytest_runtest_logreport").report
         assert rep.failed
         # see that the node is really down 
         node = hookrecorder.popcall("pytest_testnodedown").node
         assert node.gateway.spec.popen
         #XXX eq.geteventargs("pytest_sessionfinish")
 
-    @py.test.xfail
-    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
+def test_collected_function_causes_remote_skip(testdir):
+    sub = testdir.mkpydir("testing")
+    sub.join("test_module.py").write(py.code.Source("""
+        import py
+        path = py.path.local(%r)
+        if path.check():
+            path.remove()
+        else:
+            py.test.skip("remote skip")
+        def test_func(): 
+            pass
+        def test_func2(): 
+            pass
+    """ % str(sub.ensure("somefile"))))
+    result = testdir.runpytest('-v', '--dist=each', '--tx=popen')
+    result.stdout.fnmatch_lines([
+        "*2 skipped*"
+    ])
+
+def test_teardownfails_one_function(testdir):
+    p = testdir.makepyfile("""
+        def test_func(): 
+            pass
+        def teardown_function(function):
+            assert 0
+    """)
+    result = testdir.runpytest(p, '--dist=each', '--tx=popen')
+    result.stdout.fnmatch_lines([
+        "*def teardown_function(function):*", 
+        "*1 passed*1 error*"
+    ])
+
+

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 Aug  4 19:45:30 2009
@@ -11,7 +11,7 @@
         request.getfuncargvalue("_pytest")
 
 class TestNodeManager:
-    @py.test.xfail
+    @py.test.mark.xfail
     def test_rsync_roots_no_roots(self, mysetup):
         mysetup.source.ensure("dir1", "file1").write("hello")
         config = py.test.config._reparse([source])

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 Aug  4 19:45:30 2009
@@ -32,6 +32,7 @@
 
 class MySetup:
     def __init__(self, request):
+        self.id = 0
         self.request = request
 
     def geteventargs(self, eventname, timeout=2.0):
@@ -45,6 +46,8 @@
         self.queue = py.std.Queue.Queue()
         self.xspec = py.execnet.XSpec("popen")
         self.gateway = py.execnet.makegateway(self.xspec)
+        self.id += 1
+        self.gateway.id = str(self.id)
         self.node = TXNode(self.gateway, self.config, putevent=self.queue.put)
         assert not self.node.channel.isclosed()
         return self.node 
@@ -112,7 +115,7 @@
         node = mysetup.makenode(item.config)
         node.send(item)
         kwargs = mysetup.geteventargs("pytest_runtest_logreport")
-        rep = kwargs['rep'] 
+        rep = kwargs['report'] 
         assert rep.passed 
         print rep
         assert rep.item == item
@@ -132,10 +135,10 @@
             node.send(item)
         for outcome in "passed failed skipped".split():
             kwargs = mysetup.geteventargs("pytest_runtest_logreport")
-            rep = kwargs['rep']
-            assert getattr(rep, outcome) 
+            report = kwargs['report']
+            assert getattr(report, outcome) 
 
         node.sendlist(items)
         for outcome in "passed failed skipped".split():
-            rep = mysetup.geteventargs("pytest_runtest_logreport")['rep']
+            rep = mysetup.geteventargs("pytest_runtest_logreport")['report']
             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 Aug  4 19:45:30 2009
@@ -21,6 +21,11 @@
         self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
         self._down = False
 
+    def __repr__(self):
+        id = self.gateway.id
+        status = self._down and 'true' or 'false'
+        return "<TXNode %r down=%s>" %(id, status)
+
     def notify(self, eventname, *args, **kwargs):
         assert not args
         self.putevent((eventname, args, kwargs))
@@ -51,9 +56,9 @@
                 self._down = True
                 self.notify("pytest_testnodedown", error=None, node=self)
             elif eventname == "pytest_runtest_logreport":
-                rep = kwargs['rep']
+                rep = kwargs['report']
                 rep.node = self
-                self.notify("pytest_runtest_logreport", rep=rep)
+                self.notify("pytest_runtest_logreport", report=rep)
             else:
                 self.notify(eventname, *args, **kwargs)
         except KeyboardInterrupt: 
@@ -105,8 +110,8 @@
     def sendevent(self, eventname, *args, **kwargs):
         self.channel.send((eventname, args, kwargs))
 
-    def pytest_runtest_logreport(self, rep):
-        self.sendevent("pytest_runtest_logreport", rep=rep)
+    def pytest_runtest_logreport(self, report):
+        self.sendevent("pytest_runtest_logreport", report=report)
 
     def run(self):
         channel = self.channel
@@ -115,6 +120,7 @@
             self.config.basetemp = py.path.local(basetemp)
         self.config.pluginmanager.do_configure(self.config)
         self.config.pluginmanager.register(self)
+        self.runner = self.config.pluginmanager.getplugin("pytest_runner")
         self.sendevent("slaveready")
         try:
             while 1:
@@ -124,12 +130,26 @@
                     break
                 if isinstance(task, list):
                     for item in task:
-                        item.config.hook.pytest_runtest_protocol(item=item) 
+                        self.run_single(item=item)
                 else:
-                    task.config.hook.pytest_runtest_protocol(item=task) 
+                    self.run_single(item=task)
         except KeyboardInterrupt:
             raise
         except:
             er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True)
             self.sendevent("pytest_internalerror", excrepr=er)
             raise
+
+    def run_single(self, item):
+        call = self.runner.CallInfo(item._checkcollectable, when='setup')
+        if call.excinfo:
+            # likely it is not collectable here because of
+            # platform/import-dependency induced skips 
+            # XXX somewhat ugly shortcuts - also makes a collection
+            #     failure into an ItemTestReport - this might confuse
+            #     pytest_runtest_logreport hooks 
+            rep = self.runner.pytest_runtest_makereport(item=item, call=call)
+            self.pytest_runtest_logreport(rep)
+            return
+        item.config.hook.pytest_runtest_protocol(item=item) 
+

Modified: py/trunk/py/test/funcargs.py
==============================================================================
--- py/trunk/py/test/funcargs.py	(original)
+++ py/trunk/py/test/funcargs.py	Tue Aug  4 19:45:30 2009
@@ -103,6 +103,15 @@
                 self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
 
     def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
+        """ cache and return result of calling setup().  
+
+        The scope and the ``extrakey`` determine the cache key. 
+        The scope also determines when teardown(result) 
+        will be called.  valid scopes are: 
+        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. 
+        """
         if not hasattr(self.config, '_setupcache'):
             self.config._setupcache = {} # XXX weakref? 
         cachekey = (self._getscopeitem(scope), extrakey)
@@ -165,7 +174,7 @@
         line = "%s:%s" %(fspath, lineno)
         msg = "funcargument %r not found for: %s" %(argname, line)
         msg += "\n available funcargs: %s" %(", ".join(available),)
-        raise LookupError(msg)
+        raise self.Error(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 Aug  4 19:45:30 2009
@@ -137,9 +137,9 @@
     session.shouldclose = channel.isclosed 
    
     class Failures(list):
-        def pytest_runtest_logreport(self, rep):
-            if rep.failed:
-                self.append(rep)
+        def pytest_runtest_logreport(self, report):
+            if report.failed:
+                self.append(report)
         pytest_collectreport = pytest_runtest_logreport
         
     failreports = Failures()

Modified: py/trunk/py/test/plugin/hookspec.py
==============================================================================
--- py/trunk/py/test/plugin/hookspec.py	(original)
+++ py/trunk/py/test/plugin/hookspec.py	Tue Aug  4 19:45:30 2009
@@ -1,42 +1,28 @@
 """
-py.test hooks / extension points 
+hook specifications for py.test plugins 
 """
 
-# ------------------------------------------------------------------------------
-# Command line and configuration hooks 
-# ------------------------------------------------------------------------------
+# -------------------------------------------------------------------------
+# Command line and configuration 
+# -------------------------------------------------------------------------
+
 def pytest_addoption(parser):
     """ called before commandline parsing.  """
 
+def pytest_namespace():
+    """ return dict of name->object which will get stored at py.test. namespace"""
+
 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_namespace(config):
-    """ return dict of name->object to become available at py.test.*"""
-
-
 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. """
@@ -47,9 +33,12 @@
 def pytest_collectstart(collector):
     """ collector starts collecting. """
 
-def pytest_collectreport(rep):
+def pytest_collectreport(report):
     """ collector finished collecting. """
 
+def pytest_deselected(items):
+    """ called for test items deselected by keyword. """
+
 def pytest_make_collect_report(collector):
     """ perform a collection and return a collection. """ 
 pytest_make_collect_report.firstresult = True
@@ -58,9 +47,9 @@
 def pytest_itemstart(item, node=None):
     """ test item gets collected. """
 
-# ------------------------------------------------------------------------------
-# Python test function related hooks 
-# ------------------------------------------------------------------------------
+# -------------------------------------------------------------------------
+# 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.  """
@@ -73,9 +62,14 @@
 def pytest_generate_tests(metafunc):
     """ generate (multiple) parametrized calls to a test function."""
 
-# ------------------------------------------------------------------------------
+# -------------------------------------------------------------------------
 # generic runtest related hooks 
-# ------------------------------------------------------------------------------
+# -------------------------------------------------------------------------
+
+def pytest_runtest_protocol(item):
+    """ implement fixture, run and report protocol. """
+pytest_runtest_protocol.firstresult = True
+
 def pytest_runtest_setup(item):
     """ called before pytest_runtest_call(). """ 
 
@@ -85,20 +79,35 @@
 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. """
+    """ make ItemTestReport for the given item and call outcome. """ 
 pytest_runtest_makereport.firstresult = True
 
-def pytest_runtest_logreport(rep):
+def pytest_runtest_logreport(report):
     """ process item test report. """ 
 
-# ------------------------------------------------------------------------------
-# generic reporting hooks (invoked from pytest_terminal.py) 
-# ------------------------------------------------------------------------------
+# special handling for final teardown - somewhat internal for now
+def pytest__teardown_final(session):
+    """ called before test session finishes. """
+pytest__teardown_final.firstresult = True
+
+def pytest__teardown_final_logerror(rep):
+    """ called if runtest_teardown_final failed. """ 
+
+# -------------------------------------------------------------------------
+# test session related hooks 
+# -------------------------------------------------------------------------
+
+def pytest_sessionstart(session):
+    """ before session.main() is called. """
+
+def pytest_sessionfinish(session, exitstatus):
+    """ whole test run finishes. """
+
+# -------------------------------------------------------------------------
+# hooks for influencing reporting (invoked from pytest_terminal)
+# -------------------------------------------------------------------------
+
 def pytest_report_teststatus(rep):
     """ return shortletter and verbose word. """
 pytest_report_teststatus.firstresult = True
@@ -112,33 +121,17 @@
     """
 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. """
@@ -152,3 +145,22 @@
 def pytest_looponfailinfo(failreports, rootdirs):
     """ info for repeating failing tests. """
 
+
+# -------------------------------------------------------------------------
+# error handling and internal debugging 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_keyboard_interrupt(excinfo):
+    """ called for keyboard interrupt. """
+
+def pytest_trace(category, msg):
+    """ called for debug info. """ 

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 Aug  4 19:45:30 2009
@@ -57,7 +57,7 @@
     def _makecallparser(self, method):
         name = method.__name__
         args, varargs, varkw, default = py.std.inspect.getargspec(method)
-        if args[0] != "self":
+        if not args or 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
@@ -113,6 +113,19 @@
     assert call._name == "xyz"
     py.test.raises(ValueError, "rec.popcall('abc')")
 
+def test_hookrecorder_basic_no_args_hook():
+    import sys
+    comregistry = py._com.Registry() 
+    rec = HookRecorder(comregistry)
+    apimod = type(sys)('api')
+    def xyz():
+        pass
+    apimod.xyz = xyz
+    rec.start_recording(apimod)
+    rec.hook.xyz()
+    call = rec.popcall("xyz")
+    assert call._name == "xyz"
+
 reg = py._com.comregistry
 def test_functional_default(testdir, _pytest):
     assert _pytest.comregistry == py._com.comregistry 

Added: py/trunk/py/test/plugin/pytest_capture.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/pytest_capture.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,254 @@
+"""
+configurable per-test stdout/stderr capturing mechanisms. 
+
+This plugin captures stdout/stderr output for each test separately. 
+In case of test failures this captured output is shown grouped 
+togtther with the test. 
+
+The plugin also provides test function arguments that help to
+assert stdout/stderr output from within your tests, see the 
+`funcarg example`_. 
+
+
+Capturing of input/output streams during tests 
+---------------------------------------------------
+
+By default ``sys.stdout`` and ``sys.stderr`` are substituted with
+temporary streams during the execution of tests and setup/teardown code.  
+During the whole testing process it will re-use the same temporary 
+streams allowing to play well with the logging module which easily
+takes ownership on these streams. 
+
+Also, 'sys.stdin' is substituted with a file-like "null" object that 
+does not return any values.  This is to immediately error out
+on tests that wait on reading something from stdin. 
+
+You can influence output capturing mechanisms from the command line::
+
+    py.test -s            # disable all capturing
+    py.test --capture=sys # set StringIO() to each of sys.stdout/stderr 
+    py.test --capture=fd  # capture stdout/stderr on Filedescriptors 1/2 
+
+If you set capturing values in a conftest file like this::
+
+    # conftest.py
+    conf_capture = 'fd'
+
+then all tests in that directory will execute with "fd" style capturing. 
+
+sys-level capturing 
+------------------------------------------
+
+Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr`` 
+will be replaced with StringIO() objects.   
+
+FD-level capturing and subprocesses
+------------------------------------------
+
+The ``fd`` based method means that writes going to system level files
+based on the standard file descriptors will be captured, for example 
+writes such as ``os.write(1, 'hello')`` will be captured properly. 
+Capturing on fd-level will include output generated from 
+any subprocesses created during a test. 
+
+.. _`funcarg example`:
+
+Example Usage of the capturing Function arguments
+---------------------------------------------------
+
+You can use the `capsys funcarg`_ and `capfd funcarg`_ to 
+capture writes to stdout and stderr streams.  Using the
+funcargs frees your test from having to care about setting/resetting 
+the old streams and also interacts well with py.test's own 
+per-test capturing.  Here is an example test function:
+
+.. sourcecode:: python
+
+    def test_myoutput(capsys):
+        print "hello" 
+        print >>sys.stderr, "world"
+        out, err = capsys.readouterr()
+        assert out == "hello\\n"
+        assert err == "world\\n"
+        print "next"
+        out, err = capsys.readouterr()
+        assert out == "next\\n" 
+
+The ``readouterr()`` call snapshots the output so far - 
+and capturing will be continued.  After the test 
+function finishes the original streams will 
+be restored.  If you want to capture on 
+the filedescriptor level you can use the ``capfd`` function
+argument which offers the same interface. 
+"""
+
+import py
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group._addoption('-s', action="store_const", const="no", dest="capture", 
+        help="shortcut for --capture=no.")
+    group._addoption('--capture', action="store", default=None,
+        metavar="method", type="choice", choices=['fd', 'sys', 'no'],
+        help="set capturing method during tests: fd (default)|sys|no.")
+
+def addouterr(rep, outerr):
+    repr = getattr(rep, 'longrepr', None)
+    if not hasattr(repr, 'addsection'):
+        return
+    for secname, content in zip(["out", "err"], outerr):
+        if content:
+            repr.addsection("Captured std%s" % secname, content.rstrip())
+
+def pytest_configure(config):
+    config.pluginmanager.register(CaptureManager(), 'capturemanager')
+
+class CaptureManager:
+    def __init__(self):
+        self._method2capture = {}
+
+    def _startcapture(self, method):
+        if method == "fd": 
+            return py.io.StdCaptureFD()
+        elif method == "sys":
+            return py.io.StdCapture()
+        else:
+            raise ValueError("unknown capturing method: %r" % method)
+
+    def _getmethod(self, config, fspath):
+        if config.option.capture:
+            return config.option.capture
+        return config._conftest.rget("conf_capture", path=fspath)
+
+    def resumecapture_item(self, item):
+        method = self._getmethod(item.config, item.fspath)
+        if not hasattr(item, 'outerr'):
+            item.outerr = ('', '') # we accumulate outerr on the item
+        return self.resumecapture(method)
+
+    def resumecapture(self, method):
+        if hasattr(self, '_capturing'):
+            raise ValueError("cannot resume, already capturing with %r" % 
+                (self._capturing,))
+        if method != "no":
+            cap = self._method2capture.get(method)
+            if cap is None:
+                cap = self._startcapture(method)
+                self._method2capture[method] = cap 
+            else:
+                cap.resume()
+        self._capturing = method 
+
+    def suspendcapture(self):
+        self.deactivate_funcargs()
+        method = self._capturing
+        if method != "no":
+            cap = self._method2capture[method]
+            outerr = cap.suspend()
+        else:
+            outerr = "", ""
+        del self._capturing
+        return outerr 
+
+    def activate_funcargs(self, pyfuncitem):
+        if not hasattr(pyfuncitem, 'funcargs'):
+            return
+        assert not hasattr(self, '_capturing_funcargs')
+        l = []
+        for name, obj in pyfuncitem.funcargs.items():
+            if name in ('capsys', 'capfd'):
+                obj._start()
+                l.append(obj)
+        if l:
+            self._capturing_funcargs = l
+
+    def deactivate_funcargs(self):
+        if hasattr(self, '_capturing_funcargs'):
+            for capfuncarg in self._capturing_funcargs:
+                capfuncarg._finalize()
+            del self._capturing_funcargs
+
+    def pytest_make_collect_report(self, __call__, collector):
+        method = self._getmethod(collector.config, collector.fspath)
+        self.resumecapture(method)
+        try:
+            rep = __call__.execute(firstresult=True)
+        finally:
+            outerr = self.suspendcapture()
+        addouterr(rep, outerr)
+        return rep
+
+    def pytest_runtest_setup(self, item):
+        self.resumecapture_item(item)
+
+    def pytest_runtest_call(self, item):
+        self.resumecapture_item(item)
+        self.activate_funcargs(item)
+
+    def pytest_runtest_teardown(self, item):
+        self.resumecapture_item(item)
+
+    def pytest_runtest_teardown(self, item):
+        self.resumecapture_item(item)
+
+    def pytest__teardown_final(self, __call__, session):
+        method = self._getmethod(session.config, None)
+        self.resumecapture(method)
+        try:
+            rep = __call__.execute(firstresult=True)
+        finally:
+            outerr = self.suspendcapture()
+        if rep:
+            addouterr(rep, outerr)
+        return rep
+
+    def pytest_keyboard_interrupt(self, excinfo):
+        if hasattr(self, '_capturing'):
+            self.suspendcapture()
+
+    def pytest_runtest_makereport(self, __call__, item, call):
+        self.deactivate_funcargs()
+        rep = __call__.execute(firstresult=True)
+        outerr = self.suspendcapture()
+        outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1])
+        if not rep.passed:
+            addouterr(rep, outerr)
+        if not rep.passed or rep.when == "teardown":
+            outerr = ('', '')
+        item.outerr = outerr 
+        return rep
+
+def pytest_funcarg__capsys(request):
+    """captures writes to sys.stdout/sys.stderr and makes 
+    them available successively via a ``capsys.readouterr()`` method 
+    which returns a ``(out, err)`` tuple of captured snapshot strings. 
+    """ 
+    return CaptureFuncarg(request, py.io.StdCapture)
+
+def pytest_funcarg__capfd(request):
+    """captures writes to file descriptors 1 and 2 and makes 
+    snapshotted ``(out, err)`` string tuples available 
+    via the ``capsys.readouterr()`` method. 
+    """ 
+    return CaptureFuncarg(request, py.io.StdCaptureFD)
+
+
+class CaptureFuncarg:
+    def __init__(self, request, captureclass):
+        self._cclass = captureclass
+        #request.addfinalizer(self._finalize)
+
+    def _start(self):
+        self.capture = self._cclass()
+
+    def _finalize(self):
+        if hasattr(self, 'capture'):
+            self.capture.reset()
+            del self.capture 
+
+    def readouterr(self):
+        return self.capture.readouterr()
+
+    def close(self):
+        self.capture.reset()
+        del self.capture

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 Aug  4 19:45:30 2009
@@ -1,4 +1,4 @@
-""" Plugin implementing defaults and general options. """ 
+""" default hooks and general py.test options. """ 
 
 import py
 
@@ -38,7 +38,7 @@
     return item.reportinfo()
 
 def pytest_addoption(parser):
-    group = parser.getgroup("general", "test collection and failure interaction options")
+    group = parser.getgroup("general", "general testing options")
     group._addoption('-v', '--verbose', action="count", 
                dest="verbose", default=0, help="increase verbosity."),
     group._addoption('-x', '--exitfirst',
@@ -60,9 +60,6 @@
                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',
@@ -70,26 +67,8 @@
                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", 
@@ -115,7 +94,7 @@
 def pytest_configure(config):
     fixoptions(config)
     setsession(config)
-    loadplugins(config)
+    #xxxloadplugins(config)
 
 def fixoptions(config):
     if config.option.numprocesses:
@@ -124,7 +103,7 @@
     if config.option.distload:
         config.option.dist = "load"
 
-def loadplugins(config):
+def xxxloadplugins(config):
     for name in config.getvalue("plugin"):
         print "importing", name
         config.pluginmanager.import_plugin(name)

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 Aug  4 19:45:30 2009
@@ -1,5 +1,16 @@
 """
-automatically collect and execute doctests. 
+collect and execute doctests from modules and test files. 
+
+Usage
+-------------
+
+By default all files matching the ``test_*.txt`` pattern will 
+be run with the ``doctest`` module.  If you issue::
+
+    py.test --doctest-modules
+
+all python files in your projects will be doctest-run 
+as well. 
 """
 
 import py
@@ -9,6 +20,7 @@
     group = parser.addgroup("doctest options")
     group.addoption("--doctest-modules", 
         action="store_true", default=False,
+        help="search all python files for doctests", 
         dest="doctestmodules")
     
 def pytest_collect_file(path, parent):
@@ -33,19 +45,19 @@
         super(DoctestItem, self).__init__(name=name, parent=parent)
         self.fspath = path 
 
-    def repr_failure(self, excinfo, outerr):
+    def repr_failure(self, excinfo):
         if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
             doctestfailure = excinfo.value
             example = doctestfailure.example
             test = doctestfailure.test
             filename = test.filename 
-            lineno = example.lineno + 1
+            lineno = test.lineno + example.lineno + 1
             message = excinfo.type.__name__
             reprlocation = ReprFileLocation(filename, lineno, message)
             checker = py.compat.doctest.OutputChecker() 
             REPORT_UDIFF = py.compat.doctest.REPORT_UDIFF
             filelines = py.path.local(filename).readlines(cr=0)
-            i = max(0, lineno - 10)
+            i = max(test.lineno, max(0, lineno - 10)) # XXX? 
             lines = []
             for line in filelines[i:lineno]:
                 lines.append("%03d %s" % (i+1, line))
@@ -55,9 +67,9 @@
             return ReprFailDoctest(reprlocation, lines)
         elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
             excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
-            return super(DoctestItem, self).repr_failure(excinfo, outerr)
+            return super(DoctestItem, self).repr_failure(excinfo)
         else: 
-            return super(DoctestItem, self).repr_failure(excinfo, outerr)
+            return super(DoctestItem, self).repr_failure(excinfo)
 
 class DoctestTextfile(DoctestItem):
     def runtest(self):
@@ -120,8 +132,8 @@
         """)
         reprec = testdir.inline_run(p)
         call = reprec.getcall("pytest_runtest_logreport")
-        assert call.rep.failed
-        assert call.rep.longrepr 
+        assert call.report.failed
+        assert call.report.longrepr 
         # XXX 
         #testitem, = items
         #excinfo = py.test.raises(Failed, "testitem.runtest()")
@@ -140,6 +152,28 @@
         reprec = testdir.inline_run(p, "--doctest-modules")
         reprec.assertoutcome(failed=1) 
 
+    def test_doctestmodule_external(self, testdir):
+        p = testdir.makepyfile("""
+            #
+            def somefunc():
+                '''
+                    >>> i = 0
+                    >>> i + 1
+                    2
+                '''
+        """)
+        result = testdir.runpytest(p, "--doctest-modules")
+        result.stdout.fnmatch_lines([
+            '004 *>>> i = 0',
+            '005 *>>> i + 1',
+            '*Expected:',
+            "*    2",
+            "*Got:",
+            "*    1",
+            "*:5: DocTestFailure"
+        ])
+        
+
     def test_txtfile_failing(self, testdir):
         p = testdir.maketxtfile("""
             >>> i = 0

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 Aug  4 19:45:30 2009
@@ -1,5 +1,5 @@
 """
-cleanup gateways that were instantiated during a test function run. 
+cleanup execnet gateways during test function runs.
 """
 import py
 
@@ -24,7 +24,7 @@
     def pytest_sessionstart(self, session):
         self._gateways = []
 
-    def pytest_sessionfinish(self, session, exitstatus, excrepr=None):
+    def pytest_sessionfinish(self, session, exitstatus):
         l = []
         for gw in self._gateways:
             gw.exit()

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 Aug  4 19:45:30 2009
@@ -1,20 +1,20 @@
 """
-write and report coverage data using the 'figleaf' module. 
+write and report coverage data with 'figleaf'. 
+
 """
 import py
 
-figleaf = py.test.importorskip("figleaf")
-import figleaf.annotate_html
+figleaf = py.test.importorskip("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 '
+            help=('trace python 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.')
+            help='path to coverage tracing file.')
     group.addoption('--figleaf-html', action='store', default='html',
             dest='figleafhtml', 
             help='path to the coverage html dir.')

Modified: py/trunk/py/test/plugin/pytest_hooklog.py
==============================================================================
--- py/trunk/py/test/plugin/pytest_hooklog.py	(original)
+++ py/trunk/py/test/plugin/pytest_hooklog.py	Tue Aug  4 19:45:30 2009
@@ -1,4 +1,4 @@
-""" log calling of plugin hooks to a file. """ 
+""" log invocations of extension hooks to a file. """ 
 import py
 
 def pytest_addoption(parser):

Deleted: /py/trunk/py/test/plugin/pytest_iocapture.py
==============================================================================
--- /py/trunk/py/test/plugin/pytest_iocapture.py	Tue Aug  4 19:45:30 2009
+++ (empty file)
@@ -1,73 +0,0 @@
-"""
-'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
-
-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):
-    if hasattr(pyfuncitem, 'funcargs'):
-        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
-
-    def finalize(self):
-        if self._capture:
-            self._capture.reset()
-
-    def reset(self):
-        res = None
-        if self._capture:
-            res = self._capture.reset()
-        self._capture = self._captureclass()
-        return res 
-
-class TestCapture:
-    def test_std_functional(self, testdir):        
-        reprec = testdir.inline_runsource("""
-            def test_hello(capsys):
-                print 42
-                out, err = capsys.reset()
-                assert out.startswith("42")
-        """)
-        reprec.assertoutcome(passed=1)
-        
-    def test_stdfd_functional(self, testdir):        
-        reprec = testdir.inline_runsource("""
-            def test_hello(capfd):
-                import os
-                os.write(1, "42")
-                out, err = capfd.reset()
-                assert out.startswith("42")
-        """)
-        reprec.assertoutcome(passed=1)
-
-    def test_funcall_yielded_no_funcargs(self, testdir):        
-        reprec = testdir.inline_runsource("""
-            def test_hello():
-                yield lambda: None
-        """)
-        reprec.assertoutcome(passed=1)
-

Added: py/trunk/py/test/plugin/pytest_keyword.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/pytest_keyword.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,94 @@
+"""
+mark test functions with keywords that may hold values. 
+
+Marking functions and setting rich attributes
+----------------------------------------------------
+
+By default, all filename parts and class/function names of a test
+function are put into the set of keywords for a given test.  You can
+specify additional kewords like this::
+
+    @py.test.mark.webtest 
+    def test_send_http():
+        ... 
+
+This will set an attribute 'webtest' on the given test function
+and by default all such attributes signal keywords.  You can 
+also set values in this attribute which you could read from
+a hook in order to do something special with respect to
+the test function::
+
+    @py.test.mark.timeout(seconds=5)
+    def test_receive():
+        ...
+
+This will set the "timeout" attribute with a Marker object 
+that has a 'seconds' attribute. 
+
+"""
+import py
+
+def pytest_namespace():
+    return {'mark': Mark()}
+
+
+class Mark(object):
+    def __getattr__(self, name):
+        if name[0] == "_":
+            raise AttributeError(name)
+        return MarkerDecorator(name)
+
+class MarkerDecorator:
+    """ decorator for setting function attributes. """
+    def __init__(self, name):
+        self.markname = name
+
+    def __repr__(self):
+        d = self.__dict__.copy()
+        name = d.pop('markname')
+        return "<MarkerDecorator %r %r>" %(name, d)
+
+    def __call__(self, *args, **kwargs):
+        if not args:
+            if hasattr(self, 'kwargs'):
+                raise TypeError("double mark-keywords?") 
+            self.kwargs = kwargs.copy()
+            return self 
+        else:
+            if not len(args) == 1 or not hasattr(args[0], 'func_dict'):
+                raise TypeError("need exactly one function to decorate, "
+                                "got %r" %(args,))
+            func = args[0]
+            mh = MarkHolder(getattr(self, 'kwargs', {}))
+            setattr(func, self.markname, mh)
+            return func
+
+class MarkHolder:
+    def __init__(self, kwargs):
+        self.__dict__.update(kwargs)
+
+def test_pytest_mark_api():
+    mark = Mark()
+    py.test.raises(TypeError, "mark(x=3)")
+
+    def f(): pass
+    mark.hello(f)
+    assert f.hello
+
+    mark.world(x=3, y=4)(f)
+    assert f.world 
+    assert f.world.x == 3
+    assert f.world.y == 4
+
+    py.test.raises(TypeError, "mark.some(x=3)(f=5)")
+
+def test_mark_plugin(testdir):
+    p = testdir.makepyfile("""
+        import py
+        pytest_plugins = "keyword" 
+        @py.test.mark.hello
+        def test_hello():
+            assert hasattr(test_hello, 'hello')
+    """)
+    result = testdir.runpytest(p)
+    assert result.stdout.fnmatch_lines(["*passed*"])

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 Aug  4 19:45:30 2009
@@ -1,17 +1,43 @@
 """
-    "monkeypatch" funcarg for safely patching objects, 
-    dictionaries and environment variables during the execution of 
-    a test.  "monkeypatch" has three helper functions: 
+safely patch object attributes, dicts and environment variables. 
 
-        monkeypatch.setattr(obj, name, value) 
-        monkeypatch.setitem(obj, name, value) 
-        monkeypatch.setenv(name, value) 
+Usage
+----------------
+
+Use the `monkeypatch funcarg`_ to safely patch the environment
+variables, object attributes or dictionaries.  For example, if you want
+to set the environment variable ``ENV1`` and patch the
+``os.path.abspath`` function to return a particular value during a test
+function execution you can write it down like this:
+
+.. sourcecode:: python 
+
+    def test_mytest(monkeypatch):
+        monkeypatch.setenv('ENV1', 'myval')
+        monkeypatch.setattr(os.path, 'abspath', lambda x: '/')
+        ... # your test code 
+
+The function argument will do the modifications and memorize the 
+old state.  After the test function finished execution all 
+modifications will be reverted.  See the `monkeypatch blog post`_ 
+for an extensive discussion. 
 
-    After the test has run modifications will be undone. 
+.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
 """
+
 import os
 
 def pytest_funcarg__monkeypatch(request):
+    """The returned ``monkeypatch`` funcarg provides three 
+    helper methods to modify objects, dictionaries or os.environ::
+
+        monkeypatch.setattr(obj, name, value)  
+        monkeypatch.setitem(mapping, name, value) 
+        monkeypatch.setenv(name, value) 
+
+    All such modifications will be undone when the requesting 
+    test function finished its execution. 
+    """
     monkeypatch = MonkeyPatch()
     request.addfinalizer(monkeypatch.finalize)
     return monkeypatch

Added: py/trunk/py/test/plugin/pytest_pastebin.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/pytest_pastebin.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,130 @@
+"""
+submit failure or test session information to a pastebin service. 
+
+Usage
+----------
+
+**Creating a URL for each test failure**::
+
+    py.test --pastebin=failed 
+
+This will submit full failure information to a remote Paste service and
+provide a URL for each failure.  You may select tests as usual or add
+for example ``-x`` if you only want to send one particular failure. 
+
+**Creating a URL for a whole test session log**::
+
+    py.test --pastebin=all 
+
+Currently only pasting to the http://paste.pocoo.org service is implemented.  
+
+"""
+import py, sys
+
+class url:
+    base = "http://paste.pocoo.org"
+    xmlrpc = base + "/xmlrpc/"
+    show = base + "/show/"
+
+def pytest_addoption(parser):
+    group = parser.getgroup("general")
+    group._addoption('--pastebin', metavar="mode",
+        action='store', dest="pastebin", default=None, 
+        type="choice", choices=['failed', 'all'], 
+        help="send failed|all info to Pocoo pastebin service.")
+
+def pytest_configure(__call__, config):
+    import tempfile
+    __call__.execute()
+    if config.option.pastebin == "all":
+        config._pastebinfile = tempfile.TemporaryFile()
+        tr = config.pluginmanager.impname2plugin['terminalreporter']
+        oldwrite = tr._tw.write 
+        def tee_write(s, **kwargs):
+            oldwrite(s, **kwargs)
+            config._pastebinfile.write(str(s))
+        tr._tw.write = tee_write 
+
+def pytest_unconfigure(config): 
+    if hasattr(config, '_pastebinfile'):
+        config._pastebinfile.seek(0)
+        sessionlog = config._pastebinfile.read()
+        config._pastebinfile.close()
+        del config._pastebinfile
+        proxyid = getproxy().newPaste("python", sessionlog)
+        pastebinurl = "%s%s" % (url.show, proxyid)
+        print >>sys.stderr, "session-log:", pastebinurl
+        tr = config.pluginmanager.impname2plugin['terminalreporter']
+        del tr._tw.__dict__['write']
+        
+def getproxy():
+    return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes
+
+def pytest_terminal_summary(terminalreporter):
+    if terminalreporter.config.option.pastebin != "failed":
+        return
+    tr = terminalreporter
+    if 'failed' in tr.stats:
+        terminalreporter.write_sep("=", "Sending information to Paste Service")
+        if tr.config.option.debug:
+            terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
+        serverproxy = getproxy()
+        for rep in terminalreporter.stats.get('failed'):
+            try:
+                msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
+            except AttributeError:
+                msg = tr._getfailureheadline(rep)
+            tw = py.io.TerminalWriter(stringio=True)
+            rep.toterminal(tw)
+            s = tw.stringio.getvalue()
+            assert len(s)
+            proxyid = serverproxy.newPaste("python", s)
+            pastebinurl = "%s%s" % (url.show, proxyid)
+            tr.write_line("%s --> %s" %(msg, pastebinurl))
+
+   
+class TestPasting:
+    def pytest_funcarg__pastebinlist(self, request):
+        mp = request.getfuncargvalue("monkeypatch") 
+        pastebinlist = []
+        class MockProxy:
+            def newPaste(self, language, code):
+                pastebinlist.append((language, code))
+        mp.setitem(globals(), 'getproxy', MockProxy) 
+        return pastebinlist 
+
+    def test_failed(self, testdir, pastebinlist):
+        testpath = testdir.makepyfile("""
+            import py
+            def test_pass():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                py.test.skip("")
+        """)
+        reprec = testdir.inline_run(testpath, "--paste=failed")
+        assert len(pastebinlist) == 1
+        assert pastebinlist[0][0] == "python"
+        s = pastebinlist[0][1]
+        assert s.find("def test_fail") != -1
+        assert reprec.countoutcomes() == [1,1,1]
+
+    def test_all(self, testdir, pastebinlist):
+        testpath = testdir.makepyfile("""
+            import py
+            def test_pass():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                py.test.skip("")
+        """)
+        reprec = testdir.inline_run(testpath, "--pastebin=all")
+        assert reprec.countoutcomes() == [1,1,1]
+        assert len(pastebinlist) == 1
+        assert pastebinlist[0][0] == "python"
+        s = pastebinlist[0][1]
+        for x in 'test_fail test_skip skipped'.split():
+            assert s.find(x), (s, x)
+         

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 Aug  4 19:45:30 2009
@@ -1,6 +1,5 @@
 """
-interactive debugging with a PDB prompt. 
-
+interactive debugging with the Python Debugger.
 """
 import py
 import pdb, sys, linecache
@@ -24,11 +23,18 @@
 class PdbInvoke:
     def pytest_runtest_makereport(self, item, call):
         if call.excinfo and not call.excinfo.errisinstance(Skipped): 
+            # XXX hack hack hack to play well with capturing
+            capman = item.config.pluginmanager.impname2plugin['capturemanager']
+            capman.suspendcapture() 
+
             tw = py.io.TerminalWriter()
             repr = call.excinfo.getrepr()
             repr.toterminal(tw) 
             post_mortem(call.excinfo._excinfo[2])
 
+            # XXX hack end 
+            capman.resumecapture_item(item)
+
 class Pdb(py.std.pdb.Pdb):
     def do_list(self, arg):
         self.lastcmd = 'list'

Deleted: /py/trunk/py/test/plugin/pytest_pocoo.py
==============================================================================
--- /py/trunk/py/test/plugin/pytest_pocoo.py	Tue Aug  4 19:45:30 2009
+++ (empty file)
@@ -1,63 +0,0 @@
-"""
-py.test plugin for sending testing failure information to paste.pocoo.org 
-"""
-import py
-
-class url:
-    base = "http://paste.pocoo.org"
-    xmlrpc = base + "/xmlrpc/"
-    show = base + "/show/"
-
-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 test_toproxy(testdir, monkeypatch):
-    l = []
-    class MockProxy:
-        def newPaste(self, language, code):
-            l.append((language, code))
-    monkeypatch.setitem(globals(), 'getproxy', MockProxy) 
-    testdir.plugins.insert(0, globals())
-    testpath = testdir.makepyfile("""
-        import py
-        def test_pass():
-            pass
-        def test_fail():
-            assert 0
-        def test_skip():
-            py.test.skip("")
-    """)
-    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 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 Aug  4 19:45:30 2009
@@ -4,38 +4,33 @@
 """
 import py
 
-lint = py.test.importorskip("pylint") 
+pylint = py.test.importorskip("pylint.lint") 
 
 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.')
+                    help='run pylint on python files.')
 
 def pytest_collect_file(path, parent):
     if path.ext == ".py":
         if parent.config.getvalue('pylint'):
-            return PylintItem(path, parent, self.lint)
+            return PylintItem(path, parent)
 
-def pytest_terminal_summary(terminalreporter):
-    print 'placeholder for pylint output'
+#def pytest_terminal_summary(terminalreporter):
+#    print 'placeholder for pylint output'
 
 class PylintItem(py.test.collect.Item):
-    def __init__(self, path, parent, lintlib):
-        name = self.__class__.__name__ + ":" + path.basename
-        super(PylintItem, self).__init__(name=name, parent=parent)
-        self.fspath = path
-        self.lint = lintlib
-
     def runtest(self):
-        # run lint here
         capture = py.io.StdCaptureFD()
-        #pylib.org has docs on py.io.stdcaptureFD
-        self.linter = self.lint.PyLinter()  #TODO: should this be in the PylintPlugin?
-        self.linter.check(str(self.fspath))
-        out, err = capture.reset()
+        try:
+            linter = pylint.lint.PyLinter()
+            linter.check(str(self.fspath))
+        finally:
+            out, err = capture.reset()
         rating = out.strip().split('\n')[-1]
         print ">>>",
         print rating
+        assert 0
 
 

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 Aug  4 19:45:30 2009
@@ -1,5 +1,5 @@
 """
-funcargs and support code for testing py.test functionality. 
+funcargs and support code for testing py.test's own functionality. 
 """
 
 import py
@@ -7,6 +7,7 @@
 import inspect
 from py.__.test.config import Config as pytestConfig
 import hookspec
+import subprocess
 
 pytest_plugins = '_pytest'
 
@@ -128,7 +129,12 @@
             
     def mkdir(self, name):
         return self.tmpdir.mkdir(name)
-    
+
+    def mkpydir(self, name):
+        p = self.mkdir(name)
+        p.ensure("__init__.py")
+        return p
+
     def genitems(self, colitems):
         return list(self.session.genitems(colitems))
 
@@ -295,6 +301,9 @@
         assert script.check()
         return py.std.sys.executable, script
 
+    def runpython(self, script):
+        return self.run(py.std.sys.executable, script)
+
     def runpytest(self, *args):
         p = py.path.local.make_numbered_dir(prefix="runpytest-", 
             keep=None, rootdir=self.tmpdir)
@@ -332,7 +341,7 @@
     # functionality for test reports 
 
     def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
-        return [x.rep for x in self.getcalls(names)]
+        return [x.report for x in self.getcalls(names)]
 
     def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport"):
         """ return a testreport whose dotted import path matches """
@@ -397,7 +406,7 @@
         skipped = False
         when = "call" 
 
-    recorder.hook.pytest_runtest_logreport(rep=rep)
+    recorder.hook.pytest_runtest_logreport(report=rep)
     failures = recorder.getfailures()
     assert failures == [rep]
     failures = recorder.getfailures()
@@ -411,14 +420,14 @@
         when = "call" 
     rep.passed = False
     rep.skipped = True
-    recorder.hook.pytest_runtest_logreport(rep=rep)
+    recorder.hook.pytest_runtest_logreport(report=rep)
 
     modcol = testdir.getmodulecol("")
     rep = modcol.config.hook.pytest_make_collect_report(collector=modcol)
     rep.passed = False
     rep.failed = True
     rep.skipped = False
-    recorder.hook.pytest_collectreport(rep=rep)
+    recorder.hook.pytest_collectreport(report=rep)
 
     passed, skipped, failed = recorder.listoutcomes()
     assert not passed and skipped and failed
@@ -431,7 +440,7 @@
 
     recorder.unregister()
     recorder.clear() 
-    recorder.hook.pytest_runtest_logreport(rep=rep)
+    recorder.hook.pytest_runtest_logreport(report=rep)
     py.test.raises(ValueError, "recorder.getfailures()")
 
 class LineComp:
@@ -506,3 +515,109 @@
     assert result.stdout.fnmatch_lines([
         "*1 passed*"
     ])
+
+#
+# experimental funcargs for venv/install-tests
+#
+
+def pytest_funcarg__venv(request):
+    p = request.config.mktemp(request.function.__name__, numbered=True)
+    venv = VirtualEnv(str(p)) 
+    return venv 
+   
+def pytest_funcarg__py_setup(request):
+    rootdir = py.path.local(py.__file__).dirpath().dirpath()
+    setup = rootdir.join('setup.py')
+    if not setup.check():
+        py.test.skip("not found: %r" % setup)
+    return SetupBuilder(setup)
+
+class SetupBuilder:
+    def __init__(self, setup_path):
+        self.setup_path = setup_path
+        assert setup_path.check()
+
+    def make_sdist(self, destdir=None):
+        temp = py.path.local.mkdtemp()
+        try:
+            args = ['python', str(self.setup_path), 'sdist', 
+                    '--dist-dir', str(temp)]
+            subcall(args)
+            l = temp.listdir('py-*')
+            assert len(l) == 1
+            sdist = l[0]
+            if destdir is None:
+                destdir = self.setup_path.dirpath('build')
+                assert destdir.check()
+            else:
+                destdir = py.path.local(destdir)
+            target = destdir.join(sdist.basename)
+            sdist.copy(target)
+            return target 
+        finally:
+            temp.remove()
+
+def subcall(args):
+    if hasattr(subprocess, 'check_call'):
+        subprocess.check_call(args)
+    else:
+        subprocess.call(args)
+# code taken from Ronny Pfannenschmidt's virtualenvmanager 
+
+class VirtualEnv(object):
+    def __init__(self, path):
+        #XXX: supply the python executable
+        self.path = path
+
+    def __repr__(self):
+        return "<VirtualEnv at %r>" %(self.path)
+
+    def _cmd(self, name):
+        return os.path.join(self.path, 'bin', name)
+
+    def ensure(self):
+        if not os.path.exists(self._cmd('python')):
+            self.create()
+
+    def create(self, sitepackages=False):
+        args = ['virtualenv', self.path]
+        if not sitepackages:
+            args.append('--no-site-packages')
+        subcall(args)
+
+    def makegateway(self):
+        python = self._cmd('python')
+        return py.execnet.makegateway("popen//python=%s" %(python,))
+
+    def pcall(self, cmd, *args, **kw):
+        self.ensure()
+        return subprocess.call([
+                self._cmd(cmd)
+            ] + list(args),
+            **kw)
+
+
+    def easy_install(self, *packages, **kw):
+        args = []
+        if 'index' in kw:
+            index = kw['index']
+            if isinstance(index, (list, tuple)):
+                for i in index:
+                    args.extend(['-i', i])
+            else:
+                args.extend(['-i', index])
+
+        args.extend(packages)
+        self.pcall('easy_install', *args)
+
+
+    @property
+    def has_pip(self):
+        return os.path.exists(self._cmd('pip'))
+
+    def pip_install(self, *packages):
+        if not self.has_pip:
+            self.easy_install('pip')
+
+        self.pcall('pip', *packages)
+

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 Aug  4 19:45:30 2009
@@ -1,23 +1,51 @@
 """
-help performing checks for deprecation and other warnings. Provides: 
+helpers for asserting deprecation and other warnings. 
 
-    recwarn: function argument where one can call recwarn.pop() to get
-             the last warning that would have been shown. 
+Example usage 
+---------------------
 
-    py.test.deprecated_call(func, *args, **kwargs): 
-            assert that a function call triggers a deprecation warning. 
+You can use the ``recwarn`` funcarg to track 
+warnings within a test function:
+
+.. sourcecode:: python
+
+    def test_hello(recwarn):
+        from warnings import warn
+        warn("hello", DeprecationWarning)
+        w = recwarn.pop(DeprecationWarning)
+        assert issubclass(w.category, DeprecationWarning)
+        assert 'hello' in str(w.message)
+        assert w.filename
+        assert w.lineno
+
+You can also call a global helper for checking
+taht a certain function call yields a Deprecation
+warning:
+
+.. sourcecode:: python
+
+    import py
+            
+    def test_global():
+        py.test.deprecated_call(myfunction, 17)
+        
+        
 """
 
 import py
 import os
 
 def pytest_funcarg__recwarn(request):
-    """ check that warnings have been raised. """ 
+    """Return a WarningsRecorder instance that provides these methods:
+
+    * ``pop(category=None)``: return last warning matching the category.
+    * ``clear()``: clear list of warnings 
+    """
     warnings = WarningsRecorder()
     request.addfinalizer(warnings.finalize)
     return warnings
 
-def pytest_namespace(config):
+def pytest_namespace():
     return {'deprecated_call': deprecated_call}
 
 def deprecated_call(func, *args, **kwargs):

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 Aug  4 19:45:30 2009
@@ -1,6 +1,5 @@
 """
-perform ReST specific tests on .txt files, including
-linkchecks and remote URL checks. 
+perform ReST syntax, local and remote reference tests on .rst/.txt files. 
 """
 import py
 
@@ -17,7 +16,7 @@
            help="force generation of html files.")
 
 def pytest_collect_file(path, parent):
-    if path.ext == ".txt":
+    if path.ext in (".txt", ".rst"):
         project = getproject(path)
         if project is not None:
             return ReSTFile(path, parent=parent, project=project)
@@ -106,12 +105,14 @@
 
     def register_pygments(self):
         # taken from pygments-main/external/rst-directive.py 
+        from docutils.parsers.rst import directives
         try:
             from pygments.formatters import HtmlFormatter
         except ImportError:
             def pygments_directive(name, arguments, options, content, lineno,
                                    content_offset, block_text, state, state_machine):
                 return []
+            pygments_directive.options = {}
         else:
             # The default formatter
             DEFAULT = HtmlFormatter(noclasses=True)
@@ -121,7 +122,6 @@
             }
 
             from docutils import nodes
-            from docutils.parsers.rst import directives
 
             from pygments import highlight
             from pygments.lexers import get_lexer_by_name, TextLexer
@@ -138,10 +138,10 @@
                 parsed = highlight(u'\n'.join(content), lexer, formatter)
                 return [nodes.raw('', parsed, format='html')]
 
+            pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
+
         pygments_directive.arguments = (1, 0, 1)
         pygments_directive.content = 1
-        pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
-
         directives.register_directive('sourcecode', pygments_directive)
 
     def resolve_linkrole(self, name, text, check=True):
@@ -362,6 +362,7 @@
 
 class TestApigenLinkRole:
     disabled = True
+
     # these tests are moved here from the former py/doc/conftest.py
     def test_resolve_linkrole(self):
         from py.__.doc.conftest import get_apigen_relpath

Deleted: /py/trunk/py/test/plugin/pytest_resultdb.py
==============================================================================
--- /py/trunk/py/test/plugin/pytest_resultdb.py	Tue Aug  4 19:45:30 2009
+++ (empty file)
@@ -1,374 +0,0 @@
-"""XXX in progress: resultdb plugin for database logging of test results. 
-
-Saves test results to a datastore.
-
-XXX this needs to be merged with resultlog plugin
-
-Also mixes in some early ideas about an archive abstraction for test 
-results.
-""" 
-import py
-
-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):
-        self.archive_path = archive_path
-        import simplejson
-        self.simplejson = simplejson
-        
-    def init_db(self):
-        if os.path.exists(self.archive_path):
-            data_file = open(self.archive_path)
-            archive = self.simplejson.load(data_file)
-            self.archive = archive
-        else:
-            self.archive = []
-            self._flush()
-
-    def append_data(self, data):
-        runid = py.std.uuid.uuid4()
-        for item in data:
-            item = item.copy()
-            item['runid'] = str(runid)
-            self.archive.append(item)
-            self._flush()
-
-    def get_all_data(self):
-        return self.archive
-
-    def _flush(self):
-        data_file = open(self.archive_path, 'w')
-        self.simplejson.dump(self.archive, data_file)
-        data_file.close()
-
-
-class SQLiteResultArchive(object):
-    def __init__(self, archive_path):
-        self.archive_path = archive_path
-        import sqlite3
-        self.sqlite3 = sqlite3
-        
-    def init_db(self):
-        if not os.path.exists(self.archive_path):
-            conn = self.sqlite3.connect(self.archive_path)
-            cursor = conn.cursor()
-            try:
-                cursor.execute(SQL_CREATE_TABLES)
-                conn.commit()
-            finally:
-                cursor.close()
-                conn.close()
-
-    def append_data(self, data):
-        flat_data = []
-        runid = py.std.uuid.uuid4()
-        for item in data:
-            item = item.copy()
-            item['runid'] = str(runid)
-            flat_data.append(self.flatten(item))
-        conn = self.sqlite3.connect(self.archive_path)
-        cursor = conn.cursor()
-        cursor.executemany(SQL_INSERT_DATA, flat_data)
-        conn.commit()
-        cursor.close()
-        conn.close()
-
-    def get_all_data(self):
-        conn = self.sqlite3.connect(self.archive_path)
-        conn.row_factory = self.sqlite3.Row
-        cursor = conn.cursor()
-        cursor.execute(SQL_SELECT_DATA)
-        data = cursor.fetchall()
-        cursor.close()
-        conn.close()
-        data = [self.unflatten(item) for item in data]
-        return data
-
-    def flatten(self, item):
-        return (item.get('runid', None),
-                item.get('name', None),
-                item.get('passed', False),
-                item.get('skipped', False),
-                item.get('failed', False),
-                item.get('shortrepr', None),
-                item.get('longrepr', None),
-                item.get('fspath', None),
-                item.get('itemname', None),
-                )
-
-    def unflatten(self, item):
-        names = ("runid name passed skipped failed shortrepr "
-                "longrepr fspath itemname").split()
-        d = {}
-        for i, name in enumerate(names):
-            d[name] = item[i]
-        return d
-
-
-class ResultDB(ResultLog):
-    def __init__(self, cls, db_path):
-        self.archive = cls(db_path)
-        self.archive.init_db()
-
-    def write_log_entry(self, testpath, shortrepr, longrepr):
-        data = {}
-        event_excludes = ['colitem', 'longrepr']
-        for item in vars(event).keys():
-            if item not in event_excludes:
-                data[item] = getattr(event, item)
-        # use the locally calculated longrepr & shortrepr        
-        data['longrepr'] = longrepr
-        data['shortrepr'] = shortrepr
-
-        data['testpath'] = unicode(testpath)
-        self.archive.append_data([data])
-
-
-SQL_CREATE_TABLES = """
-create table pytest_results (
-    runid varchar(36),
-    name varchar,
-    passed int,
-    skipped int,
-    failed int,
-    shortrepr varchar,
-    longrepr varchar,
-    fspath varchar,
-    itemname varchar
-    );
-"""
-SQL_INSERT_DATA = """
-insert into pytest_results (
-    runid,
-    name,
-    passed,
-    skipped,
-    failed,
-    shortrepr,
-    longrepr,
-    fspath,
-    itemname)
-values (?, ?, ?, ?, ?, ?, ?, ?, ?);          
-"""
-SQL_SELECT_DATA = """
-select 
-    runid,
-    name,
-    passed,
-    skipped,
-    failed,
-    shortrepr,
-    longrepr,
-    fspath,
-    itemname
-from pytest_results;
-"""
-
-
-# ===============================================================================
-#
-# plugin tests 
-#
-# ===============================================================================
-
-import os, StringIO
-
-class BaseResultArchiveTests(object):
-    cls = None
-
-    def setup_class(cls):
-        # XXX refactor setup into a funcarg? 
-        cls.tempdb = "test_tempdb"
-
-    def test_init_db(self, testdir):
-        tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
-        archive = self.cls(tempdb_path)
-        archive.init_db()
-        assert os.path.exists(tempdb_path)
-
-    def test_db_insert(self, testdir):
-        tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
-        archive = self.cls(tempdb_path)
-        archive.init_db()
-        assert len(archive.get_all_data()) == 0
-
-        data = [{'name': 'tmppackage/test_whatever.py:test_hello',
-                    'fspath': '/Users/brian/work/tmppackage/test_whatever.py',
-                    'name': 'test_hello',
-                    'longrepr': '',
-                    'passed': True,
-                    'shortrepr': '.'
-                }]
-        archive.append_data(data)
-        result = archive.get_all_data()
-        print result
-        assert len(result) == 1
-        for key, value in data[0].items():
-            assert value == result[0][key]
-        assert 'runid' in result[0]
-        
-        # make sure the data is persisted
-        tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
-        archive = self.cls(tempdb_path)
-        archive.init_db()
-        assert len(archive.get_all_data()) == 1
-
-
-class TestJSONResultArchive(BaseResultArchiveTests):
-    cls = JSONResultArchive
-
-    def setup_method(self, method):
-        py.test.importorskip("simplejson")
-
-    
-class TestSQLiteResultArchive(BaseResultArchiveTests):
-    cls = SQLiteResultArchive
-
-    def setup_method(self, method):
-        py.test.importorskip("sqlite3")
-
-    def test_init_db_sql(self, testdir):
-        py.test.importorskip("sqlite3")
-        tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
-        archive = self.cls(tempdb_path)
-        archive.init_db()
-        assert os.path.exists(tempdb_path)
-        
-        # is table in the database? 
-        import sqlite3
-        conn = sqlite3.connect(tempdb_path)
-        cursor = conn.cursor()
-        cursor.execute("""SELECT name FROM sqlite_master
-                        ORDER BY name;""")
-        tables = cursor.fetchall()
-        cursor.close()
-        conn.close()
-        assert len(tables) == 1
-    
-def verify_archive_item_shape(item):
-    names = ("runid name passed skipped failed shortrepr "
-                "longrepr fspath itemname").split()
-    for name in names:
-        assert name in item
-
-class TestWithFunctionIntegration:
-    def getarchive(self, testdir, arg):
-        py.test.importorskip("sqlite3")
-        py.test.importorskip("simplejson")
-        resultdb = testdir.tmpdir.join("resultdb")
-        args = ["--resultdb=%s" % resultdb, "--resultdb_format=sqlite"] + [arg]
-        testdir.runpytest(*args)
-        assert resultdb.check(file=1)
-        archive = SQLiteResultArchive(unicode(resultdb))
-        archive.init_db()
-        return archive
-        
-    def test_collection_report(self, testdir):
-        py.test.skip("Needs a rewrite for db version.")
-        ok = testdir.makepyfile(test_collection_ok="")
-        skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
-        fail = testdir.makepyfile(test_collection_fail="XXX")
-
-        lines = self.getresultdb(testdir, ok) 
-        assert not lines
-
-        lines = self.getresultdb(testdir, skip)
-        assert len(lines) == 2
-        assert lines[0].startswith("S ")
-        assert lines[0].endswith("test_collection_skip.py")
-        assert lines[1].startswith(" ")
-        assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'")
-
-        lines = self.getresultdb(testdir, fail)
-        assert lines
-        assert lines[0].startswith("F ")
-        assert lines[0].endswith("test_collection_fail.py"), lines[0]
-        for x in lines[1:]:
-            assert x.startswith(" ")
-        assert "XXX" in "".join(lines[1:])
-
-    def test_log_test_outcomes(self, testdir):
-        mod = testdir.makepyfile(test_mod="""
-            import py 
-            def test_pass(): pass
-            def test_skip(): py.test.skip("hello")
-            def test_fail(): raise ValueError("val")
-        """)
-
-        archive = self.getarchive(testdir, mod)
-        data = archive.get_all_data()
-        for item in data:
-            verify_archive_item_shape(item)
-        assert len(data) == 3
-        assert len([item for item in data if item['passed'] == True]) == 1
-        assert len([item for item in data if item['skipped'] == True]) == 1
-        assert len([item for item in data if item['failed'] == True]) == 1
-
-    def test_internal_exception(self):
-        py.test.skip("Needs a rewrite for db version.")
-        # they are produced for example by a teardown failing
-        # at the end of the run
-        try:
-            raise ValueError
-        except ValueError:
-            excinfo = py.code.ExceptionInfo()
-        reslog = ResultDB(StringIO.StringIO())        
-        reslog.pytest_internalerror(excinfo.getrepr)
-        entry = reslog.logfile.getvalue()
-        entry_lines = entry.splitlines()
-
-        assert entry_lines[0].startswith('! ')
-        assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc
-        assert entry_lines[-1][0] == ' '
-        assert 'ValueError' in entry  
-
-def test_generic(testdir):
-    testdir.makepyfile("""
-        import py
-        def test_pass():
-            pass
-        def test_fail():
-            assert 0
-        def test_skip():
-            py.test.skip("")
-    """)
-    testdir.runpytest("--resultdb=result.sqlite")
-    #testdir.tmpdir.join("result.sqlite")
-    

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 Aug  4 19:45:30 2009
@@ -6,7 +6,7 @@
 
 def pytest_addoption(parser):
     group = parser.addgroup("resultlog", "resultlog plugin options")
-    group.addoption('--resultlog', action="store", dest="resultlog", metavar="path",
+    group.addoption('--resultlog', action="store", dest="resultlog", metavar="path", default=None,
            help="path for machine-readable result log.")
 
 def pytest_configure(config):
@@ -59,25 +59,25 @@
         testpath = generic_path(node)
         self.write_log_entry(testpath, shortrepr, longrepr) 
 
-    def pytest_runtest_logreport(self, rep):
-        code = rep.shortrepr 
-        if rep.passed:
+    def pytest_runtest_logreport(self, report):
+        code = report.shortrepr 
+        if report.passed:
             longrepr = ""
-        elif rep.failed:
-            longrepr = str(rep.longrepr) 
-        elif rep.skipped:
-            longrepr = str(rep.longrepr.reprcrash.message)
-        self.log_outcome(rep.item, code, longrepr) 
-
-    def pytest_collectreport(self, rep):
-        if not rep.passed:
-            if rep.failed: 
+        elif report.failed:
+            longrepr = str(report.longrepr) 
+        elif report.skipped:
+            longrepr = str(report.longrepr.reprcrash.message)
+        self.log_outcome(report.item, code, longrepr) 
+
+    def pytest_collectreport(self, report):
+        if not report.passed:
+            if report.failed: 
                 code = "F"
             else:
-                assert rep.skipped
+                assert report.skipped
                 code = "S"
-            longrepr = str(rep.longrepr.reprcrash)
-            self.log_outcome(rep.collector, code, longrepr)    
+            longrepr = str(report.longrepr.reprcrash)
+            self.log_outcome(report.collector, code, longrepr)    
 
     def pytest_internalerror(self, excrepr):
         path = excrepr.reprcrash.path 

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 Aug  4 19:45:30 2009
@@ -1,9 +1,5 @@
 """ 
-    collect and run test items. 
-
-    * executing test items 
-    * running collectors 
-    * and generating report events about it 
+collect and run test items and create reports. 
 """
 
 import py
@@ -23,27 +19,28 @@
 def pytest_configure(config):
     config._setupstate = SetupState()
 
-def pytest_sessionfinish(session, exitstatus, excrepr=None):
-    # XXX see above
+def pytest_sessionfinish(session, exitstatus):
     if hasattr(session.config, '_setupstate'):
-        session.config._setupstate.teardown_all()
+        hook = session.config.hook
+        rep = hook.pytest__teardown_final(session=session)
+        if rep:
+            hook.pytest__teardown_final_logerror(rep=rep)
 
 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 
+    result = excinfo = None
+    try:
+        result = collector._memocollect()
+    except KeyboardInterrupt:
+        raise
+    except:
+        excinfo = py.code.ExceptionInfo()
+    return CollectReport(collector, result, excinfo)
 
 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)
+            item.config.hook.pytest_runtest_logreport(report=rep)
     else:
         runtestprotocol(item)
     return True
@@ -53,7 +50,7 @@
     reports = [rep]
     if rep.passed:
         reports.append(call_and_report(item, "call", log))
-        reports.append(call_and_report(item, "teardown", log))
+    reports.append(call_and_report(item, "teardown", log))
     return reports
 
 def pytest_runtest_setup(item):
@@ -64,40 +61,52 @@
         item.runtest()
 
 def pytest_runtest_makereport(item, call):
-    return ItemTestReport(item, call.excinfo, call.when, call.outerr)
+    return ItemTestReport(item, call.excinfo, call.when)
 
 def pytest_runtest_teardown(item):
     item.config._setupstate.teardown_exact(item)
 
+def pytest__teardown_final(session):
+    call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
+    if call.excinfo:
+        rep = TeardownErrorReport(call.excinfo)
+        return rep 
+
+def pytest_report_teststatus(rep):
+    if rep.when in ("setup", "teardown"):
+        if rep.failed:
+            #      category, shortletter, verbose-word 
+            return "error", "E", "ERROR"
+        elif rep.skipped:
+            return "skipped", "s", "SKIPPED"
+        else:
+            return "", "", ""
 #
 # Implementation
 
 def call_and_report(item, when, log=True):
-    call = RuntestHookCall(item, when)
+    call = call_runtest_hook(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) 
+        hook.pytest_runtest_logreport(report=report) 
     return report
 
+def call_runtest_hook(item, when):
+    hookname = "pytest_runtest_" + when 
+    hook = getattr(item.config.hook, hookname)
+    return CallInfo(lambda: hook(item=item), when=when)
 
-class RuntestHookCall:
+class CallInfo:
     excinfo = None 
-    _prefix = "pytest_runtest_"
-    def __init__(self, item, when):
+    def __init__(self, func, 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()
+            self.result = func()
+        except KeyboardInterrupt:
+            raise
+        except:
+            self.excinfo = py.code.ExceptionInfo()
 
 def forked_run_report(item):
     # for now, we run setup/teardown in the subprocess 
@@ -147,10 +156,9 @@
 class ItemTestReport(BaseReport):
     failed = passed = skipped = False
 
-    def __init__(self, item, excinfo=None, when=None, outerr=None):
+    def __init__(self, item, excinfo=None, when=None):
         self.item = item 
         self.when = when
-        self.outerr = outerr
         if item and when != "setup":
             self.keywords = item.readkeywords() 
         else:
@@ -171,32 +179,42 @@
             elif excinfo.errisinstance(Skipped):
                 self.skipped = True 
                 shortrepr = "s"
-                longrepr = self.item._repr_failure_py(excinfo, outerr)
+                longrepr = self.item._repr_failure_py(excinfo)
             else:
                 self.failed = True
                 shortrepr = self.item.shortfailurerepr
                 if self.when == "call":
-                    longrepr = self.item.repr_failure(excinfo, outerr)
+                    longrepr = self.item.repr_failure(excinfo)
                 else: # exception in setup or teardown 
-                    longrepr = self.item._repr_failure_py(excinfo, outerr)
+                    longrepr = self.item._repr_failure_py(excinfo)
                     shortrepr = shortrepr.lower()
             self.shortrepr = shortrepr 
             self.longrepr = longrepr 
 
+    def __repr__(self):
+        status = (self.passed and "passed" or 
+                  self.skipped and "skipped" or 
+                  self.failed and "failed" or 
+                  "CORRUPT")
+        l = [repr(self.item.name), "when=%r" % self.when, "outcome %r" % status,]
+        if hasattr(self, 'node'):
+            l.append("txnode=%s" % self.node.gateway.id)
+        info = " " .join(map(str, l))
+        return "<ItemTestReport %s>" % info 
+
     def getnode(self):
         return self.item 
 
 class CollectReport(BaseReport):
     skipped = failed = passed = False 
 
-    def __init__(self, collector, result, excinfo=None, outerr=None):
+    def __init__(self, collector, result, excinfo=None):
         self.collector = collector 
         if not excinfo:
             self.passed = True
             self.result = result 
         else:
-            self.outerr = outerr
-            self.longrepr = self.collector._repr_failure_py(excinfo, outerr)
+            self.longrepr = self.collector._repr_failure_py(excinfo)
             if excinfo.errisinstance(Skipped):
                 self.skipped = True
                 self.reason = str(excinfo.value)
@@ -206,6 +224,13 @@
     def getnode(self):
         return self.collector 
 
+class TeardownErrorReport(BaseReport):
+    skipped = passed = False 
+    failed = True
+    when = "teardown"
+    def __init__(self, excinfo):
+        self.longrepr = excinfo.getrepr(funcargs=True)
+
 class SetupState(object):
     """ shared state for setting up/tearing down test items or collectors. """
     def __init__(self):
@@ -225,11 +250,14 @@
         colitem = self.stack.pop()
         self._teardown_with_finalization(colitem)
 
-    def _teardown_with_finalization(self, colitem): 
+    def _callfinalizers(self, colitem):
         finalizers = self._finalizers.pop(colitem, None)
         while finalizers:
             fin = finalizers.pop()
             fin()
+
+    def _teardown_with_finalization(self, colitem): 
+        self._callfinalizers(colitem) 
         if colitem: 
             colitem.teardown()
         for colitem in self._finalizers:
@@ -242,12 +270,14 @@
         assert not self._finalizers
 
     def teardown_exact(self, item):
-        assert self.stack and self.stack[-1] == item
-        self._pop_and_teardown()
+        if item == self.stack[-1]:
+            self._pop_and_teardown()
+        else:
+            self._callfinalizers(item)
      
     def prepare(self, colitem): 
         """ setup objects along the collector chain to the test-method
-            Teardown any unneccessary previously setup objects."""
+            and teardown previously setup objects."""
         needed_collectors = colitem.listchain() 
         while self.stack: 
             if self.stack == needed_collectors[:len(self.stack)]: 

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 Aug  4 19:45:30 2009
@@ -1,6 +1,29 @@
+"""
+Implements terminal reporting of the full testing process.
+
+This is a good source for looking at the various reporting hooks. 
+"""
 import py
 import sys
 
+def pytest_addoption(parser):
+    group = parser.getgroup("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('--debug',
+               action="store_true", dest="debug", default=False,
+               help="generate and show debugging information.")
+
 def pytest_configure(config):
     if config.option.collectonly:
         reporter = CollectonlyReporter(config)
@@ -13,7 +36,7 @@
             name = attr.split("_")[-1]
             assert hasattr(self.reporter._tw, name), name
             setattr(reporter._tw, name, getattr(config, attr))
-    config.pluginmanager.register(reporter)
+    config.pluginmanager.register(reporter, 'terminalreporter')
 
 class TerminalReporter:
     def __init__(self, config, file=None):
@@ -161,11 +184,15 @@
                 fspath, lineno, msg = self._getreportinfo(item)
                 self.write_fspath_result(fspath, "")
 
-    def pytest_runtest_logreport(self, rep):
-        if rep.passed and rep.when in ("setup", "teardown"):
-            return 
-        fspath = rep.item.fspath 
+    def pytest__teardown_final_logerror(self, rep):
+        self.stats.setdefault("error", []).append(rep)
+ 
+    def pytest_runtest_logreport(self, report):
+        rep = report
         cat, letter, word = self.getcategoryletterword(rep)
+        if not letter and not word:
+            # probably passed setup/teardown
+            return
         if isinstance(word, tuple):
             word, markup = word
         else:
@@ -186,15 +213,15 @@
                 self._tw.write(" " + line)
                 self.currentfspath = -2
 
-    def pytest_collectreport(self, rep):
-        if not rep.passed:
-            if rep.failed:
-                self.stats.setdefault("failed", []).append(rep)
-                msg = rep.longrepr.reprcrash.message 
-                self.write_fspath_result(rep.collector.fspath, "F")
-            elif rep.skipped:
-                self.stats.setdefault("skipped", []).append(rep)
-                self.write_fspath_result(rep.collector.fspath, "S")
+    def pytest_collectreport(self, report):
+        if not report.passed:
+            if report.failed:
+                self.stats.setdefault("error", []).append(report)
+                msg = report.longrepr.reprcrash.message 
+                self.write_fspath_result(report.collector.fspath, "E")
+            elif report.skipped:
+                self.stats.setdefault("skipped", []).append(report)
+                self.write_fspath_result(report.collector.fspath, "S")
 
     def pytest_sessionstart(self, session):
         self.write_sep("=", "test session starts", bold=True)
@@ -228,20 +255,30 @@
         for i, testarg in py.builtin.enumerate(self.config.args):
             self.write_line("test object %d: %s" %(i+1, testarg))
 
-    def pytest_sessionfinish(self, __call__, session, exitstatus, excrepr=None):
+    def pytest_sessionfinish(self, __call__, session, exitstatus):
         __call__.execute() 
         self._tw.line("")
         if exitstatus in (0, 1, 2):
+            self.summary_errors()
             self.summary_failures()
             self.summary_skips()
             self.config.hook.pytest_terminal_summary(terminalreporter=self)
-        if excrepr is not None:
-            self.summary_final_exc(excrepr)
         if exitstatus == 2:
-            self.write_sep("!", "KEYBOARD INTERRUPT")
+            self._report_keyboardinterrupt()
         self.summary_deselected()
         self.summary_stats()
 
+    def pytest_keyboard_interrupt(self, excinfo):
+        self._keyboardinterrupt_memo = excinfo.getrepr()
+
+    def _report_keyboardinterrupt(self):
+        self.write_sep("!", "KEYBOARD INTERRUPT")
+        excrepr = self._keyboardinterrupt_memo
+        if self.config.option.verbose:
+            excrepr.toterminal(self._tw)
+        else:
+            excrepr.reprcrash.toterminal(self._tw)
+
     def pytest_looponfailinfo(self, failreports, rootdirs):
         if failreports:
             self.write_sep("#", "LOOPONFAILING", red=True)
@@ -274,9 +311,11 @@
     def _getfailureheadline(self, rep):
         if hasattr(rep, "collector"):
             return str(rep.collector.fspath)
-        else:
+        elif hasattr(rep, 'item'):
             fspath, lineno, msg = self._getreportinfo(rep.item)
             return msg
+        else:
+            return "test session" 
 
     def _getreportinfo(self, item):
         try:
@@ -298,16 +337,39 @@
             for rep in self.stats['failed']:
                 msg = self._getfailureheadline(rep)
                 self.write_sep("_", msg)
-                if hasattr(rep, 'node'):
-                    self.write_line(self.gateway2info.get(
-                        rep.node.gateway, "node %r (platinfo not found? strange)")
-                            [:self._tw.fullwidth-1])
+                self.write_platinfo(rep)
+                rep.toterminal(self._tw)
+
+    def summary_errors(self):
+        if 'error' in self.stats and self.config.option.tbstyle != "no":
+            self.write_sep("=", "ERRORS")
+            for rep in self.stats['error']:
+                msg = self._getfailureheadline(rep)
+                if not hasattr(rep, 'when'):
+                    # collect
+                    msg = "ERROR during collection " + msg
+                elif rep.when == "setup":
+                    msg = "ERROR at setup of " + msg 
+                elif rep.when == "teardown":
+                    msg = "ERROR at teardown of " + msg 
+                self.write_sep("_", msg)
+                self.write_platinfo(rep)
                 rep.toterminal(self._tw)
 
+    def write_platinfo(self, rep):
+        if hasattr(rep, 'node'):
+            self.write_line(self.gateway2info.get(
+                rep.node.gateway, 
+                "node %r (platinfo not found? strange)")
+                    [:self._tw.fullwidth-1])
+
     def summary_stats(self):
         session_duration = py.std.time.time() - self._sessionstarttime
 
         keys = "failed passed skipped deselected".split()
+        for key in self.stats.keys():
+            if key not in keys:
+                keys.append(key)
         parts = []
         for key in keys:
             val = self.stats.get(key, None)
@@ -331,14 +393,6 @@
                     for num, fspath, lineno, reason in fskips:
                         self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason))
 
-    def summary_final_exc(self, excrepr):
-        self.write_sep("!")
-        if self.config.option.verbose:
-            excrepr.toterminal(self._tw)
-        else:
-            excrepr.reprcrash.toterminal(self._tw)
-
-
 class CollectonlyReporter:
     INDENT = "  "
 
@@ -364,13 +418,13 @@
     def pytest_itemstart(self, item, node=None):
         self.outindent(item)
 
-    def pytest_collectreport(self, rep):
-        if not rep.passed:
-            self.outindent("!!! %s !!!" % rep.longrepr.reprcrash.message)
-            self._failed.append(rep)
+    def pytest_collectreport(self, report):
+        if not report.passed:
+            self.outindent("!!! %s !!!" % report.longrepr.reprcrash.message)
+            self._failed.append(report)
         self.indent = self.indent[:-len(self.INDENT)]
 
-    def pytest_sessionfinish(self, session, exitstatus, excrepr=None):
+    def pytest_sessionfinish(self, session, exitstatus):
         if self._failed:
             self.out.sep("!", "collection failures")
         for rep in self._failed:
@@ -395,375 +449,3 @@
     except (TypeError, ValueError):
         return str(v)
 
-# ===============================================================================
-#
-# plugin tests 
-#
-# ===============================================================================
-
-import pytest_runner as runner # XXX 
-
-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
-            def test_ok():
-                pass
-            def test_skip():
-                py.test.skip("xx")
-            def test_func():
-                assert 0
-        """)
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        rep.config.pluginmanager.register(rep)
-        rep.config.hook.pytest_sessionstart(session=testdir.session)
-
-        for item in testdir.genitems([modcol]):
-            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_sessionfinish(session=testdir.session, exitstatus=1)
-        linecomp.assert_contains_lines([
-            "    def test_func():",
-            ">       assert 0",
-            "E       assert 0",
-        ])
-
-    def test_pass_skip_fail_verbose(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("""
-            import py
-            def test_ok():
-                pass
-            def test_skip():
-                py.test.skip("xx")
-            def test_func():
-                assert 0
-        """, configargs=("-v",))
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        rep.config.pluginmanager.register(rep)
-        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_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_sessionfinish(session=testdir.session, exitstatus=1)
-        linecomp.assert_contains_lines([
-            "    def test_func():",
-            ">       assert 0",
-            "E       assert 0",
-        ])
-
-    def test_collect_fail(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("import xyz")
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        rep.config.pluginmanager.register(rep)
-        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_sessionfinish(session=testdir.session, exitstatus=1)
-        linecomp.assert_contains_lines([
-            ">   import xyz",
-            "E   ImportError: No module named xyz"
-        ])
-
-    def test_internalerror(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("def test_one(): pass")
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        excinfo = py.test.raises(ValueError, "raise ValueError('hello')")
-        rep.pytest_internalerror(excinfo.getrepr())
-        linecomp.assert_contains_lines([
-            "INTERNALERROR> *raise ValueError*"
-        ])
-
-    def test_gwmanage_events(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("""
-            def test_one():
-                pass
-        """, configargs=("-v",))
-
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        class gw1:
-            id = "X1"
-            spec = py.execnet.XSpec("popen")
-        class gw2:
-            id = "X2"
-            spec = py.execnet.XSpec("popen")
-        class rinfo:
-            version_info = (2, 5, 1, 'final', 0)
-            executable = "hello"
-            platform = "xyz"
-            cwd = "qwe"
-        
-        rep.pyexecnet_gwmanage_newgateway(gw1, rinfo)
-        linecomp.assert_contains_lines([
-            "X1*popen*xyz*2.5*"
-        ])
-
-        rep.pyexecnet_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2])
-        linecomp.assert_contains_lines([
-            "rsyncstart: hello -> X1, X2"
-        ])
-        rep.pyexecnet_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2])
-        linecomp.assert_contains_lines([
-            "rsyncfinish: hello -> X1, X2"
-        ])
-
-    def test_writeline(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("def test_one(): pass")
-        stringio = py.std.cStringIO.StringIO()
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        rep.write_fspath_result(py.path.local("xy.py"), '.')
-        rep.write_line("hello world")
-        lines = linecomp.stringio.getvalue().split('\n')
-        assert not lines[0]
-        assert lines[1].endswith("xy.py .")
-        assert lines[2] == "hello world"
-
-    def test_looponfailreport(self, testdir, linecomp):
-        modcol = testdir.getmodulecol("""
-            def test_fail():
-                assert 0
-            def test_fail2():
-                raise ValueError()
-        """)
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        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",
-            "*test_looponfailreport.py:4: ValueError*",
-            "*waiting*", 
-            "*%s*" % (modcol.config.topdir),
-        ])
-
-    def test_tb_option(self, testdir, linecomp):
-        # XXX usage of testdir 
-        for tbopt in ["long", "short", "no"]:
-            print 'testing --tb=%s...' % tbopt
-            modcol = testdir.getmodulecol("""
-                import py
-                def g():
-                    raise IndexError
-                def test_func():
-                    print 6*7
-                    g()  # --calling--
-            """, configargs=("--tb=%s" % tbopt,))
-            rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-            rep.config.pluginmanager.register(rep)
-            rep.config.hook.pytest_sessionstart(session=testdir.session)
-            for item in testdir.genitems([modcol]):
-                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
-                assert 'print 6*7' in s
-            else:
-                assert 'print 6*7' not in s
-            if tbopt != "no":
-                assert '--calling--' in s
-                assert 'IndexError' in s
-            else:
-                assert 'FAILURES' not in s
-                assert '--calling--' not in s
-                assert 'IndexError' not in s
-            linecomp.stringio.truncate(0)
-
-    def test_show_path_before_running_test(self, testdir, linecomp):
-        item = testdir.getitem("def test_func(): pass")
-        tr = TerminalReporter(item.config, file=linecomp.stringio)
-        item.config.pluginmanager.register(tr)
-        tr.config.hook.pytest_itemstart(item=item)
-        linecomp.assert_contains_lines([
-            "*test_show_path_before_running_test.py*"
-        ])
-
-    def test_itemreport_reportinfo(self, testdir, linecomp):
-        testdir.makeconftest("""
-            import py
-            class Function(py.test.collect.Function):
-                def reportinfo(self):
-                    return "ABCDE", 42, "custom"    
-        """)
-        item = testdir.getitem("def test_func(): pass")
-        tr = TerminalReporter(item.config, file=linecomp.stringio)
-        item.config.pluginmanager.register(tr)
-        tr.config.hook.pytest_itemstart(item=item)
-        linecomp.assert_contains_lines([
-            "*ABCDE "
-        ])
-        tr.config.option.verbose = True
-        tr.config.hook.pytest_itemstart(item=item)
-        linecomp.assert_contains_lines([
-            "*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():
-                assert 0
-            def test_spamegg():
-                import py; py.test.skip('skip me please!')
-            def test_interrupt_me():
-                raise KeyboardInterrupt   # simulating the user
-        """, configargs=("-v",)*verbose)
-        #""", configargs=("--showskipsummary",) + ("-v",)*verbose)
-        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
-        modcol.config.pluginmanager.register(rep)
-        modcol.config.hook.pytest_sessionstart(session=testdir.session)
-        try:
-            for item in testdir.genitems([modcol]):
-                modcol.config.hook.pytest_runtest_logreport(
-                    rep=basic_run_report(item))
-        except KeyboardInterrupt:
-            excinfo = py.code.ExceptionInfo()
-        else:
-            py.test.fail("no KeyboardInterrupt??")
-        s = linecomp.stringio.getvalue()
-        if not verbose:
-            assert s.find("_keyboard_interrupt.py Fs") != -1
-        modcol.config.hook.pytest_sessionfinish(
-            session=testdir.session, exitstatus=2, excrepr=excinfo.getrepr())
-        text = linecomp.stringio.getvalue()
-        linecomp.assert_contains_lines([
-            "    def test_foobar():",
-            ">       assert 0",
-            "E       assert 0",
-        ])
-        #assert "Skipped: 'skip me please!'" in text
-        assert "_keyboard_interrupt.py:6: KeyboardInterrupt" in text
-        see_details = "raise KeyboardInterrupt   # simulating the user" in text
-        assert see_details == verbose
-
-    def test_keyboard_interrupt(self, testdir, linecomp):
-        self.pseudo_keyboard_interrupt(testdir, linecomp)
-        
-    def test_verbose_keyboard_interrupt(self, testdir, linecomp):
-        self.pseudo_keyboard_interrupt(testdir, linecomp, verbose=True)
-
-    def test_skip_reasons_folding(self):
-        class longrepr:
-            class reprcrash:
-                path = 'xyz'
-                lineno = 3
-                message = "justso"
-
-        ev1 = runner.CollectReport(None, None)
-        ev1.when = "execute"
-        ev1.skipped = True
-        ev1.longrepr = longrepr 
-        
-        ev2 = runner.ItemTestReport(None, excinfo=longrepr)
-        ev2.skipped = True
-
-        l = folded_skips([ev1, ev2])
-        assert len(l) == 1
-        num, fspath, lineno, reason = l[0]
-        assert num == 2
-        assert fspath == longrepr.reprcrash.path
-        assert lineno == longrepr.reprcrash.lineno
-        assert reason == longrepr.reprcrash.message
-
-class TestCollectonly:
-    def test_collectonly_basic(self, testdir, linecomp):
-        modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
-            def test_func():
-                pass
-        """)
-        rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
-        modcol.config.pluginmanager.register(rep)
-        indent = rep.indent
-        rep.config.hook.pytest_collectstart(collector=modcol)
-        linecomp.assert_contains_lines([
-           "<Module 'test_collectonly_basic.py'>"
-        ])
-        item = modcol.join("test_func")
-        rep.config.hook.pytest_itemstart(item=item)
-        linecomp.assert_contains_lines([
-           "  <Function 'test_func'>", 
-        ])
-        rep.config.hook.pytest_collectreport(
-            rep=runner.CollectReport(modcol, [], excinfo=None))
-        assert rep.indent == indent 
-
-    def test_collectonly_skipped_module(self, testdir, linecomp):
-        modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
-            import py
-            py.test.skip("nomod")
-        """)
-        rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
-        modcol.config.pluginmanager.register(rep)
-        cols = list(testdir.genitems([modcol]))
-        assert len(cols) == 0
-        linecomp.assert_contains_lines("""
-            <Module 'test_collectonly_skipped_module.py'>
-              !!! Skipped: 'nomod' !!!
-        """)
-
-    def test_collectonly_failed_module(self, testdir, linecomp):
-        modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
-            raise ValueError(0)
-        """)
-        rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
-        modcol.config.pluginmanager.register(rep)
-        cols = list(testdir.genitems([modcol]))
-        assert len(cols) == 0
-        linecomp.assert_contains_lines("""
-            <Module 'test_collectonly_failed_module.py'>
-              !!! 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) 
-

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 Aug  4 19:45:30 2009
@@ -1,19 +1,24 @@
 """
 automatically discover and run traditional "unittest.py" style tests. 
 
-you can mix unittest TestCase subclasses and 
-py.test style tests in one test module. 
+Usage
+----------------
 
-XXX consider user-specified test_suite() 
+This plugin collects and runs Python `unittest.py style`_ tests. 
+It will automatically collect ``unittest.TestCase`` subclasses 
+and their ``test`` methods from the test modules of a project
+(usually following the ``test_*.py`` pattern). 
 
-this code is somewhat derived from Guido Wesdorps 
-
-    http://johnnydebris.net/svn/projects/py_unittest
+This plugin is enabled by default. 
 
+.. _`unittest.py style`: http://docs.python.org/library/unittest.html
 """
 import py
+import sys
 
 def pytest_pycollect_makeitem(collector, name, obj):
+    if 'unittest' not in sys.modules:
+        return # nobody could have possibly derived a subclass 
     if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
         return UnitTestCase(name, parent=collector)
 

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 Aug  4 19:45:30 2009
@@ -1,15 +1,26 @@
 """
-mark tests as expected-to-fail and report them separately. 
+mark python test functions as expected-to-fail and report them separately. 
 
-example: 
+usage
+------------
 
-    @py.test.xfail
+Use the generic mark decorator to mark your test functions as 
+'expected to fail':: 
+
+    @py.test.mark.xfail
     def test_hello():
         ...
-        assert 0
+
+This test will be executed but no traceback will be reported 
+when it fails. Instead terminal reporting will list it in the 
+"expected to fail" section or "unexpectedly passing" section.  
+
 """
+
 import py
 
+pytest_plugins = ['keyword']
+
 def pytest_runtest_makereport(__call__, item, call):
     if call.when != "call":
         return
@@ -38,41 +49,36 @@
     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))
+        for rep in xfailed:
+            entry = rep.longrepr.reprcrash 
+            modpath = rep.item.getmodpath(includemodule=True)
+            pos = "%s %s:%d: " %(modpath, entry.path, entry.lineno)
+            reason = rep.longrepr.reprcrash.message
+            tr._tw.line("%s %s" %(pos, reason))
 
     xpassed = terminalreporter.stats.get("xpassed")
     if xpassed:
         tr.write_sep("_", "UNEXPECTEDLY PASSING TESTS")
-        for event in xpassed:
-            tr._tw.line("%s: xpassed" %(event.item,))
+        for rep in xpassed:
+            fspath, lineno, modpath = rep.item.reportinfo()
+            pos = "%s %s:%d: unexpectedly passing" %(modpath, fspath, lineno)
+            tr._tw.line(pos)
 
-def xfail_decorator(func):
-    func.xfail = True
-    return func
- 
-def pytest_namespace(config):
-    return dict(xfail=xfail_decorator)
 
-# ===============================================================================
+# =============================================================================
 #
 # plugin tests 
 #
-# ===============================================================================
+# =============================================================================
 
-def test_xfail(testdir, linecomp):
+def test_xfail(testdir):
     p = testdir.makepyfile(test_one="""
         import py
-        @py.test.xfail
+        @py.test.mark.xfail
         def test_this():
             assert 0
 
-        @py.test.xfail
+        @py.test.mark.xfail
         def test_that():
             assert 1
     """)

Added: py/trunk/py/test/plugin/test_pytest_capture.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/test_pytest_capture.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,328 @@
+import py, os, sys
+from py.__.test.plugin.pytest_capture import CaptureManager
+
+class TestCaptureManager:
+
+    def test_configure_per_fspath(self, testdir):
+        config = testdir.parseconfig(testdir.tmpdir)
+        assert config.getvalue("capture") is None
+        capman = CaptureManager()
+        assert capman._getmethod(config, None) == "fd" # default
+
+        for name in ('no', 'fd', 'sys'):
+            sub = testdir.tmpdir.mkdir("dir" + name)
+            sub.ensure("__init__.py")
+            sub.join("conftest.py").write('conf_capture = %r' % name)
+            assert capman._getmethod(config, sub.join("test_hello.py")) == name
+
+    @py.test.mark.multi(method=['no', 'fd', 'sys'])
+    def test_capturing_basic_api(self, method):
+        capouter = py.io.StdCaptureFD()
+        old = sys.stdout, sys.stderr, sys.stdin
+        try:
+            capman = CaptureManager()
+            capman.resumecapture(method)
+            print "hello"
+            out, err = capman.suspendcapture()
+            if method == "no":
+                assert old == (sys.stdout, sys.stderr, sys.stdin)
+            else:
+                assert out == "hello\n"
+            capman.resumecapture(method)
+            out, err = capman.suspendcapture()
+            assert not out and not err 
+        finally:
+            capouter.reset()
+      
+    def test_juggle_capturings(self, testdir):
+        capouter = py.io.StdCaptureFD()
+        try:
+            config = testdir.parseconfig(testdir.tmpdir)
+            capman = CaptureManager()
+            capman.resumecapture("fd")
+            py.test.raises(ValueError, 'capman.resumecapture("fd")')
+            py.test.raises(ValueError, 'capman.resumecapture("sys")')
+            os.write(1, "hello\n")
+            out, err = capman.suspendcapture()
+            assert out == "hello\n"
+            capman.resumecapture("sys")
+            os.write(1, "hello\n")
+            print >>sys.stderr, "world"
+            out, err = capman.suspendcapture()
+            assert not out
+            assert err == "world\n"
+        finally:
+            capouter.reset()
+
+def test_collect_capturing(testdir):
+    p = testdir.makepyfile("""
+        print "collect %s failure" % 13
+        import xyz42123
+    """)
+    result = testdir.runpytest(p)
+    result.stdout.fnmatch_lines([
+        "*Captured stdout*",
+        "*collect 13 failure*", 
+    ])
+
+class TestPerTestCapturing:
+    def test_capture_and_fixtures(self, testdir):
+        p = testdir.makepyfile("""
+            def setup_module(mod):
+                print "setup module"
+            def setup_function(function):
+                print "setup", function.__name__
+            def test_func1():
+                print "in func1"
+                assert 0
+            def test_func2():
+                print "in func2"
+                assert 0
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "setup module*",
+            "setup test_func1*",
+            "in func1*",
+            "setup test_func2*",
+            "in func2*", 
+        ]) 
+
+    @py.test.mark.xfail
+    def test_capture_scope_cache(self, testdir):
+        p = testdir.makepyfile("""
+            import sys
+            def setup_module(func):
+                print "module-setup"
+            def setup_function(func):
+                print "function-setup"
+            def test_func():
+                print "in function"
+                assert 0
+            def teardown_function(func):
+                print "in teardown"
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*test_func():*",
+            "*Captured stdout during setup*", 
+            "module-setup*", 
+            "function-setup*", 
+            "*Captured stdout*",
+            "in teardown*",
+        ])
+
+
+    def test_no_carry_over(self, testdir):
+        p = testdir.makepyfile("""
+            def test_func1():
+                print "in func1"
+            def test_func2():
+                print "in func2"
+                assert 0
+        """)
+        result = testdir.runpytest(p)
+        s = result.stdout.str()
+        assert "in func1" not in s 
+        assert "in func2" in s 
+
+
+    def test_teardown_capturing(self, testdir):
+        p = testdir.makepyfile("""
+            def setup_function(function):
+                print "setup func1"
+            def teardown_function(function):
+                print "teardown func1"
+                assert 0
+            def test_func1(): 
+                print "in func1"
+                pass
+        """)
+        result = testdir.runpytest(p)
+        assert result.stdout.fnmatch_lines([
+            '*teardown_function*',
+            '*Captured stdout*',
+            "setup func1*",
+            "in func1*",
+            "teardown func1*",
+            #"*1 fixture failure*"
+        ])
+
+    def test_teardown_final_capturing(self, testdir):
+        p = testdir.makepyfile("""
+            def teardown_module(mod):
+                print "teardown module"
+                assert 0
+            def test_func():
+                pass
+        """)
+        result = testdir.runpytest(p)
+        assert result.stdout.fnmatch_lines([
+            "*def teardown_module(mod):*", 
+            "*Captured stdout*", 
+            "*teardown module*", 
+            "*1 error*", 
+        ])
+
+    def test_capturing_outerr(self, testdir): 
+        p1 = testdir.makepyfile("""
+            import sys 
+            def test_capturing():
+                print 42
+                print >>sys.stderr, 23 
+            def test_capturing_error():
+                print 1
+                print >>sys.stderr, 2
+                raise ValueError
+        """)
+        result = testdir.runpytest(p1)
+        result.stdout.fnmatch_lines([
+            "*test_capturing_outerr.py .F", 
+            "====* FAILURES *====",
+            "____*____", 
+            "*test_capturing_outerr.py:8: ValueError",
+            "*--- Captured stdout ---*",
+            "1",
+            "*--- Captured stderr ---*",
+            "2",
+        ])
+
+class TestLoggingInteraction:
+    def test_logging_stream_ownership(self, testdir):
+        p = testdir.makepyfile("""
+            def test_logging():
+                import logging
+                import StringIO
+                stream = StringIO.StringIO()
+                logging.basicConfig(stream=stream)
+                stream.close() # to free memory/release resources
+        """)
+        result = testdir.runpytest(p)
+        result.stderr.str().find("atexit") == -1
+
+    def test_capturing_and_logging_fundamentals(self, testdir):
+        # here we check a fundamental feature 
+        rootdir = str(py.path.local(py.__file__).dirpath().dirpath())
+        p = testdir.makepyfile("""
+            import sys
+            sys.path.insert(0, %r)
+            import py, logging
+            cap = py.io.StdCaptureFD(out=False, in_=False)
+            logging.warn("hello1")
+            outerr = cap.suspend()
+
+            print "suspeneded and captured", outerr
+
+            logging.warn("hello2")
+
+            cap.resume()
+            logging.warn("hello3")
+
+            outerr = cap.suspend()
+            print "suspend2 and captured", outerr
+        """ % rootdir)
+        result = testdir.runpython(p)
+        assert result.stdout.fnmatch_lines([
+            "suspeneded and captured*hello1*",
+            "suspend2 and captured*hello2*WARNING:root:hello3*",
+        ])
+        assert "atexit" not in result.stderr.str()
+        
+           
+    def test_logging_and_immediate_setupteardown(self, testdir):
+        p = testdir.makepyfile("""
+            import logging
+            def setup_function(function):
+                logging.warn("hello1")
+
+            def test_logging():
+                logging.warn("hello2")
+                assert 0
+
+            def teardown_function(function):
+                logging.warn("hello3")
+                assert 0
+        """)
+        for optargs in (('--capture=sys',), ('--capture=fd',)):
+            print optargs
+            result = testdir.runpytest(p, *optargs)
+            s = result.stdout.str()
+            result.stdout.fnmatch_lines([
+                "*WARN*hello3",  # errors show first!
+                "*WARN*hello1", 
+                "*WARN*hello2", 
+            ])
+            # verify proper termination
+            assert "closed" not in s
+
+    def test_logging_and_crossscope_fixtures(self, testdir):
+        p = testdir.makepyfile("""
+            import logging
+            def setup_module(function):
+                logging.warn("hello1")
+
+            def test_logging():
+                logging.warn("hello2")
+                assert 0
+
+            def teardown_module(function):
+                logging.warn("hello3")
+                assert 0
+        """)
+        for optargs in (('--capture=sys',), ('--capture=fd',)):
+            print optargs
+            result = testdir.runpytest(p, *optargs)
+            s = result.stdout.str()
+            result.stdout.fnmatch_lines([
+                "*WARN*hello3",  # errors come first
+                "*WARN*hello1", 
+                "*WARN*hello2", 
+            ])
+            # verify proper termination
+            assert "closed" not in s
+
+class TestCaptureFuncarg:
+    def test_std_functional(self, testdir):        
+        reprec = testdir.inline_runsource("""
+            def test_hello(capsys):
+                print 42
+                out, err = capsys.readouterr()
+                assert out.startswith("42")
+        """)
+        reprec.assertoutcome(passed=1)
+        
+    def test_stdfd_functional(self, testdir):        
+        reprec = testdir.inline_runsource("""
+            def test_hello(capfd):
+                import os
+                os.write(1, "42")
+                out, err = capfd.readouterr()
+                assert out.startswith("42")
+                capfd.close()
+        """)
+        reprec.assertoutcome(passed=1)
+
+    def test_partial_setup_failure(self, testdir):        
+        p = testdir.makepyfile("""
+            def test_hello(capfd, missingarg):
+                pass
+        """)
+        result = testdir.runpytest(p)
+        assert result.stdout.fnmatch_lines([
+            "*test_partial_setup_failure*",
+            "*1 error*",
+        ])
+
+    def test_keyboardinterrupt_disables_capturing(self, testdir):        
+        p = testdir.makepyfile("""
+            def test_hello(capfd):
+                import os
+                os.write(1, "42")
+                raise KeyboardInterrupt()
+        """)
+        result = testdir.runpytest(p)
+        result.stdout.fnmatch_lines([
+            "*KEYBOARD INTERRUPT*"
+        ])
+        assert result.ret == 2
+

Modified: py/trunk/py/test/plugin/test_pytest_runner.py
==============================================================================
--- py/trunk/py/test/plugin/test_pytest_runner.py	(original)
+++ py/trunk/py/test/plugin/test_pytest_runner.py	Tue Aug  4 19:45:30 2009
@@ -86,7 +86,8 @@
         #assert rep.skipped.reason == "hello"
         #assert rep.skipped.location.lineno == 3
         #assert rep.skipped.location.lineno == 3
-        assert len(reports) == 1
+        assert len(reports) == 2
+        assert reports[1].passed # teardown 
 
     def test_failure_in_setup_function(self, testdir):
         reports = testdir.runitem("""
@@ -101,7 +102,7 @@
         assert not rep.passed 
         assert rep.failed 
         assert rep.when == "setup"
-        assert len(reports) == 1
+        assert len(reports) == 2
 
     def test_failure_in_teardown_function(self, testdir):
         reports = testdir.runitem("""
@@ -125,7 +126,7 @@
         testdir.makepyfile(conftest="""
             import py
             class Function(py.test.collect.Function):
-                def repr_failure(self, excinfo, outerr):
+                def repr_failure(self, excinfo):
                     return "hello" 
         """)
         reports = testdir.runitem("""
@@ -142,7 +143,7 @@
         #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):
+    def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
         testdir.makepyfile(conftest="""
             import py
             class Function(py.test.collect.Function):
@@ -156,7 +157,7 @@
             def test_func():
                 pass
         """)
-        assert len(reports) == 1
+        assert len(reports) == 2
         rep = reports[0]
         print rep
         assert not rep.skipped 
@@ -167,21 +168,6 @@
         #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("""
@@ -207,7 +193,6 @@
         else: 
             py.test.fail("did not raise")
 
-
 class TestExecutionNonForked(BaseFunctionalTests):
     def getrunner(self):
         def f(item):
@@ -285,3 +270,4 @@
         "*CRASHED*",
         "*1 failed*"
     ])
+

Added: py/trunk/py/test/plugin/test_pytest_runner_xunit.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/test_pytest_runner_xunit.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,142 @@
+#
+# test correct setup/teardowns at
+# module, class, and instance level
+
+def test_module_and_function_setup(testdir):
+    reprec = testdir.inline_runsource(""" 
+        modlevel = []
+        def setup_module(module):
+            assert not modlevel
+            module.modlevel.append(42)
+
+        def teardown_module(module):
+            modlevel.pop()
+
+        def setup_function(function):
+            function.answer = 17
+
+        def teardown_function(function):
+            del function.answer
+
+        def test_modlevel():
+            assert modlevel[0] == 42
+            assert test_modlevel.answer == 17
+
+        class TestFromClass: 
+            def test_module(self):
+                assert modlevel[0] == 42
+                assert not hasattr(test_modlevel, 'answer') 
+    """)
+    rep = reprec.matchreport("test_modlevel") 
+    assert rep.passed 
+    rep = reprec.matchreport("test_module") 
+    assert rep.passed 
+
+def test_class_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSimpleClassSetup:
+            clslevel = []
+            def setup_class(cls):
+                cls.clslevel.append(23)
+
+            def teardown_class(cls):
+                cls.clslevel.pop()
+
+            def test_classlevel(self):
+                assert self.clslevel[0] == 23
+
+        class TestInheritedClassSetupStillWorks(TestSimpleClassSetup):
+            def test_classlevel_anothertime(self):
+                assert self.clslevel == [23]
+
+        def test_cleanup():
+            assert not TestSimpleClassSetup.clslevel 
+            assert not TestInheritedClassSetupStillWorks.clslevel
+    """)
+    reprec.assertoutcome(passed=1+2+1)
+
+
+def test_method_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSetupMethod:
+            def setup_method(self, meth):
+                self.methsetup = meth 
+            def teardown_method(self, meth):
+                del self.methsetup 
+
+            def test_some(self):
+                assert self.methsetup == self.test_some 
+
+            def test_other(self):
+                assert self.methsetup == self.test_other
+    """)
+    reprec.assertoutcome(passed=2)
+       
+def test_method_generator_setup(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSetupTeardownOnInstance: 
+            def setup_class(cls):
+                cls.classsetup = True 
+
+            def setup_method(self, method):
+                self.methsetup = method 
+
+            def test_generate(self):
+                assert self.classsetup 
+                assert self.methsetup == self.test_generate 
+                yield self.generated, 5
+                yield self.generated, 2
+
+            def generated(self, value):
+                assert self.classsetup 
+                assert self.methsetup == self.test_generate 
+                assert value == 5
+    """)
+    reprec.assertoutcome(passed=1, failed=1)
+
+def test_func_generator_setup(testdir):
+    reprec = testdir.inline_runsource(""" 
+        import sys
+
+        def setup_module(mod):
+            print "setup_module"
+            mod.x = []
+        
+        def setup_function(fun):
+            print "setup_function"
+            x.append(1)
+
+        def teardown_function(fun):
+            print "teardown_function" 
+            x.pop()
+        
+        def test_one():
+            assert x == [1]
+            def check():
+                print "check" 
+                print >>sys.stderr, "e" 
+                assert x == [1]
+            yield check 
+            assert x == [1]
+    """)
+    rep = reprec.matchreport("test_one", names="pytest_runtest_logreport") 
+    assert rep.passed 
+        
+def test_method_setup_uses_fresh_instances(testdir):
+    reprec = testdir.inline_runsource("""
+        class TestSelfState1: 
+            def __init__(self):
+                self.hello = 42
+            def test_hello(self):
+                self.world = 23
+            def test_afterhello(self):
+                assert not hasattr(self, 'world')
+                assert self.hello == 42
+        class TestSelfState2: 
+            def test_hello(self):
+                self.world = 10
+            def test_world(self):
+                assert not hasattr(self, 'world')
+    """)
+    reprec.assertoutcome(passed=4, failed=0)
+

Added: py/trunk/py/test/plugin/test_pytest_terminal.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/plugin/test_pytest_terminal.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,624 @@
+"""
+terminal reporting of the full testing process.
+"""
+import py
+import sys
+
+# ===============================================================================
+# plugin tests 
+#
+# ===============================================================================
+
+import pytest_runner as runner # XXX 
+from pytest_terminal import TerminalReporter, CollectonlyReporter
+from pytest_terminal import repr_pythonversion, folded_skips
+
+def basic_run_report(item):
+    return runner.call_and_report(item, "call", log=False)
+
+class Option:
+    def __init__(self, verbose=False, dist=None):
+        self.verbose = verbose
+        self.dist = dist
+    def _getcmdargs(self):
+        l = []
+        if self.verbose:
+            l.append('-v')
+        if self.dist:
+            l.append('--dist=%s' % self.dist)
+            l.append('--tx=popen')
+        return l
+    def _getcmdstring(self):
+        return " ".join(self._getcmdargs())
+
+def pytest_generate_tests(metafunc):
+    if "option" in metafunc.funcargnames:
+        metafunc.addcall(
+            id="default", 
+            funcargs={'option': Option(verbose=False)}
+        )
+        metafunc.addcall(
+            id="verbose", 
+            funcargs={'option': Option(verbose=True)}
+        )
+        nodist = getattr(metafunc.function, 'nodist', False)
+        if not nodist:
+            metafunc.addcall(
+                id="verbose-dist", 
+                funcargs={'option': Option(dist='each', verbose=True)}
+            )
+
+class TestTerminal:
+    def test_pass_skip_fail(self, testdir, option):
+        p = testdir.makepyfile("""
+            import py
+            def test_ok():
+                pass
+            def test_skip():
+                py.test.skip("xx")
+            def test_func():
+                assert 0
+        """)
+        result = testdir.runpytest(*option._getcmdargs())
+        if option.verbose:
+            if not option.dist:
+                result.stdout.fnmatch_lines([
+                    "*test_pass_skip_fail.py:2: *test_ok*PASS*",
+                    "*test_pass_skip_fail.py:4: *test_skip*SKIP*",
+                    "*test_pass_skip_fail.py:6: *test_func*FAIL*",
+                ])
+            else:
+                expected = [
+                    "*PASS*test_pass_skip_fail.py:2: *test_ok*", 
+                    "*SKIP*test_pass_skip_fail.py:4: *test_skip*", 
+                    "*FAIL*test_pass_skip_fail.py:6: *test_func*", 
+                ]
+                for line in expected:
+                    result.stdout.fnmatch_lines([line])
+        else:
+            result.stdout.fnmatch_lines([
+            "*test_pass_skip_fail.py .sF"
+        ])
+        result.stdout.fnmatch_lines([
+            "    def test_func():",
+            ">       assert 0",
+            "E       assert 0",
+        ])
+
+    def test_collect_fail(self, testdir, option):
+        p = testdir.makepyfile("import xyz")
+        result = testdir.runpytest(*option._getcmdargs())
+        result.stdout.fnmatch_lines([
+            "*test_collect_fail.py E*",
+            ">   import xyz",
+            "E   ImportError: No module named xyz",
+            "*1 error*",
+        ])
+
+    def test_internalerror(self, testdir, linecomp):
+        modcol = testdir.getmodulecol("def test_one(): pass")
+        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
+        excinfo = py.test.raises(ValueError, "raise ValueError('hello')")
+        rep.pytest_internalerror(excinfo.getrepr())
+        linecomp.assert_contains_lines([
+            "INTERNALERROR> *raise ValueError*"
+        ])
+
+    def test_gwmanage_events(self, testdir, linecomp):
+        modcol = testdir.getmodulecol("""
+            def test_one():
+                pass
+        """, configargs=("-v",))
+
+        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
+        class gw1:
+            id = "X1"
+            spec = py.execnet.XSpec("popen")
+        class gw2:
+            id = "X2"
+            spec = py.execnet.XSpec("popen")
+        class rinfo:
+            version_info = (2, 5, 1, 'final', 0)
+            executable = "hello"
+            platform = "xyz"
+            cwd = "qwe"
+        
+        rep.pyexecnet_gwmanage_newgateway(gw1, rinfo)
+        linecomp.assert_contains_lines([
+            "X1*popen*xyz*2.5*"
+        ])
+
+        rep.pyexecnet_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2])
+        linecomp.assert_contains_lines([
+            "rsyncstart: hello -> X1, X2"
+        ])
+        rep.pyexecnet_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2])
+        linecomp.assert_contains_lines([
+            "rsyncfinish: hello -> X1, X2"
+        ])
+
+    def test_writeline(self, testdir, linecomp):
+        modcol = testdir.getmodulecol("def test_one(): pass")
+        stringio = py.std.cStringIO.StringIO()
+        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
+        rep.write_fspath_result(py.path.local("xy.py"), '.')
+        rep.write_line("hello world")
+        lines = linecomp.stringio.getvalue().split('\n')
+        assert not lines[0]
+        assert lines[1].endswith("xy.py .")
+        assert lines[2] == "hello world"
+
+    def test_looponfailreport(self, testdir, linecomp):
+        modcol = testdir.getmodulecol("""
+            def test_fail():
+                assert 0
+            def test_fail2():
+                raise ValueError()
+        """)
+        rep = TerminalReporter(modcol.config, file=linecomp.stringio)
+        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",
+            "*test_looponfailreport.py:4: ValueError*",
+            "*waiting*", 
+            "*%s*" % (modcol.config.topdir),
+        ])
+
+    def test_tb_option(self, testdir, option):
+        p = testdir.makepyfile("""
+            import py
+            def g():
+                raise IndexError
+            def test_func():
+                print 6*7
+                g()  # --calling--
+        """)
+        for tbopt in ["long", "short", "no"]:
+            print 'testing --tb=%s...' % tbopt
+            result = testdir.runpytest('--tb=%s' % tbopt)
+            s = result.stdout.str()
+            if tbopt == "long":
+                assert 'print 6*7' in s
+            else:
+                assert 'print 6*7' not in s
+            if tbopt != "no":
+                assert '--calling--' in s
+                assert 'IndexError' in s
+            else:
+                assert 'FAILURES' not in s
+                assert '--calling--' not in s
+                assert 'IndexError' not in s
+
+    def test_show_path_before_running_test(self, testdir, linecomp):
+        item = testdir.getitem("def test_func(): pass")
+        tr = TerminalReporter(item.config, file=linecomp.stringio)
+        item.config.pluginmanager.register(tr)
+        tr.config.hook.pytest_itemstart(item=item)
+        linecomp.assert_contains_lines([
+            "*test_show_path_before_running_test.py*"
+        ])
+
+    def test_itemreport_reportinfo(self, testdir, linecomp):
+        testdir.makeconftest("""
+            import py
+            class Function(py.test.collect.Function):
+                def reportinfo(self):
+                    return "ABCDE", 42, "custom"    
+        """)
+        item = testdir.getitem("def test_func(): pass")
+        tr = TerminalReporter(item.config, file=linecomp.stringio)
+        item.config.pluginmanager.register(tr)
+        tr.config.hook.pytest_itemstart(item=item)
+        linecomp.assert_contains_lines([
+            "*ABCDE "
+        ])
+        tr.config.option.verbose = True
+        tr.config.hook.pytest_itemstart(item=item)
+        linecomp.assert_contains_lines([
+            "*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 test_keyboard_interrupt_dist(self, testdir, option):
+        p = testdir.makepyfile("""
+            raise KeyboardInterrupt
+        """)
+        result = testdir.runpytest(*option._getcmdargs())
+        assert result.ret == 2
+        result.stdout.fnmatch_lines(['*KEYBOARD INTERRUPT*'])
+
+    @py.test.mark.nodist
+    def test_keyboard_interrupt(self, testdir, option):
+        p = testdir.makepyfile("""
+            def test_foobar():
+                assert 0
+            def test_spamegg():
+                import py; py.test.skip('skip me please!')
+            def test_interrupt_me():
+                raise KeyboardInterrupt   # simulating the user
+        """)
+
+        result = testdir.runpytest(*option._getcmdargs())
+        result.stdout.fnmatch_lines([
+            "    def test_foobar():",
+            ">       assert 0",
+            "E       assert 0",
+            "*_keyboard_interrupt.py:6: KeyboardInterrupt*", 
+        ])
+        if option.verbose:
+            result.stdout.fnmatch_lines([
+                "*raise KeyboardInterrupt   # simulating the user*",
+            ])
+        result.stdout.fnmatch_lines(['*KEYBOARD INTERRUPT*'])
+
+    def test_skip_reasons_folding(self):
+        class longrepr:
+            class reprcrash:
+                path = 'xyz'
+                lineno = 3
+                message = "justso"
+
+        ev1 = runner.CollectReport(None, None)
+        ev1.when = "execute"
+        ev1.skipped = True
+        ev1.longrepr = longrepr 
+        
+        ev2 = runner.ItemTestReport(None, excinfo=longrepr)
+        ev2.skipped = True
+
+        l = folded_skips([ev1, ev2])
+        assert len(l) == 1
+        num, fspath, lineno, reason = l[0]
+        assert num == 2
+        assert fspath == longrepr.reprcrash.path
+        assert lineno == longrepr.reprcrash.lineno
+        assert reason == longrepr.reprcrash.message
+
+class TestCollectonly:
+    def test_collectonly_basic(self, testdir, linecomp):
+        modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
+            def test_func():
+                pass
+        """)
+        rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
+        modcol.config.pluginmanager.register(rep)
+        indent = rep.indent
+        rep.config.hook.pytest_collectstart(collector=modcol)
+        linecomp.assert_contains_lines([
+           "<Module 'test_collectonly_basic.py'>"
+        ])
+        item = modcol.join("test_func")
+        rep.config.hook.pytest_itemstart(item=item)
+        linecomp.assert_contains_lines([
+           "  <Function 'test_func'>", 
+        ])
+        rep.config.hook.pytest_collectreport(
+            report=runner.CollectReport(modcol, [], excinfo=None))
+        assert rep.indent == indent 
+
+    def test_collectonly_skipped_module(self, testdir, linecomp):
+        modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
+            import py
+            py.test.skip("nomod")
+        """)
+        rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
+        modcol.config.pluginmanager.register(rep)
+        cols = list(testdir.genitems([modcol]))
+        assert len(cols) == 0
+        linecomp.assert_contains_lines("""
+            <Module 'test_collectonly_skipped_module.py'>
+              !!! Skipped: 'nomod' !!!
+        """)
+
+    def test_collectonly_failed_module(self, testdir, linecomp):
+        modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
+            raise ValueError(0)
+        """)
+        rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
+        modcol.config.pluginmanager.register(rep)
+        cols = list(testdir.genitems([modcol]))
+        assert len(cols) == 0
+        linecomp.assert_contains_lines("""
+            <Module 'test_collectonly_failed_module.py'>
+              !!! 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_collectonly_simple(self, testdir):
+        p = testdir.makepyfile("""
+            def test_func1():
+                pass
+            class TestClass:
+                def test_method(self):
+                    pass
+        """)
+        result = testdir.runpytest("--collectonly", p)
+        stderr = result.stderr.str().strip()
+        assert stderr.startswith("inserting into sys.path")
+        assert result.ret == 0
+        extra = result.stdout.fnmatch_lines(py.code.Source("""
+            <Module '*.py'>
+              <Function 'test_func1'*>
+              <Class 'TestClass'>
+                <Instance '()'>
+                  <Function 'test_method'*>
+        """).strip())
+
+    def test_collectonly_error(self, testdir):
+        p = testdir.makepyfile("import Errlkjqweqwe")
+        result = testdir.runpytest("--collectonly", p)
+        stderr = result.stderr.str().strip()
+        assert stderr.startswith("inserting into sys.path")
+        assert result.ret == 1
+        extra = result.stdout.fnmatch_lines(py.code.Source("""
+            <Module '*.py'>
+              *ImportError*
+            !!!*failures*!!!
+            *test_collectonly_error.py:1*
+        """).strip())
+
+
+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) 
+
+class TestFixtureReporting:
+    def test_setup_fixture_error(self, testdir):
+        p = testdir.makepyfile("""
+            def setup_function(function):
+                print "setup func"
+                assert 0
+            def test_nada():
+                pass
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at setup of test_nada*",
+            "*setup_function(function):*",
+            "*setup func*",
+            "*assert 0*",
+            "*1 error*",
+        ])
+        assert result.ret != 0
+    
+    def test_teardown_fixture_error(self, testdir):
+        p = testdir.makepyfile("""
+            def test_nada():
+                pass
+            def teardown_function(function):
+                print "teardown func"
+                assert 0
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at teardown*", 
+            "*teardown_function(function):*",
+            "*assert 0*",
+            "*Captured stdout*",
+            "*teardown func*",
+            "*1 passed*1 error*",
+        ])
+
+    def test_teardown_fixture_error_and_test_failure(self, testdir):
+        p = testdir.makepyfile("""
+            def test_fail():
+                assert 0, "failingfunc"
+
+            def teardown_function(function):
+                print "teardown func"
+                assert False
+        """)
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*ERROR at teardown of test_fail*", 
+            "*teardown_function(function):*",
+            "*assert False*",
+            "*Captured stdout*",
+            "*teardown func*",
+
+            "*test_fail*", 
+            "*def test_fail():",
+            "*failingfunc*",
+            "*1 failed*1 error*",
+         ])
+
+class TestTerminalFunctional:
+    def test_skipped_reasons(self, testdir):
+        testdir.makepyfile(
+            test_one="""
+                from conftest import doskip
+                def setup_function(func):
+                    doskip()
+                def test_func():
+                    pass
+                class TestClass:
+                    def test_method(self):
+                        doskip()
+           """,
+           test_two = """
+                from conftest import doskip
+                doskip()
+           """,
+           conftest = """
+                import py
+                def doskip():
+                    py.test.skip('test')
+            """
+        )
+        result = testdir.runpytest() 
+        extra = result.stdout.fnmatch_lines([
+            "*test_one.py ss",
+            "*test_two.py S",
+            "___* skipped test summary *_", 
+            "*conftest.py:3: *3* Skipped: 'test'", 
+        ])
+        assert result.ret == 0
+
+    def test_deselected(self, testdir):
+        testpath = testdir.makepyfile("""
+                def test_one():
+                    pass
+                def test_two():
+                    pass
+                def test_three():
+                    pass
+           """
+        )
+        result = testdir.runpytest("-k", "test_two:", testpath)
+        extra = result.stdout.fnmatch_lines([
+            "*test_deselected.py ..", 
+            "=* 1 test*deselected by 'test_two:'*=", 
+        ])
+        assert result.ret == 0
+
+    def test_no_skip_summary_if_failure(self, testdir):
+        testdir.makepyfile("""
+            import py
+            def test_ok():
+                pass
+            def test_fail():
+                assert 0
+            def test_skip():
+                py.test.skip("dontshow")
+        """)
+        result = testdir.runpytest() 
+        assert result.stdout.str().find("skip test summary") == -1
+        assert result.ret == 1
+
+    def test_passes(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_passes():
+                pass
+            class TestClass:
+                def test_method(self):
+                    pass
+        """)
+        old = p1.dirpath().chdir()
+        try:
+            result = testdir.runpytest()
+        finally:
+            old.chdir()
+        extra = result.stdout.fnmatch_lines([
+            "test_passes.py ..", 
+            "* 2 pass*",
+        ])
+        assert result.ret == 0
+
+    def test_header_trailer_info(self, testdir):
+        p1 = testdir.makepyfile("""
+            def test_passes():
+                pass
+        """)
+        result = testdir.runpytest()
+        verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
+        extra = result.stdout.fnmatch_lines([
+            "*===== test session starts ====*",
+            "python: platform %s -- Python %s*" %(
+                    py.std.sys.platform, verinfo), # , py.std.sys.executable),
+            "*test_header_trailer_info.py .",
+            "=* 1 passed in *.[0-9][0-9] seconds *=", 
+        ])
+
+    def test_traceback_failure(self, testdir):
+        p1 = testdir.makepyfile("""
+            def g():
+                return 2
+            def f(x):
+                assert x == g()
+            def test_onefails():
+                f(3)
+        """)
+        result = testdir.runpytest(p1)
+        result.stdout.fnmatch_lines([
+            "*test_traceback_failure.py F", 
+            "====* FAILURES *====",
+            "____*____", 
+            "",
+            "    def test_onefails():",
+            ">       f(3)",
+            "",
+            "*test_*.py:6: ",
+            "_ _ _ *",
+            #"",
+            "    def f(x):",
+            ">       assert x == g()",
+            "E       assert 3 == 2",
+            "E        +  where 2 = g()",
+            "",
+            "*test_traceback_failure.py:4: AssertionError"
+        ])
+
+
+    def test_showlocals(self, testdir): 
+        p1 = testdir.makepyfile("""
+            def test_showlocals():
+                x = 3
+                y = "x" * 5000 
+                assert 0
+        """)
+        result = testdir.runpytest(p1, '-l')
+        result.stdout.fnmatch_lines([
+            #"_ _ * Locals *", 
+            "x* = 3",
+            "y* = 'xxxxxx*"
+        ])
+
+    def test_verbose_reporting(self, testdir):
+        p1 = testdir.makepyfile("""
+            import py
+            def test_fail():
+                raise ValueError()
+            def test_pass():
+                pass
+            class TestClass:
+                def test_skip(self):
+                    py.test.skip("hello")
+            def test_gen():
+                def check(x):
+                    assert x == 1
+                yield check, 0
+        """)
+        result = testdir.runpytest(p1, '-v')
+        result.stdout.fnmatch_lines([
+            "*test_verbose_reporting.py:2: test_fail*FAIL*", 
+            "*test_verbose_reporting.py:4: test_pass*PASS*",
+            "*test_verbose_reporting.py:7: TestClass.test_skip*SKIP*",
+            "*test_verbose_reporting.py:10: test_gen*FAIL*",
+        ])
+        assert result.ret == 1
+        result = testdir.runpytest(p1, '-v', '-n 1')
+        result.stdout.fnmatch_lines([
+            "*FAIL*test_verbose_reporting.py:2: test_fail*", 
+        ])
+        assert result.ret == 1
+

Modified: py/trunk/py/test/pluginmanager.py
==============================================================================
--- py/trunk/py/test/pluginmanager.py	(original)
+++ py/trunk/py/test/pluginmanager.py	Tue Aug  4 19:45:30 2009
@@ -3,6 +3,7 @@
 """
 import py
 from py.__.test.plugin import hookspec
+from py.__.test.outcome import Skipped
 
 def check_old_use(mod, modname):
     clsname = modname[len('pytest_'):].capitalize() + "Plugin" 
@@ -96,10 +97,20 @@
         modname = canonical_importname(spec)
         if modname in self.impname2plugin:
             return
-        mod = importplugin(modname)
-        check_old_use(mod, modname) 
-        self.register(mod)
-        self.consider_module(mod)
+        try:
+            mod = importplugin(modname)
+        except KeyboardInterrupt:
+            raise
+        except Skipped, e:
+            self._warn("could not import plugin %r, reason: %r" %(
+                (modname, e.msg)))
+        else:
+            check_old_use(mod, modname) 
+            self.register(mod)
+            self.consider_module(mod)
+
+    def _warn(self, msg):
+        print "===WARNING=== %s" % (msg,)
 
     def _checkplugin(self, plugin):
         # =====================================================
@@ -163,7 +174,7 @@
         if hasattr(self, '_config'):
             self.call_plugin(plugin, "pytest_addoption", parser=self._config._parser)
             self.call_plugin(plugin, "pytest_configure", config=self._config)
-            #dic = self.call_plugin(plugin, "pytest_namespace", config=self._config)
+            #dic = self.call_plugin(plugin, "pytest_namespace")
             #self._updateext(dic)
 
     def call_plugin(self, plugin, methname, **kwargs):
@@ -180,7 +191,7 @@
         config.pluginmanager.register(self)
         self._config = config
         config.hook.pytest_configure(config=self._config)
-        for dic in config.hook.pytest_namespace(config=config) or []:
+        for dic in config.hook.pytest_namespace() or []:
             self._updateext(dic)
 
     def do_unconfigure(self, config):
@@ -243,3 +254,36 @@
         py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
     )
 
+if __name__ == "__main__":
+    import py.__.test.plugin
+    basedir = py.path.local(py.__.test.plugin.__file__).dirpath()
+    name2text = {}
+    for p in basedir.listdir("pytest_*"):
+        if p.ext == ".py" or (
+           p.check(dir=1) and p.join("__init__.py").check()):
+            impname = p.purebasename 
+            if impname.find("__") != -1:
+                continue
+            try:
+                plugin = importplugin(impname)
+            except (ImportError, py.__.test.outcome.Skipped):
+                name2text[impname] = "IMPORT ERROR"
+            else:
+                doc = plugin.__doc__ or ""
+                doc = doc.strip()
+                name2text[impname] = doc
+           
+    for name in sorted(name2text.keys()):
+        text = name2text[name]
+        if name[0] == "_":
+            continue
+        print "%-20s %s" % (name, text.split("\n")[0])
+
+        #text = py.std.textwrap.wrap(name2text[name], 
+        #    width = 80,
+        #    initial_indent="%s: " % name, 
+        #    replace_whitespace = False)
+        #for line in text:
+        #    print line
+     
+

Modified: py/trunk/py/test/pycollect.py
==============================================================================
--- py/trunk/py/test/pycollect.py	(original)
+++ py/trunk/py/test/pycollect.py	Tue Aug  4 19:45:30 2009
@@ -271,8 +271,9 @@
             traceback = ntraceback.filter()
         return traceback 
 
-    def repr_failure(self, excinfo, outerr):
-        return self._repr_failure_py(excinfo, outerr)
+    def repr_failure(self, excinfo, outerr=None):
+        assert outerr is None, "XXX outerr usage is deprecated"
+        return self._repr_failure_py(excinfo)
 
     shortfailurerepr = "F"
 

Modified: py/trunk/py/test/session.py
==============================================================================
--- py/trunk/py/test/session.py	(original)
+++ py/trunk/py/test/session.py	Tue Aug  4 19:45:30 2009
@@ -45,7 +45,7 @@
                 if rep.passed:
                     for x in self.genitems(rep.result, keywordexpr):
                         yield x 
-                self.config.hook.pytest_collectreport(rep=rep)
+                self.config.hook.pytest_collectreport(report=rep)
             if self.shouldstop:
                 break
 
@@ -79,19 +79,18 @@
         """ setup any neccessary resources ahead of the test run. """
         self.config.hook.pytest_sessionstart(session=self)
         
-    def pytest_runtest_logreport(self, rep):
-        if rep.failed:
+    def pytest_runtest_logreport(self, report):
+        if report.failed:
             self._testsfailed = True
             if self.config.option.exitfirst:
                 self.shouldstop = True
     pytest_collectreport = pytest_runtest_logreport
 
-    def sessionfinishes(self, exitstatus=0, excinfo=None):
+    def sessionfinishes(self, exitstatus):
         """ teardown any resources after a test run. """ 
         self.config.hook.pytest_sessionfinish(
             session=self, 
             exitstatus=exitstatus, 
-            excrepr=excinfo and excinfo.getrepr() or None
         )
 
     def getinitialitems(self, colitems):
@@ -114,13 +113,14 @@
                 if not self.config.option.collectonly: 
                     item.config.hook.pytest_runtest_protocol(item=item)
         except KeyboardInterrupt:
-            captured_excinfo = py.code.ExceptionInfo()
+            excinfo = py.code.ExceptionInfo()
+            self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
             exitstatus = outcome.EXIT_INTERRUPTED
         except:
-            captured_excinfo = py.code.ExceptionInfo()
+            excinfo = py.code.ExceptionInfo()
             self.config.pluginmanager.notify_exception(captured_excinfo)
             exitstatus = outcome.EXIT_INTERNALERROR
         if exitstatus == 0 and self._testsfailed:
             exitstatus = outcome.EXIT_TESTSFAILED
-        self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo)
+        self.sessionfinishes(exitstatus=exitstatus)
         return exitstatus

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 Aug  4 19:45:30 2009
@@ -54,40 +54,6 @@
         ])
         assert result.ret == 1
 
-    def test_collectonly_simple(self, testdir):
-        p = testdir.makepyfile("""
-            def test_func1():
-                pass
-            class TestClass:
-                def test_method(self):
-                    pass
-        """)
-        result = testdir.runpytest("--collectonly", p)
-        stderr = result.stderr.str().strip()
-        assert stderr.startswith("inserting into sys.path")
-        assert result.ret == 0
-        extra = result.stdout.fnmatch_lines(py.code.Source("""
-            <Module '*.py'>
-              <Function 'test_func1'*>
-              <Class 'TestClass'>
-                <Instance '()'>
-                  <Function 'test_method'*>
-        """).strip())
-
-    def test_collectonly_error(self, testdir):
-        p = testdir.makepyfile("import Errlkjqweqwe")
-        result = testdir.runpytest("--collectonly", p)
-        stderr = result.stderr.str().strip()
-        assert stderr.startswith("inserting into sys.path")
-        assert result.ret == 1
-        extra = result.stdout.fnmatch_lines(py.code.Source("""
-            <Module '*.py'>
-              *ImportError*
-            !!!*failures*!!!
-            *test_collectonly_error.py:1*
-        """).strip())
-
-
     def test_nested_import_error(self, testdir):
         p = testdir.makepyfile("""
                 import import_fails
@@ -101,378 +67,3 @@
             "E   ImportError: No module named does_not_work",
         ])
         assert result.ret == 1
-
-    def test_skipped_reasons(self, testdir):
-        testdir.makepyfile(
-            test_one="""
-                from conftest import doskip
-                def setup_function(func):
-                    doskip()
-                def test_func():
-                    pass
-                class TestClass:
-                    def test_method(self):
-                        doskip()
-           """,
-           test_two = """
-                from conftest import doskip
-                doskip()
-           """,
-           conftest = """
-                import py
-                def doskip():
-                    py.test.skip('test')
-            """
-        )
-        result = testdir.runpytest() 
-        extra = result.stdout.fnmatch_lines([
-            "*test_one.py ss",
-            "*test_two.py S",
-            "___* skipped test summary *_", 
-            "*conftest.py:3: *3* Skipped: 'test'", 
-        ])
-        assert result.ret == 0
-
-    def test_deselected(self, testdir):
-        testpath = testdir.makepyfile("""
-                def test_one():
-                    pass
-                def test_two():
-                    pass
-                def test_three():
-                    pass
-           """
-        )
-        result = testdir.runpytest("-k", "test_two:", testpath)
-        extra = result.stdout.fnmatch_lines([
-            "*test_deselected.py ..", 
-            "=* 1 test*deselected by 'test_two:'*=", 
-        ])
-        assert result.ret == 0
-
-    def test_no_skip_summary_if_failure(self, testdir):
-        testdir.makepyfile("""
-            import py
-            def test_ok():
-                pass
-            def test_fail():
-                assert 0
-            def test_skip():
-                py.test.skip("dontshow")
-        """)
-        result = testdir.runpytest() 
-        assert result.stdout.str().find("skip test summary") == -1
-        assert result.ret == 1
-
-    def test_passes(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_passes():
-                pass
-            class TestClass:
-                def test_method(self):
-                    pass
-        """)
-        old = p1.dirpath().chdir()
-        try:
-            result = testdir.runpytest()
-        finally:
-            old.chdir()
-        extra = result.stdout.fnmatch_lines([
-            "test_passes.py ..", 
-            "* 2 pass*",
-        ])
-        assert result.ret == 0
-
-    def test_header_trailer_info(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_passes():
-                pass
-        """)
-        result = testdir.runpytest()
-        verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
-        extra = result.stdout.fnmatch_lines([
-            "*===== test session starts ====*",
-            "python: platform %s -- Python %s*" %(
-                    py.std.sys.platform, verinfo), # , py.std.sys.executable),
-            "*test_header_trailer_info.py .",
-            "=* 1 passed in *.[0-9][0-9] seconds *=", 
-        ])
-
-    def test_traceback_failure(self, testdir):
-        p1 = testdir.makepyfile("""
-            def g():
-                return 2
-            def f(x):
-                assert x == g()
-            def test_onefails():
-                f(3)
-        """)
-        result = testdir.runpytest(p1)
-        result.stdout.fnmatch_lines([
-            "*test_traceback_failure.py F", 
-            "====* FAILURES *====",
-            "____*____", 
-            "",
-            "    def test_onefails():",
-            ">       f(3)",
-            "",
-            "*test_*.py:6: ",
-            "_ _ _ *",
-            #"",
-            "    def f(x):",
-            ">       assert x == g()",
-            "E       assert 3 == 2",
-            "E        +  where 2 = g()",
-            "",
-            "*test_traceback_failure.py:4: AssertionError"
-        ])
-
-    def test_capturing_outerr(self, testdir): 
-        p1 = testdir.makepyfile("""
-            import sys 
-            def test_capturing():
-                print 42
-                print >>sys.stderr, 23 
-            def test_capturing_error():
-                print 1
-                print >>sys.stderr, 2
-                raise ValueError
-        """)
-        result = testdir.runpytest(p1)
-        result.stdout.fnmatch_lines([
-            "*test_capturing_outerr.py .F", 
-            "====* FAILURES *====",
-            "____*____", 
-            "*test_capturing_outerr.py:8: ValueError",
-            "*--- Captured stdout ---*",
-            "1",
-            "*--- Captured stderr ---*",
-            "2",
-        ])
-
-    def test_showlocals(self, testdir): 
-        p1 = testdir.makepyfile("""
-            def test_showlocals():
-                x = 3
-                y = "x" * 5000 
-                assert 0
-        """)
-        result = testdir.runpytest(p1, '-l')
-        result.stdout.fnmatch_lines([
-            #"_ _ * Locals *", 
-            "x* = 3",
-            "y* = 'xxxxxx*"
-        ])
-
-    def test_verbose_reporting(self, testdir):
-        p1 = testdir.makepyfile("""
-            import py
-            def test_fail():
-                raise ValueError()
-            def test_pass():
-                pass
-            class TestClass:
-                def test_skip(self):
-                    py.test.skip("hello")
-            def test_gen():
-                def check(x):
-                    assert x == 1
-                yield check, 0
-        """)
-        result = testdir.runpytest(p1, '-v')
-        result.stdout.fnmatch_lines([
-            "*test_verbose_reporting.py:2: test_fail*FAIL*", 
-            "*test_verbose_reporting.py:4: test_pass*PASS*",
-            "*test_verbose_reporting.py:7: TestClass.test_skip*SKIP*",
-            "*test_verbose_reporting.py:10: test_gen*FAIL*",
-        ])
-        assert result.ret == 1
-        result = testdir.runpytest(p1, '-v', '-n 1')
-        result.stdout.fnmatch_lines([
-            "*FAIL*test_verbose_reporting.py:2: test_fail*", 
-        ])
-        assert result.ret == 1
-
-class TestDistribution:
-    def test_dist_conftest_options(self, testdir):
-        p1 = testdir.tmpdir.ensure("dir", 'p1.py')
-        p1.dirpath("__init__.py").write("")
-        p1.dirpath("conftest.py").write(py.code.Source("""
-            print "importing conftest", __file__
-            import py
-            Option = py.test.config.Option 
-            option = py.test.config.addoptions("someopt", 
-                Option('--someopt', action="store_true", dest="someopt", default=False))
-            dist_rsync_roots = ['../dir']
-            print "added options", option
-            print "config file seen from conftest", py.test.config
-        """))
-        p1.write(py.code.Source("""
-            import py, conftest
-            def test_1(): 
-                print "config from test_1", py.test.config
-                print "conftest from test_1", conftest.__file__
-                print "test_1: py.test.config.option.someopt", py.test.config.option.someopt
-                print "test_1: conftest", conftest
-                print "test_1: conftest.option.someopt", conftest.option.someopt
-                assert conftest.option.someopt 
-        """))
-        result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt')
-        assert result.ret == 0
-        extra = result.stdout.fnmatch_lines([
-            "*1 passed*", 
-        ])
-
-    def test_manytests_to_one_popen(self, testdir):
-        p1 = testdir.makepyfile("""
-                import py
-                def test_fail0():
-                    assert 0
-                def test_fail1():
-                    raise ValueError()
-                def test_ok():
-                    pass
-                def test_skip():
-                    py.test.skip("hello")
-            """, 
-        )
-        result = testdir.runpytest(p1, '-d', '--tx=popen', '--tx=popen')
-        result.stdout.fnmatch_lines([
-            "*1*popen*Python*",
-            "*2*popen*Python*",
-            "*2 failed, 1 passed, 1 skipped*",
-        ])
-        assert result.ret == 1
-
-    def test_dist_conftest_specified(self, testdir):
-        p1 = testdir.makepyfile("""
-                import py
-                def test_fail0():
-                    assert 0
-                def test_fail1():
-                    raise ValueError()
-                def test_ok():
-                    pass
-                def test_skip():
-                    py.test.skip("hello")
-            """, 
-        )
-        testdir.makeconftest("""
-            pytest_option_tx = 'popen popen popen'.split()
-        """)
-        result = testdir.runpytest(p1, '-d')
-        result.stdout.fnmatch_lines([
-            "*1*popen*Python*",
-            "*2*popen*Python*",
-            "*3*popen*Python*",
-            "*2 failed, 1 passed, 1 skipped*",
-        ])
-        assert result.ret == 1
-
-    def test_dist_tests_with_crash(self, testdir):
-        if not hasattr(py.std.os, 'kill'):
-            py.test.skip("no os.kill")
-        
-        p1 = testdir.makepyfile("""
-                import py
-                def test_fail0():
-                    assert 0
-                def test_fail1():
-                    raise ValueError()
-                def test_ok():
-                    pass
-                def test_skip():
-                    py.test.skip("hello")
-                def test_crash():
-                    import time
-                    import os
-                    time.sleep(0.5)
-                    os.kill(os.getpid(), 15)
-            """
-        )
-        result = testdir.runpytest(p1, '-d', '--tx=3*popen')
-        result.stdout.fnmatch_lines([
-            "*popen*Python*",
-            "*popen*Python*",
-            "*popen*Python*",
-            "*node down*",
-            "*3 failed, 1 passed, 1 skipped*"
-        ])
-        assert result.ret == 1
-
-    def test_distribution_rsyncdirs_example(self, testdir):
-        source = testdir.mkdir("source")
-        dest = testdir.mkdir("dest")
-        subdir = source.mkdir("example_pkg")
-        subdir.ensure("__init__.py")
-        p = subdir.join("test_one.py")
-        p.write("def test_5(): assert not __file__.startswith(%r)" % str(p))
-        result = testdir.runpytest("-d", "--rsyncdir=%(subdir)s" % locals(), 
-            "--tx=popen//chdir=%(dest)s" % locals(), p)
-        assert result.ret == 0
-        result.stdout.fnmatch_lines([
-            "*1* *popen*platform*",
-            #"RSyncStart: [G1]",
-            #"RSyncFinished: [G1]",
-            "*1 passed*"
-        ])
-        assert dest.join(subdir.basename).check(dir=1)
-
-    def test_dist_each(self, testdir):
-        interpreters = []
-        for name in ("python2.4", "python2.5"):
-            interp = py.path.local.sysfind(name)
-            if interp is None:
-                py.test.skip("%s not found" % name)
-            interpreters.append(interp)
-
-        testdir.makepyfile(__init__="", test_one="""
-            import sys
-            def test_hello():
-                print "%s...%s" % sys.version_info[:2]
-                assert 0
-        """)
-        args = ["--dist=each"]
-        args += ["--tx", "popen//python=%s" % interpreters[0]]
-        args += ["--tx", "popen//python=%s" % interpreters[1]]
-        result = testdir.runpytest(*args)
-        result.stdout.fnmatch_lines(["2...4"])
-        result.stdout.fnmatch_lines(["2...5"])
-
-
-class TestInteractive:
-    def test_simple_looponfail_interaction(self, testdir):
-        p1 = testdir.makepyfile("""
-            def test_1():
-                assert 1 == 0 
-        """)
-        p1.setmtime(p1.mtime() - 50.0)  
-        child = testdir.spawn_pytest("--looponfail %s" % p1)
-        child.expect("assert 1 == 0")
-        child.expect("test_simple_looponfail_interaction.py:")
-        child.expect("1 failed")
-        child.expect("waiting for changes")
-        p1.write(py.code.Source("""
-            def test_1():
-                assert 1 == 1
-        """))
-        child.expect("MODIFIED.*test_simple_looponfail_interaction.py", timeout=4.0)
-        child.expect("1 passed", timeout=5.0)
-        child.kill(15)
-       
-class TestKeyboardInterrupt: 
-    def test_raised_in_testfunction(self, testdir):
-        p1 = testdir.makepyfile("""
-            import py
-            def test_fail():
-                raise ValueError()
-            def test_inter():
-                raise KeyboardInterrupt()
-        """)
-        result = testdir.runpytest(p1)
-        result.stdout.fnmatch_lines([
-            #"*test_inter() INTERRUPTED",
-            "*KEYBOARD INTERRUPT*",
-            "*1 failed*", 
-        ])
-

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 Aug  4 19:45:30 2009
@@ -212,38 +212,6 @@
         for col in col.listchain():
             assert col.config is config 
 
-
-class TestGuardedCall:
-    def test_guardedcall_ok(self, testdir):
-        config = testdir.parseconfig()
-        def myfunc(x):
-            print x
-            print >>py.std.sys.stderr, "hello"
-            return 7
-        call = config.guardedcall(lambda: myfunc(3))
-        assert call.excinfo is None
-        assert call.result == 7
-        assert call.stdout.startswith("3")
-        assert call.stderr.startswith("hello")
-
-    def test_guardedcall_fail(self, testdir):
-        config = testdir.parseconfig()
-        def myfunc(x):
-            print x
-            raise ValueError(17)
-        call = config.guardedcall(lambda: myfunc(3))
-        assert call.excinfo 
-        assert call.excinfo.type == ValueError 
-        assert not hasattr(call, 'result')
-        assert call.stdout.startswith("3")
-        assert not call.stderr 
-
-    def test_guardedcall_keyboardinterrupt(self, testdir):
-        config = testdir.parseconfig()
-        def myfunc():
-            raise KeyboardInterrupt
-        py.test.raises(KeyboardInterrupt, config.guardedcall, myfunc)
-
 class TestOptionEffects:
     def test_boxed_option_default(self, testdir):
         tmpdir = testdir.tmpdir.ensure("subdir", dir=1)
@@ -258,29 +226,6 @@
         config = py.test.config._reparse([testdir.tmpdir])
         assert not config.option.boxed
 
-    def test_config_iocapturing(self, testdir):
-        config = testdir.parseconfig(testdir.tmpdir)
-        assert config.getvalue("iocapture")
-        tmpdir = testdir.tmpdir.ensure("sub-with-conftest", dir=1)
-        tmpdir.join("conftest.py").write(py.code.Source("""
-            pytest_option_iocapture = "no"
-        """))
-        config = py.test.config._reparse([tmpdir])
-        assert config.getvalue("iocapture") == "no"
-        capture = config._getcapture()
-        assert isinstance(capture, py.io.StdCapture)
-        assert not capture._out
-        assert not capture._err
-        assert not capture._in
-        assert isinstance(capture, py.io.StdCapture)
-        for opt, cls in (("sys", py.io.StdCapture),  
-                         ("fd", py.io.StdCaptureFD), 
-                        ):
-            config.option.iocapture = opt
-            capture = config._getcapture()
-            assert isinstance(capture, cls) 
-
-
 class TestConfig_gettopdir:
     def test_gettopdir(self, testdir):
         from py.__.test.config import gettopdir

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 Aug  4 19:45:30 2009
@@ -152,6 +152,7 @@
             def test_func(something): pass
         """)
         req = funcargs.FuncargRequest(item)
+        py.test.raises(req.Error, req.getfuncargvalue, "notexists")
         val = req.getfuncargvalue("something") 
         assert val == 1
         val = req.getfuncargvalue("something") 
@@ -181,6 +182,21 @@
         print ss.stack
         assert teardownlist == [1]
 
+    def test_request_addfinalizer_partial_setup_failure(self, testdir):
+        p = testdir.makepyfile("""
+            l = []
+            def pytest_funcarg__something(request): 
+                request.addfinalizer(lambda: l.append(None))
+            def test_func(something, missingarg): 
+                pass
+            def test_second():
+                assert len(l) == 1
+        """)
+        result = testdir.runpytest(p)
+        assert result.stdout.fnmatch_lines([
+            "*1 passed*1 error*"
+            ])
+
     def test_request_getmodulepath(self, testdir):
         modcol = testdir.getmodulecol("def test_somefunc(): pass")
         item, = testdir.genitems([modcol])

Added: py/trunk/py/test/testing/test_install.py
==============================================================================
--- (empty file)
+++ py/trunk/py/test/testing/test_install.py	Tue Aug  4 19:45:30 2009
@@ -0,0 +1,16 @@
+import py
+
+def test_make_sdist_and_run_it(capfd, py_setup, venv):
+    try:
+        sdist = py_setup.make_sdist(venv.path)
+        venv.easy_install(str(sdist)) 
+        gw = venv.makegateway()
+        ch = gw.remote_exec("import py ; channel.send(py.__version__)")
+        version = ch.receive()
+        assert version == py.__version__
+    except KeyboardInterrupt:
+        raise
+    except:
+        print capfd.readouterr()
+        raise
+    capfd.close()

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 Aug  4 19:45:30 2009
@@ -26,8 +26,10 @@
         py.test.raises(SyntaxError, "py.test.importorskip('x y z')")
         py.test.raises(SyntaxError, "py.test.importorskip('x=y')")
         path = py.test.importorskip("py", minversion=".".join(py.__version__))
+        mod = py.std.new.module("hello123")
+        mod.__version__ = "1.3" 
         py.test.raises(Skipped, """
-            py.test.importorskip("py", minversion="5.0")
+            py.test.importorskip("hello123", minversion="5.0")
         """)
     except Skipped:
         print py.code.ExceptionInfo()

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 Aug  4 19:45:30 2009
@@ -1,13 +1,11 @@
 import py
 from py.__.test import parseopt 
 
-pytest_plugins = 'pytest_iocapture'
-
 class TestParser:
     def test_init(self, capsys):
         parser = parseopt.Parser(usage="xyz")
         py.test.raises(SystemExit, 'parser.parse(["-h"])')
-        out, err = capsys.reset()
+        out, err = capsys.readouterr()
         assert out.find("xyz") != -1
 
     def test_group_add_and_get(self):

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 Aug  4 19:45:30 2009
@@ -7,12 +7,27 @@
         monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule')
         py.test.raises(ImportError, "pluginmanager.consider_env()")
 
-    def test_preparse_args(self, monkeypatch):
+    def test_preparse_args(self):
         pluginmanager = PluginManager()
         py.test.raises(ImportError, """
             pluginmanager.consider_preparse(["xyz", "-p", "hello123"])
         """)
 
+    def test_plugin_skip(self, testdir, monkeypatch):
+        testdir.makepyfile(pytest_skipping1="""
+            import py
+            py.test.skip("hello")
+        """)
+        result = testdir.runpytest("-p", "skipping1")
+        result.stdout.fnmatch_lines([
+            "*WARNING*could not import plugin*skipping1*hello*"
+        ])
+        monkeypatch.setenv("PYTEST_PLUGINS", "skipping1")
+        result = testdir.runpytest()
+        result.stdout.fnmatch_lines([
+            "*WARNING*could not import plugin*skipping1*hello*"
+        ])
+
     def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
         pluginmanager = PluginManager()
         testdir.syspathinsert()
@@ -160,7 +175,7 @@
 
     def test_do_ext_namespace(self, testdir):
         testdir.makeconftest("""
-            def pytest_namespace(config):
+            def pytest_namespace():
                 return {'hello': 'world'}
         """)
         p = testdir.makepyfile("""
@@ -221,30 +236,6 @@
         assert not pluginmanager.listattr("hello")
         assert pluginmanager.listattr("x") == [42]
 
-    @py.test.xfail # setup call methods
-    def test_call_setup_participants(self, testdir):
-        testdir.makepyfile(
-            conftest="""
-                import py
-                def pytest_method(self, x):
-                    return x+1
-                pytest_plugin = "pytest_someplugin",
-            """
-        )
-        testdir.makepyfile(pytest_someplugin="""
-                def pytest_method(self, x):
-                    return x+1
-        """)
-        modcol = testdir.getmodulecol("""
-            def pytest_method(x):
-                return x+0 
-        """)
-        l = []
-        call = modcol.config.pluginmanager.setupcall(modcol, "pytest_method", 1)
-        assert len(call.methods) == 3
-        results = call.execute()
-        assert results == [1,2,2]
-
 def test_collectattr():
     class A:
         def pytest_hello(self):

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 Aug  4 19:45:30 2009
@@ -39,6 +39,7 @@
         modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
         py.test.raises(ImportError, "modcol.obj")
 
+class TestDisabled:
     def test_disabled_module(self, testdir):
         modcol = testdir.getmodulecol("""
             disabled = True
@@ -51,7 +52,6 @@
         assert len(l) == 1
         py.test.raises(Skipped, "modcol.setup()")
 
-class TestClass:
     def test_disabled_class(self, testdir):
         modcol = testdir.getmodulecol("""
             class TestClass:
@@ -67,6 +67,15 @@
         assert len(l) == 1
         py.test.raises(Skipped, "modcol.setup()")
 
+    def test_disabled_class_functional(self, testdir):
+        reprec = testdir.inline_runsource("""
+            class TestSimpleClassSetup:
+                disabled = True
+                def test_classlevel(self): pass
+                def test_classlevel2(self): pass
+        """)
+        reprec.assertoutcome(skipped=2)
+
 class TestGenerator:
     def test_generative_functions(self, testdir): 
         modcol = testdir.getmodulecol("""

Deleted: /py/trunk/py/test/testing/test_setup_functional.py
==============================================================================
--- /py/trunk/py/test/testing/test_setup_functional.py	Tue Aug  4 19:45:30 2009
+++ (empty file)
@@ -1,141 +0,0 @@
-#
-# test correct setup/teardowns at
-# module, class, and instance level
-
-def test_module_and_function_setup(testdir):
-    reprec = testdir.inline_runsource(""" 
-        modlevel = []
-        def setup_module(module):
-            assert not modlevel
-            module.modlevel.append(42)
-
-        def teardown_module(module):
-            modlevel.pop()
-
-        def setup_function(function):
-            function.answer = 17
-
-        def teardown_function(function):
-            del function.answer
-
-        def test_modlevel():
-            assert modlevel[0] == 42
-            assert test_modlevel.answer == 17
-
-        class TestFromClass: 
-            def test_module(self):
-                assert modlevel[0] == 42
-                assert not hasattr(test_modlevel, 'answer') 
-    """)
-    rep = reprec.matchreport("test_modlevel") 
-    assert rep.passed 
-    rep = reprec.matchreport("test_module") 
-    assert rep.passed 
-
-def test_class_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSimpleClassSetup:
-            clslevel = []
-            def setup_class(cls):
-                cls.clslevel.append(23)
-
-            def teardown_class(cls):
-                cls.clslevel.pop()
-
-            def test_classlevel(self):
-                assert self.clslevel[0] == 23
-
-        class TestInheritedClassSetupStillWorks(TestSimpleClassSetup):
-            def test_classlevel_anothertime(self):
-                assert self.clslevel == [23]
-
-        def test_cleanup():
-            assert not TestSimpleClassSetup.clslevel 
-            assert not TestInheritedClassSetupStillWorks.clslevel
-    """)
-    reprec.assertoutcome(passed=1+2+1)
-
-def test_method_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSetupMethod:
-            def setup_method(self, meth):
-                self.methsetup = meth 
-            def teardown_method(self, meth):
-                del self.methsetup 
-
-            def test_some(self):
-                assert self.methsetup == self.test_some 
-
-            def test_other(self):
-                assert self.methsetup == self.test_other
-    """)
-    reprec.assertoutcome(passed=2)
-       
-def test_method_generator_setup(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSetupTeardownOnInstance: 
-            def setup_class(cls):
-                cls.classsetup = True 
-
-            def setup_method(self, method):
-                self.methsetup = method 
-
-            def test_generate(self):
-                assert self.classsetup 
-                assert self.methsetup == self.test_generate 
-                yield self.generated, 5
-                yield self.generated, 2
-
-            def generated(self, value):
-                assert self.classsetup 
-                assert self.methsetup == self.test_generate 
-                assert value == 5
-    """)
-    reprec.assertoutcome(passed=1, failed=1)
-
-def test_func_generator_setup(testdir):
-    reprec = testdir.inline_runsource(""" 
-        import sys
-
-        def setup_module(mod):
-            print "setup_module"
-            mod.x = []
-        
-        def setup_function(fun):
-            print "setup_function"
-            x.append(1)
-
-        def teardown_function(fun):
-            print "teardown_function" 
-            x.pop()
-        
-        def test_one():
-            assert x == [1]
-            def check():
-                print "check" 
-                print >>sys.stderr, "e" 
-                assert x == [1]
-            yield check 
-            assert x == [1]
-    """)
-    rep = reprec.matchreport("test_one", names="pytest_runtest_logreport") 
-    assert rep.passed 
-        
-def test_method_setup_uses_fresh_instances(testdir):
-    reprec = testdir.inline_runsource("""
-        class TestSelfState1: 
-            def __init__(self):
-                self.hello = 42
-            def test_hello(self):
-                self.world = 23
-            def test_afterhello(self):
-                assert not hasattr(self, 'world')
-                assert self.hello == 42
-        class TestSelfState2: 
-            def test_hello(self):
-                self.world = 10
-            def test_world(self):
-                assert not hasattr(self, 'world')
-    """)
-    reprec.assertoutcome(passed=4, failed=0)
-

Modified: py/trunk/setup.py
==============================================================================
--- py/trunk/setup.py	(original)
+++ py/trunk/setup.py	Tue Aug  4 19:45:30 2009
@@ -1,14 +1,9 @@
 """
-autogenerated by gensetup.py
-setup file for 'py' package based on:
+py lib / py.test setup.py file, autogenerated by gensetup.py
         
-revision: 1181:8a8203ee5eb85837b6a40d95d861af42008d1a4c
-         
 """
 import os, sys
         
-import ez_setup
-ez_setup.use_setuptools()
 from setuptools import setup
             
 long_description = """
@@ -30,12 +25,13 @@
 
 
 """
+trunk = None
 def main():
     setup(
         name='py',
         description='py.test and pylib: advanced testing tool and networking lib',
         long_description = long_description, 
-        version='1.0.0b5', 
+        version= trunk or '1.0.0', 
         url='http://pylib.org', 
         license='MIT license',
         platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], 
@@ -49,7 +45,7 @@
                                           'py.svnwcrevert = py.cmdline:pysvnwcrevert',
                                           'py.test = py.cmdline:pytest',
                                           'py.which = py.cmdline:pywhich']},
-        classifiers=['Development Status :: 4 - Beta',
+        classifiers=['Development Status :: 5 - Production/Stable',
                      'Intended Audience :: Developers',
                      'License :: OSI Approved :: MIT License',
                      'Operating System :: POSIX',



More information about the pytest-commit mailing list