Compare commits

...

58 Commits

Author SHA1 Message Date
holger krekel
e80714d701 fixes for python 2.4
--HG--
branch : 1.0.x
2009-07-31 15:35:22 +02:00
holger krekel
5156216871 regen manifest, improve docs generation
--HG--
branch : 1.0.x
2009-07-31 14:43:04 +02:00
holger krekel
a7382df5e9 fix/work around some corner cases for dist-testing
--HG--
branch : 1.0.x
2009-07-31 14:22:02 +02:00
holger krekel
737c32c783 handle final teardown properly, add a new experimental hook for it.
--HG--
branch : 1.0.x
2009-07-31 14:22:02 +02:00
holger krekel
61c53602f2 introduce new "Error" outcome and group setup/teardown and collection failures into that category. Report them separately.
--HG--
branch : 1.0.x
2009-07-31 14:22:02 +02:00
holger krekel
be949f4037 * reworked capturing to only capture once per runtest cycle
* added readouterr() method to py.io capturing helpers

--HG--
branch : 1.0.x
2009-07-31 14:21:02 +02:00
holger krekel
2514b8faaf fix a svn-1.6 issue
--HG--
branch : 1.0.x
2009-07-30 21:31:31 +02:00
holger krekel
ad34e50b71 properly handle test items that get locally collected but cannot be collected on the remote side (often due to platform reasons)
--HG--
branch : 1.0.x
2009-07-30 09:52:12 +02:00
holger krekel
dcf194ebb8 simplify py.test.mark API,
add more plugin docs

--HG--
branch : 1.0.x
2009-07-28 14:26:32 +02:00
holger krekel
5b205e0711 improve assert re-inteprpretation for comparisons
--HG--
branch : 1.0.x
2009-07-26 15:31:23 +02:00
holger krekel
9aa781907e enable capturing during collect
added a few xfailed tests for fixture reporting

--HG--
branch : 1.0.x
2009-07-25 18:45:04 +02:00
holger krekel
04e9197fd6 * reworked per-test output capturing into the pytest_iocapture.py plugin
* removed all capturing code from config object and pytest_default plugins

* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)

* added a few logging tests

--HG--
branch : 1.0.x
2009-07-25 18:09:01 +02:00
holger krekel
875ebc18ef targetting a b9
--HG--
branch : 1.0.x
2009-07-23 20:16:27 +02:00
holger krekel
73b10812c5 remove not used files
--HG--
branch : 1.0.x
2009-07-22 16:11:26 +02:00
holger krekel
de23d773d2 Added tag 1.0.0b8 for changeset 0eaa0fdf2ba0
--HG--
branch : 1.0.x
2009-07-22 16:09:57 +02:00
holger krekel
7fabb3df69 update docs, leave out internal plugins
--HG--
branch : 1.0.x
2009-07-22 16:09:49 +02:00
holger krekel
3e226f9392 Added tag 1.0.0b8 for changeset c63f35c266cb
--HG--
branch : 1.0.x
2009-07-22 15:05:18 +02:00
holger krekel
5a4b103ab0 regen links and add oejskit place holder text
--HG--
branch : 1.0.x
2009-07-22 15:05:09 +02:00
holger krekel
ea11934596 Added tag 1.0.0b8 for changeset 86f1e1b6e49b
--HG--
branch : 1.0.x
2009-07-22 15:03:33 +02:00
holger krekel
87daedd719 regen MANIFEST, update changelog
--HG--
branch : 1.0.x
2009-07-22 14:58:05 +02:00
holger krekel
155ffa07de applying samuele's patch fixing a regression
resolves issue #26

--HG--
branch : 1.0.x
2009-07-22 14:50:17 +02:00
holger krekel
6216ab2bb7 adding a test and some support code for creating
an "sdist" package, unpacking in a virtualenv and
importing and verifying the version

--HG--
branch : 1.0.x
2009-07-22 14:47:10 +02:00
holger krekel
066f8e854d * refine keyboardinterrupt handling for --dist
* generate tests for various options

--HG--
branch : 1.0.x
2009-07-22 14:39:09 +02:00
holger krekel
d128854674 poll for child process status after exit procedure
--HG--
branch : 1.0.x
2009-07-22 13:24:21 +02:00
holger krekel
b7789714fd regen plugin docs and links
--HG--
branch : 1.0.x
2009-07-20 18:56:02 +02:00
holger krekel
4f735a66c5 improve unittest documentation, enable plugin by default
--HG--
branch : 1.0.x
2009-07-20 18:54:18 +02:00
holger krekel
16aa1571c0 new cleaner layout, many improvements to docs
--HG--
branch : 1.0.x
2009-07-20 18:54:08 +02:00
holger krekel
8be0ea942a introduced pytest_keyboardinterrupt hook
removed optional argument from pytest_sessionfinish hook

--HG--
branch : 1.0.x
2009-07-20 14:01:40 +02:00
holger krekel
04a98700d9 * move some test_pytest_terminal tests to become functional/acceptance tests
* refine pytest_namespace to not take a config object

--HG--
branch : 1.0.x
2009-07-17 18:07:37 +02:00
holger krekel
e43e58afed split off tests from pytest_terminal reporter plugin
--HG--
branch : 1.0.x
2009-07-17 16:19:19 +02:00
holger krekel
60a7556ca3 resolves issue 25
fix documentation for cached_setup, thanks to anonymous for reporting.

--HG--
branch : 1.0.x
2009-07-17 14:52:11 +02:00
holger krekel
64a5075545 trying to tweak setup and py version attributes to not cause merging conflicts
--HG--
branch : 1.0.x
2009-07-14 21:36:51 +02:00
holger krekel
142d53acfc regen setup/manifest
--HG--
branch : 1.0.x
2009-07-14 21:28:50 +02:00
holger krekel
013381fae1 * add a script for generating plugin docs
* improve generation of docs
* refine plugins docs

--HG--
branch : 1.0.x
2009-07-14 21:17:13 +02:00
holger krekel
cd0972ede8 adding a new execnet example
--HG--
branch : 1.0.x
2009-07-10 13:44:33 +02:00
holger krekel
9590ccb65d merge trunk changes
--HG--
branch : 1.0.x
2009-07-09 13:16:37 +02:00
holger krekel
a2837ab778 fix test
--HG--
branch : trunk
2009-07-09 13:13:31 +02:00
holger krekel
605f36c905 fix logging interaction issue
--HG--
branch : trunk
2009-07-09 13:12:00 +02:00
holger krekel
8a085c035a merging 1.0.x changes
--HG--
branch : trunk
2009-07-08 20:08:07 +02:00
holger krekel
2603a0b76e fix figleaf plugin import
--HG--
branch : 1.0.x
2009-07-08 19:57:45 +02:00
holger krekel
8f0a85aee1 * streamlining plugin docstrings
* better organisation of example directory, also does not break
  whole-test run anymore

--HG--
branch : 1.0.x
2009-07-08 19:18:26 +02:00
holger krekel
81d5e572ca merging some old changes (still struggling with mercurial a bit)
--HG--
branch : 1.0.x
2009-07-08 16:44:40 +02:00
holger krekel
88a0714dfa * refix handling of partial setup failures
* shuffle / consolidate related tests
* re-gen setup.py

--HG--
branch : 1.0.x
2009-07-08 16:41:30 +02:00
holger krekel
cbf05b325f merging 1.0.x branch
--HG--
branch : trunk
2009-07-08 14:03:00 +02:00
holger krekel
d611471af2 adding example scripts from tutorial
--HG--
branch : trunk
2009-07-08 14:02:21 +02:00
holger krekel
183af95526 * refined funcarg docs and CHANGELOG
* fixed funcarg setup and error-raising issue

--HG--
branch : 1.0.x
2009-07-05 14:22:01 +02:00
holger krekel
cd5ffcc605 extending and fixing docs about
- test config, funcargs
- talks and tutorials

--HG--
branch : 1.0.x
2009-07-04 15:14:14 +01:00
holger krekel
2113645e11 fixing setup.py in trunk
--HG--
branch : trunk
2009-07-01 16:34:21 +01:00
holger krekel
e0aa84d5de merge 1.0.x changes
--HG--
branch : trunk
2009-07-01 14:39:20 +01:00
holger krekel
1f5d156ab3 also aadd a changelog entry
--HG--
branch : 1.0.x
2009-07-01 14:37:00 +01:00
holger krekel
6167ba8923 (radomir, holger) tweak doctest reporting for docstrings from python modules
--HG--
branch : 1.0.x
2009-07-01 14:24:19 +01:00
holger krekel
d056149b67 trying to get together a working 1.0.0b7
without ez_setup.py

--HG--
branch : 1.0.x
2009-06-30 11:42:57 +01:00
holger krekel
c8d2873fb2 merge 1.0.x branch
--HG--
branch : trunk
2009-06-29 11:50:02 +01:00
holger krekel
608ff40cc1 Added tag 1.0.0b6 for changeset 2cc0507f117f
--HG--
branch : 1.0.x
2009-06-29 11:40:24 +01:00
holger krekel
937e51b4ae improve docs some further, cleaner release announcement
--HG--
branch : 1.0.x
2009-06-26 18:12:06 +02:00
holger krekel
296a660a73 doc refinements
introduce '__channelexec__' + docs

