Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fabb3df69 | ||
|
|
3e226f9392 | ||
|
|
5a4b103ab0 | ||
|
|
ea11934596 | ||
|
|
87daedd719 | ||
|
|
155ffa07de | ||
|
|
6216ab2bb7 | ||
|
|
066f8e854d | ||
|
|
d128854674 | ||
|
|
b7789714fd | ||
|
|
4f735a66c5 | ||
|
|
16aa1571c0 | ||
|
|
8be0ea942a | ||
|
|
04a98700d9 | ||
|
|
e43e58afed | ||
|
|
60a7556ca3 | ||
|
|
64a5075545 | ||
|
|
142d53acfc | ||
|
|
013381fae1 | ||
|
|
cd0972ede8 | ||
|
|
9590ccb65d | ||
|
|
a2837ab778 | ||
|
|
605f36c905 | ||
|
|
8a085c035a | ||
|
|
2603a0b76e | ||
|
|
8f0a85aee1 | ||
|
|
81d5e572ca | ||
|
|
88a0714dfa | ||
|
|
cbf05b325f | ||
|
|
d611471af2 | ||
|
|
183af95526 | ||
|
|
cd5ffcc605 | ||
|
|
2113645e11 | ||
|
|
e0aa84d5de | ||
|
|
1f5d156ab3 | ||
|
|
6167ba8923 | ||
|
|
d056149b67 | ||
|
|
c8d2873fb2 | ||
|
|
608ff40cc1 | ||
|
|
937e51b4ae | ||
|
|
296a660a73 | ||
|
|
5a52e21728 | ||
|
|
bb5ef52a58 |
4
.hgtags
4
.hgtags
@@ -6,3 +6,7 @@
|
||||
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
|
||||
|
||||
33
CHANGELOG
33
CHANGELOG
@@ -1,4 +1,33 @@
|
||||
Changes between 1.0.0b3 and 1.0.0b6
|
||||
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 +50,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
|
||||
=============================================
|
||||
|
||||
|
||||
44
MANIFEST
44
MANIFEST
@@ -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,38 @@ doc/test/examples.txt
|
||||
doc/test/extend.txt
|
||||
doc/test/features.txt
|
||||
doc/test/funcargs.txt
|
||||
doc/test/plugin/doctest.txt
|
||||
doc/test/plugin/execnetcleanup.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/pdb.txt
|
||||
doc/test/plugin/pocoo.txt
|
||||
doc/test/plugin/pytester.txt
|
||||
doc/test/plugin/recwarn.txt
|
||||
doc/test/plugin/restdoc.txt
|
||||
doc/test/plugin/resultlog.txt
|
||||
doc/test/plugin/runner.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 +79,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
|
||||
@@ -336,6 +357,8 @@ py/test/plugin/pytest_tmpdir.py
|
||||
py/test/plugin/pytest_unittest.py
|
||||
py/test/plugin/pytest_xfail.py
|
||||
py/test/plugin/test_pytest_runner.py
|
||||
py/test/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 +377,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 +385,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 +408,4 @@ py/xmlobj/testing/test_html.py
|
||||
py/xmlobj/testing/test_xml.py
|
||||
py/xmlobj/visit.py
|
||||
py/xmlobj/xml.py
|
||||
setup.py
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
157
doc/download.txt
157
doc/download.txt
@@ -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.
|
||||
|
||||
|
||||
@@ -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 ...
|
||||
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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;*/
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
===============================================
|
||||
|
||||
@@ -292,7 +289,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 +299,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
|
||||
|
||||
@@ -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).
|
||||
|
||||
44
doc/test/plugin/doctest.txt
Normal file
44
doc/test/plugin/doctest.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_doctest.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
39
doc/test/plugin/figleaf.txt
Normal file
39
doc/test/plugin/figleaf.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_figleaf.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
165
doc/test/plugin/hookspec.txt
Normal file
165
doc/test/plugin/hookspec.txt
Normal file
@@ -0,0 +1,165 @@
|
||||
|
||||
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. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# 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. """
|
||||
|
||||
49
doc/test/plugin/index.txt
Normal file
49
doc/test/plugin/index.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
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_ convenient capturing of writes to stdout/stderror streams and file descriptors.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
.. _`xfail`: xfail.html
|
||||
.. _`figleaf`: figleaf.html
|
||||
.. _`monkeypatch`: monkeypatch.html
|
||||
.. _`iocapture`: iocapture.html
|
||||
.. _`recwarn`: recwarn.html
|
||||
.. _`unittest`: unittest.html
|
||||
.. _`doctest`: doctest.html
|
||||
.. _`oejskit`: oejskit.html
|
||||
.. _`restdoc`: restdoc.html
|
||||
.. _`pocoo`: pocoo.html
|
||||
.. _`resultlog`: resultlog.html
|
||||
.. _`terminal`: terminal.html
|
||||
70
doc/test/plugin/iocapture.txt
Normal file
70
doc/test/plugin/iocapture.txt
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
pytest_iocapture plugin
|
||||
=======================
|
||||
|
||||
convenient capturing of writes to stdout/stderror streams and file descriptors.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Example Usage
|
||||
----------------------
|
||||
|
||||
You can use the `capsys funcarg`_ to capture writes
|
||||
to stdout and stderr streams by using it in a test
|
||||
likes this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_myoutput(capsys):
|
||||
print "hello"
|
||||
print >>sys.stderr, "world"
|
||||
out, err = capsys.reset()
|
||||
assert out == "hello\n"
|
||||
assert err == "world\n"
|
||||
print "next"
|
||||
out, err = capsys.reset()
|
||||
assert out == "next\n"
|
||||
|
||||
The ``reset()`` call returns a tuple and will restart
|
||||
capturing so that you can successively check for output.
|
||||
After the test function finishes the original streams
|
||||
will be restored.
|
||||
|
||||
.. _`capsys funcarg`:
|
||||
|
||||
|
||||
the 'capsys' test function argument
|
||||
-----------------------------------
|
||||
|
||||
captures writes to sys.stdout/sys.stderr and makes
|
||||
them available successively via a ``capsys.reset()`` method
|
||||
which returns a ``(out, err)`` tuple of captured strings.
|
||||
|
||||
.. _`capfd funcarg`:
|
||||
|
||||
|
||||
the 'capfd' test function argument
|
||||
----------------------------------
|
||||
|
||||
captures writes to file descriptors 1 and 2 and makes
|
||||
them available successively via a ``capsys.reset()`` method
|
||||
which returns a ``(out, err)`` tuple of captured strings.
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_iocapture.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_iocapture.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
65
doc/test/plugin/monkeypatch.txt
Normal file
65
doc/test/plugin/monkeypatch.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_monkeypatch.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
12
doc/test/plugin/oejskit.txt
Normal file
12
doc/test/plugin/oejskit.txt
Normal 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',
|
||||
35
doc/test/plugin/pocoo.txt
Normal file
35
doc/test/plugin/pocoo.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_pocoo.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_pocoo.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
65
doc/test/plugin/recwarn.txt
Normal file
65
doc/test/plugin/recwarn.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_recwarn.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
39
doc/test/plugin/restdoc.txt
Normal file
39
doc/test/plugin/restdoc.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_restdoc.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
35
doc/test/plugin/resultlog.txt
Normal file
35
doc/test/plugin/resultlog.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_resultlog.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
28
doc/test/plugin/terminal.txt
Normal file
28
doc/test/plugin/terminal.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_terminal.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
38
doc/test/plugin/unittest.txt
Normal file
38
doc/test/plugin/unittest.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_unittest.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
40
doc/test/plugin/xfail.txt
Normal file
40
doc/test/plugin/xfail.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
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_.
|
||||
|
||||
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_xfail.py
|
||||
.. _`extend`: ../extend.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`contact`: ../../contact.html
|
||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
||||
@@ -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
26
doc/test/talks.txt
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
31
example/execnet/redirect_remote_output.py
Normal file
31
example/execnet/redirect_remote_output.py
Normal 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()
|
||||
|
||||
93
example/execnet/svn-sync-repo.py
Normal file
93
example/execnet/svn-sync-repo.py
Normal 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
140
example/execnet/sysinfo.py
Normal 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)
|
||||
|
||||
3
example/funcarg/conftest.py
Normal file
3
example/funcarg/conftest.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import py
|
||||
|
||||
collect_ignore = 'mysetup', 'mysetup2', 'test_simpleprovider.py', 'parametrize'
|
||||
276
ez_setup.py
276
ez_setup.py
@@ -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:])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
266
makepluginlist.py
Normal file
266
makepluginlist.py
Normal file
@@ -0,0 +1,266 @@
|
||||
|
||||
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 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:
|
||||
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()
|
||||
for link in self.links:
|
||||
#warn(repr(self.link))
|
||||
self.Print(".. _`%s`: %s" % (link[0], link[1]))
|
||||
|
||||
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)
|
||||
|
||||
@@ -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.0b8"
|
||||
|
||||
initpkg(__name__,
|
||||
description = "py.test and pylib: advanced testing tool and networking lib",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,5 +10,5 @@ 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 terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb unittest".split()
|
||||
|
||||
|
||||
4
py/test/dist/dsession.py
vendored
4
py/test/dist/dsession.py
vendored
@@ -75,7 +75,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):
|
||||
@@ -138,6 +138,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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,20 @@ pytest_runtest_makereport.firstresult = True
|
||||
def pytest_runtest_logreport(rep):
|
||||
""" process item test report. """
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# generic reporting hooks (invoked from pytest_terminal.py)
|
||||
# ------------------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------------
|
||||
# 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 +113,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 +137,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. """
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Plugin implementing defaults and general options. """
|
||||
""" default hooks and general py.test options. """
|
||||
|
||||
import py
|
||||
|
||||
@@ -115,7 +115,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 +124,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)
|
||||
|
||||
@@ -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):
|
||||
@@ -39,13 +51,13 @@ class DoctestItem(py.test.collect.Item):
|
||||
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))
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
"""
|
||||
'capsys' and 'capfd' funcargs for capturing stdout/stderror either
|
||||
by intercepting sys.stdout/stderr or File Descriptors 1/2.
|
||||
convenient capturing of writes to stdout/stderror streams and file descriptors.
|
||||
|
||||
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.
|
||||
Example Usage
|
||||
----------------------
|
||||
|
||||
You can use the `capsys funcarg`_ to capture writes
|
||||
to stdout and stderr streams by using it in a test
|
||||
likes this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
def test_myoutput(capsys):
|
||||
print "hello"
|
||||
print >>sys.stderr, "world"
|
||||
out, err = capsys.reset()
|
||||
assert out == "hello\\n"
|
||||
assert err == "world\\n"
|
||||
print "next"
|
||||
out, err = capsys.reset()
|
||||
assert out == "next\\n"
|
||||
|
||||
The ``reset()`` call returns a tuple and will restart
|
||||
capturing so that you can successively check for output.
|
||||
After the test function finishes the original streams
|
||||
will be restored.
|
||||
"""
|
||||
|
||||
import py
|
||||
|
||||
def pytest_funcarg__capsys(request):
|
||||
""" capture writes to sys.stdout/sys.stderr. """
|
||||
"""captures writes to sys.stdout/sys.stderr and makes
|
||||
them available successively via a ``capsys.reset()`` method
|
||||
which returns a ``(out, err)`` tuple of captured strings.
|
||||
"""
|
||||
capture = Capture(py.io.StdCapture)
|
||||
request.addfinalizer(capture.finalize)
|
||||
return capture
|
||||
|
||||
def pytest_funcarg__capfd(request):
|
||||
""" capture writes to filedescriptors 1 and 2"""
|
||||
"""captures writes to file descriptors 1 and 2 and makes
|
||||
them available successively via a ``capsys.reset()`` method
|
||||
which returns a ``(out, err)`` tuple of captured strings.
|
||||
"""
|
||||
capture = Capture(py.io.StdCaptureFD)
|
||||
request.addfinalizer(capture.finalize)
|
||||
return capture
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"""
|
||||
py.test.mark / keyword plugin
|
||||
|
||||
"""
|
||||
import py
|
||||
|
||||
def pytest_namespace(config):
|
||||
def pytest_namespace():
|
||||
mark = KeywordDecorator({})
|
||||
return {'mark': mark}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
interactive debugging with a PDB prompt.
|
||||
|
||||
interactive debugging with the Python Debugger.
|
||||
"""
|
||||
import py
|
||||
import pdb, sys, linecache
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -506,3 +507,104 @@ 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))
|
||||
venv.create()
|
||||
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
|
||||
|
||||
def make_sdist(self, destdir=None):
|
||||
temp = py.path.local.mkdtemp()
|
||||
try:
|
||||
args = ['python', str(self.setup_path), 'sdist',
|
||||
'--dist-dir', str(temp)]
|
||||
subprocess.check_call(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()
|
||||
|
||||
# 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)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
return os.path.exists(self._cmd('python'))
|
||||
|
||||
def create(self, sitepackages=False):
|
||||
args = ['virtualenv', self.path]
|
||||
if not sitepackages:
|
||||
args.append('--no-site-packages')
|
||||
subprocess.check_call(args)
|
||||
|
||||
def makegateway(self):
|
||||
python = self._cmd('python')
|
||||
return py.execnet.makegateway("popen//python=%s" %(python,))
|
||||
|
||||
def pcall(self, cmd, *args, **kw):
|
||||
assert self.valid
|
||||
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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
@@ -362,6 +361,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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,10 +19,16 @@ def pytest_addoption(parser):
|
||||
def pytest_configure(config):
|
||||
config._setupstate = SetupState()
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus, excrepr=None):
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
# XXX see above
|
||||
if hasattr(session.config, '_setupstate'):
|
||||
session.config._setupstate.teardown_all()
|
||||
# prevent logging module atexit handler from choking on
|
||||
# its attempt to close already closed streams
|
||||
# see http://bugs.python.org/issue6333
|
||||
mod = py.std.sys.modules.get("logging", None)
|
||||
if mod is not None:
|
||||
mod.raiseExceptions = False
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
call = collector.config.guardedcall(
|
||||
@@ -53,7 +55,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):
|
||||
@@ -225,11 +227,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 +247,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)]:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -228,20 +233,29 @@ 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_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)
|
||||
@@ -331,14 +345,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 +376,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 +401,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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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("""
|
||||
@@ -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
|
||||
@@ -285,3 +286,17 @@ def test_functional_boxed(testdir):
|
||||
"*CRASHED*",
|
||||
"*1 failed*"
|
||||
])
|
||||
|
||||
def test_logging_interaction(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)
|
||||
assert result.stderr.str().find("atexit") == -1
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
359
py/test/plugin/test_pytest_terminal.py
Normal file
359
py/test/plugin/test_pytest_terminal.py
Normal file
@@ -0,0 +1,359 @@
|
||||
"""
|
||||
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 F*",
|
||||
"> 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, 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 failed*1 passed*"
|
||||
])
|
||||
|
||||
def test_request_getmodulepath(self, testdir):
|
||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||
item, = testdir.genitems([modcol])
|
||||
|
||||
9
py/test/testing/test_install.py
Normal file
9
py/test/testing/test_install.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import py
|
||||
|
||||
def test_make_sdist_and_run_it(py_setup, venv):
|
||||
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__
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -39,6 +39,7 @@ class TestModule:
|
||||
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
|
||||
py.test.raises(ImportError, "modcol.obj")
|
||||
|
||||
class TestDisabled:
|
||||
def test_disabled_module(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
disabled = True
|
||||
@@ -51,7 +52,6 @@ class TestModule:
|
||||
assert len(l) == 1
|
||||
py.test.raises(Skipped, "modcol.setup()")
|
||||
|
||||
class TestClass:
|
||||
def test_disabled_class(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
class TestClass:
|
||||
@@ -67,6 +67,15 @@ class TestClass:
|
||||
assert len(l) == 1
|
||||
py.test.raises(Skipped, "modcol.setup()")
|
||||
|
||||
def test_disabled_class_functional(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
class TestSimpleClassSetup:
|
||||
disabled = True
|
||||
def test_classlevel(self): pass
|
||||
def test_classlevel2(self): pass
|
||||
""")
|
||||
reprec.assertoutcome(skipped=2)
|
||||
|
||||
class TestGenerator:
|
||||
def test_generative_functions(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
|
||||
11
setup.py
11
setup.py
@@ -1,11 +1,9 @@
|
||||
"""
|
||||
autogenerated by gensetup.py
|
||||
|
||||
py lib / py.test setup.py file, autogenerated by gensetup.py
|
||||
|
||||
"""
|
||||
import os, sys
|
||||
|
||||
import ez_setup
|
||||
ez_setup.use_setuptools()
|
||||
from setuptools import setup
|
||||
|
||||
long_description = """
|
||||
@@ -27,12 +25,13 @@ For questions please check out http://pylib.org/contact.html
|
||||
|
||||
|
||||
"""
|
||||
trunk = None
|
||||
def main():
|
||||
setup(
|
||||
name='py',
|
||||
description='py.test and pylib: advanced testing tool and networking lib',
|
||||
long_description = long_description,
|
||||
version='1.0.0b6',
|
||||
version= trunk or '1.0.0b8',
|
||||
url='http://pylib.org',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
@@ -146,4 +145,4 @@ def main():
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user