From commits-noreply at bitbucket.org Mon Aug 3 08:30:43 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 03 Aug 2009 06:30:43 -0000 Subject: [py-svn] commit/py-3k: yangyan5: make a simple test(test_test) which will fail under python3.1 Message-ID: <20090803063043.31361.43806@domU-12-31-39-00-D4-C1.compute-1.internal> 1 new changeset in py-3k: http://www.bitbucket.org/yangyan5/py-3k/changeset/5c0cfbe9469e/ changeset: r1165:5c0cfbe9469e user: yangyan5 date: 2009-08-03 08:30:27 summary: make a simple test(test_test) which will fail under python3.1 affected #: 5 files (693 bytes) Repository URL: http://bitbucket.org/yangyan5/py-3k/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From hpk at codespeak.net Tue Aug 4 19:45:35 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 4 Aug 2009 19:45:35 +0200 (CEST) Subject: [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 Message-ID: <20090804174535.0D84516804C@codespeak.net> 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 = ('\n') + googlefragment = """ + + +""" 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("", self.googlefragment + "") 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__ = ") 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 "" % ( + 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 "" %(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 "" %(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 "" %(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 "" % 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([ - "" - ]) - item = modcol.join("test_func") - rep.config.hook.pytest_itemstart(item=item) - linecomp.assert_contains_lines([ - " ", - ]) - 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(""" - - !!! 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(""" - - !!! 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([ + "" + ]) + item = modcol.join("test_func") + rep.config.hook.pytest_itemstart(item=item) + linecomp.assert_contains_lines([ + " ", + ]) + 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(""" + + !!! 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(""" + + !!! 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(""" + + + + + + """).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(""" + + *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(""" - - - - - - """).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(""" - - *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', From hpk at codespeak.net Tue Aug 4 19:47:31 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 4 Aug 2009 19:47:31 +0200 (CEST) Subject: [py-svn] r66741 - py/dist Message-ID: <20090804174731.D586916804C@codespeak.net> Author: hpk Date: Tue Aug 4 19:47:31 2009 New Revision: 66741 Added: py/dist/ - copied from r66740, py/trunk/ Log: copying svn trunk to dist From hpk at codespeak.net Tue Aug 4 19:49:15 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Tue, 4 Aug 2009 19:49:15 +0200 (CEST) Subject: [py-svn] r66742 - py/trunk Message-ID: <20090804174915.4056316804C@codespeak.net> Author: hpk Date: Tue Aug 4 19:49:14 2009 New Revision: 66742 Removed: py/trunk/ Log: trunk now lives at mercurial, only "dist" stays From hpk at codespeak.net Wed Aug 19 18:37:58 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Wed, 19 Aug 2009 18:37:58 +0200 (CEST) Subject: [py-svn] r66995 - in py/dist: . doc doc/announce doc/test doc/test/plugin example/assertion py py/bin py/code py/code/testing py/execnet py/io py/io/testing py/misc/testing py/path/svn py/path/svn/testing py/test py/test/dist py/test/dist/testing py/test/plugin py/test/testing Message-ID: <20090819163758.2C4E4168012@codespeak.net> Author: hpk Date: Wed Aug 19 18:37:53 2009 New Revision: 66995 Added: py/dist/doc/announce/release-1.0.1.txt py/dist/doc/faq.txt py/dist/doc/test/config.html py/dist/doc/test/customize.txt py/dist/doc/test/index.txt py/dist/doc/test/mission.txt py/dist/doc/test/plugin/helpconfig.txt py/dist/doc/test/plugin/nose.txt py/dist/doc/test/talks.txt py/dist/doc/test/test.html py/dist/py/misc/testing/test_install.py py/dist/py/test/plugin/pytest_helpconfig.py py/dist/py/test/plugin/pytest_nose.py py/dist/py/test/plugin/test_pytest_helpconfig.py py/dist/py/test/plugin/test_pytest_nose.py Removed: py/dist/doc/test/config.txt py/dist/doc/test/extend.txt py/dist/doc/test/test.txt py/dist/py/bin/_genscripts.py py/dist/py/bin/gendoc.py py/dist/py/test/testing/test_install.py Modified: py/dist/CHANGELOG py/dist/MANIFEST py/dist/doc/bin.txt py/dist/doc/confrest.py py/dist/doc/conftest.py py/dist/doc/contact.txt py/dist/doc/download.txt py/dist/doc/index.txt py/dist/doc/test/attic.txt py/dist/doc/test/features.txt py/dist/doc/test/funcargs.txt py/dist/doc/test/plugin/ (props changed) py/dist/doc/test/plugin/capture.txt py/dist/doc/test/plugin/doctest.txt py/dist/doc/test/plugin/figleaf.txt py/dist/doc/test/plugin/hooklog.txt py/dist/doc/test/plugin/hookspec.txt py/dist/doc/test/plugin/index.txt py/dist/doc/test/plugin/keyword.txt py/dist/doc/test/plugin/links.txt py/dist/doc/test/plugin/monkeypatch.txt py/dist/doc/test/plugin/pastebin.txt py/dist/doc/test/plugin/pdb.txt py/dist/doc/test/plugin/recwarn.txt py/dist/doc/test/plugin/restdoc.txt py/dist/doc/test/plugin/resultlog.txt py/dist/doc/test/plugin/terminal.txt py/dist/doc/test/plugin/unittest.txt py/dist/doc/test/plugin/xfail.txt py/dist/doc/test/quickstart.txt py/dist/doc/test/xunit_setup.txt py/dist/doc/xml.txt py/dist/example/assertion/failure_demo.py py/dist/example/assertion/test_failures.py py/dist/py/__init__.py py/dist/py/_com.py py/dist/py/code/excinfo.py py/dist/py/code/testing/test_excinfo.py py/dist/py/code/testing/test_source.py py/dist/py/execnet/gateway.py py/dist/py/execnet/gwmanage.py py/dist/py/io/stdcapture.py py/dist/py/io/terminalwriter.py py/dist/py/io/testing/test_terminalwriter.py py/dist/py/misc/testing/test_com.py py/dist/py/path/svn/testing/test_wccommand.py py/dist/py/path/svn/wccommand.py py/dist/py/test/collect.py py/dist/py/test/config.py py/dist/py/test/defaultconftest.py py/dist/py/test/dist/dsession.py py/dist/py/test/dist/testing/acceptance_test.py py/dist/py/test/dist/txnode.py py/dist/py/test/plugin/conftest.py py/dist/py/test/plugin/hookspec.py py/dist/py/test/plugin/pytest__pytest.py py/dist/py/test/plugin/pytest_capture.py py/dist/py/test/plugin/pytest_default.py py/dist/py/test/plugin/pytest_execnetcleanup.py py/dist/py/test/plugin/pytest_hooklog.py py/dist/py/test/plugin/pytest_monkeypatch.py py/dist/py/test/plugin/pytest_pastebin.py py/dist/py/test/plugin/pytest_runner.py py/dist/py/test/plugin/pytest_terminal.py py/dist/py/test/plugin/pytest_unittest.py py/dist/py/test/plugin/pytest_xfail.py py/dist/py/test/plugin/test_pytest_capture.py py/dist/py/test/plugin/test_pytest_runner.py py/dist/py/test/plugin/test_pytest_runner_xunit.py py/dist/py/test/plugin/test_pytest_terminal.py py/dist/py/test/pluginmanager.py py/dist/py/test/pycollect.py py/dist/py/test/testing/acceptance_test.py py/dist/py/test/testing/test_collect.py py/dist/py/test/testing/test_config.py py/dist/py/test/testing/test_pluginmanager.py py/dist/py/test/testing/test_pycollect.py py/dist/setup.py Log: trying to copy over the 1.0.1 snapshot Modified: py/dist/CHANGELOG ============================================================================== --- py/dist/CHANGELOG (original) +++ py/dist/CHANGELOG Wed Aug 19 18:37:53 2009 @@ -1,3 +1,37 @@ +Changes between 1.0.0 and 1.0.1 +===================================== + +* added a 'pytest_nose' plugin which handles nose.SkipTest, + nose-style function/method/generator setup/teardown and + tries to report functions correctly. + +* capturing of unicode writes or encoded strings to sys.stdout/err + work better, also terminalwriting was adapted and somewhat + unified between windows and linux. + +* improved documentation layout and content a lot + +* added a "--help-config" option to show conftest.py / ENV-var names for + all longopt cmdline options, and some special conftest.py variables. + renamed 'conf_capture' conftest setting to 'option_capture' accordingly. + +* fix issue #27: better reporting on non-collectable items given on commandline + (e.g. pyc files) + +* fix issue #33: added --version flag (thanks Benjamin Peterson) + +* fix issue #32: adding support for "incomplete" paths to wcpath.status() + +* "Test" prefixed classes are *not* collected by default anymore if they + have an __init__ method + +* monkeypatch setenv() now accepts a "prepend" parameter + +* improved reporting of collection error tracebacks + +* simplified multicall mechanism and plugin architecture, + renamed some internal methods and argnames + Changes between 1.0.0b9 and 1.0.0 ===================================== Modified: py/dist/MANIFEST ============================================================================== --- py/dist/MANIFEST (original) +++ py/dist/MANIFEST Wed Aug 19 18:37:53 2009 @@ -3,9 +3,15 @@ MANIFEST README.txt _findpy.py +bin-for-dist/all-plat.sh +bin-for-dist/gendoc.py +bin-for-dist/genscripts.py +bin-for-dist/gensetup.py +bin-for-dist/makepluginlist.py doc/announce/release-0.9.0.txt doc/announce/release-0.9.2.txt doc/announce/release-1.0.0.txt +doc/announce/release-1.0.1.txt doc/announce/releases.txt doc/bin.txt doc/code.txt @@ -14,6 +20,7 @@ doc/contact.txt doc/download.txt doc/execnet.txt +doc/faq.txt doc/img/pylib.png doc/index.txt doc/io.txt @@ -22,21 +29,26 @@ doc/path.txt doc/style.css doc/test/attic.txt -doc/test/config.txt +doc/test/config.html +doc/test/customize.txt doc/test/dist.txt doc/test/examples.txt -doc/test/extend.txt +doc/test/extend.html doc/test/features.txt doc/test/funcargs.txt +doc/test/index.txt +doc/test/mission.txt doc/test/plugin/capture.txt doc/test/plugin/doctest.txt doc/test/plugin/figleaf.txt +doc/test/plugin/helpconfig.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/nose.txt doc/test/plugin/oejskit.txt doc/test/plugin/pastebin.txt doc/test/plugin/pdb.txt @@ -48,7 +60,7 @@ doc/test/plugin/xfail.txt doc/test/quickstart.txt doc/test/talks.txt -doc/test/test.txt +doc/test/test.html doc/test/xunit_setup.txt doc/xml.txt example/assertion/failure_demo.py @@ -80,13 +92,10 @@ example/genhtml.py example/genhtmlcss.py example/genxml.py -makepluginlist.py py/LICENSE py/__init__.py py/_com.py py/bin/_findpy.py -py/bin/_genscripts.py -py/bin/gendoc.py py/bin/py.cleanup py/bin/py.countloc py/bin/py.lookup @@ -239,6 +248,7 @@ py/misc/testing/test_com.py py/misc/testing/test_error.py py/misc/testing/test_initpkg.py +py/misc/testing/test_install.py py/misc/testing/test_std.py py/misc/testing/test_svnlook.py py/misc/testing/test_terminal.py @@ -342,9 +352,11 @@ py/test/plugin/pytest_doctest.py py/test/plugin/pytest_execnetcleanup.py py/test/plugin/pytest_figleaf.py +py/test/plugin/pytest_helpconfig.py py/test/plugin/pytest_hooklog.py py/test/plugin/pytest_keyword.py py/test/plugin/pytest_monkeypatch.py +py/test/plugin/pytest_nose.py py/test/plugin/pytest_pastebin.py py/test/plugin/pytest_pdb.py py/test/plugin/pytest_pylint.py @@ -358,6 +370,8 @@ py/test/plugin/pytest_unittest.py py/test/plugin/pytest_xfail.py py/test/plugin/test_pytest_capture.py +py/test/plugin/test_pytest_helpconfig.py +py/test/plugin/test_pytest_nose.py py/test/plugin/test_pytest_runner.py py/test/plugin/test_pytest_runner_xunit.py py/test/plugin/test_pytest_terminal.py @@ -379,7 +393,6 @@ 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 Added: py/dist/doc/announce/release-1.0.1.txt ============================================================================== --- (empty file) +++ py/dist/doc/announce/release-1.0.1.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,48 @@ +1.0.1: improved reporting, nose/unittest.py support, bug fixes +----------------------------------------------------------------------- + +This is a bugfix release of pylib/py.test also coming with: + +* improved documentation, improved navigation +* test failure reporting improvements +* support for directly running existing nose/unittest.py style tests + +visit here for more info, including quickstart and tutorials: + + http://pytest.org and http://pylib.org + + +Changelog 1.0.0 to 1.0.1 +------------------------ + +* added a default 'pytest_nose' plugin which handles nose.SkipTest, + nose-style function/method/generator setup/teardown and + tries to report functions correctly. + +* improved documentation, better navigation: see http://pytest.org + +* added a "--help-config" option to show conftest.py / ENV-var names for + all longopt cmdline options, and some special conftest.py variables. + renamed 'conf_capture' conftest setting to 'option_capture' accordingly. + +* unicode fixes: capturing and unicode writes to sys.stdout + (through e.g a print statement) now work within tests, + they are encoded as "utf8" by default, also terminalwriting + was adapted and somewhat unified between windows and linux + +* fix issue #27: better reporting on non-collectable items given on commandline + (e.g. pyc files) + +* fix issue #33: added --version flag (thanks Benjamin Peterson) + +* fix issue #32: adding support for "incomplete" paths to wcpath.status() + +* "Test" prefixed classes are *not* collected by default anymore if they + have an __init__ method + +* monkeypatch setenv() now accepts a "prepend" parameter + +* improved reporting of collection error tracebacks + +* simplified multicall mechanism and plugin architecture, + renamed some internal methods and argnames Modified: py/dist/doc/bin.txt ============================================================================== --- py/dist/doc/bin.txt (original) +++ py/dist/doc/bin.txt Wed Aug 19 18:37:53 2009 @@ -15,7 +15,7 @@ The ``py.test`` executable is the main entry point into the py-lib testing tool, see the `py.test documentation`_. -.. _`py.test documentation`: test/test.html +.. _`py.test documentation`: test/index.html ``py.cleanup`` ============== @@ -51,6 +51,7 @@ Usage: ``py.rest [PATHS] [options]`` +[deprecated in 1.0, will likely be separated] Loot recursively for .txt files starting from ``PATHS`` and convert them to html using docutils or to pdf files, if the ``--pdf`` option is used. For conversion to PDF you will need several command line tools, on Ubuntu Linux Modified: py/dist/doc/confrest.py ============================================================================== --- py/dist/doc/confrest.py (original) +++ py/dist/doc/confrest.py Wed Aug 19 18:37:53 2009 @@ -45,9 +45,9 @@ def a_docref(self, name, relhtmlpath): docpath = self.project.docpath - return html.a(name, class_="menu", + return html.div(html.a(name, class_="menu", href=relpath(self.targetpath.strpath, - docpath.join(relhtmlpath).strpath)) + docpath.join(relhtmlpath).strpath))) def a_apigenref(self, name, relhtmlpath): apipath = self.project.apigenpath @@ -57,19 +57,30 @@ def fill_menubar(self): items = [ - 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("install", "download.html"), + self.a_docref("contact", "contact.html"), + self.a_docref("faq", "faq.html"), + html.div( + html.h3("py.test:"), + self.a_docref("doc index", "test/index.html"), + self.a_docref("features", "test/features.html"), + self.a_docref("quickstart", "test/quickstart.html"), + self.a_docref("tutorials", "test/talks.html"), + self.a_docref("plugins", "test/plugin/index.html"), + self.a_docref("funcargs", "test/funcargs.html"), + self.a_docref("customize", "test/customize.html"), + ), + html.div( + html.h3("supporting APIs:"), + self.a_docref("pylib index", "index.html"), + self.a_docref("py.execnet", "execnet.html"), + self.a_docref("py.path", "path.html"), + self.a_docref("py.code", "code.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("install", "download.html"), ] self.menubar = html.div(id=css.menubar, *[ html.div(item) for item in items]) Modified: py/dist/doc/conftest.py ============================================================================== --- py/dist/doc/conftest.py (original) +++ py/dist/doc/conftest.py Wed Aug 19 18:37:53 2009 @@ -3,3 +3,5 @@ #py.test.importorskip("pygments") pytest_plugins = ['pytest_restdoc'] rsyncdirs = ['.'] + +collect_ignore = ['test/attic.txt'] Modified: py/dist/doc/contact.txt ============================================================================== --- py/dist/doc/contact.txt (original) +++ py/dist/doc/contact.txt Wed Aug 19 18:37:53 2009 @@ -5,9 +5,12 @@ - #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 development commits, +- `Testing In Python`_: a mailing list for testing tools and discussion. + +- `commit mailing list`_ or `@pylibcommit`_ to follow development commits, - `bitbucket issue tracker`_ use this bitbucket issue tracker to report bugs or request features. @@ -22,6 +25,8 @@ .. _tetamap: http://tetamap.wordpress.com +.. _`@pylibcommit`: http://twitter.com/pylibcommit + .. get an account on codespeak @@ -33,14 +38,13 @@ you are new to the python developer community please come to the IRC or the mailing list and ask questions, get involved. +.. _`Testing in Python`: http://lists.idyll.org/listinfo/testing-in-python .. _FOAF: http://en.wikipedia.org/wiki/FOAF .. _us: http://codespeak.net/mailman/listinfo/py-dev .. _codespeak: http://codespeak.net/ .. _`py-dev`: .. _`development mailing list`: .. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev -.. _`subversion commit mailing list`: .. _`py-svn`: -.. _`py-svn general commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn -.. _`development bug/feature tracker`: https://codespeak.net/issue/py-dev/ +.. _`commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn Modified: py/dist/doc/download.txt ============================================================================== --- py/dist/doc/download.txt (original) +++ py/dist/doc/download.txt Wed Aug 19 18:37:53 2009 @@ -7,7 +7,7 @@ Latest Release, see `PyPI project page`_ -using setuptools / easy_install +using easy_install =================================================== With a working `setuptools installation`_ you can type:: @@ -16,16 +16,11 @@ to get the latest release of the py lib. The ``-U`` switch will trigger an upgrade if you already have an older version installed. +On Linux systems you may need to execute the command as superuser and +on Windows you might need to write down the full path to ``easy_install``. The py lib and its tools are expected to work well on Linux, Windows and OSX, Python versions 2.3, 2.4, 2.5 and 2.6. -We provide binary eggs for Windows machines. - -On other systems you need a working C-compiler in order to -install the full py lib. If you don't have a compiler available -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 0.8 versions of the py lib on your system, please download and execute http://codespeak.net/svn/py/build/winpathclean.py @@ -36,9 +31,11 @@ print py.version -.. _`checkout`: +.. _mercurial: http://mercurial.selenic.com/wiki/ +.. _checkout: +.. _tarball: -Installing from version control / develop mode +Working from version control or a tarball ================================================= To follow development or help with fixing things @@ -47,41 +44,59 @@ hg clone https://bitbucket.org/hpk42/py-trunk/ -With a working `setuptools installation`_ you can then issue:: +This currrently contains a 1.0.x branch and the +default 'trunk' branch where mainline development +takes place. There also is a readonly subversion +checkout available:: - python setup.py develop + svn co https://codespeak.net/svn/py/dist -in order to work with your checkout version. +You can also go to the python package index and +download and unpack a TAR file:: -For enhancing one of the plugins you may go to -the ``py/test/plugin/`` sub directory. + http://pypi.python.org/pypi/py/ -.. _mercurial: http://mercurial.selenic.com/wiki/ +activating checkout with setuptools +-------------------------------------------- + +With a working `setuptools installation`_ you can issue:: + + python setup.py develop + +in order to work with the tools and the lib of your checkout. .. _`no-setuptools`: -Working without setuptools / from source -========================================== +activating a checkout or tarball without setuptools +------------------------------------------------------------- + +To import the py lib the ``py`` package directory needs to +be on the ``$PYTHONPATH``. If you exexute scripts directly +from ``py/bin/`` or ``py\bin\win32`` they will find their +containing py lib automatically. -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. +It is usually a good idea to add the parent directory of the ``py`` package +directory to your ``PYTHONPATH`` and ``py/bin`` or ``py\bin\win32`` to your +system wide ``PATH`` settings. There are helper scripts that set ``PYTHONPATH`` and ``PATH`` on your system: -There are also helper scripts to set the environment -on windows:: +on windows execute:: + # inside autoexec.bat or shell startup c:\\path\to\checkout\py\env.cmd -and on linux/osx you can add something like this to -your shell initialization:: +on linux/OSX add this to your shell initialization:: - eval `python ~/path/to/checkout/py/env.py` + # inside .bashrc + eval `python ~/path/to/checkout/py/env.py` both of which which will get you good settings for ``PYTHONPATH`` and ``PATH``. -Note also that the command line scripts will look + +note: scripts look for "nearby" py-lib +----------------------------------------------------- + +Note that the `command line scripts`_ will look for "nearby" py libs, so if you have a layout like this:: mypkg/ @@ -90,39 +105,26 @@ tests/ py/ -then issuing ``py.test subpkg1`` will use the py lib -from that projects root directory. +issuing ``py.test subpkg1`` will use the py lib +from that projects root directory. + +.. _`command line scripts`: bin.html Debian and RPM packages =================================== -As of July 2009 pytest/pylib 1.0 RPMs and Debian packages -are not yet available. So you will only find older -versions. - -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.* +As of August 2009 pytest/pylib 1.0 RPMs and Debian packages +are not available. You will only find 0.9 versions - +on Debian systems look for ``python-codespeak-lib`` +and Dwayne Bailey has put together a Fedora `RPM`_. + +If you can help with providing/upgrading distribution +packages please use of the contact_ channels in case +of questions or need for changes. -Dwayne Bailey has thankfully put together a Fedora `RPM`_. +.. _contact: contact.html .. _`RPM`: http://translate.sourceforge.net/releases/testing/fedora/pylib-0.9.2-1.fc9.noarch.rpm .. _`setuptools installation`: http://pypi.python.org/pypi/setuptools -.. _tarball: - -Installing from a TAR archive -=================================================== - -You need a working `setuptools installation`_. - -Go to the python package index (pypi) and download a tar file: - - http://pypi.python.org/pypi/py/ - -and unpack it to a directory, where you then type:: - - python setup.py install - Added: py/dist/doc/faq.txt ============================================================================== --- (empty file) +++ py/dist/doc/faq.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,123 @@ +================================== +Frequently Asked Questions +================================== + +.. contents:: + :local: + :depth: 2 + +On naming, nose and magic +============================ + +Why the ``py`` naming? what is it? +------------------------------------ + +Because the name was kind of available and there was the +idea to have the package evolve into a "standard" library +kind of thing that works cross-python versions and is +not tied to a particular CPython revision or its release +cycle. Clearly, this was ambitious and the naming +has maybe haunted the project rather than helping it. +There may be a project name change and possibly a +split up into different projects sometime. + +Why the ``py.test`` naming? +------------------------------------ + +the py lib contains other command line tools that +all share the ``py.`` prefix which makes it easy +to use TAB-completion on the shell. Another motivation +was to make it obvious where testing functionality +for the ``py.test`` command line tool is: in the +``py.test`` package name space. + +What's the relation to ``nosetests``? +---------------------------------------- + +py.test and nose_ share basic philosophy when it comes +to running Python tests. In fact, +with py.test-1.0.1 it is easy to run many test suites +that currently work with ``nosetests``. nose_ was created +as a clone of ``py.test`` when it was in the ``0.8`` release +cycle so some of the newer features_ introduced with py.test-1.0 +have no counterpart in nose_. + +.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/ +.. _features: test/features.html + +What's all this "magic" with py.test? +---------------------------------------- + +"All this magic" usually boils down to two issues: + +* There is a special tweak to importing: `py/__init__.py`_ contains + a dictionary which maps the importable ``py.*`` namespaces to + objects in files. When looking at the project source code + you see imports like ``from py.__.test.session import Session``. The + the double ``__`` underscore indicates the "normal" python + filesystem/namespace coupled import, i.e. it points to + ``py/test/session.py``'s ``Session`` object. However, + from the outside you use the "non-underscore" `py namespaces`_ + so this distinction usually only shows up if you hack + on internal code or see internal tracebacks. + +* when an ``assert`` fails, py.test re-interprets the expression + to show intermediate values. This allows to use the plain ``assert`` + statement instead of the many methods that you otherwise need + to mimick this behaviour. This means that in case of a failing + assert, your expressions gets evaluated *twice*. If your expression + has side effects the outcome may be different. If the test suddenly + passes you will get a detailed message. It is good practise, anyway, + to not have asserts with side effects. ``py.test --nomagic`` turns + off assert re-intepretation. + +Other than that, ``py.test`` has bugs or quirks like any other computer +software. In fact, it has a *strong* focus on running robustly and has +over a thousand automated tests for its own code base. + +.. _`py namespaces`: index.html +.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/1.0.x/py/__init__.py + + +function arguments and parametrized tests +=============================================== + +.. _`why pytest_pyfuncarg__ methods?`: + +Why the ``pytest_funcarg__*`` name for funcarg factories? +--------------------------------------------------------------- + +When experimenting with funcargs an explicit registration mechanism +was considered. 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 factory. Besides removing the need +for an indirection it allows to "grep" for ``pytest_funcarg__MYARG`` +and will safely find all factory functions for the ``MYARG`` function +argument. It helps to alleviates the de-coupling of function +argument usage and creation. + +.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration + +Can i yield multiple values from a factory function? +----------------------------------------------------- + +There are two reasons why yielding from a factory function +is not possible: + +* Calling factories for obtaining test function arguments + is part of setting up and running a test. At that + point it is not possible to add new test calls to + the test collection anymore. + +* If multiple factories yielded values there would + be no natural place to determine the combination + policy - in real-world examples some combinations + often should not run. + +Use the `pytest_generate_tests`_ hook to solve both issues +and implement the `parametrization scheme of your choice`_. + +.. _`pytest_generate_tests`: test/funcargs.html#parametrizing-tests +.. _`parametrization scheme of your choice`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ + + Modified: py/dist/doc/index.txt ============================================================================== --- py/dist/doc/index.txt (original) +++ py/dist/doc/index.txt Wed Aug 19 18:37:53 2009 @@ -38,7 +38,7 @@ .. _`py.io`: io.html .. _`py.path`: path.html .. _`py.code`: code.html -.. _`py.test`: test/test.html +.. _`py.test`: test/index.html .. _`py lib scripts`: bin.html .. _`py.xml`: xml.html .. _`miscellaneous features`: misc.html Modified: py/dist/doc/test/attic.txt ============================================================================== --- py/dist/doc/test/attic.txt (original) +++ py/dist/doc/test/attic.txt Wed Aug 19 18:37:53 2009 @@ -72,3 +72,46 @@ .. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`test generators`: funcargs.html#test-generators + +.. _`generative tests`: + +generative tests: yielding parametrized tests +==================================================== + +Deprecated since 1.0 in favour of `test generators`_. + +*Generative tests* are test methods that are *generator functions* which +``yield`` callables and their arguments. This is useful for running a +test function multiple times against different parameters. Example:: + + def test_generative(): + for x in (42,17,49): + yield check, x + + def check(arg): + assert arg % 7 == 0 # second generated tests fails! + +Note that ``test_generative()`` will cause three tests +to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` +of which the middle one will obviously fail. + +To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example:: + + def test_generative(): + for x in (42,17,49): + yield "case %d" % x, check, x + + +disabling a test class +---------------------- + +If you want to disable a complete test class you +can set the class-level attribute ``disabled``. +For example, in order to avoid running some tests on Win32:: + + class TestPosixOnly: + disabled = sys.platform == 'win32' + + def test_xxx(self): + ... Added: py/dist/doc/test/config.html ============================================================================== --- (empty file) +++ py/dist/doc/test/config.html Wed Aug 19 18:37:53 2009 @@ -0,0 +1,18 @@ + + + + + + + + + + + Deleted: /py/dist/doc/test/config.txt ============================================================================== --- /py/dist/doc/test/config.txt Wed Aug 19 18:37:53 2009 +++ (empty file) @@ -1,96 +0,0 @@ -.. contents:: - :local: - :depth: 2 - -available test options ------------------------------ - -You can see command line options by running:: - - py.test -h - -This will display all available command line options -including the ones added by plugins `loaded at tool startup`_. - -.. _`loaded at tool startup`: extend.html#tool-startup - -.. _conftestpy: -.. _collectignore: - -conftest.py: project specific test configuration --------------------------------------------------------- - -A unique feature of py.test are its powerful ``conftest.py`` files which -allow to `set option defaults`_, `implement hooks`_, `specify funcargs`_ -or set particular variables to influence the testing process: - -* ``pytest_plugins``: list of named plugins to load - -* ``collect_ignore``: list of paths to ignore during test collection (relative to the containing - ``conftest.py`` file) - -* ``rsyncdirs``: list of to-be-rsynced directories for distributed - testing - -You may put a conftest.py files in your project root directory or into -your package directory if you want to add project-specific test options. - -``py.test`` loads all ``conftest.py`` files upwards from the command -line specified test files. It will lookup configuration values -right-to-left, i.e. the closer conftest files will be checked first. -You may have a ``conftest.py`` in your very home directory to have some -global configuration values. - -There is a flag that may help you debugging your conftest.py -configuration:: - - py.test --traceconfig - -.. _`implement hooks`: extend.html#conftest.py-plugin -.. _`specify funcargs`: funcargs.html#application-setup-tutorial-example - -.. _`set option defaults`: - -setting option defaults -------------------------------- - -py.test will lookup values of options in this order: - -* option value supplied at command line -* content of environment variable ``PYTEST_OPTION_NAME=...`` -* ``name = ...`` setting in the nearest ``conftest.py`` file. - -The name of an option usually is the one you find -in the longform of the option, i.e. the name -behind the ``--`` double-dash that you get with ``py.test -h``. - -IOW, you can set default values for options per project, per -home-directoray, per shell session or per test-run. - -.. _`basetemp`: - -Temporary directories -------------------------------------------- - -``py.test`` runs provide means to create per-test session -temporary (sub) directories through the config object. -You can create directories by calling a method -on the config object: - -- ``config.mktemp(basename)``: create and returns a new tempdir - -- ``config.ensuretemp(basename)``: create or return a new tempdir - -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 Added: py/dist/doc/test/customize.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/customize.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,421 @@ +================================================ +Customizing and Extending py.test +================================================ + +.. contents:: + :local: + :depth: 2 + +basic test configuration +=================================== + +available command line options +--------------------------------- + +You can see command line options by running:: + + py.test -h + +This will display all available command line options +in your specific environment. + +.. _`project-specific test configuration`: +.. _`collect_ignore`: + +conftest.py: project specific hooks and configuration +-------------------------------------------------------- + +A unique feature of py.test are its ``conftest.py`` files which +allow to: + +* `set option defaults`_ + +* `implement hooks`_ + +* `specify funcargs`_ + +or set particular variables to influence the testing process: + +* ``pytest_plugins``: list of named plugins to load + +* ``collect_ignore``: list of paths to ignore during test collection, relative to the containing ``conftest.py`` file + +* ``rsyncdirs``: list of to-be-rsynced directories for distributed + testing, relative to the containing ``conftest.py`` file. + +You may put a conftest.py files in your project root directory or into +your package directory if you want to add project-specific test options. + +``py.test`` loads all ``conftest.py`` files upwards from the command +line file arguments. It usually looks up configuration values +right-to-left, i.e. the closer conftest files will be checked first. +This means you can have a ``conftest.py`` in your very home directory to +have some global configuration values. + +.. _`specify funcargs`: funcargs.html#application-setup-tutorial-example + +.. _`set option defaults`: + +setting persistent option defaults +------------------------------------ + +py.test will lookup option values in this order: + +* command line +* conftest.py files +* environment variables + +To find out about the particular switches and type:: + + py.test --help-config + +This will print information about all options in your +environment, including your local plugins. + +.. _`basetemp`: + +Temporary directories +------------------------------------------- + +You can create directories by calling one of two methods +on the config object: + +- ``config.mktemp(basename)``: create and return a new tempdir + +- ``config.ensuretemp(basename)``: create or return a new tempdir + +temporary directories are created as sub directories of a per-session +testdir and will keep around the directories of the last three test +runs. You can set the base temporary directory through the command line +`--basetemp`` option. When distributing tests on the same machine, +``py.test`` takes care to configure a basetemp directory for the sub +processes such that all temporary data lands below below a single +per-test run basetemp directory. + +.. _`function arguments`: funcargs.html +.. _`extensions`: + +Plugin basics +========================= + +.. _`local plugin`: + +project specific "local" or named "global" plugins +-------------------------------------------------------------- + +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`: +.. _`loaded at tool startup`: +.. _`test tool starts up`: + +Plugin discovery at tool startup +-------------------------------------------- + +py.test loads plugin modules at tool startup in the following way: + +* by reading the ``PYTEST_PLUGINS`` environment variable + and importing the comma-separated list of named plugins. + +* by pre-scanning the command line for the ``-p name`` option + and loading the specified plugin before actual command line parsing. + +* by loading all `conftest.py plugin`_ files as inferred by the command line + invocation + +* by recursively loading all plugins specified by the + ``pytest_plugins`` variable in a ``conftest.py`` file + +Note that at tool startup only ``conftest.py`` files in +the directory of the specified test modules (or the current dir if None) +or any of the parent directories are found. There is no try to +pre-scan all subdirectories to find ``conftest.py`` files or test +modules. + +Specifying plugins in a test module or plugin +----------------------------------------------- + +You can specify plugins in a test module or a plugin like this: + +.. sourcecode:: python + + pytest_plugins = "name1", "name2", + +When the test module or plugin is loaded the specified plugins +will be loaded. If you specify plugins without the ``pytest_`` +prefix it will be automatically added. All plugin names +must be lowercase. + +.. _`conftest.py plugin`: +.. _`conftestplugin`: + +conftest.py as anonymous per-project plugins +-------------------------------------------------- + +The purpose of ``conftest.py`` files is to allow `project-specific +test configuration`_. They thus make for a good place to implement +project-specific test related features through hooks. For example you may +set the `collect_ignore`_ variable depending on a command line option +by defining the following hook in a ``conftest.py`` file: + +.. _`exclude-file-example`: + +.. sourcecode:: python + + # ./conftest.py in your root or package dir + collect_ignore = ['hello', 'test_world.py'] + def pytest_addoption(parser): + parser.addoption("--runall", action="store_true", default=False) + def pytest_configure(config): + if config.getvalue("runall"): + collect_ignore[:] = [] + + +.. _`well specified hooks`: +.. _`implement hooks`: + +Important py.test hooks +==================================== + +py.test calls hooks functions to implement its `test collection`_, +running and reporting process. When py.test loads a plugin it validates +that all hook functions conform to the `hook definition specification`_. + +The hook function name and its +argument names need to match exactly but it is allowed for an implementation +to accept *less* parameters. You'll get useful errors on mistyped hook or +argument names. Read on for some introductory 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`: plugin/hookspec.html + +.. _`configuration hooks`: + +command line parsing and configuration hooks +-------------------------------------------------------------------- + +When the `test tool starts up`_ it will invoke all hooks that add +command line options in the python standard optparse style. + +.. sourcecode:: python + + def pytest_addoption(parser): + """ add command line options. """" + parser.addoption("--myopt", dest="myopt", action="store_true") + +After all these hooks have been called, the command line is parser +and a ``config`` object is created and another hook is invoked, +for example: + +.. sourcecode:: python + + def pytest_configure(config): + config.getvalue("myopt") + +When the test run finishes this corresponding finalizer hook is called: + + def pytest_unconfigure(config): + ... + + +adding global py.test helpers and functionality +-------------------------------------------------------------------- + +If you want to make global helper functions or objects available +to your test code you can implement: + + 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. + +If you want to provide helpers that are specific to a test function run or need +to be setup per test function run, please refer to the `funcargs mechanism`_. + +.. _`funcargs mechanism`: funcargs.html + + +generic "runtest" hooks +------------------------------ + +Each test item is usually executed by calling the following three hooks: + +.. sourcecode:: python + + pytest_runtest_setup(item) + pytest_runtest_call(item) + pytest_runtest_teardown(item) + +For each of the three invocations a `call object`_ encapsulates +information about the outcome of the call and is subsequently used +to make a report object: + +.. sourcecode:: python + + report = hook.pytest_runtest_makereport(item, call) + +For example, the `pytest_pdb plugin`_ uses this hook to activate +interactive debugging on failures when ``--pdb`` is specified on the +command line. + +Usually three reports will be generated for a single test item for each +of the three runtest hooks respectively. If ``pytest_runtest_setup`` +fails then ``pytest_runtest_teardown`` will be called but not +``pytest_runtest_call``. + +Each of the up to three reports is eventually fed to the logreport hook: + +.. sourcecode:: python + + pytest_runtest_logreport(report) + +A ``report`` object contains status and reporting information: + +.. sourcecode:: python + + report.longrepr = string/lines/object to print + report.when = "setup", "call" or "teardown" + report.shortrepr = letter for progress-report + report.passed = True or False + report.failed = True or False + report.skipped = True or False + +The `pytest_terminal plugin`_ uses this hook to print information +about a test run. + +The whole protocol described here is implemented via this hook: + +.. sourcecode:: python + + pytest_runtest_protocol(item) -> True + +.. _`call object`: + +The call object contains information about a performed call: + +.. sourcecode:: python + + call.excinfo = ExceptionInfo object or None + call.when = "setup", "call" or "teardown" + call.outerr = None or tuple of strings representing captured stdout/stderr + +.. _`pytest_pdb plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_pdb.py +.. _`pytest_terminal plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_terminal.py + + +generic collection hooks +------------------------------ + +py.test calls the following two fundamental hooks for collecting files and directories: + +.. sourcecode:: python + + def pytest_collect_directory(path, parent): + """ return Collection node or None for the given path. """ + + def pytest_collect_file(path, parent): + """ return Collection node or None for the given path. """ + +Both return a `collection node`_ for a given path. All returned +nodes from all hook implementations will participate in the +collection and running protocol. The ``parent`` object is +the parent node and may be used to access command line +options via the ``parent.config`` object. + + +Python test function and module hooks +---------------------------------------------------- + +For influencing the collection of objects in Python modules +you can use the following hook: + +.. sourcecode:: python + + def pytest_pycollect_makeitem(collector, name, obj): + """ return custom item/collector for a python object in a module, or None. """ + +This hook will be called for each Python object in a collected +Python module. The return value is a custom `collection node`_ or None. + +.. XXX or ``False`` if you want to indicate that the given item should not be collected. + + +.. _`collection process`: +.. _`collection node`: +.. _`test collection`: + + +Test Collection process +====================================================== + +the collection tree +--------------------------------- + +The collecting process is iterative so that distribution +and execution of tests can start as soon as the first test +item is collected. Collection nodes with children are +called "Collectors" and terminal nodes are called "Items". +Here is an example of such a tree, generated with the +command ``py.test --collectonly py/xmlobj``:: + + + + + + + + + + + + + + + + +By default all directories not starting with a dot are traversed, +looking for ``test_*.py`` and ``*_test.py`` files. Those Python +files are imported under their `package name`_. + +The Module collector looks for test functions +and test classes and methods. Test functions and methods +are prefixed ``test`` by default. Test classes must +start with a capitalized ``Test`` prefix. + +.. _`package name`: + +constructing the package name for test modules +------------------------------------------------- + +Test modules are imported under their fully qualified +name. Given a filesystem ``fspath`` it is constructed as follows: + +* walk the directories up to the last one that contains + an ``__init__.py`` file. + +* perform ``sys.path.insert(0, basedir)``. + +* import the root package as ``root`` + +* determine the fully qualified name for ``fspath`` by either: + + * calling ``root.__pkg__.getimportname(fspath)`` if the + ``__pkg__`` exists.` or + + * otherwise use the relative path of the module path to + the base dir and turn slashes into dots and strike + the trailing ``.py``. + Deleted: /py/dist/doc/test/extend.txt ============================================================================== --- /py/dist/doc/test/extend.txt Wed Aug 19 18:37:53 2009 +++ (empty file) @@ -1,329 +0,0 @@ -================================================ -Extending and customizing py.test -================================================ - -.. _`local plugin`: - -py.test implements much of its functionality by calling `well specified -hooks`_. Python modules which contain such hook functions are called -plugins. Hook functions are discovered in ``conftest.py`` files or in -`named plugins`_. ``conftest.py`` files are sometimes called -"anonymous" or 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`: - -Plugin discovery at tool startup --------------------------------------------- - -py.test loads plugin modules at tool startup in the following way: - -* by reading the ``PYTEST_PLUGINS`` environment variable - and importing the comma-separated list of named plugins. - -* by pre-scanning the command line for the ``-p name`` option - and loading the specified plugin before actual command line parsing. - -* by loading all `conftest.py plugin`_ files as inferred by the command line - invocation - -* by recursively loading all plugins specified by the - ``pytest_plugins`` variable in a ``conftest.py`` file - -Note that at tool startup only ``conftest.py`` files in -the directory of the specified test modules (or the current dir if None) -or any of the parent directories are found. There is no try to -pre-scan all subdirectories to find ``conftest.py`` files or test -modules. - -Specifying plugins in a test module or plugin ------------------------------------------------ - -You can specify plugins in a test module or a plugin like this: - -.. sourcecode:: python - - pytest_plugins = "name1", "name2", - -When the test module or plugin is loaded the specified plugins -will be loaded. If you specify plugins without the ``pytest_`` -prefix it will be automatically added. All plugin names -must be lowercase. - -.. _`conftest.py plugin`: -.. _`conftestplugin`: - -conftest.py as anonymous per-project plugins --------------------------------------------------- - -The purpose of ``conftest.py`` files is to allow `project-specific -test configuration`_. They thus make for a good place to implement -project-specific test related features through hooks. For example you may -set the `collect_ignore`_ variable depending on a command line option -by defining the following hook in a ``conftest.py`` file: - -.. _`exclude-file-example`: - -.. sourcecode:: python - - # ./conftest.py in your root or package dir - collect_ignore = ['hello', 'test_world.py'] - def pytest_addoption(parser): - parser.addoption("--runall", action="store_true", default=False) - def pytest_configure(config): - if config.getvalue("runall"): - collect_ignore[:] = [] - -.. _`project-specific test configuration`: config.html#conftestpy -.. _`collect_ignore`: config.html#collectignore - -.. _`well specified hooks`: - -Available py.test hooks -==================================== - -py.test calls hooks functions to implement its `test collection`_, running and -reporting process. Upon loading of a plugin py.test performs -strict checking on contained hook functions. Function and argument names -need to match exactly one of `hook definition specification`_. It thus -provides useful error reporting on mistyped hook or argument names -and minimizes version incompatibilites. Below you find some introductory -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`: plugin/hookspec.html - -.. _`configuration hooks`: - -command line parsing and configuration hooks --------------------------------------------------------------------- - -When the `test tool starts up`_ it will invoke all hooks that add -command line options in the python standard optparse style. - -.. sourcecode:: python - - def pytest_addoption(parser): - """ add command line options. """" - parser.addoption("--myopt", dest="myopt", action="store_true") - -After all these hooks have been called, the command line is parser -and a ``config`` object is created and another hook is invoked, -for example: - -.. sourcecode:: python - - def pytest_configure(config): - config.getvalue("myopt") - -When the test run finishes this corresponding finalizer hook is called: - - def pytest_unconfigure(config): - ... - - -adding global py.test helpers and functionality --------------------------------------------------------------------- - -If you want to make global helper functions or objects available -to your test code you can implement: - - 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. - -If you want to provide helpers that are specific to a test function run or need -to be setup per test function run, please refer to the `funcargs mechanism`_. - -.. _`funcargs mechanism`: funcargs.html - - -generic "runtest" hooks ------------------------------- - -Each test item is usually executed by calling the following three hooks: - -.. sourcecode:: python - - pytest_runtest_setup(item) - pytest_runtest_call(item) - pytest_runtest_teardown(item) - -For each of the three invocations a `call object`_ encapsulates -information about the outcome of the call and is subsequently used -to make a report object: - -.. sourcecode:: python - - report = hook.pytest_runtest_makereport(item, call) - -For example, the `pytest_pdb plugin`_ uses this hook to activate -interactive debugging on failures when ``--pdb`` is specified on the -command line. - -Usually three reports will be generated for a single test item. However, -if the ``pytest_runtest_setup`` fails no call or teardown hooks -will be called and only one report will be created. - -Each of the up to three reports is eventually fed to the logreport hook: - -.. sourcecode:: python - - pytest_runtest_logreport(report) - -A ``report`` object contains status and reporting information: - -.. sourcecode:: python - - report.longrepr = string/lines/object to print - report.when = "setup", "call" or "teardown" - report.shortrepr = letter for progress-report - report.passed = True or False - report.failed = True or False - report.skipped = True or False - -The `pytest_terminal plugin`_ uses this hook to print information -about a test run. - -The protocol described here is implemented via this hook: - -.. sourcecode:: python - - pytest_runtest_protocol(item) -> True - -.. _`call object`: - -The call object contains information about a performed call: - -.. sourcecode:: python - - call.excinfo = ExceptionInfo object or None - call.when = "setup", "call" or "teardown" - call.outerr = None or tuple of strings representing captured stdout/stderr - -.. _`pytest_pdb plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_pdb.py -.. _`pytest_terminal plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_terminal.py - - -generic collection hooks ------------------------------- - -py.test calls the following two fundamental hooks for collecting files and directories: - -.. sourcecode:: python - - def pytest_collect_directory(path, parent): - """ return Collection node or None for the given path. """ - - def pytest_collect_file(path, parent): - """ return Collection node or None for the given path. """ - -Both return a `collection node`_ for a given path. All returned -nodes from all hook implementations will participate in the -collection and running protocol. The ``parent`` object is -the parent node and may be used to access command line -options via the ``parent.config`` object. - - -Python test function and module hooks ----------------------------------------------------- - -For influencing the collection of objects in Python modules -you can use the following hook: - -.. sourcecode:: python - - def pytest_pycollect_makeitem(collector, name, obj): - """ return custom item/collector for a python object in a module, or None. """ - -This hook will be called for each Python object in a collected -Python module. The return value is a custom `collection node`_ or None. - -.. XXX or ``False`` if you want to indicate that the given item should not be collected. - - - -Included default plugins -============================= - -You can find the source code of all default plugins in - - http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/ - -Additionally you can check out some more contributed plugins here - - http://bitbucket.org/hpk42/py-trunk/src/tip/contrib/ - - -.. _`collection process`: -.. _`collection node`: -.. _`test collection`: - - -Test Collection process -====================================================== - -The collecting process is iterative so that distribution -and execution of tests can start as soon as the first test -item is collected. Collection nodes with children are -called "Collectors" and terminal nodes are called "Items". -Here is an example of such a tree, generated with the -command ``py.test --collectonly py/xmlobj``:: - - - - - - - - - - - - - - - - -By default all directories not starting with a dot are traversed, -looking for ``test_*.py`` and ``*_test.py`` files. Those Python -files are imported under their `package name`_. - -The Module collector looks for test functions -and test classes and methods. Test functions and methods -are prefixed ``test`` by default. Test classes must -start with a capitalized ``Test`` prefix. - -.. _`package name`: - -constructing the package name for test modules -------------------------------------------------- - -Test modules are imported under their fully qualified -name. Given a filesystem ``fspath`` it is constructed as follows: - -* walk the directories up to the last one that contains - an ``__init__.py`` file. - -* perform ``sys.path.insert(0, basedir)``. - -* import the root package as ``root`` - -* determine the fully qualified name for ``fspath`` by either: - - * calling ``root.__pkg__.getimportname(fspath)`` if the - ``__pkg__`` exists.` or - - * otherwise use the relative path of the module path to - the base dir and turn slashes into dots and strike - the trailing ``.py``. - Modified: py/dist/doc/test/features.txt ============================================================================== --- py/dist/doc/test/features.txt (original) +++ py/dist/doc/test/features.txt Wed Aug 19 18:37:53 2009 @@ -1,117 +1,77 @@ ================================================== -py.test features +py.test feature overview ================================================== -py.test is an extensible tool for running all kinds -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 no-boilerplate effort. -It works and is tested against linux, windows and osx -on CPython 2.3 - CPython 2.6. - -.. contents:: List of Contents +.. contents:: + :local: :depth: 1 +mature command line testing tool +==================================================== + +py.test is a command line tool to collect and run automated tests. It +runs well on Linux, Windows and OSX Python 2.4 through to 2.6 versions. +It can distribute a single test run to multiple machines. It is used in +many projects, ranging from running 10 thousands of tests integrated +with buildbot to a few inlined tests on a command line script. + .. _`autocollect`: automatically collects and executes tests =============================================== -py.test discovers tests automatically by inspecting specified -directories or files. By default, it collects all python -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. - -.. _`collection process`: extend.html#collection-process - - -funcargs and xUnit style setups -=================================================== - -py.test provides powerful means for managing test -state and fixtures. Apart from the `traditional -xUnit style setup`_ for unittests it features the -simple and powerful `funcargs mechanism`_ for handling -both complex and simple test scenarious. - -.. _`funcargs mechanism`: funcargs.html -.. _`traditional xUnit style setup`: xunit_setup.html - -load-balance tests to multiple CPUs -=================================== - -For large test suites you can distribute your -tests to multiple CPUs by issuing for example:: - - py.test -n 3 - -Read more on `distributed testing`_. - -.. _`distributed testing`: dist.html - -Distribute tests across machines -=================================== - -py.test supports the sending of tests to -remote ssh-accounts or socket servers. -It can `ad-hoc run your test on multiple -platforms one a single test run`. Ad-hoc -means that there are **no installation -requirements whatsoever** on the remote side. +py.test discovers tests automatically by looking at +specified directories and its files for common +naming patterns. As ``py.test`` operates as a separate +cmdline tool you can easily have a command line utility and +some tests in the same file. -.. _`ad-hoc run your test on multiple platforms one a single test run`: dist.html#atonce +supports many testing practises and methods +================================================================== -extensive debugging support -=================================== +py.test supports many testing methods conventionally used in +the Python community. It runs traditional `unittest.py`_, +`doctest.py`_, supports `xUnit style setup`_ and nose_ specific +setups and test suites. It offers minimal no-boilerplate model +for configuring and deploying tests written as simple Python +functions or methods. It also integrates `coverage testing +with figleaf`_ or `Javasript unit- and functional testing`_. -testing starts immediately --------------------------- +.. _`Javasript unit- and functional testing`: plugin/oejskit.html +.. _`coverage testing with figleaf`: plugin/figleaf.html -Testing starts as soon as the first ``test item`` -is collected. The collection process is iterative -and does not need to complete before your first -test items are executed. +no-boilerplate test functions with Python +=================================================== -support for modules containing tests --------------------------------------- +automatic Python test discovery +------------------------------------ -As ``py.test`` operates as a separate cmdline -tool you can easily have a command line utility and -some tests in the same file. +By default, all python modules with a ``test_*.py`` +filename are inspected for finding tests: -debug with the ``print`` statement ----------------------------------- +* functions with a name beginning with ``test_`` +* classes with a leading ``Test`` name and ``test`` prefixed methods. +* ``unittest.TestCase`` subclasses + +test functions can run with different argument sets +----------------------------------------------------------- + +py.test offers the unique `funcargs mechanism`_ for setting up +and passing project-specific objects to Python test functions. +Test Parametrization happens by triggering a call to the same test +functions with different argument values. + +per-test capturing of output, including subprocesses +---------------------------------------------------- + +By default, ``py.test`` captures all writes to stdout/stderr. +Output from ``print`` statements as well as from subprocesses +is captured_. When a test fails, the associated captured outputs are shown. +This allows you to put debugging print statements in your code without +being overwhelmed by all the output that might be generated by tests +that do not fail. -By default, ``py.test`` catches text written to stdout/stderr during -the execution of each individual test. This output will only be -displayed however if the test fails; you will not see it -otherwise. This allows you to put debugging print statements in your -code without being overwhelmed by all the output that might be -generated by tests that do not fail. - -Each failing test that produced output during the running of the test -function will have its output displayed in the ``recorded stdout`` section. - -During Setup and Teardown ("Fixture") capturing is performed separately so -that you will only see this output if the actual fixture functions fail. - -The catching of stdout/stderr output can be disabled using the -``--nocapture`` or ``-s`` option to the ``py.test`` tool. Any output will -in this case be displayed as soon as it is generated. - -test execution order --------------------------------- - -Tests usually run in the order in which they appear in the files. -However, tests should not rely on running one after another, as -this prevents more advanced usages: running tests -distributedly or selectively, or in "looponfailing" mode, -will cause them to run in random order. +.. _captured: plugin/capture.html assert with the ``assert`` statement ---------------------------------------- @@ -142,79 +102,86 @@ py.test.raises(Exception, func, *args, **kwargs) py.test.raises(Exception, "func(*args, **kwargs)") -both of which execute the given function with args and kwargs and +both of which execute the specified function with args and kwargs and asserts that the given ``Exception`` is raised. The reporter will provide you with helpful output in case of failures such as *no exception* or *wrong exception*. -useful tracebacks, recursion detection --------------------------------------- -A lot of care is taken to present nice tracebacks in case of test -failure. Try:: +information-rich tracebacks, PDB introspection +------------------------------------------------------- - py.test py/doc/example/pytest/failure_demo.py +.. _`example tracebacks`: http://paste.pocoo.org/show/134814/ -to see a variety of tracebacks, each representing a different -failure situation. +A lot of care is taken to present useful failure information +and in particular nice and concise Python tracebacks. This +is especially useful if you need to regularly look at failures +from nightly runs, i.e. are detached from the actual test +running session. Here are `example tracebacks`_ for a number of failing +test functions. You can modify traceback printing styles through the +command line. Using the `--pdb`` option you can automatically activate +a PDB `Python debugger`_ when a test fails. -``py.test`` uses the same order for presenting tracebacks as Python -itself: the oldest function call is shown first, and the most recent call is -shown last. A ``py.test`` reported traceback starts with your -failing test function. If the maximum recursion depth has been -exceeded during the running of a test, for instance because of -infinite recursion, ``py.test`` will indicate where in the -code the recursion was taking place. You can inhibit -traceback "cutting" magic by supplying ``--fulltrace``. +advanced skipping of tests +------------------------------- -There is also the possibility of using ``--tb=short`` to get regular CPython -tracebacks. Or you can use ``--tb=no`` to not show any tracebacks at all. +If you want to skip tests you can use ``py.test.skip`` within +test or setup functions. Example:: -no inheritance requirement --------------------------- + def test_hello(): + if sys.platform != "win32": + py.test.skip("only win32 supported") -Test classes are recognized by their leading ``Test`` name. Unlike -``unitest.py``, you don't need to inherit from some base class to make -them be found by the test runner. Besides being easier, it also allows -you to write test classes that subclass from application level -classes. +You can also use a helper to skip on a failing import:: -testing for deprecated APIs ------------------------------- + docutils = py.test.importorskip("docutils") -In your tests you can use ``py.test.deprecated_call(func, *args, **kwargs)`` -to test that a particular function call triggers a DeprecationWarning. -This is useful for testing phasing out of old APIs in your projects. +or to skip if a library does not have the right version:: + docutils = py.test.importorskip("docutils", minversion="0.3") -advanced test selection / skipping -========================================================= +The version will be read from the specified module's ``__version__`` attribute. -dynamically skipping tests -------------------------------- +.. _`funcargs mechanism`: funcargs.html +.. _`unittest.py`: http://docs.python.org/library/unittest.html +.. _`doctest.py`: http://docs.python.org/library/doctest.html +.. _`xUnit style setup`: xunit_setup.html +.. _`pytest_nose`: plugin/nose.html -If you want to skip tests you can use ``py.test.skip`` within -test or setup functions. Example:: +load-balance test runs to multiple CPUs +======================================== + +For large test suites you can distribute your +tests to multiple CPUs by issuing for example:: - py.test.skip("message") + py.test -n 3 -You can also use a helper to skip on a failing import:: +Read more on `distributed testing`_. - docutils = py.test.importorskip("docutils") +.. _`distributed testing`: dist.html -or to skip if a library does not have the right version:: +ad-hoc run tests cross-platform +================================================== - docutils = py.test.importorskip("docutils", minversion="0.3") +py.test supports the sending of tests to +remote ssh-accounts, socket servers. +It can `ad-hoc run your test on multiple +platforms one a single test run`. Ad-hoc +means that there are **no installation +requirements whatsoever** on the remote side. -The version will be read from the module's ``__version__`` attribute. +.. _`ad-hoc run your test on multiple platforms one a single test run`: dist.html#atonce + +advanced test selection and running modes +========================================================= .. _`selection by keyword`: -selecting/unselecting tests by keyword ---------------------------------------------- +``py.test --looponfailing`` allows to run a test suite, +memorize all failures and then loop over the failing set +of tests until they all pass. It will re-start running +the tests when it detects file changes in your project. -Pytest's keyword mechanism provides a powerful way to -group and selectively run tests in your test code base. You can selectively run tests by specifiying a keyword on the command line. Examples:: @@ -246,65 +213,21 @@ .. _`pytest_keyword`: plugin/keyword.html - -disabling a test class ----------------------- - -If you want to disable a complete test class you -can set the class-level attribute ``disabled``. -For example, in order to avoid running some tests on Win32:: - - class TestPosixOnly: - disabled = sys.platform == 'win32' - - def test_xxx(self): - ... - -.. _`test generators`: funcargs.html#test-generators - -.. _`generative tests`: - -generative tests: yielding parametrized tests -==================================================== - -Deprecated since 1.0 in favour of `test generators`_. - -*Generative tests* are test methods that are *generator functions* which -``yield`` callables and their arguments. This is useful for running a -test function multiple times against different parameters. Example:: - - def test_generative(): - for x in (42,17,49): - yield check, x - - def check(arg): - assert arg % 7 == 0 # second generated tests fails! - -Note that ``test_generative()`` will cause three tests -to get run, notably ``check(42)``, ``check(17)`` and ``check(49)`` -of which the middle one will obviously fail. - -To make it easier to distinguish the generated tests it is possible to specify an explicit name for them, like for example:: - - def test_generative(): - for x in (42,17,49): - yield "case %d" % x, check, x - easy to extend ========================================= -Since 1.0 py.test has advanced `extension mechanisms`_ -and a growing `list of plugins`_. +py.test has advanced `extension mechanisms`_ +with a growing `list of default plugins`_. One can can easily modify or add aspects for for purposes such as: * reporting extensions * customizing collection and execution of tests -* running non-python tests -* managing custom test state setup +* running and managing non-python tests +* managing domain-specific test state setup -.. _`list of plugins`: plugin/index.html -.. _`extension mechanisms`: extend.html +.. _`list of default plugins`: plugin/index.html +.. _`extension mechanisms`: customize.html#extensions .. _`reStructured Text`: http://docutils.sourceforge.net .. _`Python debugger`: http://docs.python.org/lib/module-pdb.html Modified: py/dist/doc/test/funcargs.txt ============================================================================== --- py/dist/doc/test/funcargs.txt (original) +++ py/dist/doc/test/funcargs.txt Wed Aug 19 18:37:53 2009 @@ -2,8 +2,15 @@ **funcargs**: test function arguments FTW ========================================================== +.. contents:: + :local: + :depth: 2 + +Goals of the "funcarg" mechanism +========================================== + Since version 1.0 py.test features the "funcarg" mechanism which -allows a test function to take arguments independently provided +allows a Python 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. @@ -14,7 +21,6 @@ * 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`_. @@ -26,7 +32,248 @@ .. _`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 + +.. _`tutorial examples`: + +Tutorial Examples +======================================= + + +.. _`application setup tutorial example`: +.. _appsetup: + +application specific test setup and fixtures +--------------------------------------------------------- + +Here is a basic useful step-wise example for handling application +specific test setup. The goal is to have one place where we have the +glue and test support code for bootstrapping and configuring application objects and allow +test modules and test functions to stay ignorant of involved details. + +step 1: use and implement a test/app-specific "mysetup" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Let's write a simple test function living in a test file +``test_sample.py`` that uses a ``mysetup`` funcarg for accessing test +specific setup. + +.. sourcecode:: python + + # ./test_sample.py + def test_answer(mysetup): + app = mysetup.myapp() + answer = app.question() + assert answer == 42 + +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 factory method in a `local plugin`_ by putting the +following code into a local ``conftest.py``: + +.. sourcecode:: python + + # ./conftest.py + + from myapp import MyApp + + def pytest_funcarg__mysetup(request): + return MySetup() + + class MySetup: + def myapp(self): + return MyApp() + +To run the example we represent our application by putting a pseudo MyApp object into ``myapp.py``: + +.. sourcecode:: python + + # ./myapp.py + class MyApp: + def question(self): + return 6 * 9 + +You can now run the test with ``py.test test_sample.py`` which will +show this failure: + +.. sourcecode:: python + + ========================= test session starts ========================= + python: platform linux2 -- Python 2.6.2 + test object 1: /home/hpk/hg/py/trunk/example/funcarg/mysetup + + test_sample.py F + + ============================== FAILURES =============================== + _____________________________ test_answer _____________________________ + + mysetup = + + def test_answer(mysetup): + app = mysetup.myapp() + answer = app.question() + > assert answer == 42 + E assert 54 == 42 + + test_sample.py:5: AssertionError + ====================== 1 failed in 0.11 seconds ======================= + +This means that our ``mysetup`` object was successfully instantiated, +we asked it to provide an application instance and checking +its ``question`` method resulted in the wrong answer. If you are +confused as to what the concrete question or answers actually mean, +please see here_ :) Otherwise proceed to step 2. + +.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy +.. _`local plugin`: customize.html#local-plugin + +.. _`tut-cmdlineoption`: + +step 2: adding a command line option +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you provide a "funcarg" from a plugin you can easily make methods +depend on command line options or environment settings. +To add a command line option we update the conftest.py of +the previous example to add a command line option +and to offer a new mysetup method: + +.. sourcecode:: python + + # ./conftest.py + import py + from myapp import MyApp + + def pytest_funcarg__mysetup(request): + return MySetup(request) + + def pytest_addoption(parser): + parser.addoption("--ssh", action="store", default=None, + help="specify ssh host to run tests with") + + + class MySetup: + def __init__(self, request): + self.config = request.config + + def myapp(self): + return MyApp() + + def getsshconnection(self): + host = self.config.option.ssh + if host is None: + py.test.skip("specify ssh host with --ssh") + return py.execnet.SshGateway(host) + + +Now any test function can use the ``mysetup.getsshconnection()`` method like this: + +.. sourcecode:: python + + # ./test_ssh.py + class TestClass: + def test_function(self, mysetup): + conn = mysetup.getsshconnection() + # work with conn + +Running ``py.test test_ssh.py`` without specifying a command line option will result in a skipped test_function: + +.. sourcecode:: python + + ========================= test session starts ========================= + python: platform linux2 -- Python 2.6.2 + test object 1: test_ssh.py + + test_ssh.py s + + ________________________ skipped test summary _________________________ + conftest.py:23: [1] Skipped: 'specify ssh host with --ssh' + ====================== 1 skipped in 0.11 seconds ====================== + +Note especially how the test function could stay clear knowing about how to construct test state values or when to skip and with what message. The test function can concentrate on actual test code and test state 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. + +.. _`accept example`: + +example: specifying and selecting acceptance tests +-------------------------------------------------------------- + +.. sourcecode:: python + + # ./conftest.py + def pytest_option(parser): + group = parser.getgroup("myproject") + group.addoption("-A", dest="acceptance", action="store_true", + help="run (slow) acceptance tests") + + def pytest_funcarg__accept(request): + return AcceptFuncarg(request) + + class AcceptFuncarg: + def __init__(self, request): + if not request.config.option.acceptance: + py.test.skip("specify -A to run acceptance tests") + self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True) + + def run(self, cmd): + """ called by test code to execute an acceptance test. """ + self.tmpdir.chdir() + return py.process.cmdexec(cmd) + + +and the actual test function example: + +.. sourcecode:: python + + def test_some_acceptance_aspect(accept): + accept.tmpdir.mkdir("somesub") + result = accept.run("ls -la") + assert "somesub" in result + +If you run this test without specifying a command line option +the test will get skipped with an appropriate message. Otherwise +you can start to add convenience and test support methods +to your AcceptFuncarg and drive running of tools or +applications and provide ways to do assertions about +the output. + +.. _`decorate a funcarg`: + +example: decorating a funcarg in a test module +-------------------------------------------------------------- + +For larger scale setups it's sometimes useful to decorare +a funcarg just for a particular test module. We can +extend the `accept example`_ by putting this in our test module: + +.. sourcecode:: python + + def pytest_funcarg__accept(request): + # call the next factory (living in our conftest.py) + arg = request.getfuncargvalue("accept") + # create a special layout in our tempdir + arg.tmpdir.mkdir("special") + return arg + + class TestSpecialAcceptance: + def test_sometest(self, accept): + assert accept.tmpdir.join("special").check() + +Our module level factory will be invoked first and it can +ask 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`_. + +sidenote: the temporary directory used here are instances of +the `py.path.local`_ class which provides many of the os.path +methods in a convenient way. + +.. _`py.path.local`: ../path.html#local +.. _`conftest plugin`: customize.html#conftestplugin .. _`funcarg factory`: @@ -51,14 +298,14 @@ .. sourcecode:: python - ============================ test session starts ============================ + =========================== test session starts ============================ python: platform linux2 -- Python 2.6.2 test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py test_simplefactory.py F - ================================= FAILURES ================================== - _______________________________ test_function _______________________________ + ================================ FAILURES ================================== + ______________________________ test_function _______________________________ myfuncarg = 42 @@ -67,7 +314,7 @@ E assert 42 == 17 test_simplefactory.py:6: AssertionError - ========================= 1 failed in 0.11 seconds ========================== + ======================== 1 failed in 0.11 seconds ========================== This means that the test function got executed and the assertion failed. @@ -107,7 +354,7 @@ ``request.config``: access to command line opts and general config -``request.param``: if exists was passed by a `parametrizing test generator`_ +``request.param``: if exists was passed by a previous `metafunc.addcall`_ .. _`useful caching and finalization helpers`: @@ -181,9 +428,9 @@ exception will be raised. .. _`test generators`: -.. _`parametrizing test generator`: +.. _`parametrizing-tests`: -generating parametrized tests with funcargs +generating parametrized tests =========================================================== You can parametrize multiple runs of the same test @@ -206,14 +453,14 @@ .. sourcecode:: python - ================================= test session starts ================================= + ============================= test session starts ========================== python: platform linux2 -- Python 2.6.2 test object 1: /home/hpk/hg/py/trunk/test_example.py test_example.py .........F - ====================================== FAILURES ======================================= - _______________________________ test_func.test_func[9] ________________________________ + ================================ FAILURES ================================== + __________________________ test_func.test_func[9] __________________________ numiter = 9 @@ -253,6 +500,8 @@ ``metafunc.config``: access to command line opts and general config +.. _`metafunc.addcall`: + the ``metafunc.addcall()`` method ----------------------------------------------- @@ -282,263 +531,4 @@ and test setup/run happens in different process. -.. _`tutorial examples`: - -Funcarg Tutorial Examples -======================================= - - -.. _`application setup tutorial example`: - -application specific test setup ---------------------------------------------------------- - -Here is a basic useful step-wise example for handling application -specific test setup. The goal is to have one place where we have the -glue and test support code for bootstrapping and configuring application objects and allow -test modules and test functions to stay ignorant of involved details. - -step 1: use and implement a test/app-specific "mysetup" -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Let's write a simple test function living in a test file -``test_sample.py`` that uses a ``mysetup`` funcarg for accessing test -specific setup. - -.. sourcecode:: python - - # ./test_sample.py - def test_answer(mysetup): - app = mysetup.myapp() - answer = app.question() - assert answer == 42 - -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 factory method in a `local plugin`_ by putting the -following code into a local ``conftest.py``: - -.. sourcecode:: python - - # ./conftest.py - - from myapp import MyApp - - def pytest_funcarg__mysetup(request): - return MySetup() - - class MySetup: - def myapp(self): - return MyApp() - -To run the example we represent our application by putting a pseudo MyApp object into ``myapp.py``: - -.. sourcecode:: python - - # ./myapp.py - class MyApp: - def question(self): - return 6 * 9 - -You can now run the test with ``py.test test_sample.py`` which will -show this failure: - -.. sourcecode:: python - - ========================= test session starts ========================= - python: platform linux2 -- Python 2.6.2 - test object 1: /home/hpk/hg/py/trunk/example/funcarg/mysetup - - test_sample.py F - - ============================== FAILURES =============================== - _____________________________ test_answer _____________________________ - - mysetup = - - def test_answer(mysetup): - app = mysetup.myapp() - answer = app.question() - > assert answer == 42 - E assert 54 == 42 - - test_sample.py:5: AssertionError - ====================== 1 failed in 0.11 seconds ======================= - -This means that our ``mysetup`` object was successfully instantiated, -we asked it to provide an application instance and checking -its ``question`` method resulted in the wrong answer. If you are -confused as to what the concrete question or answers actually mean, -please see here_ :) Otherwise proceed to step 2. - -.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy -.. _`local plugin`: extend.html#local-plugin - - -step 2: adding a command line option -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -If you provide a "funcarg" from a plugin you can easily make methods -depend on command line options or environment settings. -To add a command line option we update the conftest.py of -the previous example to add a command line option -and to offer a new mysetup method: - -.. sourcecode:: python - - # ./conftest.py - import py - from myapp import MyApp - - def pytest_funcarg__mysetup(request): - return MySetup(request) - - def pytest_addoption(parser): - parser.addoption("--ssh", action="store", default=None, - help="specify ssh host to run tests with") - - - class MySetup: - def __init__(self, request): - self.config = request.config - - def myapp(self): - return MyApp() - - def getsshconnection(self): - host = self.config.option.ssh - if host is None: - py.test.skip("specify ssh host with --ssh") - return py.execnet.SshGateway(host) - - -Now any test function can use the ``mysetup.getsshconnection()`` method like this: - -.. sourcecode:: python - - # ./test_ssh.py - class TestClass: - def test_function(self, mysetup): - conn = mysetup.getsshconnection() - # work with conn - -Running ``py.test test_ssh.py`` without specifying a command line option will result in a skipped test_function: - -.. sourcecode:: python - - ========================= test session starts ========================= - python: platform linux2 -- Python 2.6.2 - test object 1: test_ssh.py - - test_ssh.py s - - ________________________ skipped test summary _________________________ - conftest.py:23: [1] Skipped: 'specify ssh host with --ssh' - ====================== 1 skipped in 0.11 seconds ====================== - -Note especially how the test function could stay clear knowing about how to construct test state values or when to skip and with what message. The test function can concentrate on actual test code and test state 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. - -.. _`accept example`: - -example: specifying and selecting acceptance tests --------------------------------------------------------------- - -.. sourcecode:: python - - # ./conftest.py - def pytest_option(parser): - group = parser.getgroup("myproject") - group.addoption("-A", dest="acceptance", action="store_true", - help="run (slow) acceptance tests") - - def pytest_funcarg__accept(request): - return AcceptFuncarg(request) - - class AcceptFuncarg: - def __init__(self, request): - if not request.config.option.acceptance: - py.test.skip("specify -A to run acceptance tests") - self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True) - - def run(self, cmd): - """ called by test code to execute an acceptance test. """ - self.tmpdir.chdir() - return py.process.cmdexec(cmd) - - -and the actual test function example: - -.. sourcecode:: python - - def test_some_acceptance_aspect(accept): - accept.tmpdir.mkdir("somesub") - result = accept.run("ls -la") - assert "somesub" in result - -If you run this test without specifying a command line option -the test will get skipped with an appropriate message. Otherwise -you can start to add convenience and test support methods -to your AcceptFuncarg and drive running of tools or -applications and provide ways to do assertions about -the output. - -.. _`decorate a funcarg`: - -example: decorating a funcarg in a test module --------------------------------------------------------------- - -For larger scale setups it's sometimes useful to decorare -a funcarg just for a particular test module. We can -extend the `accept example`_ by putting this in our test module: - -.. sourcecode:: python - - # 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 - - class TestSpecialAcceptance: - def test_sometest(self, accept): - assert accept.tmpdir.join("special").check() - -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`_. - -sidenote: the temporary directory used here are instances of -the `py.path.local`_ class which provides many of the os.path -methods in a convenient way. - -.. _`py.path.local`: ../path.html#local -.. _`conftest plugin`: extend.html#conftestplugin - - -Questions and Answers -================================== - -.. _`why pytest_pyfuncarg__ methods?`: - -Why ``pytest_funcarg__*`` methods? ------------------------------------- - -When experimenting with funcargs we also -considered an explicit registration mechanism, i.e. calling a register -method on the config object. But lacking a good use case for this -indirection and flexibility we decided to go for `Convention over -Configuration`_ and allow to directly specify the 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). - -.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration Added: py/dist/doc/test/index.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/index.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,29 @@ +======================================= +py.test documentation index +======================================= + + +features_: overview and discussion of features. + +quickstart_: getting started with writing a simple test. + +`talks, tutorials, examples`_: tutorial examples, slides + +funcargs_: powerful parametrized test function setup + +`plugins`_: list of available plugins with usage examples and feature details. + +`distributed testing`_: ad-hoc run tests on multiple CPUs and platforms + +customize_: configuration, customization, extensions + + +.. _`plugins`: plugin/index.html +.. _`talks, tutorials, examples`: talks.html +.. _quickstart: quickstart.html +.. _features: features.html +.. _funcargs: funcargs.html +.. _customize: customize.html +.. _`distributed testing`: dist.html + + Added: py/dist/doc/test/mission.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/mission.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,13 @@ + +Mission +==================================== + +py.test strives to make testing a fun and no-boilerplate effort. + +The tool is distributed as part of the `py` package which contains supporting APIs that +are also useable independently. The project independent ``py.test`` command line tool helps you to: + +* rapidly collect and run tests +* run unit- or doctests, functional or integration tests +* distribute tests to multiple environments +* use local or global plugins for custom test types and setup Modified: py/dist/doc/test/plugin/capture.txt ============================================================================== --- py/dist/doc/test/plugin/capture.txt (original) +++ py/dist/doc/test/plugin/capture.txt Wed Aug 19 18:37:53 2009 @@ -38,7 +38,7 @@ If you set capturing values in a conftest file like this:: # conftest.py - conf_capture = 'fd' + option_capture = 'fd' then all tests in that directory will execute with "fd" style capturing. @@ -120,12 +120,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/doctest.txt ============================================================================== --- py/dist/doc/test/plugin/doctest.txt (original) +++ py/dist/doc/test/plugin/doctest.txt Wed Aug 19 18:37:53 2009 @@ -29,12 +29,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/figleaf.txt ============================================================================== --- py/dist/doc/test/plugin/figleaf.txt (original) +++ py/dist/doc/test/plugin/figleaf.txt Wed Aug 19 18:37:53 2009 @@ -24,12 +24,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Added: py/dist/doc/test/plugin/helpconfig.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/plugin/helpconfig.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,31 @@ + +pytest_helpconfig plugin +======================== + +provide version info, conftest/environment config names. + +.. contents:: + :local: + + + +command line options +-------------------- + + +``--help-config`` + show available conftest.py and ENV-variable names. +``--version`` + display py lib version and import information. + +Start improving this plugin in 30 seconds +========================================= + + +1. Download `pytest_helpconfig.py`_ plugin source code +2. put it somewhere as ``pytest_helpconfig.py`` into your import path +3. a subsequent ``py.test`` run will use your local version + +Checkout customize_, other plugins_ or `get in contact`_. + +.. include:: links.txt Modified: py/dist/doc/test/plugin/hooklog.txt ============================================================================== --- py/dist/doc/test/plugin/hooklog.txt (original) +++ py/dist/doc/test/plugin/hooklog.txt Wed Aug 19 18:37:53 2009 @@ -20,12 +20,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/hookspec.txt ============================================================================== --- py/dist/doc/test/plugin/hookspec.txt (original) +++ py/dist/doc/test/plugin/hookspec.txt Wed Aug 19 18:37:53 2009 @@ -97,7 +97,7 @@ """ called before test session finishes. """ pytest__teardown_final.firstresult = True - def pytest__teardown_final_logerror(rep): + def pytest__teardown_final_logerror(report): """ called if runtest_teardown_final failed. """ # ------------------------------------------------------------------------- @@ -114,8 +114,8 @@ # hooks for influencing reporting (invoked from pytest_terminal) # ------------------------------------------------------------------------- - def pytest_report_teststatus(rep): - """ return shortletter and verbose word. """ + def pytest_report_teststatus(report): + """ return result-category, shortletter and verbose word for reporting.""" pytest_report_teststatus.firstresult = True def pytest_terminal_summary(terminalreporter): Modified: py/dist/doc/test/plugin/index.txt ============================================================================== --- py/dist/doc/test/plugin/index.txt (original) +++ py/dist/doc/test/plugin/index.txt Wed Aug 19 18:37:53 2009 @@ -1,6 +1,6 @@ -Plugins related to Python test functions and programs -===================================================== +plugins for Python test functions +================================= xfail_ mark python test functions as expected-to-fail and report them separately. @@ -13,19 +13,21 @@ recwarn_ helpers for asserting deprecation and other warnings. -Plugins for other testing styles and languages +plugins for other testing styles and languages ============================================== +oejskit_ run javascript tests in real life browsers + unittest_ automatically discover and run traditional "unittest.py" style tests. -doctest_ collect and execute doctests from modules and test files. +nose_ nose-compatibility plugin: allow to run nose test suites natively. -oejskit_ run javascript tests in real life browsers +doctest_ collect and execute doctests from modules and test files. restdoc_ perform ReST syntax, local and remote reference tests on .rst/.txt files. -Plugins for generic reporting and failure logging +plugins for generic reporting and failure logging ================================================= pastebin_ submit failure or test session information to a pastebin service. @@ -35,8 +37,20 @@ terminal_ Implements terminal reporting of the full testing process. -internal plugins / core functionality -===================================== +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. + + +misc plugins / core functionality +================================= + +helpconfig_ provide version info, conftest/environment config names. pdb_ interactive debugging with the Python Debugger. Modified: py/dist/doc/test/plugin/keyword.txt ============================================================================== --- py/dist/doc/test/plugin/keyword.txt (original) +++ py/dist/doc/test/plugin/keyword.txt Wed Aug 19 18:37:53 2009 @@ -35,12 +35,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/links.txt ============================================================================== --- py/dist/doc/test/plugin/links.txt (original) +++ py/dist/doc/test/plugin/links.txt Wed Aug 19 18:37:53 2009 @@ -1,33 +1,37 @@ +.. _`helpconfig`: helpconfig.html .. _`terminal`: terminal.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/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 +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_monkeypatch.py +.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/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 +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_capture.py +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/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 +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_restdoc.py .. _`xfail`: xfail.html -.. _`contact`: ../../contact.html +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_pastebin.py +.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_figleaf.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_hooklog.py .. _`checkout the py.test development version`: ../../download.html#checkout +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html -.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_xfail.py +.. _`doctest`: doctest.html +.. _`get in contact`: ../../contact.html +.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/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 +.. _`customize`: ../customize.html +.. _`hooklog`: hooklog.html +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/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 +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/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 +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_unittest.py +.. _`nose`: nose.html +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_resultlog.py .. _`pdb`: pdb.html Modified: py/dist/doc/test/plugin/monkeypatch.txt ============================================================================== --- py/dist/doc/test/plugin/monkeypatch.txt (original) +++ py/dist/doc/test/plugin/monkeypatch.txt Wed Aug 19 18:37:53 2009 @@ -10,7 +10,7 @@ Usage ---------------- -Use the `monkeypatch funcarg`_ to safely patch the environment +Use the `monkeypatch funcarg`_ to safely patch 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 @@ -26,7 +26,16 @@ 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. +for an extensive discussion. + +To add to a possibly existing environment parameter you +can use this example: + +.. sourcecode:: python + + def test_mypath_finding(monkeypatch): + monkeypatch.setenv('PATH', 'x/y', prepend=":") + # x/y will be at the beginning of $PATH .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ @@ -50,12 +59,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Added: py/dist/doc/test/plugin/nose.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/plugin/nose.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,50 @@ + +pytest_nose plugin +================== + +nose-compatibility plugin: allow to run nose test suites natively. + +.. contents:: + :local: + +This is an experimental plugin for allowing to run tests written +in 'nosetests' style with py.test. + +Usage +------------- + +type:: + + py.test # instead of 'nosetests' + +and you should be able to run nose style tests. You will of course +get py.test style reporting and its feature set. + +Issues? +---------------- + +If you find issues or have suggestions please run:: + + py.test --pastebin=all + +and send the resulting URL to a some contact channel. + +Known issues +------------------ + +- nose-style doctests are not collected and executed correctly, + also fixtures don't work. + +- no nose-configuration is recognized + +Start improving this plugin in 30 seconds +========================================= + + +1. Download `pytest_nose.py`_ plugin source code +2. put it somewhere as ``pytest_nose.py`` into your import path +3. a subsequent ``py.test`` run will use your local version + +Checkout customize_, other plugins_ or `get in contact`_. + +.. include:: links.txt Modified: py/dist/doc/test/plugin/pastebin.txt ============================================================================== --- py/dist/doc/test/plugin/pastebin.txt (original) +++ py/dist/doc/test/plugin/pastebin.txt Wed Aug 19 18:37:53 2009 @@ -14,7 +14,7 @@ py.test --pastebin=failed -This will submit full failure information to a remote Paste service and +This will submit test run 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. @@ -35,12 +35,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/pdb.txt ============================================================================== --- py/dist/doc/test/plugin/pdb.txt (original) +++ py/dist/doc/test/plugin/pdb.txt Wed Aug 19 18:37:53 2009 @@ -20,12 +20,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/recwarn.txt ============================================================================== --- py/dist/doc/test/plugin/recwarn.txt (original) +++ py/dist/doc/test/plugin/recwarn.txt Wed Aug 19 18:37:53 2009 @@ -50,12 +50,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/restdoc.txt ============================================================================== --- py/dist/doc/test/plugin/restdoc.txt (original) +++ py/dist/doc/test/plugin/restdoc.txt Wed Aug 19 18:37:53 2009 @@ -24,12 +24,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/resultlog.txt ============================================================================== --- py/dist/doc/test/plugin/resultlog.txt (original) +++ py/dist/doc/test/plugin/resultlog.txt Wed Aug 19 18:37:53 2009 @@ -20,12 +20,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/terminal.txt ============================================================================== --- py/dist/doc/test/plugin/terminal.txt (original) +++ py/dist/doc/test/plugin/terminal.txt Wed Aug 19 18:37:53 2009 @@ -28,12 +28,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/unittest.txt ============================================================================== --- py/dist/doc/test/plugin/unittest.txt (original) +++ py/dist/doc/test/plugin/unittest.txt Wed Aug 19 18:37:53 2009 @@ -23,12 +23,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/plugin/xfail.txt ============================================================================== --- py/dist/doc/test/plugin/xfail.txt (original) +++ py/dist/doc/test/plugin/xfail.txt Wed Aug 19 18:37:53 2009 @@ -25,12 +25,10 @@ ========================================= -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_. +Checkout customize_, other plugins_ or `get in contact`_. .. include:: links.txt Modified: py/dist/doc/test/quickstart.txt ============================================================================== --- py/dist/doc/test/quickstart.txt (original) +++ py/dist/doc/test/quickstart.txt Wed Aug 19 18:37:53 2009 @@ -7,27 +7,61 @@ .. _here: ../download.html#no-setuptools -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:: + +With a `setuptools installation`_ (otherwise see here_) you can type:: easy_install -U py -Now open a file ``test_sample.py`` file and put the following -example content into it:: +On Linux systems you may need to execute this as the superuser and +on Windows you might need to write down the full path to ``easy_install``. + +Now create a file ``test_sample.py`` with the following content: + +.. sourcecode:: python # content of test_sample.py + def func(x): + return x + 1 def test_answer(): - assert 42 == 43 + assert f(3) == 5 You can now run the test file like this:: - py.test test_sample.py + py.test test_sample.py + +and will see output like this: + +.. sourcecode:: python + + =========================== test session starts ============================ + python: platform linux2 -- Python 2.6.2 + test object 1: test_sample.py + + test_sample.py F + + ================================= FAILURES ================================= + _______________________________ test_answer ________________________________ + + def test_answer(): + > assert func(3) == 5 + E assert 4 == 5 + E + where 4 = func(3) + + test_sample.py:6: AssertionError + ========================= 1 failed in 0.08 seconds ========================= + +This output contains Python interpreter information, a list of test objects, +a progress report and important details of the failure. + +**Where to go from here** + +`tutorials`_: a collection of starting points with code examples + +`features`_: overview and description of test 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. +`contact`_: many ways for feedback and questions +.. _`contact`: ../contact.html .. _`automatically collected`: features.html#autocollect .. _download: ../download.html .. _features: features.html Added: py/dist/doc/test/talks.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/talks.txt Wed Aug 19 18:37:53 2009 @@ -0,0 +1,61 @@ +========================== +Talks and Tutorials +========================== + +.. _`funcargs`: funcargs.html + +tutorial examples and blog postings +--------------------------------------------- + +function arguments: + +- `application setup in test functions with funcargs`_ +- `making funcargs dependendent on command line options`_ +- `monkey patching done right`_ (blog post, consult `monkeypatch + plugin`_ for actual 1.0 API) + +test parametrization: + +- `generating parametrized tests with funcargs`_ +- `parametrizing tests, generalized`_ (blog post) +- `putting test-hooks into local or global plugins`_ (blog post) + +distributed testing: + +- `simultanously test your code on all platforms`_ (blog entry) + +plugin specific examples: + +- `many examples in the docs for plugins`_ + +.. _`making funcargs dependendent on command line options`: funcargs.html#tut-cmdlineoption +.. _`many examples in the docs for plugins`: plugin/index.html +.. _`monkeypatch plugin`: plugin/monkeypatch.html +.. _`application setup in test functions with funcargs`: funcargs.html#appsetup +.. _`simultanously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/ +.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ +.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/ +.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ +.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators + +conference talks and tutorials +---------------------------------------- + +- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009): + + - testing terminology + - basic py.test usage, file system layout + - test function arguments (funcargs_) and test fixtures + - existing plugins + - distributed testing + +- `ep2009-pytest.pdf`_ 60 minute py.test talk, highlighting unique features and a roadmap (July 2009) + +- `pycon2009-pytest-introduction.zip`_ slides and files, extended version of py.test basic introduction, discusses more options, also introduces old-style xUnit setup, looponfailing and other features. + +- `pycon2009-pytest-advanced.pdf`_ contain a slightly older version of funcargs and distributed testing, compared to the EuroPython 2009 slides. + +.. _`ep2009-rapidtesting.pdf`: http://codespeak.net/download/py/ep2009-rapidtesting.pdf +.. _`ep2009-pytest.pdf`: http://codespeak.net/download/py/ep2009-pytest.pdf +.. _`pycon2009-pytest-introduction.zip`: http://codespeak.net/download/py/pycon2009-pytest-introduction.zip +.. _`pycon2009-pytest-advanced.pdf`: http://codespeak.net/download/py/pycon2009-pytest-advanced.pdf Added: py/dist/doc/test/test.html ============================================================================== --- (empty file) +++ py/dist/doc/test/test.html Wed Aug 19 18:37:53 2009 @@ -0,0 +1,18 @@ + + + + + + + + + + + Deleted: /py/dist/doc/test/test.txt ============================================================================== --- /py/dist/doc/test/test.txt Wed Aug 19 18:37:53 2009 +++ (empty file) @@ -1,37 +0,0 @@ -======================================= -py.test documentation index -======================================= - -the project independent ``py.test`` command line tool helps you to: - -* rapidly collect and run tests -* run unit- or doctests, functional or integration tests -* distribute tests to multiple environments -* 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 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 -.. _extend: extend.html -.. _config: config.html -.. _`distributed testing`: dist.html - - Modified: py/dist/doc/test/xunit_setup.txt ============================================================================== --- py/dist/doc/test/xunit_setup.txt (original) +++ py/dist/doc/test/xunit_setup.txt Wed Aug 19 18:37:53 2009 @@ -7,12 +7,10 @@ Note: - Since version 1.0 py.test offers funcargs_ for both - simple and complex test setup needs. Especially - for functional and integration, but also for unit testing, it is - highly recommended that you use this new method. + Since version 1.0 funcargs_ present the recommended way + to manage flexible and scalable test setups. -Python, Java and other languages have a tradition +Python, Java and many other languages have a tradition of using xUnit_ style testing. This typically involves the call of a ``setup`` method before a test function is run and ``teardown`` after Modified: py/dist/doc/xml.txt ============================================================================== --- py/dist/doc/xml.txt (original) +++ py/dist/doc/xml.txt Wed Aug 19 18:37:53 2009 @@ -166,4 +166,4 @@ your Tags. Hum, it's probably harder to explain this than to actually code it :-) -.. _`py.test`: test/test.html +.. _`py.test`: test/index.html Modified: py/dist/example/assertion/failure_demo.py ============================================================================== --- py/dist/example/assertion/failure_demo.py (original) +++ py/dist/example/assertion/failure_demo.py Wed Aug 19 18:37:53 2009 @@ -11,6 +11,13 @@ assert (a == b) +def test_generative(param1, param2): + assert param1 * 2 < param2 + +def pytest_generate_tests(metafunc): + if 'param1' in metafunc.funcargnames: + metafunc.addcall(funcargs=dict(param1=3, param2=6)) + class TestFailing(object): def test_simple(self): def f(): @@ -96,14 +103,9 @@ 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(): Modified: py/dist/example/assertion/test_failures.py ============================================================================== --- py/dist/example/assertion/test_failures.py (original) +++ py/dist/example/assertion/test_failures.py Wed Aug 19 18:37:53 2009 @@ -11,4 +11,4 @@ assert failed == 20, failed colreports = reprec.getreports("pytest_collectreport") failed = len([x.failed for x in colreports]) - assert failed == 5 + assert failed == 4 Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Wed Aug 19 18:37:53 2009 @@ -20,7 +20,7 @@ from initpkg import initpkg trunk = None -version = trunk or "1.0.0" +version = trunk or "1.0.1" initpkg(__name__, description = "py.test and pylib: advanced testing tool and networking lib", @@ -52,7 +52,7 @@ '_com.Registry' : ('./_com.py', 'Registry'), '_com.MultiCall' : ('./_com.py', 'MultiCall'), '_com.comregistry' : ('./_com.py', 'comregistry'), - '_com.Hooks' : ('./_com.py', 'Hooks'), + '_com.HookRelay' : ('./_com.py', 'HookRelay'), # py lib cmdline tools 'cmdline.pytest' : ('./cmdline/pytest.py', 'main',), Modified: py/dist/py/_com.py ============================================================================== --- py/dist/py/_com.py (original) +++ py/dist/py/_com.py Wed Aug 19 18:37:53 2009 @@ -5,67 +5,53 @@ import py class MultiCall: - """ Manage a specific call into many python functions/methods. + """ execute a call into multiple python functions/methods. """ - Simple example: - MultiCall([list1.append, list2.append], 42).execute() - """ - - def __init__(self, methods, *args, **kwargs): + def __init__(self, methods, kwargs, firstresult=False): self.methods = methods[:] - self.args = args - self.kwargs = kwargs + self.kwargs = kwargs.copy() + self.kwargs['__multicall__'] = self self.results = [] + self.firstresult = firstresult + + def __repr__(self): + status = "%d results, %d meths" % (len(self.results), len(self.methods)) + return "" %(status, self.kwargs) - def execute(self, firstresult=False): + def execute(self): while self.methods: - currentmethod = self.methods.pop() - res = self.execute_method(currentmethod) - if hasattr(self, '_ex1'): - self.results = [res] - break + method = self.methods.pop() + kwargs = self.getkwargs(method) + res = method(**kwargs) if res is not None: self.results.append(res) - if firstresult: - break - if not firstresult: + if self.firstresult: + return res + if not self.firstresult: return self.results - if self.results: - return self.results[-1] - def execute_method(self, currentmethod): - self.currentmethod = currentmethod - # provide call introspection if "__call__" is the first positional argument - if hasattr(currentmethod, 'im_self'): - varnames = currentmethod.im_func.func_code.co_varnames - needscall = varnames[1:2] == ('__call__',) - else: + def getkwargs(self, method): + kwargs = {} + for argname in varnames(method): try: - varnames = currentmethod.func_code.co_varnames - except AttributeError: - # builtin function - varnames = () - needscall = varnames[:1] == ('__call__',) - if needscall: - return currentmethod(self, *self.args, **self.kwargs) - else: - #try: - return currentmethod(*self.args, **self.kwargs) - #except TypeError: - # print currentmethod.__module__, currentmethod.__name__, self.args, self.kwargs - # raise - - def exclude_other_results(self): - self._ex1 = True - + kwargs[argname] = self.kwargs[argname] + except KeyError: + pass # might be optional param + return kwargs + +def varnames(rawcode): + ismethod = hasattr(rawcode, 'im_self') + rawcode = getattr(rawcode, 'im_func', rawcode) + rawcode = getattr(rawcode, 'func_code', rawcode) + try: + return rawcode.co_varnames[ismethod:] + except AttributeError: + return () class Registry: """ - Manage Plugins: Load plugins and manage calls to plugins. + Manage Plugins: register/unregister call calls to plugins. """ - logfile = None - MultiCall = MultiCall - def __init__(self, plugins=None): if plugins is None: plugins = [] @@ -73,6 +59,7 @@ def register(self, plugin): assert not isinstance(plugin, str) + assert not plugin in self._plugins self._plugins.append(plugin) def unregister(self, plugin): @@ -97,45 +84,39 @@ l.reverse() return l -class Hooks: - def __init__(self, hookspecs, registry=None): +class HookRelay: + def __init__(self, hookspecs, registry): self._hookspecs = hookspecs - if registry is None: - registry = py._com.comregistry - self.registry = registry + self._registry = registry for name, method in vars(hookspecs).items(): if name[:1] != "_": - firstresult = getattr(method, 'firstresult', False) - mm = HookCall(registry, name, firstresult=firstresult) - setattr(self, name, mm) - def __repr__(self): - return "" %(self._hookspecs, self.registry) + setattr(self, name, self._makecall(name)) -class HookCall: - def __init__(self, registry, name, firstresult, extralookup=None): - self.registry = registry + def _makecall(self, name, extralookup=None): + hookspecmethod = getattr(self._hookspecs, name) + firstresult = getattr(hookspecmethod, 'firstresult', False) + return HookCaller(self, name, firstresult=firstresult, + extralookup=extralookup) + + def _getmethods(self, name, extralookup=()): + return self._registry.listattr(name, extra=extralookup) + + def _performcall(self, name, multicall): + return multicall.execute() + +class HookCaller: + def __init__(self, hookrelay, name, firstresult, extralookup=()): + self.hookrelay = hookrelay self.name = name self.firstresult = firstresult self.extralookup = extralookup and [extralookup] or () - def clone(self, extralookup): - return HookCall(self.registry, self.name, self.firstresult, extralookup) - def __repr__(self): - mode = self.firstresult and "firstresult" or "each" - return "" %(self.name, mode, self.registry) - - def __call__(self, *args, **kwargs): - if args: - raise TypeError("only keyword arguments allowed " - "for api call to %r" % self.name) - attr = self.registry.listattr(self.name, extra=self.extralookup) - mc = MultiCall(attr, **kwargs) - # XXX this should be doable from a hook impl: - if self.registry.logfile: - self.registry.logfile.write("%s(**%s) # firstresult=%s\n" % - (self.name, kwargs, self.firstresult)) - self.registry.logfile.flush() - return mc.execute(firstresult=self.firstresult) + return "" %(self.name,) -comregistry = Registry() + def __call__(self, **kwargs): + methods = self.hookrelay._getmethods(self.name, self.extralookup) + mc = MultiCall(methods, kwargs, firstresult=self.firstresult) + return self.hookrelay._performcall(self.name, mc) + +comregistry = Registry([]) Deleted: /py/dist/py/bin/_genscripts.py ============================================================================== --- /py/dist/py/bin/_genscripts.py Wed Aug 19 18:37:53 2009 +++ (empty file) @@ -1,34 +0,0 @@ -from _findpy import py - -mydir = py.magic.autopath().dirpath() - -def getbasename(name): - assert name[:2] == "py" - return "py." + name[2:] - -def genscript_unix(name): - basename = getbasename(name) - path = mydir.join(basename) - path.write(py.code.Source(""" - #!/usr/bin/env python - from _findpy import py - py.cmdline.%s() - """ % name).strip()) - path.chmod(0755) - -def genscript_windows(name): - basename = getbasename(name) - winbasename = basename + ".cmd" - path = mydir.join("win32").join(winbasename) - path.write(py.code.Source(""" - @echo off - python "%%~dp0\..\%s" %%* - """ % (basename)).strip()) - -if __name__ == "__main__": - for name in dir(py.cmdline): - if name[0] != "_": - genscript_unix(name) - genscript_windows(name) - - Deleted: /py/dist/py/bin/gendoc.py ============================================================================== --- /py/dist/py/bin/gendoc.py Wed Aug 19 18:37:53 2009 +++ (empty file) @@ -1,23 +0,0 @@ -#!/usr/bin/env python - -import sys -import os -from _findpy import py -try: - import apigen -except ImportError: - print 'Can not find apigen - make sure PYTHONPATH is set correctly!' - py.std.sys.exit() -else: - args = list(sys.argv[1:]) - args.extend(['-p', 'apigen']) - argkeys = [a.split('=')[0] for a in args] - if '--apigen' not in argkeys: - args.append('--apigen') - if '--apigenscript' not in argkeys: - fpath = os.path.join( - os.path.dirname(apigen.__file__), 'tool', 'py_build', 'build.py') - args.append('--apigenscript=%s' % (fpath,)) - if '--apigenpath' not in argkeys: - args.append('--apigenpath=api') - py.test.cmdline.main(args) Modified: py/dist/py/code/excinfo.py ============================================================================== --- py/dist/py/code/excinfo.py (original) +++ py/dist/py/code/excinfo.py Wed Aug 19 18:37:53 2009 @@ -39,8 +39,7 @@ """ lines = py.std.traceback.format_exception_only(self.type, self.value) text = ''.join(lines) - if text.endswith('\n'): - text = text[:-1] + text = text.rstrip() if tryshort: if text.startswith(self._striptext): text = text[len(self._striptext):] Modified: py/dist/py/code/testing/test_excinfo.py ============================================================================== --- py/dist/py/code/testing/test_excinfo.py (original) +++ py/dist/py/code/testing/test_excinfo.py Wed Aug 19 18:37:53 2009 @@ -200,6 +200,11 @@ def test_excinfo_exconly(): excinfo = py.test.raises(ValueError, h) assert excinfo.exconly().startswith('ValueError') + excinfo = py.test.raises(ValueError, + "raise ValueError('hello\\nworld')") + msg = excinfo.exconly(tryshort=True) + assert msg.startswith('ValueError') + assert msg.endswith("world") def test_excinfo_repr(): excinfo = py.test.raises(ValueError, h) @@ -242,7 +247,6 @@ assert s.startswith("def get") def test_codepath_Queue_example(): - py.test.skip("try harder to get at the paths of code objects.") import Queue try: Queue.Queue().get(timeout=0.001) Modified: py/dist/py/code/testing/test_source.py ============================================================================== --- py/dist/py/code/testing/test_source.py (original) +++ py/dist/py/code/testing/test_source.py Wed Aug 19 18:37:53 2009 @@ -173,8 +173,8 @@ assert len(source) == 6 assert source.getstatementrange(2) == (1, 4) + @py.test.mark.xfail def test_getstatementrange_bug2(self): - py.test.skip("fix me (issue19)") source = Source("""\ assert ( 33 @@ -300,8 +300,8 @@ lines = deindent(source.splitlines()) assert lines == ['', 'def f():', ' def g():', ' pass', ' '] + at py.test.mark.xfail def test_source_of_class_at_eof_without_newline(): - py.test.skip("CPython's inspect.getsource is buggy") # this test fails because the implicit inspect.getsource(A) below # does not return the "x = 1" last line. tmpdir = py.test.ensuretemp("source_write_read") Modified: py/dist/py/execnet/gateway.py ============================================================================== --- py/dist/py/execnet/gateway.py (original) +++ py/dist/py/execnet/gateway.py Wed Aug 19 18:37:53 2009 @@ -88,8 +88,8 @@ self._channelfactory = ChannelFactory(self, _startcount) self._cleanup.register(self) if _startcount == 1: # only import 'py' on the "client" side - from py._com import Hooks - self.hook = Hooks(ExecnetAPI) + import py + self.hook = py._com.HookRelay(ExecnetAPI, py._com.comregistry) else: self.hook = ExecnetAPI() Modified: py/dist/py/execnet/gwmanage.py ============================================================================== --- py/dist/py/execnet/gwmanage.py (original) +++ py/dist/py/execnet/gwmanage.py Wed Aug 19 18:37:53 2009 @@ -21,7 +21,8 @@ if not spec.chdir and not spec.popen: spec.chdir = defaultchdir self.specs.append(spec) - self.hook = py._com.Hooks(py.execnet._HookSpecs) + self.hook = py._com.HookRelay( + py.execnet._HookSpecs, py._com.comregistry) def makegateways(self): assert not self.gateways Modified: py/dist/py/io/stdcapture.py ============================================================================== --- py/dist/py/io/stdcapture.py (original) +++ py/dist/py/io/stdcapture.py Wed Aug 19 18:37:53 2009 @@ -76,7 +76,7 @@ os.close(fd) if out: tmpfile = None - if isinstance(out, file): + if hasattr(out, 'write'): tmpfile = out self.out = py.io.FDCapture(1, tmpfile=tmpfile) if patchsys: @@ -84,7 +84,7 @@ if err: if mixed and out: tmpfile = self.out.tmpfile - elif isinstance(err, file): + elif hasattr(err, 'write'): tmpfile = err else: tmpfile = None Modified: py/dist/py/io/terminalwriter.py ============================================================================== --- py/dist/py/io/terminalwriter.py (original) +++ py/dist/py/io/terminalwriter.py Wed Aug 19 18:37:53 2009 @@ -139,6 +139,7 @@ Black=40, Red=41, Green=42, Yellow=43, Blue=44, Purple=45, Cyan=46, White=47, bold=1, light=2, blink=5, invert=7) + _encoding = "utf-8" def __init__(self, file=None, stringio=False): if file is None: @@ -194,58 +195,27 @@ def write(self, s, **kw): if s: - s = str(s) + s = self._getbytestring(s) if self.hasmarkup and kw: s = self.markup(s, **kw) self._file.write(s) - self._file.flush() + self._file.flush() + + def _getbytestring(self, s): + if isinstance(s, unicode): + return s.encode(self._encoding) + elif not isinstance(s, str): + return str(s) + return s def line(self, s='', **kw): self.write(s, **kw) self.write('\n') -class Win32ConsoleWriter(object): - - def __init__(self, file=None, stringio=False): - if file is None: - if stringio: - self.stringio = file = py.std.cStringIO.StringIO() - else: - file = py.std.sys.stdout - elif callable(file): - file = WriteFile(file) - self._file = file - self.fullwidth = get_terminal_width() - self.hasmarkup = should_do_markup(file) - - def sep(self, sepchar, title=None, fullwidth=None, **kw): - if fullwidth is None: - fullwidth = self.fullwidth - # the goal is to have the line be as long as possible - # under the condition that len(line) <= fullwidth - if title is not None: - # we want 2 + 2*len(fill) + len(title) <= fullwidth - # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth - # 2*len(sepchar)*N <= fullwidth - len(title) - 2 - # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) - N = (fullwidth - len(title) - 2) // (2*len(sepchar)) - fill = sepchar * N - line = "%s %s %s" % (fill, title, fill) - else: - # we want len(sepchar)*N <= fullwidth - # i.e. N <= fullwidth // len(sepchar) - line = sepchar * (fullwidth // len(sepchar)) - # in some situations there is room for an extra sepchar at the right, - # in particular if we consider that with a sepchar like "_ " the - # trailing space is not important at the end of the line - if len(line) + len(sepchar.rstrip()) <= fullwidth: - line += sepchar.rstrip() - - self.line(line, **kw) - +class Win32ConsoleWriter(TerminalWriter): def write(self, s, **kw): if s: - s = str(s) + s = self._getbytestring(s) if self.hasmarkup: handle = GetStdHandle(STD_OUTPUT_HANDLE) @@ -269,8 +239,8 @@ if self.hasmarkup: SetConsoleTextAttribute(handle, FOREGROUND_WHITE) - def line(self, s='', **kw): - self.write(s + '\n', **kw) + def line(self, s="", **kw): + self.write(s+"\n", **kw) if sys.platform == 'win32': TerminalWriter = Win32ConsoleWriter Modified: py/dist/py/io/testing/test_terminalwriter.py ============================================================================== --- py/dist/py/io/testing/test_terminalwriter.py (original) +++ py/dist/py/io/testing/test_terminalwriter.py Wed Aug 19 18:37:53 2009 @@ -37,6 +37,16 @@ assert len(l) == 1 assert l[0] == "hello\n" + def test_line_unicode(self): + tw = self.getwriter() + for encoding in 'utf8', 'latin1': + tw._encoding = encoding + msg = unicode('b\u00f6y', 'utf8') + tw.line(msg) + l = self.getlines() + assert not isinstance(l[0], unicode) + assert unicode(l[0], encoding) == msg + "\n" + def test_sep_no_title(self): tw = self.getwriter() tw.sep("-", fullwidth=60) @@ -85,6 +95,16 @@ l = self.getlines() assert len(l[0]) == len(l[1]) +class TestTmpfile(BaseTests): + def getwriter(self): + self.path = py.test.config.ensuretemp("terminalwriter").ensure("tmpfile") + self.tw = py.io.TerminalWriter(self.path.open('w+')) + return self.tw + def getlines(self): + io = self.tw._file + io.flush() + return self.path.open('r').readlines() + class TestStringIO(BaseTests): def getwriter(self): self.tw = py.io.TerminalWriter(stringio=True) Modified: py/dist/py/misc/testing/test_com.py ============================================================================== --- py/dist/py/misc/testing/test_com.py (original) +++ py/dist/py/misc/testing/test_com.py Wed Aug 19 18:37:53 2009 @@ -1,92 +1,89 @@ import py import os -from py._com import Registry, MultiCall -from py._com import Hooks - -pytest_plugins = "xfail" +from py.__._com import Registry, MultiCall, HookRelay, varnames +def test_varnames(): + def f(x): + pass + class A: + def f(self, y): + pass + assert varnames(f) == ("x",) + assert varnames(A.f) == ('y',) + assert varnames(A().f) == ('y',) + class TestMultiCall: def test_uses_copy_of_methods(self): l = [lambda: 42] - mc = MultiCall(l) + mc = MultiCall(l, {}) + repr(mc) l[:] = [] res = mc.execute() return res == 42 def test_call_passing(self): class P1: - def m(self, __call__, x): - assert __call__.currentmethod == self.m - assert len(__call__.results) == 1 - assert not __call__.methods + def m(self, __multicall__, x): + assert len(__multicall__.results) == 1 + assert not __multicall__.methods return 17 class P2: - def m(self, __call__, x): - assert __call__.currentmethod == self.m - assert __call__.args - assert __call__.results == [] - assert __call__.methods + def m(self, __multicall__, x): + assert __multicall__.results == [] + assert __multicall__.methods return 23 p1 = P1() p2 = P2() - multicall = MultiCall([p1.m, p2.m], 23) + multicall = MultiCall([p1.m, p2.m], {'x': 23}) + assert "23" in repr(multicall) reslist = multicall.execute() assert len(reslist) == 2 # ensure reversed order assert reslist == [23, 17] - def test_optionalcallarg(self): - class P1: - def m(self, x): - return x - call = MultiCall([P1().m], 23) - assert call.execute() == [23] - assert call.execute(firstresult=True) == 23 - - def test_call_subexecute(self): - def m(__call__): - subresult = __call__.execute(firstresult=True) - return subresult + 1 - - def n(): - return 1 + def test_keyword_args(self): + def f(x): + return x + 1 + class A: + def f(self, x, y): + return x + y + multicall = MultiCall([f, A().f], dict(x=23, y=24)) + assert "'x': 23" in repr(multicall) + assert "'y': 24" in repr(multicall) + reslist = multicall.execute() + assert reslist == [24+23, 24] + assert "2 results" in repr(multicall) - call = MultiCall([n, m]) - res = call.execute(firstresult=True) - assert res == 2 + def test_keywords_call_error(self): + multicall = MultiCall([lambda x: x], {}) + py.test.raises(TypeError, "multicall.execute()") - def test_call_exclude_other_results(self): - def m(__call__): - __call__.exclude_other_results() - return 10 + def test_call_subexecute(self): + def m(__multicall__): + subresult = __multicall__.execute() + return subresult + 1 def n(): return 1 - call = MultiCall([n, n, m, n]) + call = MultiCall([n, m], {}, firstresult=True) res = call.execute() - assert res == [10] - # doesn't really make sense for firstresult-mode - because - # we might not have had a chance to run at all. - #res = call.execute(firstresult=True) - #assert res == 10 + assert res == 2 def test_call_none_is_no_result(self): def m1(): return 1 def m2(): return None - mc = MultiCall([m1, m2]) - res = mc.execute(firstresult=True) + res = MultiCall([m1, m2], {}, firstresult=True).execute() assert res == 1 + res = MultiCall([m1, m2], {}).execute() + assert res == [1] class TestRegistry: - def test_MultiCall(self): - plugins = Registry() - assert hasattr(plugins, "MultiCall") def test_register(self): registry = Registry() @@ -130,14 +127,14 @@ def test_api_and_defaults(): assert isinstance(py._com.comregistry, Registry) -class TestHooks: +class TestHookRelay: def test_happypath(self): registry = Registry() class Api: def hello(self, arg): pass - mcm = Hooks(hookspecs=Api, registry=registry) + mcm = HookRelay(hookspecs=Api, registry=registry) assert hasattr(mcm, 'hello') assert repr(mcm.hello).find("hello") != -1 class Plugin: @@ -148,23 +145,21 @@ assert l == [4] assert not hasattr(mcm, 'world') - def test_needskeywordargs(self): + def test_only_kwargs(self): registry = Registry() class Api: def hello(self, arg): pass - mcm = Hooks(hookspecs=Api, registry=registry) - excinfo = py.test.raises(TypeError, "mcm.hello(3)") - assert str(excinfo.value).find("only keyword arguments") != -1 - assert str(excinfo.value).find("hello(self, arg)") + mcm = HookRelay(hookspecs=Api, registry=registry) + py.test.raises(TypeError, "mcm.hello(3)") - def test_firstresult(self): + def test_firstresult_definition(self): registry = Registry() class Api: def hello(self, arg): pass hello.firstresult = True - mcm = Hooks(hookspecs=Api, registry=registry) + mcm = HookRelay(hookspecs=Api, registry=registry) class Plugin: def hello(self, arg): return arg + 1 @@ -174,15 +169,16 @@ def test_default_plugins(self): class Api: pass - mcm = Hooks(hookspecs=Api) - assert mcm.registry == py._com.comregistry + mcm = HookRelay(hookspecs=Api, registry=py._com.comregistry) + assert mcm._registry == py._com.comregistry def test_hooks_extra_plugins(self): registry = Registry() class Api: def hello(self, arg): pass - hook_hello = Hooks(hookspecs=Api, registry=registry).hello + hookrelay = HookRelay(hookspecs=Api, registry=registry) + hook_hello = hookrelay.hello class Plugin: def hello(self, arg): return arg + 1 @@ -190,7 +186,7 @@ class Plugin2: def hello(self, arg): return arg + 2 - newhook = hook_hello.clone(extralookup=Plugin2()) + newhook = hookrelay._makecall("hello", extralookup=Plugin2()) l = newhook(arg=3) assert l == [5, 4] l2 = hook_hello(arg=3) Added: py/dist/py/misc/testing/test_install.py ============================================================================== --- (empty file) +++ py/dist/py/misc/testing/test_install.py Wed Aug 19 18:37:53 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/dist/py/path/svn/testing/test_wccommand.py ============================================================================== --- py/dist/py/path/svn/testing/test_wccommand.py (original) +++ py/dist/py/path/svn/testing/test_wccommand.py Wed Aug 19 18:37:53 2009 @@ -225,6 +225,12 @@ ''' XMLWCStatus.fromstring(xml, self.root) + def test_status_wrong_xml(self): + # testing for XML without author - this used to raise an exception + xml = u'\n\n\n' + st = XMLWCStatus.fromstring(xml, self.root) + assert len(st.incomplete) == 1 + def test_diff(self): p = self.root / 'anotherfile' out = p.diff(rev=2) Modified: py/dist/py/path/svn/wccommand.py ============================================================================== --- py/dist/py/path/svn/wccommand.py (original) +++ py/dist/py/path/svn/wccommand.py Wed Aug 19 18:37:53 2009 @@ -671,6 +671,10 @@ wcpath = rootwcpath.join(path, abs=1) rootstatus.ignored.append(wcpath) continue + elif itemstatus == 'incomplete': + wcpath = rootwcpath.join(path, abs=1) + rootstatus.incomplete.append(wcpath) + continue rev = statusel.getAttribute('revision') if itemstatus == 'added' or itemstatus == 'none': Modified: py/dist/py/test/collect.py ============================================================================== --- py/dist/py/test/collect.py (original) +++ py/dist/py/test/collect.py Wed Aug 19 18:37:53 2009 @@ -4,6 +4,7 @@ that is usually built iteratively. """ import py +pydir = py.path.local(py.__file__).dirpath() def configproperty(name): def fget(self): @@ -166,16 +167,13 @@ if colitem.fspath == fspath or colitem.name == basename: l.append(colitem) if not l: - msg = ("Collector %r does not provide %r colitem " - "existing colitems are: %s" % - (cur, fspath, colitems)) - raise AssertionError(msg) + raise self.config.Error("can't collect: %s" %(fspath,)) if basenames: if len(l) > 1: msg = ("Collector %r has more than one %r colitem " "existing colitems are: %s" % (cur, fspath, colitems)) - raise AssertionError(msg) + raise self.config.Error("xxx-too many test types for: %s" % (fspath, )) cur = l[0] else: if len(l) > 1: @@ -332,6 +330,15 @@ """ return self.collect_by_name(name) + def _prunetraceback(self, traceback): + if hasattr(self, 'fspath'): + path = self.fspath + ntraceback = traceback.cut(path=self.fspath) + if ntraceback == traceback: + ntraceback = ntraceback.cut(excludepath=pydir) + traceback = ntraceback.filter() + return traceback + class FSCollector(Collector): def __init__(self, fspath, parent=None): fspath = py.path.local(fspath) @@ -388,7 +395,12 @@ def _ignore(self, path): ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path) - return ignore_paths and path in ignore_paths + return ignore_paths and path in ignore_paths + # XXX more refined would be: + if ignore_paths: + for p in ignore_paths: + if path == p or path.relto(p): + return True def consider(self, path): if self._ignore(path): Modified: py/dist/py/test/config.py ============================================================================== --- py/dist/py/test/config.py (original) +++ py/dist/py/test/config.py Wed Aug 19 18:37:53 2009 @@ -64,7 +64,7 @@ val = eval(val) opt.default = val else: - name = "pytest_option_" + opt.dest + name = "option_" + opt.dest try: opt.default = self._conftest.rget(name) except (ValueError, KeyError): Modified: py/dist/py/test/defaultconftest.py ============================================================================== --- py/dist/py/test/defaultconftest.py (original) +++ py/dist/py/test/defaultconftest.py Wed Aug 19 18:37:53 2009 @@ -10,6 +10,5 @@ Function = py.test.collect.Function Instance = py.test.collect.Instance -pytest_plugins = "default runner capture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb pastebin unittest".split() +pytest_plugins = "default runner capture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb pastebin unittest helpconfig nose".split() -conf_capture = "fd" Modified: py/dist/py/test/dist/dsession.py ============================================================================== --- py/dist/py/test/dist/dsession.py (original) +++ py/dist/py/test/dist/dsession.py Wed Aug 19 18:37:53 2009 @@ -77,8 +77,8 @@ self.item2nodes = {} super(DSession, self).__init__(config=config) - #def pytest_configure(self, __call__, config): - # __call__.execute() + #def pytest_configure(self, __multicall__, config): + # __multicall__.execute() # try: # config.getxspecs() # except config.Error: Modified: py/dist/py/test/dist/testing/acceptance_test.py ============================================================================== --- py/dist/py/test/dist/testing/acceptance_test.py (original) +++ py/dist/py/test/dist/testing/acceptance_test.py Wed Aug 19 18:37:53 2009 @@ -65,7 +65,7 @@ """, ) testdir.makeconftest(""" - pytest_option_tx = 'popen popen popen'.split() + option_tx = 'popen popen popen'.split() """) result = testdir.runpytest(p1, '-d') result.stdout.fnmatch_lines([ Modified: py/dist/py/test/dist/txnode.py ============================================================================== --- py/dist/py/test/dist/txnode.py (original) +++ py/dist/py/test/dist/txnode.py Wed Aug 19 18:37:53 2009 @@ -145,11 +145,9 @@ 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 + # we fake a setup-error report with the obtained exception + # and do not care about capturing or non-runner 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/dist/py/test/plugin/conftest.py ============================================================================== --- py/dist/py/test/plugin/conftest.py (original) +++ py/dist/py/test/plugin/conftest.py Wed Aug 19 18:37:53 2009 @@ -15,10 +15,15 @@ # testdir.plugins.append(obj.testplugin) # break #else: - basename = request.module.__name__.split(".")[-1] - if basename.startswith("pytest_"): + modname = request.module.__name__.split(".")[-1] + if modname.startswith("pytest_"): testdir.plugins.append(vars(request.module)) - testdir.plugins.append(basename) + testdir.plugins.append(modname) + #elif modname.startswith("test_pytest"): + # pname = modname[5:] + # assert pname not in testdir.plugins + # testdir.plugins.append(pname) + # #testdir.plugins.append(vars(request.module)) else: pass # raise ValueError("need better support code") return testdir Modified: py/dist/py/test/plugin/hookspec.py ============================================================================== --- py/dist/py/test/plugin/hookspec.py (original) +++ py/dist/py/test/plugin/hookspec.py Wed Aug 19 18:37:53 2009 @@ -91,7 +91,7 @@ """ called before test session finishes. """ pytest__teardown_final.firstresult = True -def pytest__teardown_final_logerror(rep): +def pytest__teardown_final_logerror(report): """ called if runtest_teardown_final failed. """ # ------------------------------------------------------------------------- @@ -108,8 +108,8 @@ # hooks for influencing reporting (invoked from pytest_terminal) # ------------------------------------------------------------------------- -def pytest_report_teststatus(rep): - """ return shortletter and verbose word. """ +def pytest_report_teststatus(report): + """ return result-category, shortletter and verbose word for reporting.""" pytest_report_teststatus.firstresult = True def pytest_terminal_summary(terminalreporter): Modified: py/dist/py/test/plugin/pytest__pytest.py ============================================================================== --- py/dist/py/test/plugin/pytest__pytest.py (original) +++ py/dist/py/test/plugin/pytest__pytest.py Wed Aug 19 18:37:53 2009 @@ -47,7 +47,7 @@ recorder = RecordCalls() self._recorders[hookspecs] = recorder self._comregistry.register(recorder) - self.hook = py._com.Hooks(hookspecs, registry=self._comregistry) + self.hook = py._com.HookRelay(hookspecs, registry=self._comregistry) def finish_recording(self): for recorder in self._recorders.values(): Modified: py/dist/py/test/plugin/pytest_capture.py ============================================================================== --- py/dist/py/test/plugin/pytest_capture.py (original) +++ py/dist/py/test/plugin/pytest_capture.py Wed Aug 19 18:37:53 2009 @@ -32,7 +32,7 @@ If you set capturing values in a conftest file like this:: # conftest.py - conf_capture = 'fd' + option_capture = 'fd' then all tests in that directory will execute with "fd" style capturing. @@ -107,18 +107,34 @@ def __init__(self): self._method2capture = {} + def _maketempfile(self): + f = py.std.tempfile.TemporaryFile() + newf = py.io.dupfile(f) + encoding = getattr(newf, 'encoding', None) or "UTF-8" + return EncodedFile(newf, encoding) + + def _makestringio(self): + return py.std.StringIO.StringIO() + def _startcapture(self, method): if method == "fd": - return py.io.StdCaptureFD() + return py.io.StdCaptureFD( + out=self._maketempfile(), err=self._maketempfile() + ) elif method == "sys": - return py.io.StdCapture() + return py.io.StdCapture( + out=self._makestringio(), err=self._makestringio() + ) 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) + try: + return config._conftest.rget("option_capture", path=fspath) + except KeyError: + return "fd" def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) @@ -168,11 +184,11 @@ capfuncarg._finalize() del self._capturing_funcargs - def pytest_make_collect_report(self, __call__, collector): + def pytest_make_collect_report(self, __multicall__, collector): method = self._getmethod(collector.config, collector.fspath) self.resumecapture(method) try: - rep = __call__.execute(firstresult=True) + rep = __multicall__.execute() finally: outerr = self.suspendcapture() addouterr(rep, outerr) @@ -191,11 +207,11 @@ def pytest_runtest_teardown(self, item): self.resumecapture_item(item) - def pytest__teardown_final(self, __call__, session): + def pytest__teardown_final(self, __multicall__, session): method = self._getmethod(session.config, None) self.resumecapture(method) try: - rep = __call__.execute(firstresult=True) + rep = __multicall__.execute() finally: outerr = self.suspendcapture() if rep: @@ -206,9 +222,9 @@ if hasattr(self, '_capturing'): self.suspendcapture() - def pytest_runtest_makereport(self, __call__, item, call): + def pytest_runtest_makereport(self, __multicall__, item, call): self.deactivate_funcargs() - rep = __call__.execute(firstresult=True) + rep = __multicall__.execute() outerr = self.suspendcapture() outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1]) if not rep.passed: @@ -252,3 +268,22 @@ def close(self): self.capture.reset() del self.capture + +class EncodedFile(object): + def __init__(self, _stream, encoding): + self._stream = _stream + self.encoding = encoding + + def write(self, obj): + if isinstance(obj, unicode): + self._stream.write(obj.encode(self.encoding)) + else: + self._stream.write(obj) + + def writelines(self, linelist): + data = ''.join(linelist) + self.write(data) + + def __getattr__(self, name): + return getattr(self._stream, name) + Modified: py/dist/py/test/plugin/pytest_default.py ============================================================================== --- py/dist/py/test/plugin/pytest_default.py (original) +++ py/dist/py/test/plugin/pytest_default.py Wed Aug 19 18:37:53 2009 @@ -1,9 +1,10 @@ """ default hooks and general py.test options. """ +import sys import py -def pytest_pyfunc_call(__call__, pyfuncitem): - if not __call__.execute(firstresult=True): +def pytest_pyfunc_call(__multicall__, pyfuncitem): + if not __multicall__.execute(): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): testfunction(*pyfuncitem._args) @@ -60,13 +61,13 @@ action="store", dest="tbstyle", default='long', type="choice", choices=['long', 'short', 'no'], help="traceback verboseness (long/short/no).") - group._addoption('-p', action="append", dest="plugin", default = [], + group._addoption('-p', action="append", dest="plugins", default = [], help=("load the specified plugin after command line parsing. ")) group._addoption('-f', '--looponfail', action="store_true", dest="looponfail", default=False, help="run tests, re-run failing test set until all pass.") - group = parser.addgroup("test process debugging") + group = parser.addgroup("debugconfig", "test process debugging and configuration") group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", help="base temporary directory for this test run.") @@ -146,7 +147,7 @@ def test_plugin_already_exists(testdir): config = testdir.parseconfig("-p", "default") - assert config.option.plugin == ['default'] + assert config.option.plugins == ['default'] config.pluginmanager.do_configure(config) Modified: py/dist/py/test/plugin/pytest_execnetcleanup.py ============================================================================== --- py/dist/py/test/plugin/pytest_execnetcleanup.py (original) +++ py/dist/py/test/plugin/pytest_execnetcleanup.py Wed Aug 19 18:37:53 2009 @@ -32,10 +32,10 @@ #for gw in l: # gw.join() - def pytest_pyfunc_call(self, __call__, pyfuncitem): + def pytest_pyfunc_call(self, __multicall__, pyfuncitem): if self._gateways is not None: gateways = self._gateways[:] - res = __call__.execute(firstresult=True) + res = __multicall__.execute() while len(self._gateways) > len(gateways): self._gateways[-1].exit() return res Added: py/dist/py/test/plugin/pytest_helpconfig.py ============================================================================== --- (empty file) +++ py/dist/py/test/plugin/pytest_helpconfig.py Wed Aug 19 18:37:53 2009 @@ -0,0 +1,63 @@ +""" provide version info, conftest/environment config names. +""" +import py +import sys + +def pytest_addoption(parser): + group = parser.getgroup('debugconfig') + group.addoption("--help-config", action="store_true", dest="helpconfig", + help="show available conftest.py and ENV-variable names.") + group.addoption('--version', action="store_true", + help="display py lib version and import information.") + +def pytest_configure(__multicall__, config): + if config.option.version: + p = py.path.local(py.__file__).dirpath() + sys.stderr.write("This is py.test version %s, imported from %s\n" % + (py.__version__, p)) + sys.exit(0) + if not config.option.helpconfig: + return + __multicall__.execute() + options = [] + for group in config._parser._groups: + options.extend(group.options) + widths = [0] * 10 + tw = py.io.TerminalWriter() + tw.sep("-") + tw.line("%-13s | %-18s | %-25s | %s" %( + "cmdline name", "conftest.py name", "ENV-variable name", "help")) + tw.sep("-") + + options = [opt for opt in options if opt._long_opts] + options.sort(lambda x, y: cmp(x._long_opts, y._long_opts)) + for opt in options: + if not opt._long_opts: + continue + optstrings = list(opt._long_opts) # + list(opt._short_opts) + optstrings = filter(None, optstrings) + optstring = "|".join(optstrings) + line = "%-13s | %-18s | %-25s | %s" %( + optstring, + "option_%s" % opt.dest, + "PYTEST_OPTION_%s" % opt.dest.upper(), + opt.help and opt.help or "", + ) + tw.line(line[:tw.fullwidth]) + for name, help in conftest_options: + line = "%-13s | %-18s | %-25s | %s" %( + "", + name, + "", + help, + ) + tw.line(line[:tw.fullwidth]) + + tw.sep("-") + sys.exit(0) + +conftest_options = ( + ('pytest_plugins', 'list of plugin names to load'), + ('collect_ignore', '(relative) paths ignored during collection'), + ('rsyncdirs', 'to-be-rsynced directories for dist-testing'), +) Modified: py/dist/py/test/plugin/pytest_hooklog.py ============================================================================== --- py/dist/py/test/plugin/pytest_hooklog.py (original) +++ py/dist/py/test/plugin/pytest_hooklog.py Wed Aug 19 18:37:53 2009 @@ -8,14 +8,27 @@ def pytest_configure(config): hooklog = config.getvalue("hooklog") if hooklog: - assert not config.pluginmanager.comregistry.logfile - config.pluginmanager.comregistry.logfile = open(hooklog, 'w') + config._hooklogfile = open(hooklog, 'w', 0) + config._hooklog_oldperformcall = config.hook._performcall + config.hook._performcall = (lambda name, multicall: + logged_call(name=name, multicall=multicall, config=config)) + +def logged_call(name, multicall, config): + f = config._hooklogfile + f.write("%s(**%s)\n" % (name, multicall.kwargs)) + try: + res = config._hooklog_oldperformcall(name=name, multicall=multicall) + except: + f.write("-> exception") + raise + f.write("-> %r" % (res,)) + return res def pytest_unconfigure(config): - f = config.pluginmanager.comregistry.logfile - if f: - f.close() - config.pluginmanager.comregistry.logfile = None + try: + del config.hook.__dict__['_performcall'] + except KeyError: + pass # =============================================================================== # plugin tests Modified: py/dist/py/test/plugin/pytest_monkeypatch.py ============================================================================== --- py/dist/py/test/plugin/pytest_monkeypatch.py (original) +++ py/dist/py/test/plugin/pytest_monkeypatch.py Wed Aug 19 18:37:53 2009 @@ -4,7 +4,7 @@ Usage ---------------- -Use the `monkeypatch funcarg`_ to safely patch the environment +Use the `monkeypatch funcarg`_ to safely patch 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 @@ -20,7 +20,16 @@ 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. +for an extensive discussion. + +To add to a possibly existing environment parameter you +can use this example: + +.. sourcecode:: python + + def test_mypath_finding(monkeypatch): + monkeypatch.setenv('PATH', 'x/y', prepend=":") + # x/y will be at the beginning of $PATH .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ """ @@ -57,8 +66,11 @@ self._setitem.insert(0, (dictionary, name, dictionary.get(name, notset))) dictionary[name] = value - def setenv(self, name, value): - self.setitem(os.environ, name, str(value)) + def setenv(self, name, value, prepend=None): + value = str(value) + if prepend and name in os.environ: + value = value + prepend + os.environ[name] + self.setitem(os.environ, name, value) def finalize(self): for obj, name, value in self._setattr: @@ -111,6 +123,16 @@ monkeypatch.finalize() assert 'XYZ123' not in os.environ +def test_setenv_prepend(): + import os + monkeypatch = MonkeyPatch() + monkeypatch.setenv('XYZ123', 2, prepend="-") + assert os.environ['XYZ123'] == "2" + monkeypatch.setenv('XYZ123', 3, prepend="-") + assert os.environ['XYZ123'] == "3-2" + monkeypatch.finalize() + assert 'XYZ123' not in os.environ + def test_monkeypatch_plugin(testdir): reprec = testdir.inline_runsource(""" pytest_plugins = 'pytest_monkeypatch', Added: py/dist/py/test/plugin/pytest_nose.py ============================================================================== --- (empty file) +++ py/dist/py/test/plugin/pytest_nose.py Wed Aug 19 18:37:53 2009 @@ -0,0 +1,85 @@ +"""nose-compatibility plugin: allow to run nose test suites natively. + +This is an experimental plugin for allowing to run tests written +in 'nosetests' style with py.test. + +Usage +------------- + +type:: + + py.test # instead of 'nosetests' + +and you should be able to run nose style tests. You will of course +get py.test style reporting and its feature set. + +Issues? +---------------- + +If you find issues or have suggestions please run:: + + py.test --pastebin=all + +and send the resulting URL to a some contact channel. + +Known issues +------------------ + +- nose-style doctests are not collected and executed correctly, + also fixtures don't work. + +- no nose-configuration is recognized + +""" +import py +import inspect +import sys + +def pytest_runtest_makereport(__multicall__, item, call): + SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None) + if SkipTest: + if call.excinfo and call.excinfo.errisinstance(SkipTest): + # let's substitute the excinfo with a py.test.skip one + call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when) + call.excinfo = call2.excinfo + +def pytest_report_iteminfo(item): + # nose 0.11.1 uses decorators for "raises" and other helpers. + # for reporting progress by filename we fish for the filename + if isinstance(item, py.test.collect.Function): + obj = item.obj + if hasattr(obj, 'compat_co_firstlineno'): + fn = sys.modules[obj.__module__].__file__ + if fn.endswith(".pyc"): + fn = fn[:-1] + #assert 0 + #fn = inspect.getsourcefile(obj) or inspect.getfile(obj) + lineno = obj.compat_co_firstlineno + return py.path.local(fn), lineno, obj.__module__ + +def pytest_runtest_setup(item): + if isinstance(item, (py.test.collect.Function)): + if isinstance(item.parent, py.test.collect.Generator): + gen = item.parent + if not hasattr(gen, '_nosegensetup'): + call_optional(gen.obj, 'setup') + if isinstance(gen.parent, py.test.collect.Instance): + call_optional(gen.parent.obj, 'setup') + gen._nosegensetup = True + call_optional(item.obj, 'setup') + +def pytest_runtest_teardown(item): + if isinstance(item, py.test.collect.Function): + call_optional(item.obj, 'teardown') + #if hasattr(item.parent, '_nosegensetup'): + # #call_optional(item._nosegensetup, 'teardown') + # del item.parent._nosegensetup + +def pytest_make_collect_report(collector): + if isinstance(collector, py.test.collect.Generator): + call_optional(collector.obj, 'setup') + +def call_optional(obj, name): + method = getattr(obj, name, None) + if method: + method() Modified: py/dist/py/test/plugin/pytest_pastebin.py ============================================================================== --- py/dist/py/test/plugin/pytest_pastebin.py (original) +++ py/dist/py/test/plugin/pytest_pastebin.py Wed Aug 19 18:37:53 2009 @@ -8,7 +8,7 @@ py.test --pastebin=failed -This will submit full failure information to a remote Paste service and +This will submit test run 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. @@ -33,9 +33,9 @@ type="choice", choices=['failed', 'all'], help="send failed|all info to Pocoo pastebin service.") -def pytest_configure(__call__, config): +def pytest_configure(__multicall__, config): import tempfile - __call__.execute() + __multicall__.execute() if config.option.pastebin == "all": config._pastebinfile = tempfile.TemporaryFile() tr = config.pluginmanager.impname2plugin['terminalreporter'] Modified: py/dist/py/test/plugin/pytest_runner.py ============================================================================== --- py/dist/py/test/plugin/pytest_runner.py (original) +++ py/dist/py/test/plugin/pytest_runner.py Wed Aug 19 18:37:53 2009 @@ -24,7 +24,7 @@ hook = session.config.hook rep = hook.pytest__teardown_final(session=session) if rep: - hook.pytest__teardown_final_logerror(rep=rep) + hook.pytest__teardown_final_logerror(report=rep) def pytest_make_collect_report(collector): result = excinfo = None @@ -72,12 +72,12 @@ rep = TeardownErrorReport(call.excinfo) return rep -def pytest_report_teststatus(rep): - if rep.when in ("setup", "teardown"): - if rep.failed: +def pytest_report_teststatus(report): + if report.when in ("setup", "teardown"): + if report.failed: # category, shortletter, verbose-word return "error", "E", "ERROR" - elif rep.skipped: + elif report.skipped: return "skipped", "s", "SKIPPED" else: return "", "", "" @@ -108,6 +108,13 @@ except: self.excinfo = py.code.ExceptionInfo() + def __repr__(self): + if self.excinfo: + status = "exception: %s" % str(self.excinfo.value) + else: + status = "result: %r" % (self.result,) + return "" % (self.when, status) + def forked_run_report(item): # for now, we run setup/teardown in the subprocess # XXX optionally allow sharing of setup/teardown Modified: py/dist/py/test/plugin/pytest_terminal.py ============================================================================== --- py/dist/py/test/plugin/pytest_terminal.py (original) +++ py/dist/py/test/plugin/pytest_terminal.py Wed Aug 19 18:37:53 2009 @@ -7,7 +7,7 @@ import sys def pytest_addoption(parser): - group = parser.getgroup("test process debugging") + group = parser.getgroup("debugconfig") group.addoption('--collectonly', action="store_true", dest="collectonly", help="only collect tests, don't execute them."), @@ -82,7 +82,7 @@ self._tw.sep(sep, title, **markup) def getcategoryletterword(self, rep): - res = self.config.hook.pytest_report_teststatus(rep=rep) + res = self.config.hook.pytest_report_teststatus(report=rep) if res: return res for cat in 'skipped failed passed ???'.split(): @@ -181,11 +181,11 @@ else: # ensure that the path is printed before the # 1st test of a module starts running - fspath, lineno, msg = self._getreportinfo(item) - self.write_fspath_result(fspath, "") - def pytest__teardown_final_logerror(self, rep): - self.stats.setdefault("error", []).append(rep) + self.write_fspath_result(self._getfspath(item), "") + + def pytest__teardown_final_logerror(self, report): + self.stats.setdefault("error", []).append(report) def pytest_runtest_logreport(self, report): rep = report @@ -199,8 +199,7 @@ markup = {} self.stats.setdefault(cat, []).append(rep) if not self.config.option.verbose: - fspath, lineno, msg = self._getreportinfo(rep.item) - self.write_fspath_result(fspath, letter) + self.write_fspath_result(self._getfspath(rep.item), letter) else: line = self._reportinfoline(rep.item) if not hasattr(rep, 'node'): @@ -229,9 +228,9 @@ verinfo = ".".join(map(str, sys.version_info[:3])) msg = "python: platform %s -- Python %s" % (sys.platform, verinfo) - if self.config.option.verbose or self.config.option.debug: - msg += " -- " + str(sys.executable) + if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None): msg += " -- pytest-%s" % (py.__version__) + msg += " -- " + str(sys.executable) self.write_line(msg) if self.config.option.debug or self.config.option.traceconfig: @@ -241,22 +240,17 @@ if self.config.option.traceconfig: plugins = [] for plugin in self.config.pluginmanager.comregistry: - name = plugin.__class__.__name__ - if name.endswith("Plugin"): - name = name[:-6] - #if name == "Conftest": - # XXX get filename - plugins.append(name) - else: - plugins.append(str(plugin)) - + name = getattr(plugin, '__name__', None) + if name is None: + name = plugin.__class__.__name__ + plugins.append(name) plugins = ", ".join(plugins) self.write_line("active plugins: %s" %(plugins,)) 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): - __call__.execute() + def pytest_sessionfinish(self, exitstatus, __multicall__): + __multicall__.execute() self._tw.line("") if exitstatus in (0, 1, 2): self.summary_errors() @@ -293,8 +287,13 @@ self.write_line("### Watching: %s" %(rootdir,), bold=True) def _reportinfoline(self, item): + collect_fspath = self._getfspath(item) fspath, lineno, msg = self._getreportinfo(item) - if fspath: + if fspath and fspath != collect_fspath: + fspath = "%s <- %s" % ( + self.curdir.bestrelpath(collect_fspath), + self.curdir.bestrelpath(fspath)) + elif fspath: fspath = self.curdir.bestrelpath(fspath) if lineno is not None: lineno += 1 @@ -303,7 +302,7 @@ elif fspath and msg: line = "%(fspath)s: %(msg)s" elif fspath and lineno: - line = "%(fspath)s:%(lineno)s" + line = "%(fspath)s:%(lineno)s %(extrapath)s" else: line = "[noreportinfo]" return line % locals() + " " @@ -327,6 +326,13 @@ item.__reportinfo = reportinfo return reportinfo + def _getfspath(self, item): + try: + return item.fspath + except AttributeError: + fspath, lineno, msg = self._getreportinfo(item) + return fspath + # # summaries for sessionfinish # Modified: py/dist/py/test/plugin/pytest_unittest.py ============================================================================== --- py/dist/py/test/plugin/pytest_unittest.py (original) +++ py/dist/py/test/plugin/pytest_unittest.py Wed Aug 19 18:37:53 2009 @@ -55,6 +55,9 @@ if obj is not _dummy: self._obj = obj self._sort_value = sort_value + if hasattr(self.parent, 'newinstance'): + self.parent.newinstance() + self.obj = self._getobj() def runtest(self): target = self.obj @@ -87,7 +90,6 @@ def test_setup(testdir): testpath = testdir.makepyfile(test_two=""" import unittest - pytest_plugins = "pytest_unittest" # XXX class MyTestCase(unittest.TestCase): def setUp(self): self.foo = 1 @@ -98,6 +100,18 @@ rep = reprec.matchreport("test_setUp") assert rep.passed +def test_new_instances(testdir): + testpath = testdir.makepyfile(""" + import unittest + class MyTestCase(unittest.TestCase): + def test_func1(self): + self.x = 2 + def test_func2(self): + assert not hasattr(self, 'x') + """) + reprec = testdir.inline_run(testpath) + reprec.assertoutcome(passed=2) + def test_teardown(testdir): testpath = testdir.makepyfile(test_three=""" import unittest Modified: py/dist/py/test/plugin/pytest_xfail.py ============================================================================== --- py/dist/py/test/plugin/pytest_xfail.py (original) +++ py/dist/py/test/plugin/pytest_xfail.py Wed Aug 19 18:37:53 2009 @@ -19,14 +19,12 @@ import py -pytest_plugins = ['keyword'] - -def pytest_runtest_makereport(__call__, item, call): +def pytest_runtest_makereport(__multicall__, item, call): if call.when != "call": return if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'): if 'xfail' in item.obj.func_dict: - res = __call__.execute(firstresult=True) + res = __multicall__.execute() if call.excinfo: res.skipped = True res.failed = res.passed = False @@ -35,12 +33,11 @@ res.failed = True return res -def pytest_report_teststatus(rep): - """ return shortletter and verbose word. """ - if 'xfail' in rep.keywords: - if rep.skipped: +def pytest_report_teststatus(report): + if 'xfail' in report.keywords: + if report.skipped: return "xfailed", "x", "xfail" - elif rep.failed: + elif report.failed: return "xpassed", "P", "xpass" # called by the terminalreporter instance/plugin @@ -54,6 +51,9 @@ modpath = rep.item.getmodpath(includemodule=True) pos = "%s %s:%d: " %(modpath, entry.path, entry.lineno) reason = rep.longrepr.reprcrash.message + i = reason.find("\n") + if i != -1: + reason = reason[:i] tr._tw.line("%s %s" %(pos, reason)) xpassed = terminalreporter.stats.get("xpassed") @@ -90,3 +90,4 @@ "*test_that*", ]) assert result.ret == 1 + Modified: py/dist/py/test/plugin/test_pytest_capture.py ============================================================================== --- py/dist/py/test/plugin/test_pytest_capture.py (original) +++ py/dist/py/test/plugin/test_pytest_capture.py Wed Aug 19 18:37:53 2009 @@ -1,8 +1,7 @@ import py, os, sys -from py.__.test.plugin.pytest_capture import CaptureManager +from py.__.test.plugin.pytest_capture import CaptureManager, EncodedFile class TestCaptureManager: - def test_configure_per_fspath(self, testdir): config = testdir.parseconfig(testdir.tmpdir) assert config.getvalue("capture") is None @@ -12,7 +11,7 @@ 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) + sub.join("conftest.py").write('option_capture = %r' % name) assert capman._getmethod(config, sub.join("test_hello.py")) == name @py.test.mark.multi(method=['no', 'fd', 'sys']) @@ -54,6 +53,42 @@ finally: capouter.reset() + at py.test.mark.multi(method=['fd', 'sys']) +def test_capturing_unicode(testdir, method): + testdir.makepyfile(""" + # taken from issue 227 from nosetests + def test_unicode(): + import sys + print sys.stdout + print u'b\\u00f6y' + """) + result = testdir.runpytest("--capture=%s" % method) + result.stdout.fnmatch_lines([ + "*1 passed*" + ]) + + at py.test.mark.multi(method=['fd', 'sys']) +def test_capturing_bytes_in_utf8_encoding(testdir, method): + testdir.makepyfile(""" + def test_unicode(): + print '\\xe2' + """) + result = testdir.runpytest("--capture=%s" % method) + result.stdout.fnmatch_lines([ + "*1 passed*" + ]) + +def test_UnicodeFile(testdir): + p = testdir.makepyfile("hello") + f = p.open('w') + pf = EncodedFile(f, "UTF-8") + pf.write(u'b\\00f6y\n') + pf.write('b\\00f6y\n') + pf.close() + assert f.closed + lines = p.readlines() + assert lines[0] == lines[1] + def test_collect_capturing(testdir): p = testdir.makepyfile(""" print "collect %s failure" % 13 Added: py/dist/py/test/plugin/test_pytest_helpconfig.py ============================================================================== --- (empty file) +++ py/dist/py/test/plugin/test_pytest_helpconfig.py Wed Aug 19 18:37:53 2009 @@ -0,0 +1,18 @@ +import py, os + +def test_version(testdir): + assert py.version == py.__version__ + result = testdir.runpytest("--version") + assert result.ret == 0 + p = py.path.local(py.__file__).dirpath() + assert result.stderr.fnmatch_lines([ + '*py.test*%s*imported from*%s*' % (py.version, p) + ]) + +def test_helpconfig(testdir): + result = testdir.runpytest("--help-config") + assert result.ret == 0 + assert result.stdout.fnmatch_lines([ + "*cmdline*conftest*ENV*", + ]) + Added: py/dist/py/test/plugin/test_pytest_nose.py ============================================================================== --- (empty file) +++ py/dist/py/test/plugin/test_pytest_nose.py Wed Aug 19 18:37:53 2009 @@ -0,0 +1,87 @@ +import py +py.test.importorskip("nose") + +def test_nose_setup(testdir): + p = testdir.makepyfile(""" + l = [] + + def test_hello(): + assert l == [1] + def test_world(): + assert l == [1,2] + test_hello.setup = lambda: l.append(1) + test_hello.teardown = lambda: l.append(2) + """) + result = testdir.runpytest(p, '-p', 'nose') + result.stdout.fnmatch_lines([ + "*2 passed*" + ]) + +def test_nose_test_generator_fixtures(testdir): + p = testdir.makepyfile(""" + # taken from nose-0.11.1 unit_tests/test_generator_fixtures.py + from nose.tools import eq_ + called = [] + + def outer_setup(): + called.append('outer_setup') + + def outer_teardown(): + called.append('outer_teardown') + + def inner_setup(): + called.append('inner_setup') + + def inner_teardown(): + called.append('inner_teardown') + + def test_gen(): + called[:] = [] + for i in range(0, 5): + yield check, i + + def check(i): + expect = ['outer_setup'] + for x in range(0, i): + expect.append('inner_setup') + expect.append('inner_teardown') + expect.append('inner_setup') + eq_(called, expect) + + + test_gen.setup = outer_setup + test_gen.teardown = outer_teardown + check.setup = inner_setup + check.teardown = inner_teardown + + class TestClass(object): + def setup(self): + print "setup called in", self + self.called = ['setup'] + + def teardown(self): + print "teardown called in", self + eq_(self.called, ['setup']) + self.called.append('teardown') + + def test(self): + print "test called in", self + for i in range(0, 5): + yield self.check, i + + def check(self, i): + print "check called in", self + expect = ['setup'] + #for x in range(0, i): + # expect.append('setup') + # expect.append('teardown') + #expect.append('setup') + eq_(self.called, expect) + + """) + result = testdir.runpytest(p, '-p', 'nose') + result.stdout.fnmatch_lines([ + "*10 passed*" + ]) + + Modified: py/dist/py/test/plugin/test_pytest_runner.py ============================================================================== --- py/dist/py/test/plugin/test_pytest_runner.py (original) +++ py/dist/py/test/plugin/test_pytest_runner.py Wed Aug 19 18:37:53 2009 @@ -271,3 +271,13 @@ "*1 failed*" ]) +def test_callinfo(): + ci = runner.CallInfo(lambda: 0, '123') + assert ci.when == "123" + assert ci.result == 0 + assert "result" in repr(ci) + ci = runner.CallInfo(lambda: 0/0, '123') + assert ci.when == "123" + assert not hasattr(ci, 'result') + assert ci.excinfo + assert "exc" in repr(ci) Modified: py/dist/py/test/plugin/test_pytest_runner_xunit.py ============================================================================== --- py/dist/py/test/plugin/test_pytest_runner_xunit.py (original) +++ py/dist/py/test/plugin/test_pytest_runner_xunit.py Wed Aug 19 18:37:53 2009 @@ -125,18 +125,12 @@ def test_method_setup_uses_fresh_instances(testdir): reprec = testdir.inline_runsource(""" class TestSelfState1: - def __init__(self): - self.hello = 42 + memory = [] def test_hello(self): - self.world = 23 + self.memory.append(self) + 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') + assert self != self.memory[0] """) - reprec.assertoutcome(passed=4, failed=0) + reprec.assertoutcome(passed=2, failed=0) Modified: py/dist/py/test/plugin/test_pytest_terminal.py ============================================================================== --- py/dist/py/test/plugin/test_pytest_terminal.py (original) +++ py/dist/py/test/plugin/test_pytest_terminal.py Wed Aug 19 18:37:53 2009 @@ -209,10 +209,6 @@ 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([ @@ -227,16 +223,35 @@ 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_itemreport_subclasses_show_subclassed_file(self, testdir): + p1 = testdir.makepyfile(test_p1=""" + class BaseTests: + def test_p1(self): + pass + class TestClass(BaseTests): + pass + """) + p2 = testdir.makepyfile(test_p2=""" + from test_p1 import BaseTests + class TestMore(BaseTests): + pass + """) + result = testdir.runpytest(p2) + assert result.stdout.fnmatch_lines([ + "*test_p2.py .", + "*1 passed*", + ]) + result = testdir.runpytest("-v", p2) + result.stdout.fnmatch_lines([ + "*test_p2.py <- *test_p1.py:2: TestMore.test_p1*", + ]) + def test_keyboard_interrupt_dist(self, testdir, option): p = testdir.makepyfile(""" raise KeyboardInterrupt Modified: py/dist/py/test/pluginmanager.py ============================================================================== --- py/dist/py/test/pluginmanager.py (original) +++ py/dist/py/test/pluginmanager.py Wed Aug 19 18:37:53 2009 @@ -16,10 +16,9 @@ if comregistry is None: comregistry = py._com.Registry() self.comregistry = comregistry - self.MultiCall = self.comregistry.MultiCall self.impname2plugin = {} - self.hook = py._com.Hooks( + self.hook = py._com.HookRelay( hookspecs=hookspec, registry=self.comregistry) @@ -135,15 +134,16 @@ fail = True else: method_args = getargs(method) - if '__call__' in method_args: - method_args.remove('__call__') + if '__multicall__' in method_args: + method_args.remove('__multicall__') hook = hooks[name] hookargs = getargs(hook) - for arg, hookarg in zip(method_args, hookargs): - if arg != hookarg: - Print("argument mismatch: %r != %r" %(arg, hookarg)) - Print("actual : %s" %(formatdef(method))) - Print("required:", formatdef(hook)) + for arg in method_args: + if arg not in hookargs: + Print("argument %r not available" %(arg, )) + Print("actual definition: %s" %(formatdef(method))) + Print("available hook arguments: %s" % + ", ".join(hookargs)) fail = True break #if not fail: @@ -166,20 +166,24 @@ return self.hook.pytest_internalerror(excrepr=excrepr) def do_addoption(self, parser): - methods = self.comregistry.listattr("pytest_addoption", reverse=True) - mc = py._com.MultiCall(methods, parser=parser) + mname = "pytest_addoption" + methods = self.comregistry.listattr(mname, reverse=True) + mc = py._com.MultiCall(methods, {'parser': parser}) mc.execute() def pytest_plugin_registered(self, plugin): if hasattr(self, '_config'): - self.call_plugin(plugin, "pytest_addoption", parser=self._config._parser) - self.call_plugin(plugin, "pytest_configure", config=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") #self._updateext(dic) - def call_plugin(self, plugin, methname, **kwargs): - return self.MultiCall(self.listattr(methname, plugins=[plugin]), - **kwargs).execute(firstresult=True) + def call_plugin(self, plugin, methname, kwargs): + return py._com.MultiCall( + methods=self.listattr(methname, plugins=[plugin]), + kwargs=kwargs, firstresult=True).execute() def _updateext(self, dic): if dic: @@ -200,9 +204,6 @@ config.hook.pytest_unconfigure(config=config) config.pluginmanager.unregister(self) -class Ext: - """ namespace for extension objects. """ - # # XXX old code to automatically load classes # @@ -232,8 +233,7 @@ def isgenerichook(name): return name == "pytest_plugins" or \ - name.startswith("pytest_funcarg__") or \ - name.startswith("pytest_option_") + name.startswith("pytest_funcarg__") def getargs(func): args = py.std.inspect.getargs(func.func_code)[0] Modified: py/dist/py/test/pycollect.py ============================================================================== --- py/dist/py/test/pycollect.py (original) +++ py/dist/py/test/pycollect.py Wed Aug 19 18:37:53 2009 @@ -123,8 +123,7 @@ collector=self, name=name, obj=obj) if res is not None: return res - if (self.classnamefilter(name)) and \ - py.std.inspect.isclass(obj): + if self._istestclasscandidate(name, obj): res = self._deprecated_join(name) if res is not None: return res @@ -139,14 +138,25 @@ else: return self._genfunctions(name, obj) + def _istestclasscandidate(self, name, obj): + if self.classnamefilter(name) and \ + py.std.inspect.isclass(obj): + if hasinit(obj): + # XXX WARN + return False + return True + + def _genfunctions(self, name, funcobj): module = self.getparent(Module).obj # due to _buildname2items funcobj is the raw function, we need # to work to get at the class clscol = self.getparent(Class) cls = clscol and clscol.obj or None - metafunc = funcargs.Metafunc(funcobj, config=self.config, cls=cls, module=module) - gentesthook = self.config.hook.pytest_generate_tests.clone(extralookup=module) + metafunc = funcargs.Metafunc(funcobj, config=self.config, + cls=cls, module=module) + gentesthook = self.config.hook._makecall( + "pytest_generate_tests", extralookup=module) gentesthook(metafunc=metafunc) if not metafunc._calls: return self.Function(name, parent=self) @@ -371,3 +381,9 @@ def __ne__(self, other): return not self == other + +def hasinit(obj): + init = getattr(obj, '__init__', None) + if init: + if not isinstance(init, type(object.__init__)): + return True Modified: py/dist/py/test/testing/acceptance_test.py ============================================================================== --- py/dist/py/test/testing/acceptance_test.py (original) +++ py/dist/py/test/testing/acceptance_test.py Wed Aug 19 18:37:53 2009 @@ -1,7 +1,5 @@ import py -EXPECTTIMEOUT=10.0 - class TestGeneralUsage: def test_config_error(self, testdir): testdir.makeconftest(""" @@ -67,3 +65,12 @@ "E ImportError: No module named does_not_work", ]) assert result.ret == 1 + + def test_not_collectable_arguments(self, testdir): + p1 = testdir.makepyfile("") + p2 = testdir.makefile(".pyc", "123") + result = testdir.runpytest(p1, p2) + assert result.ret != 0 + assert result.stderr.fnmatch_lines([ + "*ERROR: can't collect: %s" %(p2,) + ]) Modified: py/dist/py/test/testing/test_collect.py ============================================================================== --- py/dist/py/test/testing/test_collect.py (original) +++ py/dist/py/test/testing/test_collect.py Wed Aug 19 18:37:53 2009 @@ -145,7 +145,7 @@ names = [x.name for x in col.collect()] assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"] -class TestCollectPluginHooks: +class TestCollectPluginHookRelay: def test_pytest_collect_file(self, testdir): tmpdir = testdir.tmpdir wascalled = [] @@ -179,6 +179,18 @@ names = [rep.collector.name for rep in colreports] assert names.count("hello") == 1 +class TestPrunetraceback: + def test_collection_error(self, testdir): + p = testdir.makepyfile(""" + import not_exists + """) + result = testdir.runpytest(p) + assert "__import__" not in result.stdout.str(), "too long traceback" + result.stdout.fnmatch_lines([ + "*ERROR during collection*", + ">*import not_exists*" + ]) + class TestCustomConftests: def test_non_python_files(self, testdir): testdir.makepyfile(conftest=""" Modified: py/dist/py/test/testing/test_config.py ============================================================================== --- py/dist/py/test/testing/test_config.py (original) +++ py/dist/py/test/testing/test_config.py Wed Aug 19 18:37:53 2009 @@ -42,7 +42,7 @@ def test_parser_addoption_default_conftest(self, testdir, monkeypatch): import os - testdir.makeconftest("pytest_option_verbose=True") + testdir.makeconftest("option_verbose=True") config = testdir.parseconfig() assert config.option.verbose Deleted: /py/dist/py/test/testing/test_install.py ============================================================================== --- /py/dist/py/test/testing/test_install.py Wed Aug 19 18:37:53 2009 +++ (empty file) @@ -1,16 +0,0 @@ -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/dist/py/test/testing/test_pluginmanager.py ============================================================================== --- py/dist/py/test/testing/test_pluginmanager.py (original) +++ py/dist/py/test/testing/test_pluginmanager.py Wed Aug 19 18:37:53 2009 @@ -4,7 +4,7 @@ class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): pluginmanager = PluginManager() - monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule') + monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",") py.test.raises(ImportError, "pluginmanager.consider_env()") def test_preparse_args(self): @@ -50,7 +50,7 @@ plugin = py.test.config.pluginmanager.getplugin('x500') assert plugin is not None """) - monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'pytest_x500') + monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",") result = testdir.runpytest(p) assert result.ret == 0 extra = result.stdout.fnmatch_lines(["*1 passed in*"]) @@ -185,7 +185,7 @@ assert hello == "world" """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 passed*" ]) @@ -222,10 +222,6 @@ config.pluginmanager.register(A()) assert len(l) == 2 - def test_MultiCall(self): - pp = PluginManager() - assert hasattr(pp, 'MultiCall') - # lower level API def test_listattr(self): @@ -247,3 +243,12 @@ assert list(methods) == ['pytest_hello', 'pytest_world'] methods = py.builtin.sorted(collectattr(B())) assert list(methods) == ['pytest_hello', 'pytest_world'] + + at py.test.mark.xfail +def test_namespace_has_default_and_env_plugins(testdir): + p = testdir.makepyfile(""" + import py + py.test.mark + """) + result = testdir.runpython(p) + assert result.ret == 0 Modified: py/dist/py/test/testing/test_pycollect.py ============================================================================== --- py/dist/py/test/testing/test_pycollect.py (original) +++ py/dist/py/test/testing/test_pycollect.py Wed Aug 19 18:37:53 2009 @@ -39,6 +39,19 @@ modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") py.test.raises(ImportError, "modcol.obj") +class TestClass: + def test_class_with_init_not_collected(self, testdir): + modcol = testdir.getmodulecol(""" + class TestClass1: + def __init__(self): + pass + class TestClass2(object): + def __init__(self): + pass + """) + l = modcol.collect() + assert len(l) == 0 + class TestDisabled: def test_disabled_module(self, testdir): modcol = testdir.getmodulecol(""" Modified: py/dist/setup.py ============================================================================== --- py/dist/setup.py (original) +++ py/dist/setup.py Wed Aug 19 18:37:53 2009 @@ -31,7 +31,7 @@ name='py', description='py.test and pylib: advanced testing tool and networking lib', long_description = long_description, - version= trunk or '1.0.0', + version= trunk or '1.0.1', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], @@ -105,8 +105,6 @@ 'py.xmlobj.testing'], package_data={'py': ['LICENSE', 'bin/_findpy.py', - 'bin/_genscripts.py', - 'bin/gendoc.py', 'bin/py.cleanup', 'bin/py.countloc', 'bin/py.lookup', From hpk at codespeak.net Thu Aug 27 12:30:14 2009 From: hpk at codespeak.net (hpk at codespeak.net) Date: Thu, 27 Aug 2009 12:30:14 +0200 (CEST) Subject: [py-svn] r67229 - in py/dist: . doc doc/announce doc/test doc/test/plugin py py/misc py/test/looponfail/testing py/test/plugin Message-ID: <20090827103014.3250716801E@codespeak.net> Author: hpk Date: Thu Aug 27 12:30:12 2009 New Revision: 67229 Added: py/dist/MANIFEST.in py/dist/doc/announce/release-1.0.2.txt py/dist/doc/changelog.txt py/dist/doc/test/plugin/django.txt Removed: py/dist/MANIFEST py/dist/_findpy.py Modified: py/dist/README.txt py/dist/doc/announce/releases.txt py/dist/doc/confrest.py py/dist/doc/path.txt py/dist/doc/test/index.txt py/dist/doc/test/plugin/index.txt py/dist/doc/test/plugin/links.txt py/dist/py/__init__.py py/dist/py/misc/rest.py py/dist/py/test/looponfail/testing/test_remote.py py/dist/py/test/plugin/pytest_pytester.py py/dist/setup.py Log: trying to sync svn/py/dist to 1.0.2 release Deleted: /py/dist/MANIFEST ============================================================================== --- /py/dist/MANIFEST Thu Aug 27 12:30:12 2009 +++ (empty file) @@ -1,426 +0,0 @@ -CHANGELOG -LICENSE -MANIFEST -README.txt -_findpy.py -bin-for-dist/all-plat.sh -bin-for-dist/gendoc.py -bin-for-dist/genscripts.py -bin-for-dist/gensetup.py -bin-for-dist/makepluginlist.py -doc/announce/release-0.9.0.txt -doc/announce/release-0.9.2.txt -doc/announce/release-1.0.0.txt -doc/announce/release-1.0.1.txt -doc/announce/releases.txt -doc/bin.txt -doc/code.txt -doc/confrest.py -doc/conftest.py -doc/contact.txt -doc/download.txt -doc/execnet.txt -doc/faq.txt -doc/img/pylib.png -doc/index.txt -doc/io.txt -doc/log.txt -doc/misc.txt -doc/path.txt -doc/style.css -doc/test/attic.txt -doc/test/config.html -doc/test/customize.txt -doc/test/dist.txt -doc/test/examples.txt -doc/test/extend.html -doc/test/features.txt -doc/test/funcargs.txt -doc/test/index.txt -doc/test/mission.txt -doc/test/plugin/capture.txt -doc/test/plugin/doctest.txt -doc/test/plugin/figleaf.txt -doc/test/plugin/helpconfig.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/nose.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.html -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 -example/funcarg/mysetup/myapp.py -example/funcarg/mysetup/test_sample.py -example/funcarg/mysetup2/__init__.py -example/funcarg/mysetup2/conftest.py -example/funcarg/mysetup2/myapp.py -example/funcarg/mysetup2/test_sample.py -example/funcarg/mysetup2/test_ssh.py -example/funcarg/parametrize/test_parametrize.py -example/funcarg/parametrize/test_parametrize2.py -example/funcarg/parametrize/test_parametrize3.py -example/funcarg/test_simpleprovider.py -example/genhtml.py -example/genhtmlcss.py -example/genxml.py -py/LICENSE -py/__init__.py -py/_com.py -py/bin/_findpy.py -py/bin/py.cleanup -py/bin/py.countloc -py/bin/py.lookup -py/bin/py.rest -py/bin/py.svnwcrevert -py/bin/py.test -py/bin/py.which -py/bin/win32/py.cleanup.cmd -py/bin/win32/py.countloc.cmd -py/bin/win32/py.lookup.cmd -py/bin/win32/py.rest.cmd -py/bin/win32/py.svnwcrevert.cmd -py/bin/win32/py.test.cmd -py/bin/win32/py.which.cmd -py/builtin/__init__.py -py/builtin/enumerate.py -py/builtin/exception.py -py/builtin/reversed.py -py/builtin/set.py -py/builtin/sorted.py -py/builtin/testing/__init__.py -py/builtin/testing/test_enumerate.py -py/builtin/testing/test_exception.py -py/builtin/testing/test_reversed.py -py/builtin/testing/test_set.py -py/builtin/testing/test_sorted.py -py/cmdline/__init__.py -py/cmdline/pycleanup.py -py/cmdline/pycountloc.py -py/cmdline/pylookup.py -py/cmdline/pyrest.py -py/cmdline/pysvnwcrevert.py -py/cmdline/pytest.py -py/cmdline/pywhich.py -py/cmdline/testing/__init__.py -py/cmdline/testing/test_cmdline.py -py/cmdline/testing/test_generic.py -py/code/__init__.py -py/code/code.py -py/code/excinfo.py -py/code/frame.py -py/code/safe_repr.py -py/code/source.py -py/code/testing/__init__.py -py/code/testing/test_code.py -py/code/testing/test_excinfo.py -py/code/testing/test_frame.py -py/code/testing/test_safe_repr.py -py/code/testing/test_source.py -py/code/traceback2.py -py/compat/LICENSE -py/compat/__init__.py -py/compat/conftest.py -py/compat/doctest.py -py/compat/optparse.py -py/compat/subprocess.py -py/compat/testing/__init__.py -py/compat/testing/test_doctest.py -py/compat/testing/test_doctest.txt -py/compat/testing/test_doctest2.py -py/compat/testing/test_doctest2.txt -py/compat/testing/test_optparse.py -py/compat/testing/test_subprocess.py -py/compat/testing/test_textwrap.py -py/compat/textwrap.py -py/conftest.py -py/env.cmd -py/env.py -py/execnet/NOTES -py/execnet/__init__.py -py/execnet/channel.py -py/execnet/gateway.py -py/execnet/gwmanage.py -py/execnet/improve-remote-tracebacks.txt -py/execnet/inputoutput.py -py/execnet/message.py -py/execnet/multi.py -py/execnet/register.py -py/execnet/rsync.py -py/execnet/rsync_remote.py -py/execnet/script/__init__.py -py/execnet/script/loop_socketserver.py -py/execnet/script/quitserver.py -py/execnet/script/shell.py -py/execnet/script/socketserver.py -py/execnet/script/socketserverservice.py -py/execnet/script/xx.py -py/execnet/testing/__init__.py -py/execnet/testing/conftest.py -py/execnet/testing/test_event.py -py/execnet/testing/test_gateway.py -py/execnet/testing/test_gwmanage.py -py/execnet/testing/test_multi.py -py/execnet/testing/test_pickle.py -py/execnet/testing/test_rsync.py -py/execnet/testing/test_xspec.py -py/execnet/xspec.py -py/initpkg.py -py/io/__init__.py -py/io/dupfile.py -py/io/fdcapture.py -py/io/stdcapture.py -py/io/terminalwriter.py -py/io/testing/__init__.py -py/io/testing/test_dupfile.py -py/io/testing/test_fdcapture.py -py/io/testing/test_stdcapture.py -py/io/testing/test_terminalwriter.py -py/log/__init__.py -py/log/consumer.py -py/log/logger.py -py/log/producer.py -py/log/testing/__init__.py -py/log/testing/test_log.py -py/log/testing/test_logger.py -py/log/testing/test_warning.py -py/log/warning.py -py/magic/__init__.py -py/magic/assertion.py -py/magic/autopath.py -py/magic/exprinfo.py -py/magic/invoke.py -py/magic/patch.py -py/magic/testing/__init__.py -py/magic/testing/test_assertion.py -py/magic/testing/test_autopath.py -py/magic/testing/test_exprinfo.py -py/magic/testing/test_invoke.py -py/magic/testing/test_patch.py -py/magic/testing/test_viewtype.py -py/magic/viewtype.py -py/misc/__init__.py -py/misc/_dist.py -py/misc/buildcmodule.py -py/misc/cache.py -py/misc/cmdline/__init__.py -py/misc/cmdline/countloc.py -py/misc/difftime.py -py/misc/dynpkg.py -py/misc/error.py -py/misc/findmissingdocstrings.py -py/misc/rest.py -py/misc/std.py -py/misc/svnlook.py -py/misc/terminal_helper.py -py/misc/testing/__init__.py -py/misc/testing/data/svnlookrepo.dump -py/misc/testing/test_api.py -py/misc/testing/test_cache.py -py/misc/testing/test_com.py -py/misc/testing/test_error.py -py/misc/testing/test_initpkg.py -py/misc/testing/test_install.py -py/misc/testing/test_std.py -py/misc/testing/test_svnlook.py -py/misc/testing/test_terminal.py -py/path/__init__.py -py/path/common.py -py/path/gateway/TODO.txt -py/path/gateway/__init__.py -py/path/gateway/channeltest.py -py/path/gateway/channeltest2.py -py/path/gateway/remotepath.py -py/path/local/__init__.py -py/path/local/common.py -py/path/local/local.py -py/path/local/posix.py -py/path/local/testing/__init__.py -py/path/local/testing/test_local.py -py/path/local/testing/test_posix.py -py/path/local/testing/test_win.py -py/path/local/win.py -py/path/svn/__init__.py -py/path/svn/cache.py -py/path/svn/quoting.txt -py/path/svn/svncommon.py -py/path/svn/testing/__init__.py -py/path/svn/testing/repotest.dump -py/path/svn/testing/svntestbase.py -py/path/svn/testing/test_auth.py -py/path/svn/testing/test_test_repo.py -py/path/svn/testing/test_urlcommand.py -py/path/svn/testing/test_wccommand.py -py/path/svn/urlcommand.py -py/path/svn/wccommand.py -py/path/testing/__init__.py -py/path/testing/common.py -py/path/testing/fscommon.py -py/path/testing/test_api.py -py/process/__init__.py -py/process/cmdexec.py -py/process/forkedfunc.py -py/process/killproc.py -py/process/testing/__init__.py -py/process/testing/test_cmdexec.py -py/process/testing/test_forkedfunc.py -py/process/testing/test_killproc.py -py/rest/__init__.py -py/rest/convert.py -py/rest/directive.py -py/rest/latex.py -py/rest/rest.sty.template -py/rest/rst.py -py/rest/testing/__init__.py -py/rest/testing/data/example.rst2pdfconfig -py/rest/testing/data/example1.dot -py/rest/testing/data/formula.txt -py/rest/testing/data/formula1.txt -py/rest/testing/data/graphviz.txt -py/rest/testing/data/part1.txt -py/rest/testing/data/part2.txt -py/rest/testing/data/tocdepth.rst2pdfconfig -py/rest/testing/setup.py -py/rest/testing/test_convert.py -py/rest/testing/test_directive.py -py/rest/testing/test_htmlrest.py -py/rest/testing/test_rst.py -py/rest/testing/test_rst2pdf.py -py/rest/testing/test_transform.py -py/rest/transform.py -py/test/__init__.py -py/test/cmdline.py -py/test/collect.py -py/test/compat.py -py/test/config.py -py/test/conftesthandle.py -py/test/defaultconftest.py -py/test/dist/__init__.py -py/test/dist/dsession.py -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 -py/test/dist/testing/test_txnode.py -py/test/dist/txnode.py -py/test/funcargs.py -py/test/looponfail/__init__.py -py/test/looponfail/remote.py -py/test/looponfail/testing/__init__.py -py/test/looponfail/testing/test_remote.py -py/test/looponfail/testing/test_util.py -py/test/looponfail/util.py -py/test/outcome.py -py/test/parseopt.py -py/test/plugin/__init__.py -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_helpconfig.py -py/test/plugin/pytest_hooklog.py -py/test/plugin/pytest_keyword.py -py/test/plugin/pytest_monkeypatch.py -py/test/plugin/pytest_nose.py -py/test/plugin/pytest_pastebin.py -py/test/plugin/pytest_pdb.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_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_helpconfig.py -py/test/plugin/test_pytest_nose.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 -py/test/testing/__init__.py -py/test/testing/acceptance_test.py -py/test/testing/conftest.py -py/test/testing/import_test/package/__init__.py -py/test/testing/import_test/package/absolute_import_shared_lib.py -py/test/testing/import_test/package/module_that_imports_shared_lib.py -py/test/testing/import_test/package/shared_lib.py -py/test/testing/import_test/package/test_import.py -py/test/testing/test_collect.py -py/test/testing/test_compat.py -py/test/testing/test_config.py -py/test/testing/test_conftesthandle.py -py/test/testing/test_deprecated_api.py -py/test/testing/test_funcargs.py -py/test/testing/test_genitems.py -py/test/testing/test_outcome.py -py/test/testing/test_parseopt.py -py/test/testing/test_pickling.py -py/test/testing/test_pluginmanager.py -py/test/testing/test_pycollect.py -py/test/testing/test_recording.py -py/test/testing/test_session.py -py/test/testing/test_traceback.py -py/test/web/__init__.py -py/test/web/exception.py -py/test/web/post_multipart.py -py/test/web/webcheck.py -py/thread/__init__.py -py/thread/io.py -py/thread/pool.py -py/thread/testing/__init__.py -py/thread/testing/test_io.py -py/thread/testing/test_pool.py -py/tool/__init__.py -py/tool/testing/__init__.py -py/tool/testing/test_utestconvert.py -py/tool/utestconvert.py -py/xmlobj/__init__.py -py/xmlobj/html.py -py/xmlobj/misc.py -py/xmlobj/testing/__init__.py -py/xmlobj/testing/test_html.py -py/xmlobj/testing/test_xml.py -py/xmlobj/visit.py -py/xmlobj/xml.py -setup.py \ No newline at end of file Added: py/dist/MANIFEST.in ============================================================================== --- (empty file) +++ py/dist/MANIFEST.in Thu Aug 27 12:30:12 2009 @@ -0,0 +1,17 @@ +include CHANGELOG +include README.txt +include setup.py +include LICENSE +include py/LICENSE +include py/path/svn/testing/repotest.dump +include py/rest/rest.sty.template +exclude *.orig +exclude *.rej +prune .svn +prune .hg +graft doc +graft contrib +graft example +graft py/bin +graft py/rest/testing/data +graft py/misc/testing/data Modified: py/dist/README.txt ============================================================================== --- py/dist/README.txt (original) +++ py/dist/README.txt Thu Aug 27 12:30:12 2009 @@ -6,14 +6,4 @@ * py.code: dynamic code generation and introspection * py.path: uniform local and svn path objects -It includes code and contributions from several people, -listed in the LICENSE file. - -For questions, please see py/doc/index.txt, refer to the website -http://pylib.org or come to the #pylib IRC freenode channel or subscribe to -http://codespeak.net/mailman/listinfo/py-dev . - -have fun, - -holger krekel, holger at merlinux eu - +For questions and more information please visit http://pylib.org Deleted: /py/dist/_findpy.py ============================================================================== --- /py/dist/_findpy.py Thu Aug 27 12:30:12 2009 +++ (empty file) @@ -1,39 +0,0 @@ -#!/usr/bin/env python - -# -# try to find and import a nearby version of the 'py' package. -# otherwise use the system global default -# XXX turn this into a developer-only thing? -# -import sys -import os -from os.path import dirname as opd, exists, join, basename, abspath - -def searchpy(current): - while 1: - last = current - initpy = join(current, '__init__.py') - if not exists(initpy): - pydir = join(current, 'py') - # recognize py-package and ensure it is importable - if exists(pydir) and exists(join(pydir, '__init__.py')): - #for p in sys.path: - # if p == current: - # return True - if current != sys.path[0]: # if we are already first, then ok - print >>sys.stderr, "inserting into sys.path:", current - sys.path.insert(0, current) - return True - current = opd(current) - if last == current: - return False - -if not searchpy(abspath(os.curdir)): - if not searchpy(opd(abspath(sys.argv[0]))): - if not searchpy(opd(__file__)): - pass # let's hope it is just on sys.path - -import py - -if __name__ == '__main__': - print "py lib is at", py.__file__ Added: py/dist/doc/announce/release-1.0.2.txt ============================================================================== --- (empty file) +++ py/dist/doc/announce/release-1.0.2.txt Thu Aug 27 12:30:12 2009 @@ -0,0 +1,5 @@ +1.0.2: packaging fixes +----------------------------------------------------------------------- + +this release is purely a release for fixing packaging issues. + Modified: py/dist/doc/announce/releases.txt ============================================================================== --- py/dist/doc/announce/releases.txt (original) +++ py/dist/doc/announce/releases.txt Thu Aug 27 12:30:12 2009 @@ -7,6 +7,8 @@ .. toctree:: :maxdepth: 1 + announce/release-1.0.2 + announce/release-1.0.1 announce/release-1.0.0 announce/release-0.9.2 announce/release-0.9.0 Added: py/dist/doc/changelog.txt ============================================================================== --- (empty file) +++ py/dist/doc/changelog.txt Thu Aug 27 12:30:12 2009 @@ -0,0 +1,263 @@ +Changes between 1.0.1 and 1.0.2 +===================================== + +* fixing packaging issues, triggered by fedora redhat packaging, + also added doc, examples and contrib dirs to the tarball. + +* added a documentation link to the new pytest_django plugin. + +Changes between 1.0.0 and 1.0.1 +===================================== + +* added a 'pytest_nose' plugin which handles nose.SkipTest, + nose-style function/method/generator setup/teardown and + tries to report functions correctly. + +* capturing of unicode writes or encoded strings to sys.stdout/err + work better, also terminalwriting was adapted and somewhat + unified between windows and linux. + +* improved documentation layout and content a lot + +* added a "--help-config" option to show conftest.py / ENV-var names for + all longopt cmdline options, and some special conftest.py variables. + renamed 'conf_capture' conftest setting to 'option_capture' accordingly. + +* fix issue #27: better reporting on non-collectable items given on commandline + (e.g. pyc files) + +* fix issue #33: added --version flag (thanks Benjamin Peterson) + +* fix issue #32: adding support for "incomplete" paths to wcpath.status() + +* "Test" prefixed classes are *not* collected by default anymore if they + have an __init__ method + +* monkeypatch setenv() now accepts a "prepend" parameter + +* improved reporting of collection error tracebacks + +* simplified multicall mechanism and plugin architecture, + renamed some internal methods and argnames + +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. + +* perform setup finalization before reporting failures + +* apply modified patches from Andreas Kloeckner to allow + test functions to have no func_code (#22) and to make + "-k" and function keywords work (#20) + +* apply patch from Daniel Peolzleithner (issue #23) + +* resolve issue #18, multiprocessing.Manager() and + redirection clash + +* make __name__ == "__channelexec__" for remote_exec code + +Changes between 1.0.0b1 and 1.0.0b3 +============================================= + +* plugin classes are removed: one now defines + hooks directly in conftest.py or global pytest_*.py + files. + +* added new pytest_namespace(config) hook that allows + to inject helpers directly to the py.test.* namespace. + +* documented and refined many hooks + +* added new style of generative tests via + pytest_generate_tests hook that integrates + well with function arguments. + + +Changes between 0.9.2 and 1.0.0b1 +============================================= + +* introduced new "funcarg" setup method, + see doc/test/funcarg.txt + +* introduced plugin architecuture and many + new py.test plugins, see + doc/test/plugins.txt + +* teardown_method is now guaranteed to get + called after a test method has run. + +* new method: py.test.importorskip(mod,minversion) + will either import or call py.test.skip() + +* completely revised internal py.test architecture + +* new py.process.ForkedFunc object allowing to + fork execution of a function to a sub process + and getting a result back. + +XXX lots of things missing here XXX + +Changes between 0.9.1 and 0.9.2 +=============================== + +* refined installation and metadata, created new setup.py, + now based on setuptools/ez_setup (thanks to Ralf Schmitt + for his support). + +* improved the way of making py.* scripts available in + windows environments, they are now added to the + Scripts directory as ".cmd" files. + +* py.path.svnwc.status() now is more complete and + uses xml output from the 'svn' command if available + (Guido Wesdorp) + +* fix for py.path.svn* to work with svn 1.5 + (Chris Lamb) + +* fix path.relto(otherpath) method on windows to + use normcase for checking if a path is relative. + +* py.test's traceback is better parseable from editors + (follows the filenames:LINENO: MSG convention) + (thanks to Osmo Salomaa) + +* fix to javascript-generation, "py.test --runbrowser" + should work more reliably now + +* removed previously accidentally added + py.test.broken and py.test.notimplemented helpers. + +* there now is a py.__version__ attribute + +Changes between 0.9.0 and 0.9.1 +=============================== + +This is a fairly complete list of changes between 0.9 and 0.9.1, which can +serve as a reference for developers. + +* allowing + signs in py.path.svn urls [39106] +* fixed support for Failed exceptions without excinfo in py.test [39340] +* added support for killing processes for Windows (as well as platforms that + support os.kill) in py.misc.killproc [39655] +* added setup/teardown for generative tests to py.test [40702] +* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739] +* fixed problem with calling .remove() on wcpaths of non-versioned files in + py.path [44248] +* fixed some import and inheritance issues in py.test [41480, 44648, 44655] +* fail to run greenlet tests when pypy is available, but without stackless + [45294] +* small fixes in rsession tests [45295] +* fixed issue with 2.5 type representations in py.test [45483, 45484] +* made that internal reporting issues displaying is done atomically in py.test + [45518] +* made that non-existing files are igored by the py.lookup script [45519] +* improved exception name creation in py.test [45535] +* made that less threads are used in execnet [merge in 45539] +* removed lock required for atomical reporting issue displaying in py.test + [45545] +* removed globals from execnet [45541, 45547] +* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit + get called in 2.5 (py.execnet) [45548] +* fixed bug in joining threads in py.execnet's servemain [45549] +* refactored py.test.rsession tests to not rely on exact output format anymore + [45646] +* using repr() on test outcome [45647] +* added 'Reason' classes for py.test.skip() [45648, 45649] +* killed some unnecessary sanity check in py.test.collect [45655] +* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only + usable by Administrators [45901] +* added support for locking and non-recursive commits to py.path.svnwc [45994] +* locking files in py.execnet to prevent CPython from segfaulting [46010] +* added export() method to py.path.svnurl +* fixed -d -x in py.test [47277] +* fixed argument concatenation problem in py.path.svnwc [49423] +* restore py.test behaviour that it exits with code 1 when there are failures + [49974] +* don't fail on html files that don't have an accompanying .txt file [50606] +* fixed 'utestconvert.py < input' [50645] +* small fix for code indentation in py.code.source [50755] +* fix _docgen.py documentation building [51285] +* improved checks for source representation of code blocks in py.test [51292] +* added support for passing authentication to py.path.svn* objects [52000, + 52001] +* removed sorted() call for py.apigen tests in favour of [].sort() to support + Python 2.3 [52481] Modified: py/dist/doc/confrest.py ============================================================================== --- py/dist/doc/confrest.py (original) +++ py/dist/doc/confrest.py Thu Aug 27 12:30:12 2009 @@ -59,6 +59,7 @@ items = [ self.a_docref("install", "download.html"), self.a_docref("contact", "contact.html"), + self.a_docref("changelog", "changelog.html"), self.a_docref("faq", "faq.html"), html.div( html.h3("py.test:"), Modified: py/dist/doc/path.txt ============================================================================== --- py/dist/doc/path.txt (original) +++ py/dist/doc/path.txt Thu Aug 27 12:30:12 2009 @@ -255,9 +255,7 @@ There is some experimental small approach (:source:`py/path/gateway/`) aiming at having -a convenient Remote Path implementation -and some considerations about future -works in the according :source:`py/path/gateway/TODO.txt` +a convenient Remote Path implementation. There are various hacks out there to have Memory-Filesystems and even path objects Modified: py/dist/doc/test/index.txt ============================================================================== --- py/dist/doc/test/index.txt (original) +++ py/dist/doc/test/index.txt Thu Aug 27 12:30:12 2009 @@ -17,7 +17,9 @@ customize_: configuration, customization, extensions +changelog_: history of changes covering last releases +.. _changelog: ../changelog.html .. _`plugins`: plugin/index.html .. _`talks, tutorials, examples`: talks.html .. _quickstart: quickstart.html Added: py/dist/doc/test/plugin/django.txt ============================================================================== --- (empty file) +++ py/dist/doc/test/plugin/django.txt Thu Aug 27 12:30:12 2009 @@ -0,0 +1,7 @@ +pytest_django plugin (EXTERNAL) +========================================== + +pytest_django is a plugin for py.test that provides a set of useful tools for testing Django applications, checkout Ben Firshman's `pytest_django github page`_. + +.. _`pytest_django github page`: http://github.com/bfirsh/pytest_django/tree/master + Modified: py/dist/doc/test/plugin/index.txt ============================================================================== --- py/dist/doc/test/plugin/index.txt (original) +++ py/dist/doc/test/plugin/index.txt Thu Aug 27 12:30:12 2009 @@ -22,6 +22,8 @@ nose_ nose-compatibility plugin: allow to run nose test suites natively. +django_ support for testing django applications + doctest_ collect and execute doctests from modules and test files. restdoc_ perform ReST syntax, local and remote reference tests on .rst/.txt files. Modified: py/dist/doc/test/plugin/links.txt ============================================================================== --- py/dist/doc/test/plugin/links.txt (original) +++ py/dist/doc/test/plugin/links.txt Thu Aug 27 12:30:12 2009 @@ -1,37 +1,38 @@ .. _`helpconfig`: helpconfig.html .. _`terminal`: terminal.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_recwarn.py .. _`unittest`: unittest.html -.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_monkeypatch.py -.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_keyword.py +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_monkeypatch.py +.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_keyword.py .. _`pastebin`: pastebin.html .. _`plugins`: index.html -.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_capture.py -.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_doctest.py +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_capture.py +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_doctest.py .. _`capture`: capture.html -.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_nose.py -.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_restdoc.py +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_restdoc.py .. _`xfail`: xfail.html -.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_pastebin.py -.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_figleaf.py -.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_hooklog.py +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_pastebin.py +.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_figleaf.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_hooklog.py .. _`checkout the py.test development version`: ../../download.html#checkout -.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_helpconfig.py +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html .. _`doctest`: doctest.html .. _`get in contact`: ../../contact.html -.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_xfail.py +.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_xfail.py .. _`figleaf`: figleaf.html .. _`customize`: ../customize.html .. _`hooklog`: hooklog.html -.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_terminal.py +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_terminal.py .. _`recwarn`: recwarn.html -.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_pdb.py +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/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/1.0.1/py/test/plugin/pytest_unittest.py +.. _`django`: django.html +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_unittest.py .. _`nose`: nose.html -.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.1/py/test/plugin/pytest_resultlog.py +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.0.2/py/test/plugin/pytest_resultlog.py .. _`pdb`: pdb.html Modified: py/dist/py/__init__.py ============================================================================== --- py/dist/py/__init__.py (original) +++ py/dist/py/__init__.py Thu Aug 27 12:30:12 2009 @@ -20,7 +20,9 @@ from initpkg import initpkg trunk = None -version = trunk or "1.0.1" +version = trunk or "1.0.x" + +del trunk initpkg(__name__, description = "py.test and pylib: advanced testing tool and networking lib", Modified: py/dist/py/misc/rest.py ============================================================================== --- py/dist/py/misc/rest.py (original) +++ py/dist/py/misc/rest.py Thu Aug 27 12:30:12 2009 @@ -31,7 +31,7 @@ source_path = os.path.abspath(str(source_path)) prevdir = os.getcwd() try: - os.chdir(os.path.dirname(source_path)) + #os.chdir(os.path.dirname(source_path)) return publish_string(source, source_path, writer_name='html', settings_overrides=kwargs) finally: Modified: py/dist/py/test/looponfail/testing/test_remote.py ============================================================================== --- py/dist/py/test/looponfail/testing/test_remote.py (original) +++ py/dist/py/test/looponfail/testing/test_remote.py Thu Aug 27 12:30:12 2009 @@ -17,7 +17,9 @@ assert failures control.setup() item.fspath.write("def test_func(): assert 1\n") - (item.fspath + "c").remove() + pyc = item.fspath.new(ext=".pyc") + if pyc.check(): + pyc.remove() failures = control.runsession(failures) assert not failures @@ -37,7 +39,9 @@ def test_new(): assert 0 """)) - (modcol.fspath + "c").remove() + pyc = modcol.fspath.new(ext=".pyc") + if pyc.check(): + pyc.remove() failures = control.runsession(failures) assert not failures control.setup() Modified: py/dist/py/test/plugin/pytest_pytester.py ============================================================================== --- py/dist/py/test/plugin/pytest_pytester.py (original) +++ py/dist/py/test/plugin/pytest_pytester.py Thu Aug 27 12:30:12 2009 @@ -516,108 +516,3 @@ "*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 "" %(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/dist/setup.py ============================================================================== --- py/dist/setup.py (original) +++ py/dist/setup.py Thu Aug 27 12:30:12 2009 @@ -31,7 +31,7 @@ name='py', description='py.test and pylib: advanced testing tool and networking lib', long_description = long_description, - version= trunk or '1.0.1', + version= trunk or '1.0.x', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], From commits-noreply at bitbucket.org Sat Aug 29 01:52:14 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 28 Aug 2009 23:52:14 -0000 Subject: [py-svn] commit/py-trunk: 4 new changesets Message-ID: <20090828235214.17476.85672@domU-12-31-39-00-95-01.compute-1.internal> 4 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/d6dc694b0ea1/ changeset: r1320:d6dc694b0ea1 user: gutworth date: 2009-08-29 01:44:20 summary: implement assert debugging with builtin AST affected #: 2 files (11.1 KB) http://www.bitbucket.org/hpk42/py-trunk/changeset/ea02060287f7/ changeset: r1321:ea02060287f7 user: gutworth date: 2009-08-29 01:51:14 summary: remove magic directories from install affected #: 1 file (67 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/22a27a0c9b4a/ changeset: r1318:22a27a0c9b4a user: gutworth date: 2009-08-29 01:07:28 summary: fix location of magic AssertionError affected #: 2 files (3 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/e90a91e52615/ changeset: r1319:e90a91e52615 user: gutworth date: 2009-08-29 01:39:51 summary: make the patched compile() work with AST affected #: 2 files (678 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 03:15:33 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 01:15:33 -0000 Subject: [py-svn] commit/py-trunk: gutworth: move the old assertion reinterpreting implementation to _assertionold.py Message-ID: <20090829011533.17476.91216@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/4a681ffa2e7f/ changeset: r1322:4a681ffa2e7f user: gutworth date: 2009-08-29 03:13:49 summary: move the old assertion reinterpreting implementation to _assertionold.py Also, seperate out some common code from the two. affected #: 7 files (138 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 03:18:47 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 01:18:47 -0000 Subject: [py-svn] commit/py-trunk: gutworth: new except syntax Message-ID: <20090829011847.17476.97270@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/513df1a19127/ changeset: r1323:513df1a19127 user: gutworth date: 2009-08-29 03:17:46 summary: new except syntax affected #: 1 file (2 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 03:28:45 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 01:28:45 -0000 Subject: [py-svn] commit/py-trunk: gutworth: only test View on 2.x Message-ID: <20090829012845.17476.96830@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/74de8394972b/ changeset: r1324:74de8394972b user: gutworth date: 2009-08-29 03:28:09 summary: only test View on 2.x affected #: 1 file (173 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 13:59:04 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 11:59:04 -0000 Subject: [py-svn] commit/py-trunk: gutworth: fix interpreting is/is not/in/not in Message-ID: <20090829115904.17476.5230@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/b30d8a1bdedc/ changeset: r1325:b30d8a1bdedc user: gutworth date: 2009-08-29 13:58:54 summary: fix interpreting is/is not/in/not in affected #: 2 files (418 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 14:04:38 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 12:04:38 -0000 Subject: [py-svn] commit/py-trunk: gutworth: replace iteritems() with items() Message-ID: <20090829120438.17476.23364@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/43491391cf1f/ changeset: r1326:43491391cf1f user: gutworth date: 2009-08-29 14:03:19 summary: replace iteritems() with items() affected #: 2 files (12 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 14:07:28 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 12:07:28 -0000 Subject: [py-svn] commit/py-trunk: hpk: fixing builtin tests and print_ builtin Message-ID: <20090829120728.17476.72130@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/640abc622874/ changeset: r1327:640abc622874 user: hpk date: 2009-08-29 13:47:10 summary: fixing builtin tests and print_ builtin affected #: 2 files (170 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 14:10:14 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 12:10:14 -0000 Subject: [py-svn] commit/py-trunk: hpk: merge the benjamins and my changes, accidentally caused a new remote head Message-ID: <20090829121014.17476.93858@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/718dc7986da2/ changeset: r1328:718dc7986da2 user: hpk date: 2009-08-29 14:10:06 summary: merge the benjamins and my changes, accidentally caused a new remote head affected #: 0 files (0 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 15:54:16 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 13:54:16 -0000 Subject: [py-svn] commit/py-trunk: hpk: fix py/io classes and tests to pass 3.1 Message-ID: <20090829135416.17476.83445@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/3fcc1ff46d2a/ changeset: r1329:3fcc1ff46d2a user: hpk date: 2009-08-29 15:51:49 summary: fix py/io classes and tests to pass 3.1 introduce py.builtin._totext helper to make a 2k=unicode / 3k=str object, allow a string as data affected #: 11 files (2.7 KB) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 16:38:34 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 14:38:34 -0000 Subject: [py-svn] commit/py-trunk: 2 new changesets Message-ID: <20090829143834.17476.37718@domU-12-31-39-00-95-01.compute-1.internal> 2 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/cd018ce68f29/ changeset: r1330:cd018ce68f29 user: gutworth date: 2009-08-29 16:02:20 summary: fix tests involving Queue affected #: 1 file (43 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/3f4e7bd21287/ changeset: r1331:3f4e7bd21287 user: gutworth date: 2009-08-29 16:37:56 summary: fix the rest of py/code tests on python 3 affected #: 2 files (196 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 16:49:47 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 14:49:47 -0000 Subject: [py-svn] commit/py-trunk: hpk: * fix some syntax and 3k issues for py/path and py/process, tests only partially working Message-ID: <20090829144947.17476.40115@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/37d6e1755498/ changeset: r1332:37d6e1755498 user: hpk date: 2009-08-29 16:40:03 summary: * fix some syntax and 3k issues for py/path and py/process, tests only partially working * have py.process.cmdexec return unicode/text (for now) * rename py.builtin.basestring to _basestring affected #: 13 files (603 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 21:43:49 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 19:43:49 -0000 Subject: [py-svn] commit/py-trunk: 6 new changesets Message-ID: <20090829194349.17476.81962@domU-12-31-39-00-95-01.compute-1.internal> 6 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/1953fe8d3e01/ changeset: r1333:1953fe8d3e01 user: gutworth date: 2009-08-29 18:31:42 summary: add a test which checks the syntax of the pylib on various python versions affected #: 2 files (781 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/7561fd6c6b41/ changeset: r1334:7561fd6c6b41 user: gutworth date: 2009-08-29 18:36:08 summary: allow file to be compiled on 2.5 affected #: 1 file (20 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/07fdb03a1489/ changeset: r1335:07fdb03a1489 user: gutworth date: 2009-08-29 20:04:48 summary: make all syntax compatible with 3.1 and 2.5 affected #: 50 files (1.4 KB) http://www.bitbucket.org/hpk42/py-trunk/changeset/edfc8fe6e2bd/ changeset: r1336:edfc8fe6e2bd user: gutworth date: 2009-08-29 21:16:54 summary: fix need for py import affected #: 2 files (155 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/1bb5f3cb970e/ changeset: r1337:1bb5f3cb970e user: gutworth date: 2009-08-29 21:39:37 summary: make print write each argument individually affected #: 1 file (115 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/c39451071c6f/ changeset: r1338:c39451071c6f user: gutworth date: 2009-08-29 21:39:55 summary: fix some broken things from syntax conversion affected #: 3 files (30 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 21:54:26 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 19:54:26 -0000 Subject: [py-svn] commit/py-trunk: 3 new changesets Message-ID: <20090829195426.17476.68749@domU-12-31-39-00-95-01.compute-1.internal> 3 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/719d912ce6b9/ changeset: r1339:719d912ce6b9 user: gutworth date: 2009-08-29 21:50:29 summary: fix syntax for py3 affected #: 1 file (2 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/0739dca49e52/ changeset: r1340:0739dca49e52 user: gutworth date: 2009-08-29 21:50:44 summary: guard against tests trying to import this affected #: 1 file (75 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/b6745c468583/ changeset: r1341:b6745c468583 user: gutworth date: 2009-08-29 21:54:15 summary: fix typos in converting test_oldmagic affected #: 1 file (22 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 22:37:37 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 20:37:37 -0000 Subject: [py-svn] commit/py-trunk: 8 new changesets Message-ID: <20090829203737.17476.7287@domU-12-31-39-00-95-01.compute-1.internal> 8 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/d4aa429a80d4/ changeset: r1344:d4aa429a80d4 user: gutworth date: 2009-08-29 22:10:40 summary: handle Queue renaming affected #: 1 file (55 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/4f34077672ca/ changeset: r1345:4f34077672ca user: gutworth date: 2009-08-29 22:14:18 summary: remove usage of the new module affected #: 2 files (30 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/f960da8f0247/ changeset: r1346:f960da8f0247 user: gutworth date: 2009-08-29 22:18:21 summary: use py.builtin.builtins instead of import test affected #: 1 file (69 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/944072a726ba/ changeset: r1347:944072a726ba user: gutworth date: 2009-08-29 22:34:24 summary: add a py.builtin.execfile helper affected #: 2 files (710 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/31c8441f7676/ changeset: r1348:31c8441f7676 user: gutworth date: 2009-08-29 22:36:14 summary: add py.builtin.execfile to __init__.py affected #: 1 file (71 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/4fd733c044ae/ changeset: r1349:4fd733c044ae user: gutworth date: 2009-08-29 22:36:27 summary: use py.builtin.execfile() affected #: 2 files (22 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/4ac1fd9f3235/ changeset: r1342:4ac1fd9f3235 user: gutworth date: 2009-08-29 22:08:26 summary: use print function affected #: 1 file (105 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/b4212199607b/ changeset: r1343:b4212199607b user: gutworth date: 2009-08-29 22:08:34 summary: DeprecationWarning is in the builtin namespace affected #: 1 file (18 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Aug 29 23:25:00 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 Aug 2009 21:25:00 -0000 Subject: [py-svn] commit/py-trunk: 6 new changesets Message-ID: <20090829212500.17476.31211@domU-12-31-39-00-95-01.compute-1.internal> 6 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/09c715c60268/ changeset: r1350:09c715c60268 user: gutworth date: 2009-08-29 22:46:50 summary: add py.builtin.callable affected #: 3 files (430 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/626e07957002/ changeset: r1351:626e07957002 user: gutworth date: 2009-08-29 23:00:24 summary: fix generators on python 3 affected #: 2 files (516 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/b8effc7b79ba/ changeset: r1352:b8effc7b79ba user: gutworth date: 2009-08-29 23:02:59 summary: only use cmp() in 2.x affected #: 1 file (48 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/c2bd4a275cac/ changeset: r1353:c2bd4a275cac user: gutworth date: 2009-08-29 23:07:48 summary: use correct attribute to find the instance of a bound method affected #: 1 file (96 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/6a76cc85a0ef/ changeset: r1354:6a76cc85a0ef user: gutworth date: 2009-08-29 23:12:06 summary: get rid of usage of the new module affected #: 1 file (6 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/bc886835bede/ changeset: r1355:bc886835bede user: gutworth date: 2009-08-29 23:22:34 summary: no unbound methods in py3 affected #: 1 file (181 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sun Aug 30 15:18:18 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 30 Aug 2009 13:18:18 -0000 Subject: [py-svn] commit/py-trunk: RonnyPfannschmidt: fix module name reuse in execnet Message-ID: <20090830131818.17476.81280@domU-12-31-39-00-95-01.compute-1.internal> 1 new changeset in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/531cc70ee51e/ changeset: r1356:531cc70ee51e user: RonnyPfannschmidt date: 2009-08-30 15:00:26 summary: fix module name reuse in execnet affected #: 1 file (0 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sun Aug 30 15:26:17 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 30 Aug 2009 13:26:17 -0000 Subject: [py-svn] commit/py-trunk: 2 new changesets Message-ID: <20090830132617.17476.21171@domU-12-31-39-00-95-01.compute-1.internal> 2 new changesets in py-trunk: http://www.bitbucket.org/hpk42/py-trunk/changeset/ec6fc8a23f93/ changeset: r1357:ec6fc8a23f93 user: gutworth date: 2009-08-30 15:25:40 summary: convert argument to string affected #: 1 file (20 bytes) http://www.bitbucket.org/hpk42/py-trunk/changeset/d1bb1d7df04d/ changeset: r1358:d1bb1d7df04d user: gutworth date: 2009-08-30 15:25:48 summary: add test for conversion to string affected #: 1 file (137 bytes) Repository URL: http://bitbucket.org/hpk42/py-trunk/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.