--HG--
branch : 1.0.x
2009-06-26 17:48:46 +02:00
holger krekel
5a52e21728 merge 1.0.x changes / fixed setup.py
--HG--
branch : trunk
2009-06-24 18:08:43 +02:00
holger krekel
bb5ef52a58 merge 1.0.x release branch changes
--HG--
branch : trunk
2009-06-24 17:49:08 +02:00
102 changed files with 4005 additions and 1615 deletions

View File

@@ -11,3 +11,6 @@ syntax:glob
*.pyo
*.swp
*.html
build/
py.egg-info

View File

@@ -6,3 +6,9 @@
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8

View File

@@ -1,4 +1,67 @@
Changes between 1.0.0b3 and 1.0.0b6
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
@@ -21,6 +84,8 @@ Changes between 1.0.0b3 and 1.0.0b6
* resolve issue #18, multiprocessing.Manager() and
redirection clash
* make __name__ == "__channelexec__" for remote_exec code
Changes between 1.0.0b1 and 1.0.0b3
=============================================

View File

@@ -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,11 +28,36 @@ doc/test/examples.txt
doc/test/extend.txt
doc/test/features.txt
doc/test/funcargs.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/iocapture.txt
doc/test/plugin/keyword.txt
doc/test/plugin/monkeypatch.txt
doc/test/plugin/oejskit.txt
doc/test/plugin/pdb.txt
doc/test/plugin/pocoo.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/test_quick.py
example/funcarg/costlysetup/sub2/test_two.py
@@ -56,11 +77,9 @@ example/funcarg/test_simpleprovider.py
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
@@ -335,7 +354,10 @@ 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_iocapture.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
@@ -354,6 +376,7 @@ 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_install.py
py/test/testing/test_outcome.py
py/test/testing/test_parseopt.py
py/test/testing/test_pickling.py
@@ -361,7 +384,6 @@ 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_setup_functional.py
py/test/testing/test_traceback.py
py/test/web/__init__.py
py/test/web/exception.py
@@ -385,3 +407,4 @@ py/xmlobj/testing/test_html.py
py/xmlobj/testing/test_xml.py
py/xmlobj/visit.py
py/xmlobj/xml.py
setup.py

View File

@@ -1,39 +1,54 @@
py.test / py lib 1.0.0: distributed testing and dynamic code deployment
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:
power to testing with Python. Main news:
* new py.test plugin architecture, some examples:
* funcargs - new flexibilty and zero-boilerplate fixtures for Python testing:
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
- separate test code, configuration and setup
- ideal for integration and functional tests
- more powerful dynamic generation of tests
and much more!
* new plugin architecture, allowing project-specific and
cross-project single-file plugins. Many useful examples
shipped by default:
* funcargs - the new flexible mechanism for managing all your test setup/fixture needs!
* pytest_unittest.py: run and integrate traditional unittest.py tests
* pytest_xfail.py: mark tests as "expected to fail" and report separately.
* pytest_pocoo.py: automatically send tracebacks to pocoo paste service
* pytest_monkeypatch.py: safely monkeypatch from tests
* pytest_figleaf.py: generate html coverage reports
* pytest_resultlog.py: generate buildbot-friendly reporting output
* flexibly distribute tests to multiple computers from the command line
and many more!
See the py.test documentation for more info:
* distributed testing and distributed execution (py.execnet):
- new unified "TX" URL scheme for specifying remote resources
- new sync/async ways to handle multiple remote processes
- improved documentation
See the py.test and py lib documentation for more info:
http://pytest.org
http://pylib.org
The py lib contains the py.test tool and offers its well-tested code
independently from the testing tool, mainly:
The py lib now is smaller and focuses more on offering
functionality used by the py.test tool in independent
namespaces:
* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes
* py.code: support for dynamically running and debugging python code
* py.execnet: elastic code deployment 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-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 works well with Linux, OSX and Win32, on
Python 2.3, 2.4, 2.5 and 2.6. (Expect Python3 compatibility soon!)
Download/Install: http://codespeak.net/py/dist/download.html
best,
holger

View File

@@ -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

View File

@@ -4,6 +4,13 @@ from py.__.misc.difftime import worded_time
html = py.xml.html
class css:
#pagetitle = "pagetitle"
contentspace = "contentspace"
menubar = "menubar"
navspace = "navspace"
versioninfo = "versioninfo"
class Page(object):
doctype = ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
@@ -22,8 +29,8 @@ class Page(object):
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 +46,27 @@ class Page(object):
def fill_menubar(self):
items = [
self.a_docref("index", "index.html"),
self.a_docref("pylib index", "index.html"),
self.a_docref("py.test index", "test/test.html"),
self.a_docref("py.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,14 +79,14 @@ class Page(object):
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):
@@ -115,9 +129,9 @@ class Project:
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 +187,21 @@ class Project:
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))

View File

@@ -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

View File

@@ -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,18 +26,82 @@ 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 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
.. _`checkout`:
Installing from version control / develop mode
=================================================
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/
With a working `setuptools installation`_ you can then issue::
python setup.py develop
in order to work with your checkout version.
For enhancing one of the plugins you may go to
the ``py/test/plugin/`` sub directory.
.. _mercurial: http://mercurial.selenic.com/wiki/
.. _`no-setuptools`:
Working without setuptools / from source
==========================================
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.
There are also helper scripts to set the environment
on windows::
c:\\path\to\checkout\py\env.cmd
and on linux/osx you can add something like this to
your shell initialization::
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
for "nearby" py libs, so if you have a layout like this::
mypkg/
subpkg1/
tests/
tests/
py/
then issuing ``py.test subpkg1`` will use the py lib
from that projects root directory.
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``.
*This package is probably outdated - if somebody
*But this package is probably outdated - if somebody
can help with bringing this up to date,
that would be very much appreciated.*
@@ -46,11 +111,14 @@ Dwayne Bailey has thankfully put together a Fedora `RPM`_.
.. _`setuptools installation`: http://pypi.python.org/pypi/setuptools
.. _tarball:
Downloading a tar/zip archive and installing that
Installing from a TAR archive
===================================================
Go to the python package index (pypi) and download a tar or zip file:
You need a working `setuptools installation`_.
Go to the python package index (pypi) and download a tar file:
http://pypi.python.org/pypi/py/
@@ -58,62 +126,3 @@ and unpack it to a directory, where you then type::
python setup.py install
If you don't have a working C-compiler you can do::
python setup.py install_lib
You will then not be able to use greenlets but otherwise
``py.test`` and all tools and APIs are fine to use.
Installing from subversion / develop mode
============================================
To follow development or help with fixing things
for the next release, checkout the complete code
and documentation source::
svn co http://codespeak.net/svn/py/release/0.9.x py-0.9.x
You can then issue::
python setup.py develop
in order to work with your checkout version.
other interesting svn checkout points::
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
Working with multiple py lib versions / svn externals
=======================================================
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::
mypkg/
subpkg1/
tests/
tests/
py/ # as svn-external, could be specific tag/version
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.
You can do this by executing (on windows) a script to set the environment::
c:\\path\to\checkout\py\env.cmd
or on linux/osx you can add something like this to your shell
initialization::
eval `python ~/path/to/checkout/py/env.py`
to get good settings for PYTHONPATH and PATH.

View File

@@ -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
===================================================
@@ -233,3 +246,19 @@ socketserver::
socketgw = py.execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
print socketgw._rinfo() # print some info about the remote environment
Sending a module / checking if run through remote_exec
--------------------------------------------------------------
You can pass a module object to ``remote_exec`` in which case
its source code will be sent. No dependencies will be transferred
so the module must be self-contained or only use modules that are
installed on the "other" side. Module code can detect if it is
running in a remote_exec situation by checking for the special
``__name__`` attribute like this::
if __name__ == '__channelexec__':
# ... call module functions ...

View File

@@ -1,19 +1,19 @@
py lib: Main tools and APIs
===================================
py lib: testing and distributed programming library
====================================================
.. _`PyPI project page`: http://pypi.python.org/pypi?%3Aaction=pkg_edit&name=py
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.code`_: generate code and use advanced introspection/traceback support.
`py.path`_: use path objects to transparently access local and svn filesystems.
`py.code`_: generate python code and use advanced introspection/traceback support.
Minor support functionality
Other (minor) support functionality
===================================
`py lib scripts`_ to make python development easier.
@@ -27,6 +27,10 @@ Minor support functionality
`miscellaneous features`_ describes some small but nice py lib features.
.. _`PyPI project page`: http://pypi.python.org/pypi?%3Aaction=pkg_edit&name=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 @@ Minor support functionality
.. _`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`

View File

@@ -67,10 +67,6 @@ ul a, ol a {
dl {
}
dt {
font-weight: bold;
}
dd {
line-height: 1.5em;
margin-bottom: 1em;
@@ -85,15 +81,18 @@ blockquote {
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 @@ a[href] { color: black; text-decoration: underline; }
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 @@ span.menu_selected {
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 @@ a.menu[href]:hover {
/*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 @@ td.navibar {
padding: 0px;
}
div.pagename {
a#versioninfo {
color: blue;
}
div#pagename {
font-size: 140%;
color: blue;
text-align: center;
@@ -593,37 +595,6 @@ a.wikiaction[href]:hover {
/*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 @@ div.heading, h1 {
border-bottom: 1px solid #8CACBB;
}
h2 {
border-bottom: 1px dotted #8CACBB;
}
h1, h2, h3, h4, h5, h6 {
color: Black;
@@ -677,11 +652,10 @@ h1, h2, h3, h4, h5, h6 {
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 @@ td.toplist {
}
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 @@ div#contentspace {
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 @@ div#menubar {
}
/* 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;*/
}

View File

@@ -1,5 +1,6 @@
Test configuration
========================
.. contents::
:local:
:depth: 2
available test options
-----------------------------
@@ -68,25 +69,28 @@ home-directoray, per shell session or per test-run.
.. _`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

View File

@@ -4,15 +4,18 @@ 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 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 @@ 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`: 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 @@ 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(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.

View File

@@ -1,26 +1,23 @@
==================================================
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.
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.
py.test 1.0 works across linux, windows and osx
and on Python 2.3 - Python 2.6.
More detailed feature list:
.. contents::
.. contents:: List of Contents
:depth: 1
.. _`autocollect`:
automatically collects and executes tests
===============================================
@@ -240,11 +237,15 @@ 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=True)
@py.test.mark.webtest
def test_send_http():
...
and then use those keywords to select tests.
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
----------------------
@@ -292,7 +293,8 @@ To make it easier to distinguish the generated tests it is possible to specify a
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:
@@ -301,6 +303,7 @@ purposes such as:
* 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

View File

@@ -1,45 +1,34 @@
======================================================
**funcargs**: test setup and parametrization
======================================================
==========================================================
**funcargs**: test function arguments FTW
==========================================================
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:
Since version 1.0 py.test features the "funcarg" mechanism which
allows a test function to take arguments which will be independently
provided by factory functions. Factory functions are automatically
discovered and allow to encapsulate all neccessary setup and glue code
for running tests. Compared to `xUnit style`_ the new mechanism is
meant 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
* 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
* enable running of a test function with different values
(superseding `old-style generative tests`_)
* to enable creation of helper objects that interact with the execution
of a test function, see the `blog post about the monkeypatch funcarg`_.
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.
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: setting up test function arguments
funcarg factories: setting up test function arguments
==============================================================
Test functions can specify one ore more arguments ("funcargs")
@@ -49,22 +38,22 @@ example that you can put into a test module:
.. 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 +64,7 @@ If you run this with ``py.test test_simpleprovider.py`` you see something like t
> 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 +73,7 @@ Here is how py.test comes to execute this test function:
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 +85,17 @@ Note that if you misspell a function argument or want
to use one that isn't available, an error with a list of
available function argument is provided.
For more interesting provider functions that make good use of the
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 +107,11 @@ to access test configuration and test context:
``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 +120,8 @@ teardown/cleanup after test function execution
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 +131,52 @@ object that is to be closed when the test function finishes.
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 +184,7 @@ raised.
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 +269,7 @@ the stringified counter of the list of added calls will be used.
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 +311,12 @@ specific setup.
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 +437,7 @@ Running ``py.test test_ssh.py`` without specifying a command line option will re
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.
@@ -508,7 +497,7 @@ extend the `accept example`_ by putting this in our test class:
.. sourcecode:: python
def pytest_funcarg__accept(self, request):
arg = request.getfuncargvalue("accept") # call the next provider
arg = request.getfuncargvalue("accept") # call the next factory
# create a special layout in our tempdir
arg.tmpdir.mkdir("special")
return arg
@@ -517,8 +506,8 @@ extend the `accept example`_ by putting this in our test class:
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 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`_.
@@ -543,7 +532,7 @@ 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 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).

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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(rep):
""" 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(rep):
""" 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

48
doc/test/plugin/index.txt Normal file
View File

@@ -0,0 +1,48 @@
Plugins related to Python test functions and programs
=====================================================
xfail_ mark python tests 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.
iocapture_ 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
=================================================
pocoo_ submit failure information to paste.pocoo.org
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

View File

@@ -0,0 +1,131 @@
pytest_iocapture 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=capture``
set IO capturing method during tests: sys|fd|no.
Start improving this plugin in 30 seconds
=========================================
Do you find the above documentation or the plugin itself lacking?
1. Download `pytest_iocapture.py`_ plugin source code
2. put it somewhere as ``pytest_iocapture.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

View File

@@ -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

33
doc/test/plugin/links.txt Normal file
View File

@@ -0,0 +1,33 @@
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_recwarn.py
.. _`pytest_iocapture.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_iocapture.py
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_monkeypatch.py
.. _`plugins`: index.html
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_doctest.py
.. _`terminal`: terminal.html
.. _`hooklog`: hooklog.html
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_restdoc.py
.. _`xfail`: xfail.html
.. _`pytest_pocoo.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_pocoo.py
.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_keyword.py
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_figleaf.py
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_hooklog.py
.. _`contact`: ../../contact.html
.. _`pocoo`: pocoo.html
.. _`checkout the py.test development version`: ../../download.html#checkout
.. _`oejskit`: oejskit.html
.. _`unittest`: unittest.html
.. _`iocapture`: iocapture.html
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_xfail.py
.. _`figleaf`: figleaf.html
.. _`extend`: ../extend.html
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_terminal.py
.. _`recwarn`: recwarn.html
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/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/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_unittest.py
.. _`doctest`: doctest.html
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/69bd12627e4d304c89c2003842703ccb10dfe838/py/test/plugin/pytest_resultlog.py
.. _`pdb`: pdb.html

View File

@@ -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

View File

@@ -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',

31
doc/test/plugin/pdb.txt Normal file
View File

@@ -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

31
doc/test/plugin/pocoo.txt Normal file
View File

@@ -0,0 +1,31 @@
pytest_pocoo plugin
===================
submit failure information to paste.pocoo.org
.. contents::
:local:
command line options
--------------------
``-P, --pocoo-sendfailures``
send failures to http://paste.pocoo.org paste service
Start improving this plugin in 30 seconds
=========================================
Do you find the above documentation or the plugin itself lacking?
1. Download `pytest_pocoo.py`_ plugin source code
2. put it somewhere as ``pytest_pocoo.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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,24 @@
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.
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

View File

@@ -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

36
doc/test/plugin/xfail.txt Normal file
View File

@@ -0,0 +1,36 @@
pytest_xfail plugin
===================
mark python tests as expected-to-fail and report them separately.
.. contents::
:local:
usage
------------
Use the generic mark decorator to add the 'xfail' keyword to your
test function::
@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

View File

@@ -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
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::
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::
py.test -h
Writing and running a test
==========================
``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

26
doc/test/talks.txt Normal file
View File

@@ -0,0 +1,26 @@
==========================
Talks and Tutorials
==========================
.. _`funcargs`: funcargs.html
a list of the latest talk and tutorial material:
- `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

View File

@@ -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

View File

@@ -9,6 +9,6 @@ def test_failure_demo_fails_properly(testdir):
passed, skipped, failed = reprec.countoutcomes()
assert passed == 0
assert failed == 20, failed
colreports = reprec.getnamed("collectionreport")
colreports = reprec.getreports("pytest_collectreport")
failed = len([x.failed for x in colreports])
assert failed == 5

View File

@@ -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()

View File

@@ -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:])

140
example/execnet/sysinfo.py Normal file
View File

@@ -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)

View File

@@ -0,0 +1,3 @@
import py
collect_ignore = 'mysetup', 'mysetup2', 'test_simpleprovider.py', 'parametrize'

View 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:])

286
makepluginlist.py Normal file
View File

@@ -0,0 +1,286 @@
import py
import sys
WIDTH = 75
plugins = [
('Plugins related to Python test functions and programs',
'xfail figleaf monkeypatch iocapture recwarn',),
('Plugins for other testing styles and languages',
'unittest doctest oejskit restdoc'),
('Plugins for generic reporting and failure logging',
'pocoo resultlog terminal',),
('internal plugins / core functionality',
'pdb keyword hooklog')
#('internal plugins / core functionality',
# #'pdb keyword hooklog runner execnetcleanup # pytester',
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
#)
]
externals = {
'oejskit': 'run javascript tests in real life browsers',
}
def warn(*args):
msg = " ".join(map(str, args))
print >>sys.stderr, "WARN:", msg
class RestWriter:
_all_links = {}
def __init__(self, target):
self.target = py.path.local(target)
self.links = []
def _getmsg(self, args):
return " ".join(map(str, args))
def Print(self, *args, **kwargs):
msg = self._getmsg(args)
if 'indent' in kwargs:
indent = kwargs['indent'] * " "
lines = [(indent + x) for x in msg.split("\n")]
msg = "\n".join(lines)
self.out.write(msg)
if not msg or msg[-1] != "\n":
self.out.write("\n")
self.out.flush()
def sourcecode(self, source):
lines = str(source).split("\n")
self.Print(".. sourcecode:: python")
self.Print()
for line in lines:
self.Print(" ", line)
def _sep(self, separator, args):
msg = self._getmsg(args)
sep = len(msg) * separator
self.Print()
self.Print(msg)
self.Print(sep)
self.Print()
def h1(self, *args):
self._sep('=', args)
def h2(self, *args):
self._sep('-', args)
def h3(self, *args):
self._sep('+', args)
def li(self, *args):
msg = self._getmsg(args)
sep = "* %s" %(msg)
self.Print(sep)
def dt(self, term):
self.Print("``%s``" % term)
def dd(self, doc):
self.Print(doc, indent=4)
def para(self, *args):
msg = self._getmsg(args)
self.Print(msg)
def add_internal_link(self, name, path):
relpath = path.new(ext=".html").relto(self.target.dirpath())
self.links.append((name, relpath))
def write_links(self):
self.Print()
self.Print(".. include:: links.txt")
for link in self.links:
key = link[0]
if key in self._all_links:
assert self._all_links[key] == link[1], (key, link[1])
else:
self._all_links[key] = link[1]
def write_all_links(cls, linkpath):
p = linkpath.new(basename="links.txt")
p_writer = RestWriter(p)
p_writer.out = p_writer.target.open("w")
for name, value in cls._all_links.items():
p_writer.Print(".. _`%s`: %s" % (name, value))
p_writer.out.close()
del p_writer.out
write_all_links = classmethod(write_all_links)
def make(self, **kwargs):
self.out = self.target.open("w")
self.makerest(**kwargs)
self.write_links()
self.out.close()
print "wrote", self.target
del self.out
class PluginOverview(RestWriter):
def makerest(self, config):
plugindir = py.path.local(py.__file__).dirpath("test", "plugin")
for cat, specs in plugins:
pluginlist = specs.split()
self.h1(cat)
for name in pluginlist:
oneliner = externals.get(name, None)
docpath = self.target.dirpath(name).new(ext=".txt")
if oneliner is not None:
htmlpath = docpath.new(ext='.html')
self.para("%s_ %s" %(name, oneliner))
self.add_internal_link(name, htmlpath)
else:
doc = PluginDoc(docpath)
doc.make(config=config, name=name)
self.add_internal_link(name, doc.target)
self.para("%s_ %s" %(name, doc.oneliner))
self.Print()
class HookSpec(RestWriter):
def makerest(self, config):
module = config.pluginmanager.hook._hookspecs
source = py.code.Source(module)
self.h1("hook specification sourcecode")
self.sourcecode(source)
class PluginDoc(RestWriter):
def makerest(self, config, name):
config.pluginmanager.import_plugin(name)
plugin = config.pluginmanager.getplugin(name)
assert plugin is not None, plugin
doc = plugin.__doc__.strip()
i = doc.find("\n")
if i == -1:
oneliner = doc
moduledoc = ""
else:
oneliner = doc[:i].strip()
moduledoc = doc[i+1:].strip()
self.name = plugin.__name__.split(".")[-1]
self.oneliner = oneliner
self.moduledoc = moduledoc
self.h1("%s plugin" % self.name) # : %s" %(self.name, self.oneliner))
self.Print(self.oneliner)
self.Print()
self.Print(".. contents::")
self.Print(" :local:")
self.Print()
self.Print(moduledoc)
self.emit_funcargs(plugin)
self.emit_options(plugin)
self.emit_source(plugin, config.hg_changeset)
#self.sourcelink = (purename,
# "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
# purename + ".py")
#
def emit_source(self, plugin, hg_changeset):
basename = py.path.local(plugin.__file__).basename
if basename.endswith("pyc"):
basename = basename[:-1]
#self.para("`%s`_ source code" % basename)
#self.links.append((basename,
# "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
# basename))
self.h1("Start improving this plugin in 30 seconds")
self.para(py.code.Source("""
Do you find the above documentation or the plugin itself lacking?
1. Download `%s`_ plugin source code
2. put it somewhere as ``%s`` into your import path
3. a subsequent ``py.test`` run will use your local version
Further information: extend_ documentation, other plugins_ or contact_.
""" % (basename, basename)))
# your work appreciated if you offer back your version. In this case
# it probably makes sense if you `checkout the py.test
# development version`_ and apply your changes to the plugin
# version in there.
self.links.append((basename,
"http://bitbucket.org/hpk42/py-trunk/raw/%s/"
"py/test/plugin/%s" %(hg_changeset, basename)))
self.links.append(('extend', '../extend.html'))
self.links.append(('plugins', 'index.html'))
self.links.append(('contact', '../../contact.html'))
self.links.append(('checkout the py.test development version',
'../../download.html#checkout'))
if 0: # this breaks the page layout and makes large doc files
#self.h2("plugin source code")
self.Print()
self.para("For your convenience here is also an inlined version "
"of ``%s``:" %basename)
#self(or copy-paste from below)
self.Print()
self.sourcecode(py.code.Source(plugin))
def emit_funcargs(self, plugin):
funcargfuncs = []
prefix = "pytest_funcarg__"
for name in vars(plugin):
if name.startswith(prefix):
funcargfuncs.append(getattr(plugin, name))
if not funcargfuncs:
return
for func in funcargfuncs:
argname = func.__name__[len(prefix):]
self.Print()
self.Print(".. _`%s funcarg`:" % argname)
self.Print()
self.h2("the %r test function argument" % argname)
if func.__doc__:
doclines = func.__doc__.split("\n")
source = py.code.Source("\n".join(doclines[1:]))
source.lines.insert(0, doclines[0])
self.para(str(source))
else:
self.para("XXX missing docstring")
warn("missing docstring", func)
def emit_options(self, plugin):
from py.__.test.parseopt import Parser
options = []
parser = Parser(processopt=options.append)
if hasattr(plugin, 'pytest_addoption'):
plugin.pytest_addoption(parser)
if not options:
return
self.h2("command line options")
self.Print()
formatter = py.compat.optparse.IndentedHelpFormatter()
for opt in options:
switches = formatter.format_option_strings(opt)
self.Print("``%s``" % switches)
self.Print(opt.help, indent=4)
if __name__ == "__main__":
_config = py.test.config
_config.parse([])
_config.pluginmanager.do_configure(_config)
pydir = py.path.local(py.__file__).dirpath()
cmd = "hg tip --template '{node}'"
old = pydir.dirpath().chdir()
_config.hg_changeset = py.process.cmdexec(cmd).strip()
testdir = pydir.dirpath("doc", 'test')
ov = PluginOverview(testdir.join("plugin", "index.txt"))
ov.make(config=_config)
ov = HookSpec(testdir.join("plugin", "hookspec.txt"))
ov.make(config=_config)
RestWriter.write_all_links(testdir.join("plugin", "links.txt"))

View File

@@ -18,8 +18,9 @@ For questions please check out http://pylib.org/contact.html
"""
from initpkg import initpkg
trunk = None
version = "1.0.0b6"
version = trunk or "1.0.0b9"
initpkg(__name__,
description = "py.test and pylib: advanced testing tool and networking lib",

View File

@@ -43,3 +43,13 @@ def getsocketspec(config=None):
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})

View File

@@ -230,7 +230,7 @@ class Gateway(object):
from sys import exc_info
channel, (source, outid, errid) = item
try:
loc = { 'channel' : channel }
loc = { 'channel' : channel, '__name__': '__channelexec__'}
self._trace("execution starts:", repr(source)[:50])
close = self._local_redirect_thread_output(outid, errid)
try:

View File

@@ -63,12 +63,15 @@ class PopenCmdGateway(InstallableGateway):
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

View File

@@ -92,6 +92,11 @@ class BasicRemoteExecution:
def test_repr_doesnt_crash(self):
assert isinstance(repr(self), str)
def test_attribute__name__(self):
channel = self.gw.remote_exec("channel.send(__name__)")
name = channel.receive()
assert name == "__channelexec__"
def test_correct_setup_no_py(self):
channel = self.gw.remote_exec("""
import sys

View File

@@ -21,22 +21,53 @@ class Capture(object):
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 @@ class StdCaptureFD(Capture):
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 @@ class StdCaptureFD(Capture):
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 @@ class StdCaptureFD(Capture):
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 @@ class StdCapture(Capture):
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 @@ class StdCapture(Capture):
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

View File

@@ -30,6 +30,19 @@ class TestStdCapture:
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 @@ class TestStdCapture:
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 @@ class TestStdCapture:
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 @@ class TestStdCapture:
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 @@ def test_callcapture_nofd():
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")

View File

@@ -122,6 +122,10 @@ class Compare(Interpretable):
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 @@ class Compare(Interpretable):
raise
except:
raise Failure(self)
if not frame.is_true(self.result):
break
expr = expr2
class And(Interpretable):

View File

@@ -131,3 +131,26 @@ def test_inconsistent_assert_result(testdir):
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

View File

@@ -454,6 +454,8 @@ recursively. """
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

View File

@@ -11,12 +11,12 @@ def test_waitfinish_removes_tempdir():
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()

View File

@@ -4,7 +4,6 @@ Collectors and test Items form a tree
that is usually built iteratively.
"""
import py
from py.__.test.outcome import Skipped
def configproperty(name):
def fget(self):
@@ -31,6 +30,10 @@ class Node(object):
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 @@ class Node(object):
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 @@ class Node(object):
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 @@ class Node(object):
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 @@ class Collector(Node):
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(). """

View File

@@ -240,20 +240,6 @@ class Config(object):
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 @@ class Config(object):
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

View File

@@ -10,5 +10,6 @@ Generator = py.test.collect.Generator
Function = py.test.collect.Function
Instance = py.test.collect.Instance
pytest_plugins = "default runner terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb".split()
pytest_plugins = "default runner iocapture terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb unittest".split()
conf_capture = "fd"

View File

@@ -11,6 +11,13 @@ from py.__.test.dist.nodemanage import NodeManager
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,9 +30,14 @@ class LoopState(object):
self.shuttingdown = False
self.testsfailed = False
def __repr__(self):
return "<LoopState exitstatus=%r shuttingdown=%r len(colitems)=%d>" % (
self.exitstatus, self.shuttingdown, len(self.colitems))
def pytest_runtest_logreport(self, rep):
if rep.item in self.dsession.item2nodes:
self.dsession.removeitem(rep.item, rep.node)
if rep.when != "teardown": # otherwise we have already managed it
self.dsession.removeitem(rep.item, rep.node)
if rep.failed:
self.testsfailed = True
@@ -39,9 +51,14 @@ class LoopState(object):
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 @@ class DSession(Session):
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 @@ class DSession(Session):
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 @@ class DSession(Session):
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()
@@ -198,7 +220,9 @@ class DSession(Session):
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,7 +259,8 @@ class DSession(Session):
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")

View File

@@ -69,7 +69,8 @@ class ImmutablePickler:
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()

View File

@@ -7,7 +7,7 @@ XSpec = py.execnet.XSpec
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
@@ -155,6 +155,45 @@ class TestDSession:
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", rep=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", rep=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 @@ class TestDSession:
# 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([])
@@ -367,13 +407,36 @@ class TestDSession:
assert node.gateway.spec.popen
#XXX eq.geteventargs("pytest_sessionfinish")
@py.test.mark.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*"
])

View File

@@ -32,6 +32,7 @@ class EventQueue:
class MySetup:
def __init__(self, request):
self.id = 0
self.request = request
def geteventargs(self, eventname, timeout=2.0):
@@ -45,6 +46,8 @@ class MySetup:
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

View File

@@ -21,6 +21,11 @@ class TXNode(object):
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
self._down = False
def __repr__(self):
id = self.gateway.id
status = self._down and 'true' or 'false'
return "<TXNode %r down=%s>" %(id, status)
def notify(self, eventname, *args, **kwargs):
assert not args
self.putevent((eventname, args, kwargs))
@@ -115,6 +120,7 @@ class SlaveNode(object):
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 @@ class SlaveNode(object):
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)

View File

@@ -103,6 +103,15 @@ class FuncargRequest:
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 @@ class FuncargRequest:
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)

View File

@@ -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. """
@@ -50,6 +36,9 @@ def pytest_collectstart(collector):
def pytest_collectreport(rep):
""" 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 @@ pytest_make_collect_report.firstresult = True
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 @@ 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(). """
@@ -85,10 +79,6 @@ def pytest_runtest_call(item):
def pytest_runtest_teardown(item):
""" called after pytest_runtest_call(). """
def pytest_runtest_protocol(item):
""" run given test item and return test report. """
pytest_runtest_protocol.firstresult = True
def pytest_runtest_makereport(item, call):
""" make ItemTestReport for the given item and call outcome. """
pytest_runtest_makereport.firstresult = True
@@ -96,9 +86,28 @@ pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(rep):
""" 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 @@ def pytest_report_iteminfo(item):
"""
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_rescheduleitems(items):
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. """

View File

@@ -57,7 +57,7 @@ class HookRecorder:
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 @@ def test_hookrecorder_basic():
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

View File

@@ -1,4 +1,4 @@
""" Plugin implementing defaults and general options. """
""" default hooks and general py.test options. """
import py
@@ -60,9 +60,6 @@ def pytest_addoption(parser):
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',
@@ -84,9 +81,6 @@ def pytest_addoption(parser):
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.")
@@ -115,7 +109,7 @@ def pytest_addoption(parser):
def pytest_configure(config):
fixoptions(config)
setsession(config)
loadplugins(config)
#xxxloadplugins(config)
def fixoptions(config):
if config.option.numprocesses:
@@ -124,7 +118,7 @@ def fixoptions(config):
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)

View File

@@ -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 @@ def pytest_addoption(parser):
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 @@ class DoctestItem(py.test.collect.Item):
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 @@ class DoctestItem(py.test.collect.Item):
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):
@@ -140,6 +152,28 @@ class TestDoctests:
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

View File

@@ -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 @@ class Execnetcleanup:
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()

View File

@@ -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.')

View File

@@ -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):

View File

@@ -1,73 +1,254 @@
"""
'capsys' and 'capfd' funcargs for capturing stdout/stderror either
by intercepting sys.stdout/stderr or File Descriptors 1/2.
configurable per-test stdout/stderr capturing mechanisms.
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.
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="capture", type="choice", choices=['fd', 'sys', 'no'],
help="set IO capturing method during tests: sys|fd|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):
""" capture writes to sys.stdout/sys.stderr. """
capture = Capture(py.io.StdCapture)
request.addfinalizer(capture.finalize)
return capture
"""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):
""" capture writes to filedescriptors 1 and 2"""
capture = Capture(py.io.StdCaptureFD)
request.addfinalizer(capture.finalize)
return capture
"""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)
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
class CaptureFuncarg:
def __init__(self, request, captureclass):
self._cclass = captureclass
#request.addfinalizer(self._finalize)
def finalize(self):
if self._capture:
self._capture.reset()
def _start(self):
self.capture = self._cclass()
def reset(self):
res = None
if self._capture:
res = self._capture.reset()
self._capture = self._captureclass()
return res
def _finalize(self):
if hasattr(self, 'capture'):
self.capture.reset()
del self.capture
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)
def readouterr(self):
return self.capture.readouterr()
def close(self):
self.capture.reset()
del self.capture

View File

@@ -1,71 +1,86 @@
"""
py.test.mark / keyword plugin
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(config):
mark = KeywordDecorator({})
return {'mark': mark}
def pytest_namespace():
return {'mark': Mark()}
class KeywordDecorator:
""" decorator for setting function attributes. """
def __init__(self, keywords, lastname=None):
self._keywords = keywords
self._lastname = lastname
def __call__(self, func=None, **kwargs):
if func is None:
kw = self._keywords.copy()
kw.update(kwargs)
return KeywordDecorator(kw)
elif not hasattr(func, 'func_dict'):
kw = self._keywords.copy()
name = self._lastname
if name is None:
name = "mark"
kw[name] = func
return KeywordDecorator(kw)
func.func_dict.update(self._keywords)
return func
class Mark(object):
def __getattr__(self, name):
if name[0] == "_":
raise AttributeError(name)
kw = self._keywords.copy()
kw[name] = True
return self.__class__(kw, lastname=name)
return MarkerDecorator(name)
class MarkerDecorator:
""" decorator for setting function attributes. """
def __init__(self, name):
self.markname = name
def __repr__(self):
d = self.__dict__.copy()
name = d.pop('markname')
return "<MarkerDecorator %r %r>" %(name, d)
def __call__(self, *args, **kwargs):
if not args:
if hasattr(self, 'kwargs'):
raise TypeError("double mark-keywords?")
self.kwargs = kwargs.copy()
return self
else:
if not len(args) == 1 or not hasattr(args[0], 'func_dict'):
raise TypeError("need exactly one function to decorate, "
"got %r" %(args,))
func = args[0]
mh = MarkHolder(getattr(self, 'kwargs', {}))
setattr(func, self.markname, mh)
return func
class MarkHolder:
def __init__(self, kwargs):
self.__dict__.update(kwargs)
def test_pytest_mark_api():
mark = Mark()
py.test.raises(TypeError, "mark(x=3)")
def test_pytest_mark_getattr():
mark = KeywordDecorator({})
def f(): pass
mark.hello(f)
assert f.hello == True
assert f.hello
mark.hello("test")(f)
assert f.hello == "test"
mark.world(x=3, y=4)(f)
assert f.world
assert f.world.x == 3
assert f.world.y == 4
py.test.raises(AttributeError, "mark._hello")
py.test.raises(AttributeError, "mark.__str__")
def test_pytest_mark_call():
mark = KeywordDecorator({})
def f(): pass
mark(x=3)(f)
assert f.x == 3
def g(): pass
mark(g)
assert not g.func_dict
mark.hello(f)
assert f.hello == True
mark.hello("test")(f)
assert f.hello == "test"
mark("x1")(f)
assert f.mark == "x1"
py.test.raises(TypeError, "mark.some(x=3)(f=5)")
def test_mark_plugin(testdir):
p = testdir.makepyfile("""

View File

@@ -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
----------------
After the test has run modifications will be undone.
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/
"""
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

View File

@@ -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 @@ def pytest_configure(config):
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'

View File

@@ -1,5 +1,5 @@
"""
py.test plugin for sending testing failure information to paste.pocoo.org
submit failure information to paste.pocoo.org
"""
import py

View File

@@ -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 sys, os
import inspect
from py.__.test.config import Config as pytestConfig
import hookspec
import subprocess
pytest_plugins = '_pytest'
@@ -128,7 +129,12 @@ class TmpTestdir:
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 @@ class TmpTestdir:
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)
@@ -506,3 +515,109 @@ def test_testdir_runs_with_plugin(testdir):
assert result.stdout.fnmatch_lines([
"*1 passed*"
])
#
# experimental funcargs for venv/install-tests
#
def pytest_funcarg__venv(request):
p = request.config.mktemp(request.function.__name__, numbered=True)
venv = VirtualEnv(str(p))
return venv
def pytest_funcarg__py_setup(request):
rootdir = py.path.local(py.__file__).dirpath().dirpath()
setup = rootdir.join('setup.py')
if not setup.check():
py.test.skip("not found: %r" % setup)
return SetupBuilder(setup)
class SetupBuilder:
def __init__(self, setup_path):
self.setup_path = setup_path
assert setup_path.check()
def make_sdist(self, destdir=None):
temp = py.path.local.mkdtemp()
try:
args = ['python', str(self.setup_path), 'sdist',
'--dist-dir', str(temp)]
subcall(args)
l = temp.listdir('py-*')
assert len(l) == 1
sdist = l[0]
if destdir is None:
destdir = self.setup_path.dirpath('build')
assert destdir.check()
else:
destdir = py.path.local(destdir)
target = destdir.join(sdist.basename)
sdist.copy(target)
return target
finally:
temp.remove()
def subcall(args):
if hasattr(subprocess, 'check_call'):
subprocess.check_call(args)
else:
subprocess.call(args)
# code taken from Ronny Pfannenschmidt's virtualenvmanager
class VirtualEnv(object):
def __init__(self, path):
#XXX: supply the python executable
self.path = path
def __repr__(self):
return "<VirtualEnv at %r>" %(self.path)
def _cmd(self, name):
return os.path.join(self.path, 'bin', name)
def ensure(self):
if not os.path.exists(self._cmd('python')):
self.create()
def create(self, sitepackages=False):
args = ['virtualenv', self.path]
if not sitepackages:
args.append('--no-site-packages')
subcall(args)
def makegateway(self):
python = self._cmd('python')
return py.execnet.makegateway("popen//python=%s" %(python,))
def pcall(self, cmd, *args, **kw):
self.ensure()
return subprocess.call([
self._cmd(cmd)
] + list(args),
**kw)
def easy_install(self, *packages, **kw):
args = []
if 'index' in kw:
index = kw['index']
if isinstance(index, (list, tuple)):
for i in index:
args.extend(['-i', i])
else:
args.extend(['-i', index])
args.extend(packages)
self.pcall('easy_install', *args)
@property
def has_pip(self):
return os.path.exists(self._cmd('pip'))
def pip_install(self, *packages):
if not self.has_pip:
self.easy_install('pip')
self.pcall('pip', *packages)

View File

@@ -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):

View File

@@ -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 @@ def pytest_addoption(parser):
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 @@ class ReSTSyntaxTest(py.test.collect.Item):
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 @@ class ReSTSyntaxTest(py.test.collect.Item):
}
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 @@ class ReSTSyntaxTest(py.test.collect.Item):
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 @@ def test_deindent():
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

View File

@@ -6,7 +6,7 @@ import py
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):

View File

@@ -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,21 +19,22 @@ def pytest_addoption(parser):
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"):
@@ -53,7 +50,7 @@ def runtestprotocol(item, log=True):
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 @@ def pytest_runtest_call(item):
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)
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 BaseReport(object):
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 @@ class ItemTestReport(BaseReport):
elif excinfo.errisinstance(Skipped):
self.skipped = True
shortrepr = "s"
longrepr = self.item._repr_failure_py(excinfo, outerr)
longrepr = self.item._repr_failure_py(excinfo)
else:
self.failed = True
shortrepr = self.item.shortfailurerepr
if self.when == "call":
longrepr = self.item.repr_failure(excinfo, outerr)
longrepr = self.item.repr_failure(excinfo)
else: # exception in setup or teardown
longrepr = self.item._repr_failure_py(excinfo, outerr)
longrepr = self.item._repr_failure_py(excinfo)
shortrepr = shortrepr.lower()
self.shortrepr = shortrepr
self.longrepr = longrepr
def __repr__(self):
status = (self.passed and "passed" or
self.skipped and "skipped" or
self.failed and "failed" or
"CORRUPT")
l = [repr(self.item.name), "when=%r" % self.when, "outcome %r" % status,]
if hasattr(self, 'node'):
l.append("txnode=%s" % self.node.gateway.id)
info = " " .join(map(str, l))
return "<ItemTestReport %s>" % info
def getnode(self):
return self.item
class CollectReport(BaseReport):
skipped = failed = passed = False
def __init__(self, collector, result, excinfo=None, outerr=None):
def __init__(self, collector, result, excinfo=None):
self.collector = collector
if not excinfo:
self.passed = True
self.result = result
else:
self.outerr = outerr
self.longrepr = self.collector._repr_failure_py(excinfo, outerr)
self.longrepr = self.collector._repr_failure_py(excinfo)
if excinfo.errisinstance(Skipped):
self.skipped = True
self.reason = str(excinfo.value)
@@ -206,6 +224,13 @@ class CollectReport(BaseReport):
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 @@ class SetupState(object):
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 @@ class SetupState(object):
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)]:

View File

@@ -1,3 +1,8 @@
"""
Implements terminal reporting of the full testing process.
This is a good source for looking at the various reporting hooks.
"""
import py
import sys
@@ -161,11 +166,14 @@ class TerminalReporter:
fspath, lineno, msg = self._getreportinfo(item)
self.write_fspath_result(fspath, "")
def pytest__teardown_final_logerror(self, rep):
self.stats.setdefault("error", []).append(rep)
def pytest_runtest_logreport(self, rep):
if rep.passed and rep.when in ("setup", "teardown"):
return
fspath = rep.item.fspath
cat, letter, word = self.getcategoryletterword(rep)
if not letter and not word:
# probably passed setup/teardown
return
if isinstance(word, tuple):
word, markup = word
else:
@@ -189,9 +197,9 @@ class TerminalReporter:
def pytest_collectreport(self, rep):
if not rep.passed:
if rep.failed:
self.stats.setdefault("failed", []).append(rep)
self.stats.setdefault("error", []).append(rep)
msg = rep.longrepr.reprcrash.message
self.write_fspath_result(rep.collector.fspath, "F")
self.write_fspath_result(rep.collector.fspath, "E")
elif rep.skipped:
self.stats.setdefault("skipped", []).append(rep)
self.write_fspath_result(rep.collector.fspath, "S")
@@ -228,20 +236,30 @@ class TerminalReporter:
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 +292,11 @@ class TerminalReporter:
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 +318,39 @@ class TerminalReporter:
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 +374,6 @@ class TerminalReporter:
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 = " "
@@ -370,7 +405,7 @@ class CollectonlyReporter:
self._failed.append(rep)
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 +430,3 @@ def repr_pythonversion(v=None):
except (TypeError, ValueError):
return str(v)
# ===============================================================================
#
# plugin tests
#
# ===============================================================================
import pytest_runner as runner # XXX
def basic_run_report(item):
return runner.call_and_report(item, "call", log=False)
class TestTerminal:
def test_pass_skip_fail(self, testdir, linecomp):
modcol = testdir.getmodulecol("""
import py
def test_ok():
pass
def test_skip():
py.test.skip("xx")
def test_func():
assert 0
""")
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
rep.config.pluginmanager.register(rep)
rep.config.hook.pytest_sessionstart(session=testdir.session)
for item in testdir.genitems([modcol]):
ev = basic_run_report(item)
rep.config.hook.pytest_runtest_logreport(rep=ev)
linecomp.assert_contains_lines([
"*test_pass_skip_fail.py .sF"
])
rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
linecomp.assert_contains_lines([
" def test_func():",
"> assert 0",
"E assert 0",
])
def test_pass_skip_fail_verbose(self, testdir, linecomp):
modcol = testdir.getmodulecol("""
import py
def test_ok():
pass
def test_skip():
py.test.skip("xx")
def test_func():
assert 0
""", configargs=("-v",))
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
rep.config.pluginmanager.register(rep)
rep.config.hook.pytest_sessionstart(session=testdir.session)
items = modcol.collect()
rep.config.option.debug = True #
for item in items:
rep.config.hook.pytest_itemstart(item=item, node=None)
s = linecomp.stringio.getvalue().strip()
assert s.endswith(item.name)
rep.config.hook.pytest_runtest_logreport(rep=basic_run_report(item))
linecomp.assert_contains_lines([
"*test_pass_skip_fail_verbose.py:2: *test_ok*PASS*",
"*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP*",
"*test_pass_skip_fail_verbose.py:6: *test_func*FAIL*",
])
rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
linecomp.assert_contains_lines([
" def test_func():",
"> assert 0",
"E assert 0",
])
def test_collect_fail(self, testdir, linecomp):
modcol = testdir.getmodulecol("import xyz")
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
rep.config.pluginmanager.register(rep)
rep.config.hook.pytest_sessionstart(session=testdir.session)
l = list(testdir.genitems([modcol]))
assert len(l) == 0
linecomp.assert_contains_lines([
"*test_collect_fail.py F*"
])
rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
linecomp.assert_contains_lines([
"> import xyz",
"E ImportError: No module named xyz"
])
def test_internalerror(self, testdir, linecomp):
modcol = testdir.getmodulecol("def test_one(): pass")
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
excinfo = py.test.raises(ValueError, "raise ValueError('hello')")
rep.pytest_internalerror(excinfo.getrepr())
linecomp.assert_contains_lines([
"INTERNALERROR> *raise ValueError*"
])
def test_gwmanage_events(self, testdir, linecomp):
modcol = testdir.getmodulecol("""
def test_one():
pass
""", configargs=("-v",))
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
class gw1:
id = "X1"
spec = py.execnet.XSpec("popen")
class gw2:
id = "X2"
spec = py.execnet.XSpec("popen")
class rinfo:
version_info = (2, 5, 1, 'final', 0)
executable = "hello"
platform = "xyz"
cwd = "qwe"
rep.pyexecnet_gwmanage_newgateway(gw1, rinfo)
linecomp.assert_contains_lines([
"X1*popen*xyz*2.5*"
])
rep.pyexecnet_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2])
linecomp.assert_contains_lines([
"rsyncstart: hello -> X1, X2"
])
rep.pyexecnet_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2])
linecomp.assert_contains_lines([
"rsyncfinish: hello -> X1, X2"
])
def test_writeline(self, testdir, linecomp):
modcol = testdir.getmodulecol("def test_one(): pass")
stringio = py.std.cStringIO.StringIO()
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
rep.write_fspath_result(py.path.local("xy.py"), '.')
rep.write_line("hello world")
lines = linecomp.stringio.getvalue().split('\n')
assert not lines[0]
assert lines[1].endswith("xy.py .")
assert lines[2] == "hello world"
def test_looponfailreport(self, testdir, linecomp):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_fail2():
raise ValueError()
""")
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
reports = [basic_run_report(x) for x in modcol.collect()]
rep.pytest_looponfailinfo(reports, [modcol.config.topdir])
linecomp.assert_contains_lines([
"*test_looponfailreport.py:2: assert 0",
"*test_looponfailreport.py:4: ValueError*",
"*waiting*",
"*%s*" % (modcol.config.topdir),
])
def test_tb_option(self, testdir, linecomp):
# XXX usage of testdir
for tbopt in ["long", "short", "no"]:
print 'testing --tb=%s...' % tbopt
modcol = testdir.getmodulecol("""
import py
def g():
raise IndexError
def test_func():
print 6*7
g() # --calling--
""", configargs=("--tb=%s" % tbopt,))
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
rep.config.pluginmanager.register(rep)
rep.config.hook.pytest_sessionstart(session=testdir.session)
for item in testdir.genitems([modcol]):
rep.config.hook.pytest_runtest_logreport(
rep=basic_run_report(item))
rep.config.hook.pytest_sessionfinish(session=testdir.session, exitstatus=1)
s = linecomp.stringio.getvalue()
if tbopt == "long":
print s
assert 'print 6*7' in s
else:
assert 'print 6*7' not in s
if tbopt != "no":
assert '--calling--' in s
assert 'IndexError' in s
else:
assert 'FAILURES' not in s
assert '--calling--' not in s
assert 'IndexError' not in s
linecomp.stringio.truncate(0)
def test_show_path_before_running_test(self, testdir, linecomp):
item = testdir.getitem("def test_func(): pass")
tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr)
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*test_show_path_before_running_test.py*"
])
def test_itemreport_reportinfo(self, testdir, linecomp):
testdir.makeconftest("""
import py
class Function(py.test.collect.Function):
def reportinfo(self):
return "ABCDE", 42, "custom"
""")
item = testdir.getitem("def test_func(): pass")
tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr)
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*ABCDE "
])
tr.config.option.verbose = True
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*ABCDE:43: custom*"
])
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
item = testdir.getitem("def test_func(): pass")
class Plugin:
def pytest_report_iteminfo(self, item):
return "FGHJ", 42, "custom"
item.config.pluginmanager.register(Plugin())
tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr)
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*FGHJ "
])
tr.config.option.verbose = True
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*FGHJ:43: custom*"
])
def pseudo_keyboard_interrupt(self, testdir, linecomp, verbose=False):
modcol = testdir.getmodulecol("""
def test_foobar():
assert 0
def test_spamegg():
import py; py.test.skip('skip me please!')
def test_interrupt_me():
raise KeyboardInterrupt # simulating the user
""", configargs=("-v",)*verbose)
#""", configargs=("--showskipsummary",) + ("-v",)*verbose)
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
modcol.config.hook.pytest_sessionstart(session=testdir.session)
try:
for item in testdir.genitems([modcol]):
modcol.config.hook.pytest_runtest_logreport(
rep=basic_run_report(item))
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
else:
py.test.fail("no KeyboardInterrupt??")
s = linecomp.stringio.getvalue()
if not verbose:
assert s.find("_keyboard_interrupt.py Fs") != -1
modcol.config.hook.pytest_sessionfinish(
session=testdir.session, exitstatus=2, excrepr=excinfo.getrepr())
text = linecomp.stringio.getvalue()
linecomp.assert_contains_lines([
" def test_foobar():",
"> assert 0",
"E assert 0",
])
#assert "Skipped: 'skip me please!'" in text
assert "_keyboard_interrupt.py:6: KeyboardInterrupt" in text
see_details = "raise KeyboardInterrupt # simulating the user" in text
assert see_details == verbose
def test_keyboard_interrupt(self, testdir, linecomp):
self.pseudo_keyboard_interrupt(testdir, linecomp)
def test_verbose_keyboard_interrupt(self, testdir, linecomp):
self.pseudo_keyboard_interrupt(testdir, linecomp, verbose=True)
def test_skip_reasons_folding(self):
class longrepr:
class reprcrash:
path = 'xyz'
lineno = 3
message = "justso"
ev1 = runner.CollectReport(None, None)
ev1.when = "execute"
ev1.skipped = True
ev1.longrepr = longrepr
ev2 = runner.ItemTestReport(None, excinfo=longrepr)
ev2.skipped = True
l = folded_skips([ev1, ev2])
assert len(l) == 1
num, fspath, lineno, reason = l[0]
assert num == 2
assert fspath == longrepr.reprcrash.path
assert lineno == longrepr.reprcrash.lineno
assert reason == longrepr.reprcrash.message
class TestCollectonly:
def test_collectonly_basic(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
def test_func():
pass
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
indent = rep.indent
rep.config.hook.pytest_collectstart(collector=modcol)
linecomp.assert_contains_lines([
"<Module 'test_collectonly_basic.py'>"
])
item = modcol.join("test_func")
rep.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
" <Function 'test_func'>",
])
rep.config.hook.pytest_collectreport(
rep=runner.CollectReport(modcol, [], excinfo=None))
assert rep.indent == indent
def test_collectonly_skipped_module(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
import py
py.test.skip("nomod")
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
cols = list(testdir.genitems([modcol]))
assert len(cols) == 0
linecomp.assert_contains_lines("""
<Module 'test_collectonly_skipped_module.py'>
!!! Skipped: 'nomod' !!!
""")
def test_collectonly_failed_module(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
raise ValueError(0)
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
cols = list(testdir.genitems([modcol]))
assert len(cols) == 0
linecomp.assert_contains_lines("""
<Module 'test_collectonly_failed_module.py'>
!!! ValueError: 0 !!!
""")
def test_collectonly_fatal(self, testdir):
p1 = testdir.makeconftest("""
def pytest_collectstart(collector):
assert 0, "urgs"
""")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*INTERNAL*args*"
])
assert result.ret == 3
def test_repr_python_version(monkeypatch):
monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
assert repr_pythonversion() == "2.5.1-final-0"
py.std.sys.version_info = x = (2,3)
assert repr_pythonversion() == str(x)

View File

@@ -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)

View File

@@ -1,13 +1,21 @@
"""
mark tests as expected-to-fail and report them separately.
mark python tests as expected-to-fail and report them separately.
example:
usage
------------
Use the generic mark decorator to add the 'xfail' keyword to your
test function::
@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']

View File

@@ -0,0 +1,307 @@
import py, os, sys
from py.__.test.plugin.pytest_iocapture 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*",
])
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
@py.test.mark.xfail
def test_logging_and_crossscope_fixtures(self, testdir):
# XXX also needs final teardown reporting to work!
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 (('--iocapture=sys',), ('--iocapture=fd',)):
print optargs
result = testdir.runpytest(p, *optargs)
s = result.stdout.str()
result.stdout.fnmatch_lines([
"*WARN*hello1",
"*WARN*hello2",
"*WARN*hello3",
])
# 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

View File

@@ -86,7 +86,8 @@ class BaseFunctionalTests:
#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 @@ class BaseFunctionalTests:
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 @@ class BaseFunctionalTests:
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 @@ class BaseFunctionalTests:
#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 @@ class BaseFunctionalTests:
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 @@ class BaseFunctionalTests:
#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,6 +193,23 @@ class BaseFunctionalTests:
else:
py.test.fail("did not raise")
@py.test.mark.xfail
def test_capture_per_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"
class TestExecutionNonForked(BaseFunctionalTests):
def getrunner(self):
@@ -285,3 +288,4 @@ def test_functional_boxed(testdir):
"*CRASHED*",
"*1 failed*"
])

View File

@@ -55,6 +55,7 @@ def test_class_setup(testdir):
""")
reprec.assertoutcome(passed=1+2+1)
def test_method_setup(testdir):
reprec = testdir.inline_runsource("""
class TestSetupMethod:

View File

@@ -0,0 +1,419 @@
"""
terminal reporting of the full testing process.
"""
import py
import sys
# ===============================================================================
# plugin tests
#
# ===============================================================================
import pytest_runner as runner # XXX
from pytest_terminal import TerminalReporter, CollectonlyReporter
from pytest_terminal import repr_pythonversion, folded_skips
def basic_run_report(item):
return runner.call_and_report(item, "call", log=False)
class Option:
def __init__(self, verbose=False, dist=None):
self.verbose = verbose
self.dist = dist
def _getcmdargs(self):
l = []
if self.verbose:
l.append('-v')
if self.dist:
l.append('--dist=%s' % self.dist)
l.append('--tx=popen')
return l
def _getcmdstring(self):
return " ".join(self._getcmdargs())
def pytest_generate_tests(metafunc):
if "option" in metafunc.funcargnames:
metafunc.addcall(
id="default",
funcargs={'option': Option(verbose=False)}
)
metafunc.addcall(
id="verbose",
funcargs={'option': Option(verbose=True)}
)
nodist = getattr(metafunc.function, 'nodist', False)
if not nodist:
metafunc.addcall(
id="verbose-dist",
funcargs={'option': Option(dist='each', verbose=True)}
)
class TestTerminal:
def test_pass_skip_fail(self, testdir, option):
p = testdir.makepyfile("""
import py
def test_ok():
pass
def test_skip():
py.test.skip("xx")
def test_func():
assert 0
""")
result = testdir.runpytest(*option._getcmdargs())
if option.verbose:
if not option.dist:
result.stdout.fnmatch_lines([
"*test_pass_skip_fail.py:2: *test_ok*PASS*",
"*test_pass_skip_fail.py:4: *test_skip*SKIP*",
"*test_pass_skip_fail.py:6: *test_func*FAIL*",
])
else:
expected = [
"*PASS*test_pass_skip_fail.py:2: *test_ok*",
"*SKIP*test_pass_skip_fail.py:4: *test_skip*",
"*FAIL*test_pass_skip_fail.py:6: *test_func*",
]
for line in expected:
result.stdout.fnmatch_lines([line])
else:
result.stdout.fnmatch_lines([
"*test_pass_skip_fail.py .sF"
])
result.stdout.fnmatch_lines([
" def test_func():",
"> assert 0",
"E assert 0",
])
def test_collect_fail(self, testdir, option):
p = testdir.makepyfile("import xyz")
result = testdir.runpytest(*option._getcmdargs())
result.stdout.fnmatch_lines([
"*test_collect_fail.py E*",
"> import xyz",
"E ImportError: No module named xyz",
"*1 error*",
])
def test_internalerror(self, testdir, linecomp):
modcol = testdir.getmodulecol("def test_one(): pass")
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
excinfo = py.test.raises(ValueError, "raise ValueError('hello')")
rep.pytest_internalerror(excinfo.getrepr())
linecomp.assert_contains_lines([
"INTERNALERROR> *raise ValueError*"
])
def test_gwmanage_events(self, testdir, linecomp):
modcol = testdir.getmodulecol("""
def test_one():
pass
""", configargs=("-v",))
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
class gw1:
id = "X1"
spec = py.execnet.XSpec("popen")
class gw2:
id = "X2"
spec = py.execnet.XSpec("popen")
class rinfo:
version_info = (2, 5, 1, 'final', 0)
executable = "hello"
platform = "xyz"
cwd = "qwe"
rep.pyexecnet_gwmanage_newgateway(gw1, rinfo)
linecomp.assert_contains_lines([
"X1*popen*xyz*2.5*"
])
rep.pyexecnet_gwmanage_rsyncstart(source="hello", gateways=[gw1, gw2])
linecomp.assert_contains_lines([
"rsyncstart: hello -> X1, X2"
])
rep.pyexecnet_gwmanage_rsyncfinish(source="hello", gateways=[gw1, gw2])
linecomp.assert_contains_lines([
"rsyncfinish: hello -> X1, X2"
])
def test_writeline(self, testdir, linecomp):
modcol = testdir.getmodulecol("def test_one(): pass")
stringio = py.std.cStringIO.StringIO()
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
rep.write_fspath_result(py.path.local("xy.py"), '.')
rep.write_line("hello world")
lines = linecomp.stringio.getvalue().split('\n')
assert not lines[0]
assert lines[1].endswith("xy.py .")
assert lines[2] == "hello world"
def test_looponfailreport(self, testdir, linecomp):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_fail2():
raise ValueError()
""")
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
reports = [basic_run_report(x) for x in modcol.collect()]
rep.pytest_looponfailinfo(reports, [modcol.config.topdir])
linecomp.assert_contains_lines([
"*test_looponfailreport.py:2: assert 0",
"*test_looponfailreport.py:4: ValueError*",
"*waiting*",
"*%s*" % (modcol.config.topdir),
])
def test_tb_option(self, testdir, option):
p = testdir.makepyfile("""
import py
def g():
raise IndexError
def test_func():
print 6*7
g() # --calling--
""")
for tbopt in ["long", "short", "no"]:
print 'testing --tb=%s...' % tbopt
result = testdir.runpytest('--tb=%s' % tbopt)
s = result.stdout.str()
if tbopt == "long":
assert 'print 6*7' in s
else:
assert 'print 6*7' not in s
if tbopt != "no":
assert '--calling--' in s
assert 'IndexError' in s
else:
assert 'FAILURES' not in s
assert '--calling--' not in s
assert 'IndexError' not in s
def test_show_path_before_running_test(self, testdir, linecomp):
item = testdir.getitem("def test_func(): pass")
tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr)
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*test_show_path_before_running_test.py*"
])
def test_itemreport_reportinfo(self, testdir, linecomp):
testdir.makeconftest("""
import py
class Function(py.test.collect.Function):
def reportinfo(self):
return "ABCDE", 42, "custom"
""")
item = testdir.getitem("def test_func(): pass")
tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr)
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*ABCDE "
])
tr.config.option.verbose = True
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*ABCDE:43: custom*"
])
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
item = testdir.getitem("def test_func(): pass")
class Plugin:
def pytest_report_iteminfo(self, item):
return "FGHJ", 42, "custom"
item.config.pluginmanager.register(Plugin())
tr = TerminalReporter(item.config, file=linecomp.stringio)
item.config.pluginmanager.register(tr)
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*FGHJ "
])
tr.config.option.verbose = True
tr.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
"*FGHJ:43: custom*"
])
def test_keyboard_interrupt_dist(self, testdir, option):
p = testdir.makepyfile("""
raise KeyboardInterrupt
""")
result = testdir.runpytest(*option._getcmdargs())
assert result.ret == 2
result.stdout.fnmatch_lines(['*KEYBOARD INTERRUPT*'])
@py.test.mark.nodist
def test_keyboard_interrupt(self, testdir, option):
p = testdir.makepyfile("""
def test_foobar():
assert 0
def test_spamegg():
import py; py.test.skip('skip me please!')
def test_interrupt_me():
raise KeyboardInterrupt # simulating the user
""")
result = testdir.runpytest(*option._getcmdargs())
result.stdout.fnmatch_lines([
" def test_foobar():",
"> assert 0",
"E assert 0",
"*_keyboard_interrupt.py:6: KeyboardInterrupt*",
])
if option.verbose:
result.stdout.fnmatch_lines([
"*raise KeyboardInterrupt # simulating the user*",
])
result.stdout.fnmatch_lines(['*KEYBOARD INTERRUPT*'])
def test_skip_reasons_folding(self):
class longrepr:
class reprcrash:
path = 'xyz'
lineno = 3
message = "justso"
ev1 = runner.CollectReport(None, None)
ev1.when = "execute"
ev1.skipped = True
ev1.longrepr = longrepr
ev2 = runner.ItemTestReport(None, excinfo=longrepr)
ev2.skipped = True
l = folded_skips([ev1, ev2])
assert len(l) == 1
num, fspath, lineno, reason = l[0]
assert num == 2
assert fspath == longrepr.reprcrash.path
assert lineno == longrepr.reprcrash.lineno
assert reason == longrepr.reprcrash.message
class TestCollectonly:
def test_collectonly_basic(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
def test_func():
pass
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
indent = rep.indent
rep.config.hook.pytest_collectstart(collector=modcol)
linecomp.assert_contains_lines([
"<Module 'test_collectonly_basic.py'>"
])
item = modcol.join("test_func")
rep.config.hook.pytest_itemstart(item=item)
linecomp.assert_contains_lines([
" <Function 'test_func'>",
])
rep.config.hook.pytest_collectreport(
rep=runner.CollectReport(modcol, [], excinfo=None))
assert rep.indent == indent
def test_collectonly_skipped_module(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
import py
py.test.skip("nomod")
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
cols = list(testdir.genitems([modcol]))
assert len(cols) == 0
linecomp.assert_contains_lines("""
<Module 'test_collectonly_skipped_module.py'>
!!! Skipped: 'nomod' !!!
""")
def test_collectonly_failed_module(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
raise ValueError(0)
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
cols = list(testdir.genitems([modcol]))
assert len(cols) == 0
linecomp.assert_contains_lines("""
<Module 'test_collectonly_failed_module.py'>
!!! ValueError: 0 !!!
""")
def test_collectonly_fatal(self, testdir):
p1 = testdir.makeconftest("""
def pytest_collectstart(collector):
assert 0, "urgs"
""")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*INTERNAL*args*"
])
assert result.ret == 3
def test_repr_python_version(monkeypatch):
monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
assert repr_pythonversion() == "2.5.1-final-0"
py.std.sys.version_info = x = (2,3)
assert repr_pythonversion() == str(x)
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*",
])

View File

@@ -3,6 +3,7 @@ managing loading and interacting with pytest plugins.
"""
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 @@ class PluginManager(object):
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 @@ class PluginManager(object):
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 @@ class PluginManager(object):
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 @@ def formatdef(func):
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

View File

@@ -271,8 +271,9 @@ class FunctionMixin(PyobjMixin):
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"

View File

@@ -86,12 +86,11 @@ class Session(object):
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 @@ class Session(object):
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

View File

@@ -227,28 +227,6 @@ class TestGeneralUsage:
"*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("""

View File

@@ -212,38 +212,6 @@ class TestConfigApi_getcolitems:
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 @@ class TestOptionEffects:
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

View File

@@ -152,6 +152,7 @@ class TestRequest:
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 @@ class TestRequest:
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])

View File

@@ -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()

View File

@@ -26,8 +26,10 @@ def test_importorskip():
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()

View File

@@ -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):

View File

@@ -7,12 +7,27 @@ class TestBootstrapping:
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 @@ class TestPytestPluginInteractions:
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 @@ class TestPytestPluginInteractions:
assert not pluginmanager.listattr("hello")
assert pluginmanager.listattr("x") == [42]
@py.test.mark.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):

Some files were not shown because too many files have changed in this diff Show More