start reorganizing docs, write more docs, shift plugin docs, to proper documentation,
use sphinx, remove old docs ... work in progress. --HG-- branch : trunk
This commit is contained in:
parent
854f6a98ae
commit
4ee3831ac9
44
CHANGELOG
44
CHANGELOG
|
@ -1,7 +1,7 @@
|
||||||
|
Changes between 1.3.4 and 2.0.0dev0
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
Changes between 1.3.4 and 1.4.0.dev0
|
- pytest-2.0 is now its own package and depends on pylib
|
||||||
==================================================
|
|
||||||
|
|
||||||
- fix issue126 - introduce py.test.set_trace() to trace execution via
|
- fix issue126 - introduce py.test.set_trace() to trace execution via
|
||||||
PDB during the running of tests even if capturing is ongoing.
|
PDB during the running of tests even if capturing is ongoing.
|
||||||
- fix issue123 - new "python -m py.test" invocation for py.test
|
- fix issue123 - new "python -m py.test" invocation for py.test
|
||||||
|
@ -21,7 +21,7 @@ Changes between 1.3.4 and 1.4.0.dev0
|
||||||
- introduce py.builtin.any()
|
- introduce py.builtin.any()
|
||||||
|
|
||||||
Changes between 1.3.3 and 1.3.4
|
Changes between 1.3.3 and 1.3.4
|
||||||
==================================================
|
----------------------------------------------
|
||||||
|
|
||||||
- fix issue111: improve install documentation for windows
|
- fix issue111: improve install documentation for windows
|
||||||
- fix issue119: fix custom collectability of __init__.py as a module
|
- fix issue119: fix custom collectability of __init__.py as a module
|
||||||
|
@ -30,7 +30,7 @@ Changes between 1.3.3 and 1.3.4
|
||||||
- fix issue118: new --tb=native for presenting cpython-standard exceptions
|
- fix issue118: new --tb=native for presenting cpython-standard exceptions
|
||||||
|
|
||||||
Changes between 1.3.2 and 1.3.3
|
Changes between 1.3.2 and 1.3.3
|
||||||
==================================================
|
----------------------------------------------
|
||||||
|
|
||||||
- fix issue113: assertion representation problem with triple-quoted strings
|
- fix issue113: assertion representation problem with triple-quoted strings
|
||||||
(and possibly other cases)
|
(and possibly other cases)
|
||||||
|
@ -45,7 +45,7 @@ Changes between 1.3.2 and 1.3.3
|
||||||
- remove trailing whitespace in all py/text distribution files
|
- remove trailing whitespace in all py/text distribution files
|
||||||
|
|
||||||
Changes between 1.3.1 and 1.3.2
|
Changes between 1.3.1 and 1.3.2
|
||||||
==================================================
|
----------------------------------------------
|
||||||
|
|
||||||
New features
|
New features
|
||||||
++++++++++++++++++
|
++++++++++++++++++
|
||||||
|
@ -120,7 +120,7 @@ Bug fixes / Maintenance
|
||||||
- ship distribute_setup.py version 0.6.13
|
- ship distribute_setup.py version 0.6.13
|
||||||
|
|
||||||
Changes between 1.3.0 and 1.3.1
|
Changes between 1.3.0 and 1.3.1
|
||||||
==================================================
|
---------------------------------------------
|
||||||
|
|
||||||
New features
|
New features
|
||||||
++++++++++++++++++
|
++++++++++++++++++
|
||||||
|
@ -192,7 +192,7 @@ Fixes / Maintenance
|
||||||
|
|
||||||
|
|
||||||
Changes between 1.2.1 and 1.3.0
|
Changes between 1.2.1 and 1.3.0
|
||||||
==================================================
|
---------------------------------------------
|
||||||
|
|
||||||
- deprecate --report option in favour of a new shorter and easier to
|
- deprecate --report option in favour of a new shorter and easier to
|
||||||
remember -r option: it takes a string argument consisting of any
|
remember -r option: it takes a string argument consisting of any
|
||||||
|
@ -257,7 +257,7 @@ Changes between 1.2.1 and 1.3.0
|
||||||
|
|
||||||
|
|
||||||
Changes between 1.2.1 and 1.2.0
|
Changes between 1.2.1 and 1.2.0
|
||||||
=====================================
|
---------------------------------------------
|
||||||
|
|
||||||
- refined usage and options for "py.cleanup"::
|
- refined usage and options for "py.cleanup"::
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ Changes between 1.2.1 and 1.2.0
|
||||||
- fix plugin links
|
- fix plugin links
|
||||||
|
|
||||||
Changes between 1.2 and 1.1.1
|
Changes between 1.2 and 1.1.1
|
||||||
=====================================
|
---------------------------------------------
|
||||||
|
|
||||||
- moved dist/looponfailing from py.test core into a new
|
- moved dist/looponfailing from py.test core into a new
|
||||||
separately released pytest-xdist plugin.
|
separately released pytest-xdist plugin.
|
||||||
|
@ -380,7 +380,7 @@ Changes between 1.2 and 1.1.1
|
||||||
|
|
||||||
|
|
||||||
Changes between 1.1.1 and 1.1.0
|
Changes between 1.1.1 and 1.1.0
|
||||||
=====================================
|
---------------------------------------------
|
||||||
|
|
||||||
- introduce automatic plugin registration via 'pytest11'
|
- introduce automatic plugin registration via 'pytest11'
|
||||||
entrypoints via setuptools' pkg_resources.iter_entry_points
|
entrypoints via setuptools' pkg_resources.iter_entry_points
|
||||||
|
@ -399,7 +399,7 @@ Changes between 1.1.1 and 1.1.0
|
||||||
report a correct location
|
report a correct location
|
||||||
|
|
||||||
Changes between 1.1.0 and 1.0.2
|
Changes between 1.1.0 and 1.0.2
|
||||||
=====================================
|
---------------------------------------------
|
||||||
|
|
||||||
* adjust and improve docs
|
* adjust and improve docs
|
||||||
|
|
||||||
|
@ -484,7 +484,7 @@ Changes between 1.1.0 and 1.0.2
|
||||||
* simplified internal localpath implementation
|
* simplified internal localpath implementation
|
||||||
|
|
||||||
Changes between 1.0.1 and 1.0.2
|
Changes between 1.0.1 and 1.0.2
|
||||||
=====================================
|
-------------------------------------------
|
||||||
|
|
||||||
* fixing packaging issues, triggered by fedora redhat packaging,
|
* fixing packaging issues, triggered by fedora redhat packaging,
|
||||||
also added doc, examples and contrib dirs to the tarball.
|
also added doc, examples and contrib dirs to the tarball.
|
||||||
|
@ -492,7 +492,7 @@ Changes between 1.0.1 and 1.0.2
|
||||||
* added a documentation link to the new django plugin.
|
* added a documentation link to the new django plugin.
|
||||||
|
|
||||||
Changes between 1.0.0 and 1.0.1
|
Changes between 1.0.0 and 1.0.1
|
||||||
=====================================
|
-------------------------------------------
|
||||||
|
|
||||||
* added a 'pytest_nose' plugin which handles nose.SkipTest,
|
* added a 'pytest_nose' plugin which handles nose.SkipTest,
|
||||||
nose-style function/method/generator setup/teardown and
|
nose-style function/method/generator setup/teardown and
|
||||||
|
@ -526,13 +526,13 @@ Changes between 1.0.0 and 1.0.1
|
||||||
renamed some internal methods and argnames
|
renamed some internal methods and argnames
|
||||||
|
|
||||||
Changes between 1.0.0b9 and 1.0.0
|
Changes between 1.0.0b9 and 1.0.0
|
||||||
=====================================
|
-------------------------------------------
|
||||||
|
|
||||||
* more terse reporting try to show filesystem path relatively to current dir
|
* more terse reporting try to show filesystem path relatively to current dir
|
||||||
* improve xfail output a bit
|
* improve xfail output a bit
|
||||||
|
|
||||||
Changes between 1.0.0b8 and 1.0.0b9
|
Changes between 1.0.0b8 and 1.0.0b9
|
||||||
=====================================
|
-------------------------------------------
|
||||||
|
|
||||||
* cleanly handle and report final teardown of test setup
|
* cleanly handle and report final teardown of test setup
|
||||||
|
|
||||||
|
@ -566,7 +566,7 @@ Changes between 1.0.0b8 and 1.0.0b9
|
||||||
|
|
||||||
|
|
||||||
Changes between 1.0.0b7 and 1.0.0b8
|
Changes between 1.0.0b7 and 1.0.0b8
|
||||||
=====================================
|
-------------------------------------------
|
||||||
|
|
||||||
* pytest_unittest-plugin is now enabled by default
|
* pytest_unittest-plugin is now enabled by default
|
||||||
|
|
||||||
|
@ -595,7 +595,7 @@ Changes between 1.0.0b7 and 1.0.0b8
|
||||||
thanks Radomir.
|
thanks Radomir.
|
||||||
|
|
||||||
Changes between 1.0.0b3 and 1.0.0b7
|
Changes between 1.0.0b3 and 1.0.0b7
|
||||||
=============================================
|
-------------------------------------------
|
||||||
|
|
||||||
* renamed py.test.xfail back to py.test.mark.xfail to avoid
|
* renamed py.test.xfail back to py.test.mark.xfail to avoid
|
||||||
two ways to decorate for xfail
|
two ways to decorate for xfail
|
||||||
|
@ -620,7 +620,7 @@ Changes between 1.0.0b3 and 1.0.0b7
|
||||||
* make __name__ == "__channelexec__" for remote_exec code
|
* make __name__ == "__channelexec__" for remote_exec code
|
||||||
|
|
||||||
Changes between 1.0.0b1 and 1.0.0b3
|
Changes between 1.0.0b1 and 1.0.0b3
|
||||||
=============================================
|
-------------------------------------------
|
||||||
|
|
||||||
* plugin classes are removed: one now defines
|
* plugin classes are removed: one now defines
|
||||||
hooks directly in conftest.py or global pytest_*.py
|
hooks directly in conftest.py or global pytest_*.py
|
||||||
|
@ -637,7 +637,7 @@ Changes between 1.0.0b1 and 1.0.0b3
|
||||||
|
|
||||||
|
|
||||||
Changes between 0.9.2 and 1.0.0b1
|
Changes between 0.9.2 and 1.0.0b1
|
||||||
=============================================
|
-------------------------------------------
|
||||||
|
|
||||||
* introduced new "funcarg" setup method,
|
* introduced new "funcarg" setup method,
|
||||||
see doc/test/funcarg.txt
|
see doc/test/funcarg.txt
|
||||||
|
@ -661,7 +661,7 @@ Changes between 0.9.2 and 1.0.0b1
|
||||||
XXX lots of things missing here XXX
|
XXX lots of things missing here XXX
|
||||||
|
|
||||||
Changes between 0.9.1 and 0.9.2
|
Changes between 0.9.1 and 0.9.2
|
||||||
===============================
|
-------------------------------------------
|
||||||
|
|
||||||
* refined installation and metadata, created new setup.py,
|
* refined installation and metadata, created new setup.py,
|
||||||
now based on setuptools/ez_setup (thanks to Ralf Schmitt
|
now based on setuptools/ez_setup (thanks to Ralf Schmitt
|
||||||
|
@ -694,7 +694,7 @@ Changes between 0.9.1 and 0.9.2
|
||||||
* there now is a py.__version__ attribute
|
* there now is a py.__version__ attribute
|
||||||
|
|
||||||
Changes between 0.9.0 and 0.9.1
|
Changes between 0.9.0 and 0.9.1
|
||||||
===============================
|
-------------------------------------------
|
||||||
|
|
||||||
This is a fairly complete list of changes between 0.9 and 0.9.1, which can
|
This is a fairly complete list of changes between 0.9 and 0.9.1, which can
|
||||||
serve as a reference for developers.
|
serve as a reference for developers.
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/pytest"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
make -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,339 @@
|
||||||
|
/*
|
||||||
|
* sphinxdoc.css_t
|
||||||
|
* ~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* Sphinx stylesheet -- sphinxdoc theme. Originally created by
|
||||||
|
* Armin Ronacher for Werkzeug.
|
||||||
|
*
|
||||||
|
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||||
|
* :license: BSD, see LICENSE for details.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import url("basic.css");
|
||||||
|
|
||||||
|
/* -- page layout ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||||
|
'Verdana', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 150%;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #BFD1D4;
|
||||||
|
color: black;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
|
||||||
|
margin: 0px 80px 0px 80px;
|
||||||
|
min-width: 740px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.document {
|
||||||
|
background-color: white;
|
||||||
|
text-align: left;
|
||||||
|
background-image: url(contents.png);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bodywrapper {
|
||||||
|
margin: 0 360px 0 0;
|
||||||
|
border-right: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 20px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul {
|
||||||
|
background-image: url(navigation.png);
|
||||||
|
height: 2em;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 2em;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul li.right {
|
||||||
|
float: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul li a {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 5px 0 5px;
|
||||||
|
line-height: 1.75em;
|
||||||
|
color: #EE9816;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.related ul li a:hover {
|
||||||
|
color: #3CA8E7;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebarwrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 15px 15px 0;
|
||||||
|
width: 310px;
|
||||||
|
float: right;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3, div.sphinxsidebar h4 {
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.1em 0 0.1em 0.5em;
|
||||||
|
color: white;
|
||||||
|
border: 2px solid #86989B;
|
||||||
|
background-color: #AFC1C4;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar h3 a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul {
|
||||||
|
padding-left: 1.5em;
|
||||||
|
margin-top: 7px;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 130%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sphinxsidebar ul ul {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer {
|
||||||
|
background-color: #E3EFF1;
|
||||||
|
color: #86989B;
|
||||||
|
padding: 3px 8px 3px 0;
|
||||||
|
clear: both;
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.footer a {
|
||||||
|
color: #86989B;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- body styles ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.8em 0 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #CA7900;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #2491CF;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.7em 0 0.3em 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #11557C;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 1.3em 0 0.2em 0;
|
||||||
|
font-size: 1.35em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 1em 0 -0.3em 0;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
|
||||||
|
color: black!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
|
||||||
|
display: none;
|
||||||
|
margin: 0 0 0 0.3em;
|
||||||
|
padding: 0 0.2em 0 0.2em;
|
||||||
|
color: #aaa!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
|
||||||
|
h5:hover a.anchor, h6:hover a.anchor {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
|
||||||
|
h5 a.anchor:hover, h6 a.anchor:hover {
|
||||||
|
color: #777;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.headerlink {
|
||||||
|
color: #c60f0f!important;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-left: 6px;
|
||||||
|
padding: 0 4px 0 4px;
|
||||||
|
text-decoration: none!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.headerlink:hover {
|
||||||
|
background-color: #ccc;
|
||||||
|
color: white!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
cite, code, tt {
|
||||||
|
font-family: 'Consolas', 'Deja Vu Sans Mono',
|
||||||
|
'Bitstream Vera Sans Mono', monospace;
|
||||||
|
font-size: 0.95em;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.descname, tt.descclassname, tt.xref {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #abc;
|
||||||
|
margin: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a tt {
|
||||||
|
border: 0;
|
||||||
|
color: #CA7900;
|
||||||
|
}
|
||||||
|
|
||||||
|
a tt:hover {
|
||||||
|
color: #2491CF;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: 'Consolas', 'Deja Vu Sans Mono',
|
||||||
|
'Bitstream Vera Sans Mono', monospace;
|
||||||
|
font-size: 0.95em;
|
||||||
|
letter-spacing: 0.015em;
|
||||||
|
line-height: 120%;
|
||||||
|
padding: 0.5em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.linenos pre {
|
||||||
|
padding: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.quotebar {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
max-width: 250px;
|
||||||
|
float: right;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0 -0.5em 0 -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td, table th {
|
||||||
|
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition, div.warning {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 1em 0 1em 0;
|
||||||
|
border: 1px solid #86989B;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p, div.warning p {
|
||||||
|
margin: 0.5em 1em 0.5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition pre, div.warning pre {
|
||||||
|
margin: 0.4em 1em 0.4em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition p.admonition-title,
|
||||||
|
div.warning p.admonition-title {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.1em 0 0.1em 0.5em;
|
||||||
|
color: white;
|
||||||
|
border-bottom: 1px solid #86989B;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #AFC1C4;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.warning {
|
||||||
|
border: 1px solid #940000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.warning p.admonition-title {
|
||||||
|
background-color: #CF0000;
|
||||||
|
border-bottom-color: #940000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admonition ul, div.admonition ol,
|
||||||
|
div.warning ul, div.warning ol {
|
||||||
|
margin: 0.1em 0.5em 0.5em 3em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.versioninfo {
|
||||||
|
margin: 1em 0 0 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #DDEAF0;
|
||||||
|
padding: 8px;
|
||||||
|
line-height: 1.3em;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewcode-back {
|
||||||
|
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||||
|
'Verdana', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.viewcode-block:target {
|
||||||
|
background-color: #f4debf;
|
||||||
|
border-top: 1px solid #ac9;
|
||||||
|
border-bottom: 1px solid #ac9;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
py.test reference documentation
|
||||||
|
================================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
funcargs.txt
|
||||||
|
xunit_setup.txt
|
||||||
|
capture.txt
|
||||||
|
monkeypatch.txt
|
||||||
|
skipping.txt
|
||||||
|
mark.txt
|
||||||
|
doctest.txt
|
||||||
|
recwarn.txt
|
||||||
|
|
65
doc/bin.txt
65
doc/bin.txt
|
@ -1,65 +0,0 @@
|
||||||
======================
|
|
||||||
pylib scripts
|
|
||||||
======================
|
|
||||||
|
|
||||||
The pylib installs several scripts to support testing and (python)
|
|
||||||
development. If working from a checkout you may also add ``bin`` to
|
|
||||||
your ``PATH`` environment variable which makes the scripts available on
|
|
||||||
your shell prompt.
|
|
||||||
|
|
||||||
``py.test`` and ``py.test-$VERSION``
|
|
||||||
============================================
|
|
||||||
|
|
||||||
The ``py.test`` executable is the main tool that the py lib offers;
|
|
||||||
in fact most code in the py lib is geared towards supporting the
|
|
||||||
testing process. See the `py.test documentation`_ for extensive
|
|
||||||
documentation. The ``py.test-$VERSION`` is the same script with
|
|
||||||
an interpreter specific suffix appended to make
|
|
||||||
several versions of py.test for using specific interpreters
|
|
||||||
accessible:
|
|
||||||
|
|
||||||
* CPython2.4: py.test-2.4
|
|
||||||
* CPython2.5: py.test-2.5
|
|
||||||
* ...
|
|
||||||
* CPython3.1: py.test-3.1
|
|
||||||
* Jython-2.5.1: py.test-jython
|
|
||||||
* pypy-$SUFFIX: py.test-pypy-$SUFFIX
|
|
||||||
|
|
||||||
.. _`py.test documentation`: test/index.html
|
|
||||||
|
|
||||||
``py.which`` and ``py.which-$VERSION``
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
Usage: ``py.which modulename``
|
|
||||||
|
|
||||||
Print the ``__file__`` of the module that is imported via ``import modulename``.
|
|
||||||
The version-suffix is the same as with ``py.test`` above.
|
|
||||||
|
|
||||||
``py.cleanup``
|
|
||||||
==============
|
|
||||||
|
|
||||||
Usage: ``py.cleanup [PATH]``
|
|
||||||
|
|
||||||
Delete pyc file recursively, starting from ``PATH`` (which defaults to the
|
|
||||||
current working directory). Don't follow links and don't recurse into
|
|
||||||
directories with a ".".
|
|
||||||
|
|
||||||
``py.countloc``
|
|
||||||
===============
|
|
||||||
|
|
||||||
Usage: ``py.countloc [PATHS]``
|
|
||||||
|
|
||||||
Count (non-empty) lines of python code and number of python files recursively
|
|
||||||
starting from a ``PATHS`` given on the command line (starting from the current
|
|
||||||
working directory). Distinguish between test files and normal ones and report
|
|
||||||
them separately.
|
|
||||||
|
|
||||||
``py.lookup``
|
|
||||||
=============
|
|
||||||
|
|
||||||
Usage: ``py.lookup SEARCH_STRING [options]``
|
|
||||||
|
|
||||||
Looks recursively at Python files for a ``SEARCH_STRING``, starting from the
|
|
||||||
present working directory. Prints the line, with the filename and line-number
|
|
||||||
prepended.
|
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
|
||||||
|
Capturing of stdout/stderr output
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
By default ``stdout`` and ``stderr`` output is captured separately for
|
||||||
|
setup and test execution code. If a test or a setup method fails its
|
||||||
|
according output will usually be shown along with the failure traceback.
|
||||||
|
In addition, ``stdin`` is set to a "null" object which will fail all
|
||||||
|
attempts to read from it. This is important if some code paths in
|
||||||
|
test otherwise might lead to waiting for input - which is usually
|
||||||
|
not desired when running automated tests.
|
||||||
|
|
||||||
|
Setting capturing methods or disabling capturing
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
There are two ways in which ``py.test`` can perform capturing:
|
||||||
|
|
||||||
|
* ``fd`` level capturing (default): All writes going to the operating
|
||||||
|
system file descriptors 1 and 2 will be captured, for example writes such
|
||||||
|
as ``os.write(1, 'hello')``. Capturing on ``fd``-level also includes
|
||||||
|
**output from subprocesses**.
|
||||||
|
|
||||||
|
* ``sys`` level capturing: The ``sys.stdout`` and ``sys.stderr`` will
|
||||||
|
will be replaced with in-memory files and the ``print`` builtin or
|
||||||
|
output from code like ``sys.stderr.write(...)`` will be captured with
|
||||||
|
this method.
|
||||||
|
|
||||||
|
|
||||||
|
You can influence output capturing mechanisms from the command line::
|
||||||
|
|
||||||
|
py.test -s # disable all capturing
|
||||||
|
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
|
||||||
|
py.test --capture=fd # also point filedescriptors 1 and 2 to temp file
|
||||||
|
|
||||||
|
If you set capturing values in a conftest file like this::
|
||||||
|
|
||||||
|
# conftest.py
|
||||||
|
option_capture = 'fd'
|
||||||
|
|
||||||
|
then all tests in that directory will execute with "fd" style capturing.
|
||||||
|
|
||||||
|
Accessing captured output from a test function
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
The `funcarg mechanism`_ allows test function a very easy
|
||||||
|
way to access the captured output by simply using the names
|
||||||
|
``capsys`` or ``capfd`` in the test function signature. Here
|
||||||
|
is an example test function that performs some output related
|
||||||
|
checks::
|
||||||
|
|
||||||
|
def test_myoutput(capsys): # or use "capfd" for fd-level
|
||||||
|
print ("hello")
|
||||||
|
sys.stderr.write("world\n")
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert out == "hello\n"
|
||||||
|
assert err == "world\n"
|
||||||
|
print "next"
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert out == "next\n"
|
||||||
|
|
||||||
|
The ``readouterr()`` call snapshots the output so far -
|
||||||
|
and capturing will be continued. After the test
|
||||||
|
function finishes the original streams will
|
||||||
|
be restored. Using ``capsys`` this way frees your
|
||||||
|
test from having to care about setting/resetting
|
||||||
|
output streams and also interacts well with py.test's
|
||||||
|
own per-test capturing.
|
||||||
|
|
||||||
|
If you want to capture on ``fd`` level you can use
|
||||||
|
the ``capfd`` function argument which offers the exact
|
||||||
|
same interface.
|
||||||
|
|
||||||
|
.. include:: links.inc
|
|
@ -1,2 +1,5 @@
|
||||||
|
|
||||||
|
Changelog history
|
||||||
|
=================================
|
||||||
|
|
||||||
.. include:: ../CHANGELOG
|
.. include:: ../CHANGELOG
|
||||||
|
|
134
doc/code.txt
134
doc/code.txt
|
@ -1,134 +0,0 @@
|
||||||
================================================================================
|
|
||||||
py.code: higher level python code and introspection objects
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
The ``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
|
|
||||||
relatively low level and not very accessible.
|
|
||||||
|
|
||||||
The ``py.code`` library tries to simplify accessing the code objects as well
|
|
||||||
as creating them. There is a small set of interfaces a user needs to deal with,
|
|
||||||
all nicely bundled together, and with a rich set of 'Pythonic' functionality.
|
|
||||||
|
|
||||||
Contents of the library
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Every object in the ``py.code`` library wraps a code Python object related
|
|
||||||
to code objects, source code, frames and tracebacks: the ``py.code.Code``
|
|
||||||
class wraps code objects, ``py.code.Source`` source snippets,
|
|
||||||
``py.code.Traceback` exception tracebacks, ``py.code.Frame`` frame
|
|
||||||
objects (as found in e.g. tracebacks) and ``py.code.ExceptionInfo`` the
|
|
||||||
tuple provided by sys.exc_info() (containing exception and traceback
|
|
||||||
information when an exception occurs). Also in the library is a helper function
|
|
||||||
``py.code.compile()`` that provides the same functionality as Python's
|
|
||||||
built-in 'compile()' function, but returns a wrapped code object.
|
|
||||||
|
|
||||||
The wrappers
|
|
||||||
============
|
|
||||||
|
|
||||||
``py.code.Code``
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Code objects are instantiated with a code object or a callable as argument,
|
|
||||||
and provide functionality to compare themselves with other Code objects, get to
|
|
||||||
the source file or its contents, create new Code objects from scratch, etc.
|
|
||||||
|
|
||||||
A quick example::
|
|
||||||
|
|
||||||
>>> import py
|
|
||||||
>>> c = py.code.Code(py.path.local.read)
|
|
||||||
>>> c.path.basename
|
|
||||||
'common.py'
|
|
||||||
>>> isinstance(c.source(), py.code.Source)
|
|
||||||
True
|
|
||||||
>>> str(c.source()).split('\n')[0]
|
|
||||||
"def read(self, mode='r'):"
|
|
||||||
|
|
||||||
``py.code.Source``
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Source objects wrap snippets of Python source code, providing a simple yet
|
|
||||||
powerful interface to read, deindent, slice, compare, compile and manipulate
|
|
||||||
them, things that are not so easy in core Python.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
>>> s = py.code.Source("""\
|
|
||||||
... def foo():
|
|
||||||
... print "foo"
|
|
||||||
... """)
|
|
||||||
>>> str(s).startswith('def') # automatic de-indentation!
|
|
||||||
True
|
|
||||||
>>> s.isparseable()
|
|
||||||
True
|
|
||||||
>>> sub = s.getstatement(1) # get the statement starting at line 1
|
|
||||||
>>> str(sub).strip() # XXX why is the strip() required?!?
|
|
||||||
'print "foo"'
|
|
||||||
|
|
||||||
|
|
||||||
``py.code.Traceback``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
Tracebacks are usually not very easy to examine, you need to access certain
|
|
||||||
somewhat hidden attributes of the traceback's items (resulting in expressions
|
|
||||||
such as 'fname = tb.tb_next.tb_frame.f_code.co_filename'). The Traceback
|
|
||||||
interface (and its TracebackItem children) tries to improve this.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
>>> import sys
|
|
||||||
>>> try:
|
|
||||||
... py.path.local(100) # illegal argument
|
|
||||||
... except:
|
|
||||||
... exc, e, tb = sys.exc_info()
|
|
||||||
>>> t = py.code.Traceback(tb)
|
|
||||||
>>> first = t[1] # get the second entry (first is in this doc)
|
|
||||||
>>> first.path.basename # second is in py/path/local.py
|
|
||||||
'local.py'
|
|
||||||
>>> isinstance(first.statement, py.code.Source)
|
|
||||||
True
|
|
||||||
>>> str(first.statement).strip().startswith('raise ValueError')
|
|
||||||
True
|
|
||||||
|
|
||||||
``py.code.Frame``
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Frame wrappers are used in ``py.code.Traceback`` items, and will usually not
|
|
||||||
directly be instantiated. They provide some nice methods to evaluate code
|
|
||||||
'inside' the frame (using the frame's local variables), get to the underlying
|
|
||||||
code (frames have a code attribute that points to a ``py.code.Code`` object)
|
|
||||||
and examine the arguments.
|
|
||||||
|
|
||||||
Example (using the 'first' TracebackItem instance created above)::
|
|
||||||
|
|
||||||
>>> frame = first.frame
|
|
||||||
>>> isinstance(frame.code, py.code.Code)
|
|
||||||
True
|
|
||||||
>>> isinstance(frame.eval('self'), py.path.local)
|
|
||||||
True
|
|
||||||
>>> [namevalue[0] for namevalue in frame.getargs()]
|
|
||||||
['cls', 'path']
|
|
||||||
|
|
||||||
``py.code.ExceptionInfo``
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
A wrapper around the tuple returned by sys.exc_info() (will call sys.exc_info()
|
|
||||||
itself if the tuple is not provided as an argument), provides some handy
|
|
||||||
attributes to easily access the traceback and exception string.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
>>> import sys
|
|
||||||
>>> try:
|
|
||||||
... foobar()
|
|
||||||
... except:
|
|
||||||
... excinfo = py.code.ExceptionInfo()
|
|
||||||
>>> excinfo.typename
|
|
||||||
'NameError'
|
|
||||||
>>> isinstance(excinfo.traceback, py.code.Traceback)
|
|
||||||
True
|
|
||||||
>>> excinfo.exconly()
|
|
||||||
"NameError: name 'foobar' is not defined"
|
|
||||||
|
|
|
@ -0,0 +1,260 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# pytest documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Fri Oct 8 17:54:28 2010.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.txt'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'pytest'
|
||||||
|
copyright = u'2010, holger krekel et aliter'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = '2.0.0'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = '2.0.0dev0'
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = ['_build', 'example', 'test', 'announce'] # XXX
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = 'sphinxdoc'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'pytestdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
# The paper size ('letter' or 'a4').
|
||||||
|
#latex_paper_size = 'letter'
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#latex_font_size = '10pt'
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'pytest.tex', u'pytest Documentation',
|
||||||
|
u'holger krekel et aliter', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#latex_preamble = ''
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output --------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'pytest', u'pytest Documentation',
|
||||||
|
[u'holger krekel et aliter'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Epub output ---------------------------------------------------
|
||||||
|
|
||||||
|
# Bibliographic Dublin Core info.
|
||||||
|
epub_title = u'pytest'
|
||||||
|
epub_author = u'holger krekel et aliter'
|
||||||
|
epub_publisher = u'holger krekel et aliter'
|
||||||
|
epub_copyright = u'2010, holger krekel et aliter'
|
||||||
|
|
||||||
|
# The language of the text. It defaults to the language option
|
||||||
|
# or en if the language is not set.
|
||||||
|
#epub_language = ''
|
||||||
|
|
||||||
|
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||||
|
#epub_scheme = ''
|
||||||
|
|
||||||
|
# The unique identifier of the text. This can be a ISBN number
|
||||||
|
# or the project homepage.
|
||||||
|
#epub_identifier = ''
|
||||||
|
|
||||||
|
# A unique identification for the text.
|
||||||
|
#epub_uid = ''
|
||||||
|
|
||||||
|
# HTML files that should be inserted before the pages created by sphinx.
|
||||||
|
# The format is a list of tuples containing the path and title.
|
||||||
|
#epub_pre_files = []
|
||||||
|
|
||||||
|
# HTML files shat should be inserted after the pages created by sphinx.
|
||||||
|
# The format is a list of tuples containing the path and title.
|
||||||
|
#epub_post_files = []
|
||||||
|
|
||||||
|
# A list of files that should not be packed into the epub file.
|
||||||
|
#epub_exclude_files = []
|
||||||
|
|
||||||
|
# The depth of the table of contents in toc.ncx.
|
||||||
|
#epub_tocdepth = 3
|
||||||
|
|
||||||
|
# Allow duplicate toc entries.
|
||||||
|
#epub_tocdup = True
|
||||||
|
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
289
doc/confrest.py
289
doc/confrest.py
|
@ -1,289 +0,0 @@
|
||||||
import py
|
|
||||||
|
|
||||||
from pytest.plugin.pytest_restdoc import convert_rest_html, strip_html_header
|
|
||||||
|
|
||||||
html = py.xml.html
|
|
||||||
|
|
||||||
class css:
|
|
||||||
#pagetitle = "pagetitle"
|
|
||||||
contentspace = "contentspace"
|
|
||||||
menubar = "menubar"
|
|
||||||
navspace = "navspace"
|
|
||||||
versioninfo = "versioninfo"
|
|
||||||
|
|
||||||
class Page(object):
|
|
||||||
doctype = ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
|
|
||||||
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
|
|
||||||
googlefragment = """
|
|
||||||
<script type="text/javascript">
|
|
||||||
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
|
||||||
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
try {
|
|
||||||
var pageTracker = _gat._getTracker("UA-7597274-3");
|
|
||||||
pageTracker._trackPageview();
|
|
||||||
} catch(err) {}</script>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, project, title, targetpath, stylesheeturl=None,
|
|
||||||
type="text/html", encoding="ISO-8859-1"):
|
|
||||||
self.project = project
|
|
||||||
self.title = project.prefix_title + title
|
|
||||||
self.targetpath = targetpath
|
|
||||||
self.stylesheeturl = stylesheeturl
|
|
||||||
self.type = type
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
self.body = html.body()
|
|
||||||
self.head = html.head()
|
|
||||||
self._root = html.html(self.head, self.body)
|
|
||||||
self.fill()
|
|
||||||
|
|
||||||
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
|
|
||||||
return html.div(html.a(name, class_="menu",
|
|
||||||
href=relpath(self.targetpath.strpath,
|
|
||||||
docpath.join(relhtmlpath).strpath)))
|
|
||||||
|
|
||||||
def a_apigenref(self, name, relhtmlpath):
|
|
||||||
apipath = self.project.apigenpath
|
|
||||||
return html.a(name, class_="menu",
|
|
||||||
href=relpath(self.targetpath.strpath,
|
|
||||||
apipath.join(relhtmlpath).strpath))
|
|
||||||
|
|
||||||
def fill_menubar(self):
|
|
||||||
items = [
|
|
||||||
self.a_docref("INSTALL", "install.html"),
|
|
||||||
self.a_docref("CONTACT", "contact.html"),
|
|
||||||
self.a_docref("CHANGELOG", "changelog.html"),
|
|
||||||
self.a_docref("FAQ", "faq.html"),
|
|
||||||
html.div(
|
|
||||||
html.h3("py.test:"),
|
|
||||||
self.a_docref("Index", "test/index.html"),
|
|
||||||
self.a_docref("Quickstart", "test/quickstart.html"),
|
|
||||||
self.a_docref("Features", "test/features.html"),
|
|
||||||
self.a_docref("Funcargs", "test/funcargs.html"),
|
|
||||||
self.a_docref("Plugins", "test/plugin/index.html"),
|
|
||||||
self.a_docref("Customize", "test/customize.html"),
|
|
||||||
self.a_docref("Tutorials", "test/talks.html"),
|
|
||||||
self.a_href("hudson-tests", "http://hudson.testrun.org")
|
|
||||||
),
|
|
||||||
html.div(
|
|
||||||
html.h3("supporting APIs:"),
|
|
||||||
self.a_docref("Index", "index.html"),
|
|
||||||
self.a_docref("py.path", "path.html"),
|
|
||||||
self.a_docref("py.code", "code.html"),
|
|
||||||
)
|
|
||||||
#self.a_docref("py.code", "code.html"),
|
|
||||||
#self.a_apigenref("api", "api/index.html"),
|
|
||||||
#self.a_apigenref("source", "source/index.html"),
|
|
||||||
#self.a_href("source", "http://bitbucket.org/hpk42/py-trunk/src/"),
|
|
||||||
]
|
|
||||||
self.menubar = html.div(id=css.menubar, *[
|
|
||||||
html.div(item) for item in items])
|
|
||||||
version = py.version
|
|
||||||
announcelink = self.a_docref("%s ANN" % version,
|
|
||||||
"announce/release-%s.html" %(version,))
|
|
||||||
self.menubar.insert(0,
|
|
||||||
html.div(announcelink))
|
|
||||||
#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)
|
|
||||||
self.head.append(html.title(self.title))
|
|
||||||
self.head.append(html.meta(name="Content-Type", content=content_type))
|
|
||||||
if self.stylesheeturl:
|
|
||||||
self.head.append(
|
|
||||||
html.link(href=self.stylesheeturl,
|
|
||||||
media="screen", rel="stylesheet",
|
|
||||||
type="text/css"))
|
|
||||||
self.fill_menubar()
|
|
||||||
|
|
||||||
self.body.append(html.div(
|
|
||||||
self.project.logo,
|
|
||||||
self.menubar,
|
|
||||||
id=css.navspace,
|
|
||||||
))
|
|
||||||
|
|
||||||
#self.body.append(html.div(self.title, id=css.pagetitle))
|
|
||||||
self.contentspace = html.div(id=css.contentspace)
|
|
||||||
self.body.append(self.contentspace)
|
|
||||||
|
|
||||||
def unicode(self, doctype=True):
|
|
||||||
page = self._root.unicode()
|
|
||||||
page = page.replace("</body>", self.googlefragment + "</body>")
|
|
||||||
if doctype:
|
|
||||||
return self.doctype + page
|
|
||||||
else:
|
|
||||||
return page
|
|
||||||
|
|
||||||
class PyPage(Page):
|
|
||||||
def get_menubar(self):
|
|
||||||
menubar = super(PyPage, self).get_menubar()
|
|
||||||
# base layout
|
|
||||||
menubar.append(
|
|
||||||
html.a("issue", href="https://codespeak.net/issue/py-dev/",
|
|
||||||
class_="menu"),
|
|
||||||
)
|
|
||||||
return menubar
|
|
||||||
|
|
||||||
|
|
||||||
def getrealname(username):
|
|
||||||
try:
|
|
||||||
import uconf
|
|
||||||
except ImportError:
|
|
||||||
return username
|
|
||||||
try:
|
|
||||||
user = uconf.system.User(username)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
return user.realname or username
|
|
||||||
except KeyError:
|
|
||||||
return username
|
|
||||||
|
|
||||||
|
|
||||||
class Project:
|
|
||||||
mydir = py.path.local(__file__).dirpath()
|
|
||||||
title = "py lib"
|
|
||||||
prefix_title = "" # we have a logo already containing "py lib"
|
|
||||||
encoding = 'latin1'
|
|
||||||
logo = html.div(
|
|
||||||
html.a(
|
|
||||||
html.img(alt="py lib", id='pyimg', height=114/2, width=154/2,
|
|
||||||
src="http://codespeak.net/img/pylib.png"),
|
|
||||||
href="http://pylib.org"))
|
|
||||||
Page = PyPage
|
|
||||||
|
|
||||||
def __init__(self, sourcepath=None):
|
|
||||||
if sourcepath is None:
|
|
||||||
sourcepath = self.mydir
|
|
||||||
self.setpath(sourcepath)
|
|
||||||
|
|
||||||
def setpath(self, sourcepath, docpath=None,
|
|
||||||
apigenpath=None, stylesheet=None):
|
|
||||||
self.sourcepath = sourcepath
|
|
||||||
if docpath is None:
|
|
||||||
docpath = sourcepath
|
|
||||||
self.docpath = docpath
|
|
||||||
if apigenpath is None:
|
|
||||||
apigenpath = docpath
|
|
||||||
self.apigenpath = apigenpath
|
|
||||||
if stylesheet is None:
|
|
||||||
p = sourcepath.join("style.css")
|
|
||||||
if p.check():
|
|
||||||
self.stylesheet = p
|
|
||||||
else:
|
|
||||||
self.stylesheet = None
|
|
||||||
else:
|
|
||||||
p = sourcepath.join(stylesheet)
|
|
||||||
if p.check():
|
|
||||||
stylesheet = p
|
|
||||||
self.stylesheet = stylesheet
|
|
||||||
#assert self.stylesheet
|
|
||||||
self.apigen_relpath = relpath(
|
|
||||||
self.docpath.strpath + '/', self.apigenpath.strpath + '/')
|
|
||||||
|
|
||||||
def get_content(self, txtpath, encoding):
|
|
||||||
return unicode(txtpath.read(), encoding)
|
|
||||||
|
|
||||||
def get_htmloutputpath(self, txtpath):
|
|
||||||
reloutputpath = txtpath.new(ext='.html').relto(self.sourcepath)
|
|
||||||
return self.docpath.join(reloutputpath)
|
|
||||||
|
|
||||||
def process(self, txtpath):
|
|
||||||
encoding = self.encoding
|
|
||||||
content = self.get_content(txtpath, encoding)
|
|
||||||
outputpath = self.get_htmloutputpath(txtpath)
|
|
||||||
|
|
||||||
stylesheet = self.stylesheet
|
|
||||||
if isinstance(stylesheet, py.path.local):
|
|
||||||
if not self.docpath.join(stylesheet.basename).check():
|
|
||||||
docpath.ensure(dir=True)
|
|
||||||
stylesheet.copy(docpath)
|
|
||||||
stylesheet = relpath(outputpath.strpath,
|
|
||||||
self.docpath.join(stylesheet.basename).strpath)
|
|
||||||
|
|
||||||
content = convert_rest_html(content, txtpath,
|
|
||||||
stylesheet=stylesheet, encoding=encoding)
|
|
||||||
content = strip_html_header(content, encoding=encoding)
|
|
||||||
|
|
||||||
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:
|
|
||||||
modified = py.process.cmdexec(
|
|
||||||
"hg tip --template 'modified {date|shortdate}'"
|
|
||||||
)
|
|
||||||
except py.process.cmdexec.Error:
|
|
||||||
modified = " "
|
|
||||||
|
|
||||||
#page.body.append(html.div(modified, id="docinfoline"))
|
|
||||||
|
|
||||||
page.contentspace.append(py.xml.raw(content))
|
|
||||||
outputpath.ensure().write(page.unicode().encode(encoding))
|
|
||||||
|
|
||||||
# XXX this function comes from apigen/linker.py, put it
|
|
||||||
# somewhere in py lib
|
|
||||||
import os
|
|
||||||
def relpath(p1, p2, sep=os.path.sep, back='..', normalize=True):
|
|
||||||
""" create a relative path from p1 to p2
|
|
||||||
|
|
||||||
sep is the seperator used for input and (depending
|
|
||||||
on the setting of 'normalize', see below) output
|
|
||||||
|
|
||||||
back is the string used to indicate the parent directory
|
|
||||||
|
|
||||||
when 'normalize' is True, any backslashes (\) in the path
|
|
||||||
will be replaced with forward slashes, resulting in a consistent
|
|
||||||
output on Windows and the rest of the world
|
|
||||||
|
|
||||||
paths to directories must end on a / (URL style)
|
|
||||||
"""
|
|
||||||
if normalize:
|
|
||||||
p1 = p1.replace(sep, '/')
|
|
||||||
p2 = p2.replace(sep, '/')
|
|
||||||
sep = '/'
|
|
||||||
# XXX would be cool to be able to do long filename
|
|
||||||
# expansion and drive
|
|
||||||
# letter fixes here, and such... iow: windows sucks :(
|
|
||||||
if (p1.startswith(sep) ^ p2.startswith(sep)):
|
|
||||||
raise ValueError("mixed absolute relative path: %r -> %r" %(p1, p2))
|
|
||||||
fromlist = p1.split(sep)
|
|
||||||
tolist = p2.split(sep)
|
|
||||||
|
|
||||||
# AA
|
|
||||||
# AA BB -> AA/BB
|
|
||||||
#
|
|
||||||
# AA BB
|
|
||||||
# AA CC -> CC
|
|
||||||
#
|
|
||||||
# AA BB
|
|
||||||
# AA -> ../AA
|
|
||||||
|
|
||||||
diffindex = 0
|
|
||||||
for x1, x2 in zip(fromlist, tolist):
|
|
||||||
if x1 != x2:
|
|
||||||
break
|
|
||||||
diffindex += 1
|
|
||||||
commonindex = diffindex - 1
|
|
||||||
|
|
||||||
fromlist_diff = fromlist[diffindex:]
|
|
||||||
tolist_diff = tolist[diffindex:]
|
|
||||||
|
|
||||||
if not fromlist_diff:
|
|
||||||
return sep.join(tolist[commonindex:])
|
|
||||||
backcount = len(fromlist_diff)
|
|
||||||
if tolist_diff:
|
|
||||||
return sep.join([back,]*(backcount-1) + tolist_diff)
|
|
||||||
return sep.join([back,]*(backcount) + tolist[commonindex:])
|
|
|
@ -1,8 +0,0 @@
|
||||||
#XXX make work: excludedirs = ['_build']
|
|
||||||
import py
|
|
||||||
pytest_plugins = ['pytest_restdoc']
|
|
||||||
collect_ignore = ['test/attic.txt']
|
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
|
||||||
if item.fspath.ext == ".txt":
|
|
||||||
py.test.importorskip("pygments") # for raising an error
|
|
|
@ -2,10 +2,6 @@
|
||||||
Customizing and Extending py.test
|
Customizing and Extending py.test
|
||||||
================================================
|
================================================
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
:depth: 2
|
|
||||||
|
|
||||||
basic test configuration
|
basic test configuration
|
||||||
===================================
|
===================================
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
=================================================
|
||||||
|
Feedback and contribute to py.test
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
contact.txt
|
||||||
|
faq.txt
|
||||||
|
|
||||||
|
.. _checkout:
|
||||||
|
|
||||||
|
Working from version control or a tarball
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
To follow development or start experiments, checkout the
|
||||||
|
complete code and documentation source with mercurial_::
|
||||||
|
|
||||||
|
hg clone https://bitbucket.org/hpk42/py-trunk/
|
||||||
|
|
||||||
|
Development takes place on the 'trunk' branch.
|
||||||
|
|
||||||
|
You can also go to the python package index and
|
||||||
|
download and unpack a TAR file::
|
||||||
|
|
||||||
|
http://pypi.python.org/pypi/py/
|
||||||
|
|
||||||
|
|
||||||
|
activating a checkout with setuptools
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
With a working Distribute_ or setuptools_ installation you can type::
|
||||||
|
|
||||||
|
python setup.py develop
|
||||||
|
|
||||||
|
in order to work inline with the tools and the lib of your checkout.
|
||||||
|
|
||||||
|
.. include:: links.inc
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
collect and run doctests from modules and test files.
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
By default all files matching the ``test*.txt`` pattern will
|
||||||
|
be run through the python standard ``doctest`` module. You
|
||||||
|
can change the pattern by issuing::
|
||||||
|
|
||||||
|
py.test --doctest-glob='*.rst'
|
||||||
|
|
||||||
|
on the command line. You can also trigger running of doctests
|
||||||
|
from docstrings in all python modules (including regular
|
||||||
|
python test modules)::
|
||||||
|
|
||||||
|
py.test --doctest-modules
|
||||||
|
|
||||||
|
You can make these changes permanent in your project by
|
||||||
|
putting them into a conftest.py file like this::
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
option_doctestmodules = True
|
||||||
|
option_doctestglob = "*.rst"
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content=" 1 ; URL=install.html" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script type="text/javascript">
|
|
||||||
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
|
||||||
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
try {
|
|
||||||
var pageTracker = _gat._getTracker("UA-7597274-3");
|
|
||||||
pageTracker._trackPageview();
|
|
||||||
} catch(err) {}</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
Using the command line
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
Example command line usages::
|
||||||
|
|
||||||
|
py.test --version # shows where pytest was imported from
|
||||||
|
py.test --funcargs # show available builtin function arguments
|
||||||
|
py.test --help-config # show configuration values
|
||||||
|
|
||||||
|
py.test -h | --help # show help
|
||||||
|
|
||||||
|
|
||||||
|
which tells you both version and import location of the tool.
|
|
@ -7,7 +7,8 @@ Quickstart
|
||||||
|
|
||||||
.. _here: ../install.html
|
.. _here: ../install.html
|
||||||
|
|
||||||
If you have the ``easy_install`` tool (otherwise see here_) just type::
|
If you have the ``easy_install`` tool (otherwise see here_) just type:
|
||||||
|
|
||||||
|
|
||||||
easy_install -U py
|
easy_install -U py
|
||||||
|
|
|
@ -1,205 +1,14 @@
|
||||||
==================================================
|
Looping on the failing test set
|
||||||
py.test feature overview
|
-----------------------------------------
|
||||||
==================================================
|
|
||||||
|
|
||||||
.. contents::
|
``py.test --looponfailing`` (implemented through the external
|
||||||
:local:
|
`pytest-xdist`_ plugin) allows to run a test suite,
|
||||||
:depth: 1
|
memorize all failures and then loop over the failing set
|
||||||
|
of tests until they all pass. It will re-start running
|
||||||
mature command line testing tool
|
the tests when it detects file changes in your project.
|
||||||
====================================================
|
|
||||||
|
|
||||||
py.test is a command line tool to collect, run and report about automated tests. It runs well on Linux, Windows and OSX and on Python 2.4 through to 3.1 versions.
|
|
||||||
It is used in many projects, ranging from running 10 thousands of tests
|
|
||||||
to a few inlined tests on a command line script. As of version 1.2 you can also
|
|
||||||
generate a no-dependency py.test-equivalent standalone script that you
|
|
||||||
can distribute along with your application.
|
|
||||||
|
|
||||||
extensive easy plugin system
|
|
||||||
======================================================
|
|
||||||
|
|
||||||
.. _`suprisingly easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
|
|
||||||
|
|
||||||
py.test delegates almost all aspects of its operation to plugins_.
|
|
||||||
It is `suprisingly easy`_ to add command line options or
|
|
||||||
do other kind of add-ons and customizations. This can
|
|
||||||
be done per-project or by distributing a global plugin.
|
|
||||||
One can can thus modify or add aspects for purposes such as:
|
|
||||||
|
|
||||||
* reporting extensions
|
|
||||||
* customizing collection and execution of tests
|
|
||||||
* running and managing non-python tests
|
|
||||||
* managing domain-specific test state setup
|
|
||||||
* adding non-python tests into the run, e.g. driven by data files
|
|
||||||
|
|
||||||
.. _`plugins`: plugin/index.html
|
|
||||||
|
|
||||||
distributing tests to your CPUs and SSH accounts
|
|
||||||
==========================================================
|
|
||||||
|
|
||||||
.. _`pytest-xdist`: plugin/xdist.html
|
|
||||||
|
|
||||||
Through the use of the separately released `pytest-xdist`_ plugin you
|
|
||||||
can seemlessly distribute runs to multiple CPUs or remote computers
|
|
||||||
through SSH and sockets. This plugin also offers a ``--looponfailing``
|
|
||||||
mode which will continously re-run only failing tests in a subprocess.
|
|
||||||
|
|
||||||
supports several testing practises and methods
|
|
||||||
==================================================================
|
|
||||||
|
|
||||||
py.test supports many testing methods conventionally used in
|
|
||||||
the Python community. It runs traditional `unittest.py`_,
|
|
||||||
`doctest.py`_, supports `xUnit style setup`_ and nose_ specific
|
|
||||||
setups and test suites. It offers minimal no-boilerplate model
|
|
||||||
for configuring and deploying tests written as simple Python
|
|
||||||
functions or methods. It also integrates `coverage testing
|
|
||||||
with figleaf`_ or `Javasript unit- and functional testing`_.
|
|
||||||
|
|
||||||
.. _`Javasript unit- and functional testing`: plugin/oejskit.html
|
|
||||||
.. _`coverage testing with figleaf`: plugin/figleaf.html
|
|
||||||
|
|
||||||
integrates well with CI systems
|
|
||||||
====================================================
|
|
||||||
|
|
||||||
py.test can produce JUnitXML style output as well as formatted
|
|
||||||
"resultlog" files that can be postprocessed by Continous Integration
|
|
||||||
systems such as Hudson or Buildbot easily. It also provides command
|
|
||||||
line options to control test configuration lookup behaviour or ignoring
|
|
||||||
certain tests or directories.
|
|
||||||
|
|
||||||
no-boilerplate test functions with Python
|
|
||||||
===================================================
|
|
||||||
|
|
||||||
.. _`autocollect`:
|
|
||||||
|
|
||||||
automatic Python test discovery
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
By default, all python modules with a ``test_*.py``
|
|
||||||
filename are inspected for finding tests:
|
|
||||||
|
|
||||||
* functions with a name beginning with ``test_``
|
|
||||||
* classes with a leading ``Test`` name and ``test`` prefixed methods.
|
|
||||||
* ``unittest.TestCase`` subclasses
|
|
||||||
|
|
||||||
parametrizing test functions and functional testing
|
|
||||||
--------------------------------------------------------------
|
|
||||||
|
|
||||||
py.test offers the unique `funcargs mechanism`_ for setting up
|
|
||||||
and passing project-specific objects to Python test functions.
|
|
||||||
Test Parametrization happens by triggering a call to the same test
|
|
||||||
function with different argument values. For doing fixtures
|
|
||||||
using the funcarg mechanism makes your test and setup code
|
|
||||||
more efficient and more readable. This is especially true
|
|
||||||
for functional tests which might depend on command line
|
|
||||||
options and a setup that needs to be shared across
|
|
||||||
a whole test run.
|
|
||||||
|
|
||||||
per-test capturing of output, including subprocesses
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
By default, ``py.test`` captures all writes to stdout/stderr.
|
|
||||||
Output from ``print`` statements as well as from subprocesses
|
|
||||||
is captured_. When a test fails, the associated captured outputs are shown.
|
|
||||||
This allows you to put debugging print statements in your code without
|
|
||||||
being overwhelmed by all the output that might be generated by tests
|
|
||||||
that do not fail.
|
|
||||||
|
|
||||||
.. _captured: plugin/capture.html
|
|
||||||
|
|
||||||
assert with the ``assert`` statement
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
``py.test`` allows to use the standard python
|
|
||||||
``assert statement`` for verifying expectations
|
|
||||||
and values in Python tests. For example, you can
|
|
||||||
write the following in your tests::
|
|
||||||
|
|
||||||
assert hasattr(x, 'attribute')
|
|
||||||
|
|
||||||
to state that your object has a certain ``attribute``. In case this
|
|
||||||
assertion fails you will see the value of ``x``. Intermediate
|
|
||||||
values are computed by executing the assert expression a second time.
|
|
||||||
If you execute code with side effects, e.g. read from a file like this::
|
|
||||||
|
|
||||||
assert f.read() != '...'
|
|
||||||
|
|
||||||
then you may get a warning from pytest if that assertions
|
|
||||||
first failed and then succeeded.
|
|
||||||
|
|
||||||
asserting expected exceptions
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
In order to write assertions about exceptions, you can use
|
|
||||||
``py.test.raises`` as a context manager like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
with py.test.raises(ZeroDivisionError):
|
|
||||||
1 / 0
|
|
||||||
|
|
||||||
and if you need to have access to the actual exception info you may use:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
with py.test.raises(RuntimeError) as excinfo:
|
|
||||||
def f():
|
|
||||||
f()
|
|
||||||
f()
|
|
||||||
|
|
||||||
# do checks related to excinfo.type, excinfo.value, excinfo.traceback
|
|
||||||
|
|
||||||
If you want to write test code that works on Python2.4 as well,
|
|
||||||
you may also use two other ways to test for an expected exception:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
py.test.raises(ExpectedException, func, *args, **kwargs)
|
|
||||||
py.test.raises(ExpectedException, "func(*args, **kwargs)")
|
|
||||||
|
|
||||||
both of which execute the specified function with args and kwargs and
|
|
||||||
asserts that the given ``ExpectedException`` is raised. The reporter will
|
|
||||||
provide you with helpful output in case of failures such as *no
|
|
||||||
exception* or *wrong exception*.
|
|
||||||
|
|
||||||
|
|
||||||
information-rich tracebacks, PDB introspection
|
|
||||||
-------------------------------------------------------
|
|
||||||
|
|
||||||
.. _`example tracebacks`: http://paste.pocoo.org/show/134814/
|
|
||||||
|
|
||||||
A lot of care is taken to present useful failure information
|
|
||||||
and in particular nice and concise Python tracebacks. This
|
|
||||||
is especially useful if you need to regularly look at failures
|
|
||||||
from nightly runs, i.e. are detached from the actual test
|
|
||||||
running session. Here are `example tracebacks`_ for a number of failing
|
|
||||||
test functions. You can modify traceback printing styles through the
|
|
||||||
command line. Using the `--pdb`` option you can automatically activate
|
|
||||||
a PDB `Python debugger`_ when a test fails.
|
|
||||||
|
|
||||||
skip or expect-to-fail a test
|
|
||||||
======================================
|
|
||||||
|
|
||||||
py.test has a dedicated `skipping plugin`_ that allows to define
|
|
||||||
|
|
||||||
* define "skip" outcomes indicating a platform or a
|
|
||||||
dependency mismatch.
|
|
||||||
|
|
||||||
* "xfail" outcomes indicating an "expected failure" either with
|
|
||||||
with or without running a test.
|
|
||||||
|
|
||||||
* skip and xfail outcomes can be applied at module, class or method
|
|
||||||
level or even only for certain argument sets of a parametrized function.
|
|
||||||
|
|
||||||
.. _`skipping plugin`: plugin/skipping.html
|
|
||||||
.. _`funcargs mechanism`: funcargs.html
|
|
||||||
.. _`unittest.py`: http://docs.python.org/library/unittest.html
|
|
||||||
.. _`doctest.py`: http://docs.python.org/library/doctest.html
|
|
||||||
.. _`xUnit style setup`: xunit_setup.html
|
|
||||||
.. _`pytest_nose`: plugin/nose.html
|
|
||||||
|
|
||||||
select tests by keyword / test name search
|
select tests by keyword / test name search
|
||||||
=========================================================
|
-----------------------------------------------------
|
||||||
|
|
||||||
.. _`selection by keyword`:
|
.. _`selection by keyword`:
|
||||||
|
|
||||||
|
@ -235,19 +44,134 @@ and then use those keywords to select tests. See the `pytest_keyword`_
|
||||||
plugin for more information.
|
plugin for more information.
|
||||||
|
|
||||||
.. _`pytest_keyword`: plugin/mark.html
|
.. _`pytest_keyword`: plugin/mark.html
|
||||||
|
skip or expect-to-fail a test
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
Looping on the failing test set
|
py.test has a dedicated `skipping plugin`_ that allows to define
|
||||||
=======================================
|
|
||||||
|
|
||||||
``py.test --looponfailing`` (implemented through the external
|
* define "skip" outcomes indicating a platform or a
|
||||||
`pytest-xdist`_ plugin) allows to run a test suite,
|
dependency mismatch.
|
||||||
memorize all failures and then loop over the failing set
|
|
||||||
of tests until they all pass. It will re-start running
|
* "xfail" outcomes indicating an "expected failure" either with
|
||||||
the tests when it detects file changes in your project.
|
with or without running a test.
|
||||||
|
|
||||||
|
* skip and xfail outcomes can be applied at module, class or method
|
||||||
|
level or even only for certain argument sets of a parametrized function.
|
||||||
|
|
||||||
|
.. _`skipping plugin`: plugin/skipping.html
|
||||||
|
.. _`funcargs mechanism`: funcargs.html
|
||||||
|
.. _`doctest.py`: http://docs.python.org/library/doctest.html
|
||||||
|
.. _`xUnit style setup`: xunit_setup.html
|
||||||
|
.. _`pytest_nose`: plugin/nose.html
|
||||||
|
|
||||||
|
|
||||||
.. _`reStructured Text`: http://docutils.sourceforge.net
|
no-boilerplate testing
|
||||||
.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
|
----------------------------------
|
||||||
|
|
||||||
|
.. _`autocollect`:
|
||||||
|
|
||||||
|
automatic Python test discovery
|
||||||
|
+++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
By default, all python modules with a ``test_*.py``
|
||||||
|
filename are inspected for finding tests:
|
||||||
|
|
||||||
|
* functions with a name beginning with ``test_``
|
||||||
|
* classes with a leading ``Test`` name and ``test`` prefixed methods.
|
||||||
|
* ``unittest.TestCase`` subclasses
|
||||||
|
|
||||||
|
parametrizing test functions and functional testing
|
||||||
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
py.test offers the unique `funcargs mechanism`_ for setting up
|
||||||
|
and passing project-specific objects to Python test functions.
|
||||||
|
Test Parametrization happens by triggering a call to the same test
|
||||||
|
function with different argument values. For doing fixtures
|
||||||
|
using the funcarg mechanism makes your test and setup code
|
||||||
|
more efficient and more readable. This is especially true
|
||||||
|
for functional tests which might depend on command line
|
||||||
|
options and a setup that needs to be shared across
|
||||||
|
a whole test run.
|
||||||
|
|
||||||
|
per-test capturing of output, including subprocesses
|
||||||
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
By default, ``py.test`` captures all writes to stdout/stderr.
|
||||||
|
Output from ``print`` statements as well as from subprocesses
|
||||||
|
is captured_. When a test fails, the associated captured outputs are shown.
|
||||||
|
This allows you to put debugging print statements in your code without
|
||||||
|
being overwhelmed by all the output that might be generated by tests
|
||||||
|
that do not fail.
|
||||||
|
|
||||||
|
.. _captured: plugin/capture.html
|
||||||
|
|
||||||
|
assert with the ``assert`` statement
|
||||||
|
+++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
``py.test`` allows to use the standard python
|
||||||
|
``assert statement`` for verifying expectations
|
||||||
|
and values in Python tests. For example, you can
|
||||||
|
write the following in your tests::
|
||||||
|
|
||||||
|
assert hasattr(x, 'attribute')
|
||||||
|
|
||||||
|
to state that your object has a certain ``attribute``. In case this
|
||||||
|
assertion fails you will see the value of ``x``. Intermediate
|
||||||
|
values are computed by executing the assert expression a second time.
|
||||||
|
If you execute code with side effects, e.g. read from a file like this::
|
||||||
|
|
||||||
|
assert f.read() != '...'
|
||||||
|
|
||||||
|
then you may get a warning from pytest if that assertions
|
||||||
|
first failed and then succeeded.
|
||||||
|
|
||||||
|
asserting expected exceptions
|
||||||
|
+++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
In order to write assertions about exceptions, you can use
|
||||||
|
``py.test.raises`` as a context manager like this:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
with py.test.raises(ZeroDivisionError):
|
||||||
|
1 / 0
|
||||||
|
|
||||||
|
and if you need to have access to the actual exception info you may use:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
with py.test.raises(RuntimeError) as excinfo:
|
||||||
|
def f():
|
||||||
|
f()
|
||||||
|
f()
|
||||||
|
|
||||||
|
# do checks related to excinfo.type, excinfo.value, excinfo.traceback
|
||||||
|
|
||||||
|
If you want to write test code that works on Python2.4 as well,
|
||||||
|
you may also use two other ways to test for an expected exception:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
py.test.raises(ExpectedException, func, *args, **kwargs)
|
||||||
|
py.test.raises(ExpectedException, "func(*args, **kwargs)")
|
||||||
|
|
||||||
|
both of which execute the specified function with args and kwargs and
|
||||||
|
asserts that the given ``ExpectedException`` is raised. The reporter will
|
||||||
|
provide you with helpful output in case of failures such as *no
|
||||||
|
exception* or *wrong exception*.
|
||||||
|
|
||||||
|
information-rich tracebacks, PDB introspection
|
||||||
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
.. _`example tracebacks`: http://paste.pocoo.org/show/134814/
|
||||||
|
|
||||||
|
A lot of care is taken to present useful failure information
|
||||||
|
and in particular nice and concise Python tracebacks. This
|
||||||
|
is especially useful if you need to regularly look at failures
|
||||||
|
from nightly runs, i.e. are detached from the actual test
|
||||||
|
running session. Here are `example tracebacks`_ for a number of failing
|
||||||
|
test functions. You can modify traceback printing styles through the
|
||||||
|
command line. Using the `--pdb`` option you can automatically activate
|
||||||
|
a PDB `Python debugger`_ when a test fails.
|
||||||
|
|
||||||
|
|
||||||
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
Examples for writing tests
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
example/funcarg_simple.txt
|
|
@ -1,12 +0,0 @@
|
||||||
==============================================================================
|
|
||||||
py.execnet: *elastic* distributed programming
|
|
||||||
==============================================================================
|
|
||||||
|
|
||||||
Since pylib 1.1 "py.execnet" ceased to exist and is now available
|
|
||||||
as a separately developed `execnet standalone package`_.
|
|
||||||
|
|
||||||
If you have previosly used "py.execnet.*" and the 1.0 API just
|
|
||||||
rename all occurences of the string "``py.execnet.``" with the
|
|
||||||
string "``execnet.``" as execnet-1.0 is API compatible.
|
|
||||||
|
|
||||||
.. _`execnet standalone package`: http://codespeak.net/execnet
|
|
68
doc/faq.txt
68
doc/faq.txt
|
@ -1,37 +1,22 @@
|
||||||
==================================
|
Frequently asked Questions
|
||||||
Frequently Asked Questions
|
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
.. contents::
|
On naming, nosetests, licensing and magic XXX
|
||||||
:local:
|
------------------------------------------------
|
||||||
:depth: 2
|
|
||||||
|
|
||||||
|
Why the ``py.test`` naming, why not ``pytest``?
|
||||||
|
++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
On naming, nosetests, licensing and magic
|
XXX
|
||||||
===========================================
|
|
||||||
|
|
||||||
Why the ``py`` naming? Why not ``pytest``?
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
This mostly has historic reasons - the aim is
|
|
||||||
to get away from the somewhat questionable 'py' name
|
|
||||||
at some point. These days (2010) the 'py' library
|
|
||||||
almost completely comprises APIs that are used
|
|
||||||
by the ``py.test`` tool. There also are some
|
|
||||||
other uses, e.g. of the ``py.path.local()`` and
|
|
||||||
other path implementations. So it requires some
|
|
||||||
work to factor them out and do the shift.
|
|
||||||
|
|
||||||
Why the ``py.test`` naming?
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
because of TAB-completion under Bash/Shells. If you hit
|
because of TAB-completion under Bash/Shells. If you hit
|
||||||
``py.<TAB>`` you'll get a list of available development
|
``py.<TAB>`` you'll get a list of available development
|
||||||
tools that all share the ``py.`` prefix. Another motivation
|
tools that all share the ``py.`` prefix. Another motivation
|
||||||
was to unify the package ("py.test") and tool filename.
|
was to unify the package ("py.test") and tool filename.
|
||||||
|
|
||||||
|
|
||||||
What's py.test's relation to ``nosetests``?
|
What's py.test's relation to ``nosetests``?
|
||||||
---------------------------------------------
|
+++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
py.test and nose_ share basic philosophy when it comes
|
py.test and nose_ share basic philosophy when it comes
|
||||||
to running Python tests. In fact,
|
to running Python tests. In fact,
|
||||||
|
@ -47,12 +32,10 @@ and py.test-1.1 have no counterpart in nose_.
|
||||||
|
|
||||||
|
|
||||||
What's this "magic" with py.test?
|
What's this "magic" with py.test?
|
||||||
----------------------------------------
|
++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
issues where people have used the term "magic" in the past:
|
Around 2007 it was claimed that py.test was magic implementation
|
||||||
|
wise XXX. It has been refactored.
|
||||||
* `py/__init__.py`_ uses the apipkg_ mechanism for lazy-importing
|
|
||||||
and full control on what API you get when importing "import py".
|
|
||||||
|
|
||||||
* when an ``assert`` statement fails, py.test re-interprets the expression
|
* when an ``assert`` statement fails, py.test re-interprets the expression
|
||||||
to show intermediate values if a test fails. If your expression
|
to show intermediate values if a test fails. If your expression
|
||||||
|
@ -61,31 +44,22 @@ issues where people have used the term "magic" in the past:
|
||||||
``py.test --no-assert`` turns off assert re-intepretation.
|
``py.test --no-assert`` turns off assert re-intepretation.
|
||||||
Sidenote: it is good practise to avoid asserts with side effects.
|
Sidenote: it is good practise to avoid asserts with side effects.
|
||||||
|
|
||||||
|
|
||||||
.. _`py namespaces`: index.html
|
.. _`py namespaces`: index.html
|
||||||
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
|
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
|
||||||
|
|
||||||
Where does my ``py.test`` come/import from?
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
You can issue::
|
|
||||||
|
|
||||||
py.test --version
|
|
||||||
|
|
||||||
which tells you both version and import location of the tool.
|
|
||||||
|
|
||||||
|
|
||||||
function arguments, parametrized tests and setup
|
function arguments, parametrized tests and setup
|
||||||
====================================================
|
-------------------------------------------------------
|
||||||
|
|
||||||
.. _funcargs: test/funcargs.html
|
.. _funcargs: test/funcargs.html
|
||||||
|
|
||||||
Is using funcarg- versus xUnit-based setup a style question?
|
Is using funcarg- versus xUnit-based setup a style question?
|
||||||
---------------------------------------------------------------
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
It depends. For simple applications or for people experienced
|
XXX
|
||||||
with nose_ or unittest-style test setup using `xUnit style setup`_
|
For simple applications and for people experienced with nose_ or
|
||||||
make some sense. For larger test suites, parametrized testing
|
unittest-style test setup using `xUnit style setup`_
|
||||||
|
feels natural. For larger test suites, parametrized testing
|
||||||
or setup of complex test resources using funcargs_ is recommended.
|
or setup of complex test resources using funcargs_ is recommended.
|
||||||
Moreover, funcargs are ideal for writing advanced test support
|
Moreover, funcargs are ideal for writing advanced test support
|
||||||
code (like e.g. the monkeypatch_, the tmpdir_ or capture_ funcargs)
|
code (like e.g. the monkeypatch_, the tmpdir_ or capture_ funcargs)
|
||||||
|
@ -101,7 +75,7 @@ in a managed class/module/function scope.
|
||||||
.. _`why pytest_pyfuncarg__ methods?`:
|
.. _`why pytest_pyfuncarg__ methods?`:
|
||||||
|
|
||||||
Why the ``pytest_funcarg__*`` name for funcarg factories?
|
Why the ``pytest_funcarg__*`` name for funcarg factories?
|
||||||
---------------------------------------------------------------
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
When experimenting with funcargs an explicit registration mechanism
|
When experimenting with funcargs an explicit registration mechanism
|
||||||
was considered. But lacking a good use case for this indirection and
|
was considered. But lacking a good use case for this indirection and
|
||||||
|
@ -115,7 +89,7 @@ argument usage and creation.
|
||||||
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
||||||
|
|
||||||
Can I yield multiple values from a factory function?
|
Can I yield multiple values from a factory function?
|
||||||
-----------------------------------------------------
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
There are two conceptual reasons why yielding from a factory function
|
There are two conceptual reasons why yielding from a factory function
|
||||||
is not possible:
|
is not possible:
|
||||||
|
@ -138,10 +112,10 @@ and implement the `parametrization scheme of your choice`_.
|
||||||
|
|
||||||
|
|
||||||
py.test interaction with other packages
|
py.test interaction with other packages
|
||||||
===============================================
|
---------------------------------------------------
|
||||||
|
|
||||||
Issues with py.test, multiprocess and setuptools?
|
Issues with py.test, multiprocess and setuptools?
|
||||||
------------------------------------------------------------
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
On windows the multiprocess package will instantiate sub processes
|
On windows the multiprocess package will instantiate sub processes
|
||||||
by pickling and thus implicitely re-import a lot of local modules.
|
by pickling and thus implicitely re-import a lot of local modules.
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
py.test Features
|
||||||
|
==================
|
||||||
|
|
||||||
|
mature command line testing tool
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
- used in many projects, ranging from 10 to 10K tests
|
||||||
|
- simple well sorted command line options
|
||||||
|
- runs on Unix, Windows from Python 2.4 up to Python 3.1 and 3.2
|
||||||
|
- is itself tested extensively on a CI server
|
||||||
|
- keyword/testname based selection of tests
|
||||||
|
|
||||||
|
extensive plugin and customization system
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
.. _`suprisingly easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
|
||||||
|
|
||||||
|
- all collection, reporting, running aspects are delegated to hook functions
|
||||||
|
- hook functions are defined per-directory, per-project or through PyPI released plugins
|
||||||
|
- it is `suprisingly easy`_ to add command line options or
|
||||||
|
do other kind of add-ons and customizations.
|
||||||
|
|
||||||
|
integrates well with CI systems
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
- produces compatible JunitXML output for Hudson or other CI servers
|
||||||
|
- produces "resultlog" text files for easy parsing
|
||||||
|
- integrates well with tox_
|
||||||
|
|
||||||
|
.. _`tox`: http://codespeak.net/tox
|
||||||
|
|
||||||
|
no-boilerplate testing with Python
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
- automatic customizable Python test discovery
|
||||||
|
- powerful parametrization of test functions
|
||||||
|
- use the ``assert`` statement for your assertions
|
||||||
|
- rely on powerful traceback and assertion reporting
|
||||||
|
- use ``print`` or ``pdb`` debugging on failures
|
||||||
|
|
||||||
|
supports several testing practises and methods
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
- supports extended `xUnit style setup`_
|
||||||
|
- can integrate nose_, `unittest.py` and `doctest.py`_ style tests
|
||||||
|
- supports generating testing coverage
|
||||||
|
- `Javasript unit- and functional testing`_
|
||||||
|
|
||||||
|
.. _`Javasript unit- and functional testing`: plugin/oejskit.html
|
||||||
|
.. _`coverage testing with figleaf`: plugin/figleaf.html
|
||||||
|
.. _`unittest.py`: http://docs.python.org/library/unittest.html
|
||||||
|
|
||||||
|
distributing tests to local/remote subprocesses
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
.. _`pytest-xdist`: plugin/xdist.html
|
||||||
|
|
||||||
|
- distribute tests to multiple CPUs
|
||||||
|
- distribute tests to remote ssh or socket connected machines
|
||||||
|
- run tests in subprocess, re-run failing ones on file-change
|
||||||
|
|
||||||
|
skip or expect-to-fail a test
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
- skip tests if there are platform or dependency mismatches
|
||||||
|
- xfail a test (on certain platforms)indicating an implementation problem
|
||||||
|
- you can use skip and xfail imperatively or as a decorating marker
|
||||||
|
|
||||||
|
.. include:: links.inc
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
What users say:
|
||||||
|
|
||||||
|
`py.test is pretty much the best thing ever`_ (Alex Gaynor)
|
||||||
|
|
||||||
|
|
||||||
|
.. _`py.test is pretty much the best thing ever`_ (Alex Gaynor)
|
||||||
|
http://twitter.com/#!/alex_gaynor/status/22389410366
|
|
@ -0,0 +1,189 @@
|
||||||
|
==============================================================
|
||||||
|
funcargs: creating and managing test function arguments
|
||||||
|
==============================================================
|
||||||
|
|
||||||
|
.. currentmodule:: pytest.plugin.pytest_python
|
||||||
|
|
||||||
|
.. _`funcarg mechanism`:
|
||||||
|
|
||||||
|
Test function arguments and factories
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
A *funcarg* is the short name for "test function argument". Like
|
||||||
|
any other Python function a test function can receive one or multiple
|
||||||
|
arguments. When ``py.test`` prepares running a test function
|
||||||
|
it looks at the neccessary function arguments, locates and calls
|
||||||
|
factories which then provide the values to be passed to the function.
|
||||||
|
|
||||||
|
Basic funcarg example
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Let's look at a simple self-contained example that you can put
|
||||||
|
into a test module::
|
||||||
|
|
||||||
|
# ./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_simplefactory.py`` you see something like this::
|
||||||
|
|
||||||
|
=========================== test session starts ============================
|
||||||
|
python: platform linux2 -- Python 2.6.2
|
||||||
|
test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py
|
||||||
|
|
||||||
|
test_simplefactory.py F
|
||||||
|
|
||||||
|
================================ FAILURES ==================================
|
||||||
|
______________________________ test_function _______________________________
|
||||||
|
|
||||||
|
myfuncarg = 42
|
||||||
|
|
||||||
|
def test_function(myfuncarg):
|
||||||
|
> assert myfuncarg == 17
|
||||||
|
E assert 42 == 17
|
||||||
|
|
||||||
|
test_simplefactory.py:6: AssertionError
|
||||||
|
======================== 1 failed in 0.11 seconds ==========================
|
||||||
|
|
||||||
|
|
||||||
|
This means that the test function was called with a ``myfuncarg`` value
|
||||||
|
of ``42`` and the assert fails accordingly. Here is how py.test
|
||||||
|
calls the 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 factory function is discovered by looking for the
|
||||||
|
name ``pytest_funcarg__myfuncarg``.
|
||||||
|
|
||||||
|
2. ``pytest_funcarg__myfuncarg(request)`` is called and
|
||||||
|
returns the value for ``myfuncarg``.
|
||||||
|
|
||||||
|
3. ``test_function(42)`` call is executed.
|
||||||
|
|
||||||
|
Note that if you misspell a function argument or want
|
||||||
|
to use one that isn't available, you'll see an error
|
||||||
|
with a list of available function arguments. You can
|
||||||
|
also issue::
|
||||||
|
|
||||||
|
py.test --funcargs test_simplefactory.py
|
||||||
|
|
||||||
|
to see available function arguments (which you can also
|
||||||
|
think of as "resources").
|
||||||
|
|
||||||
|
|
||||||
|
.. _`contact possibilities`: ../contact.html
|
||||||
|
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
|
||||||
|
|
||||||
|
.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||||
|
.. _`xUnit style`: xunit_setup.html
|
||||||
|
|
||||||
|
|
||||||
|
.. _`funcarg factory`:
|
||||||
|
.. _factory:
|
||||||
|
|
||||||
|
The funcarg **request** object
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Each funcarg factory receives a **request** object which is tied to a
|
||||||
|
specific test function call. A request object is passed to a funcarg
|
||||||
|
factory and provides access to test configuration and context:
|
||||||
|
|
||||||
|
``request.function``: python function object requesting the argument
|
||||||
|
|
||||||
|
``request.cls``: class object where the test function is defined in or None.
|
||||||
|
|
||||||
|
``request.module``: module object where the test function is defined in.
|
||||||
|
|
||||||
|
``request.config``: access to command line opts and general config
|
||||||
|
|
||||||
|
``request.param``: if exists was passed by a previous :py:meth:`~Metafunc.addcall`
|
||||||
|
|
||||||
|
|
||||||
|
.. _`useful caching and finalization helpers`:
|
||||||
|
|
||||||
|
.. automethod:: FuncargRequest.addfinalizer
|
||||||
|
|
||||||
|
.. automethod:: FuncargRequest.cached_setup
|
||||||
|
|
||||||
|
.. automethod:: FuncargRequest.applymarker
|
||||||
|
|
||||||
|
.. automethod:: FuncargRequest.getfuncargvalue
|
||||||
|
|
||||||
|
|
||||||
|
.. _`test generators`:
|
||||||
|
.. _`parametrizing-tests`:
|
||||||
|
|
||||||
|
Parametrizing multiple calls to a test function
|
||||||
|
===========================================================
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
Basic generated test example
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Let's consider this test module::
|
||||||
|
|
||||||
|
# ./test_example.py
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
if "numiter" in metafunc.funcargnames:
|
||||||
|
for i in range(10):
|
||||||
|
metafunc.addcall(funcargs=dict(numiter=i))
|
||||||
|
|
||||||
|
def test_func(numiter):
|
||||||
|
assert numiter < 9
|
||||||
|
|
||||||
|
If you run this with ``py.test test_example.py`` you'll get::
|
||||||
|
|
||||||
|
============================= test session starts ==========================
|
||||||
|
python: platform linux2 -- Python 2.6.2
|
||||||
|
test object 1: /home/hpk/hg/py/trunk/test_example.py
|
||||||
|
|
||||||
|
test_example.py .........F
|
||||||
|
|
||||||
|
================================ FAILURES ==================================
|
||||||
|
__________________________ test_func.test_func[9] __________________________
|
||||||
|
|
||||||
|
numiter = 9
|
||||||
|
|
||||||
|
def test_func(numiter):
|
||||||
|
> assert numiter < 9
|
||||||
|
E assert 9 < 9
|
||||||
|
|
||||||
|
/home/hpk/hg/py/trunk/test_example.py:10: AssertionError
|
||||||
|
|
||||||
|
|
||||||
|
Here is what happens in detail:
|
||||||
|
|
||||||
|
1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
|
||||||
|
function. It adds ten new function calls with explicit function arguments.
|
||||||
|
|
||||||
|
2. **execute tests**: ``test_func(numiter)`` is called ten times with
|
||||||
|
ten different arguments.
|
||||||
|
|
||||||
|
.. _`metafunc object`:
|
||||||
|
|
||||||
|
The **metafunc** object
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
metafunc objects are passed to the ``pytest_generate_tests`` hook.
|
||||||
|
They help to inspect a testfunction and to generate tests
|
||||||
|
according to test configuration or values specified
|
||||||
|
in the class or module where a test function is defined:
|
||||||
|
|
||||||
|
``metafunc.funcargnames``: set of required function arguments for given function
|
||||||
|
|
||||||
|
``metafunc.function``: underlying python test function
|
||||||
|
|
||||||
|
``metafunc.cls``: class object where the test function is defined in or None.
|
||||||
|
|
||||||
|
``metafunc.module``: the module object where the test function is defined in.
|
||||||
|
|
||||||
|
``metafunc.config``: access to command line opts and general config
|
||||||
|
|
||||||
|
.. automethod:: Metafunc.addcall(funcargs=None, id=_notexists, param=_notexists)
|
|
@ -1,41 +1,35 @@
|
||||||
py lib: testing and filesystem abstraction
|
.. pytest documentation master file, created by
|
||||||
====================================================
|
sphinx-quickstart on Fri Oct 8 17:54:28 2010.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
The ``py`` lib has several namespaces which help with automated testing,
|
py.test: no-boilerplate rapid testing with Python
|
||||||
and with accessing filesystems. Here is some documentation on the
|
======================================================
|
||||||
core namespaces:
|
|
||||||
|
|
||||||
`py.test`_ write and deploy unit- and functional tests to multiple machines.
|
Welcome to ``py.test`` documentation.
|
||||||
|
|
||||||
`py.code`_: generate code and use advanced introspection/traceback support.
|
Contents:
|
||||||
|
|
||||||
`py.path`_: use path objects to transparently access local and svn filesystems.
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
Other (minor) support functionality
|
overview
|
||||||
===================================
|
apiref
|
||||||
|
customize
|
||||||
|
examples
|
||||||
|
talks
|
||||||
|
develop
|
||||||
|
|
||||||
`py lib scripts`_ to make python development easier.
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
`py.xml`_ for generating in-memory xml/html object trees
|
changelog.txt
|
||||||
|
|
||||||
`py.io`_: Helper Classes for Capturing of Input/Output
|
|
||||||
|
|
||||||
`py.log`_: an alpha document about the ad-hoc logging facilities
|
|
||||||
|
|
||||||
`miscellaneous features`_ describes some small but nice py lib features.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`PyPI project page`: http://pypi.python.org/pypi/py/
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
For the latest Release, see `PyPI project page`_
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev
|
* :ref:`search`
|
||||||
.. _`py.log`: log.html
|
|
||||||
.. _`py.io`: io.html
|
|
||||||
.. _`py.path`: path.html
|
|
||||||
.. _`py.code`: code.html
|
|
||||||
.. _`py.test`: test/index.html
|
|
||||||
.. _`py lib scripts`: bin.html
|
|
||||||
.. _`py.xml`: xml.html
|
|
||||||
.. _`miscellaneous features`: misc.html
|
|
||||||
|
|
||||||
|
|
124
doc/install.txt
124
doc/install.txt
|
@ -1,43 +1,24 @@
|
||||||
|
|
||||||
.. _`index page`: http://pypi.python.org/pypi/py/
|
installing py.test
|
||||||
|
|
||||||
py.test/pylib installation info in a nutshell
|
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
**Pythons**: 2.4, 2.5, 2.6, 2.7, 3.0, 3.1.x, Jython-2.5.1, PyPy-1.2
|
**PyPI name**: pytest_
|
||||||
|
|
||||||
**Operating systems**: Linux, Windows, OSX, Unix
|
**hg repository**: https://bitbucket.org/hpk42/pytest
|
||||||
|
|
||||||
**Requirements**: setuptools_ or Distribute_
|
|
||||||
|
|
||||||
**Installers**: easy_install_ and pip_ or `standalone`_ (new for 1.2)
|
|
||||||
|
|
||||||
**Distribution names**:
|
|
||||||
|
|
||||||
* PyPI name: ``py`` (see `index page`_ for versions)
|
|
||||||
* redhat fedora: ``python-py``
|
|
||||||
* debian: ``python-codespeak-lib``
|
|
||||||
* gentoo: ``pylib``
|
|
||||||
|
|
||||||
**Installed scripts**: see `bin`_ for which and how scripts are installed.
|
|
||||||
|
|
||||||
**hg repository**: https://bitbucket.org/hpk42/py-trunk
|
|
||||||
|
|
||||||
.. _`bin`: bin.html
|
|
||||||
|
|
||||||
|
**Compatibility**: Python 2.4-3.2, Jython, PyPy on Unix/Posix and Windows
|
||||||
|
|
||||||
.. _`easy_install`:
|
.. _`easy_install`:
|
||||||
|
|
||||||
Installation using easy_install
|
Installation using easy_install
|
||||||
===================================================
|
----------------------------------------
|
||||||
|
|
||||||
Both `Distribute`_ and setuptools_ provide the ``easy_install``
|
You need setuptools_ or Distribute_ to be able to simply type::
|
||||||
installation tool with which you can type into a command line window::
|
|
||||||
|
|
||||||
easy_install -U py
|
easy_install -U pytest
|
||||||
|
|
||||||
to install the latest release of the py lib and py.test. The ``-U`` switch
|
to install the latest release of ``py.test``. The ``-U`` switch
|
||||||
will trigger an upgrade if you already have an older version installed.
|
triggers an upgrade if you already have an older version installed.
|
||||||
Note that setuptools works ok with Python2 interpreters while `Distribute`_
|
Note that setuptools works ok with Python2 interpreters while `Distribute`_
|
||||||
additionally works with Python3 and also avoid some issues on Windows.
|
additionally works with Python3 and also avoid some issues on Windows.
|
||||||
|
|
||||||
|
@ -67,9 +48,9 @@ Known issues:
|
||||||
|
|
||||||
|
|
||||||
Recommendation: install tool and dependencies virtually
|
Recommendation: install tool and dependencies virtually
|
||||||
===========================================================
|
-----------------------------------------------------------
|
||||||
|
|
||||||
It is recommended to work with virtual environments
|
I recommend to work with virtual environments
|
||||||
(e.g. virtualenv_ or buildout_ based) and use easy_install_
|
(e.g. virtualenv_ or buildout_ based) and use easy_install_
|
||||||
(or pip_) for installing py.test/pylib and any dependencies
|
(or pip_) for installing py.test/pylib and any dependencies
|
||||||
you need to run your tests. Local virtual Python environments
|
you need to run your tests. Local virtual Python environments
|
||||||
|
@ -83,7 +64,7 @@ reproducible and reliable test environment.
|
||||||
.. _standalone:
|
.. _standalone:
|
||||||
|
|
||||||
Generating a py.test standalone Script
|
Generating a py.test standalone Script
|
||||||
============================================
|
-------------------------------------------
|
||||||
|
|
||||||
If you are a maintainer or application developer and want users
|
If you are a maintainer or application developer and want users
|
||||||
to run tests you can use a facility to generate a standalone
|
to run tests you can use a facility to generate a standalone
|
||||||
|
@ -101,88 +82,7 @@ all core features and runs unchanged under Python2 and Python3 interpreters.
|
||||||
|
|
||||||
.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html
|
.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html
|
||||||
|
|
||||||
.. _mercurial: http://mercurial.selenic.com/wiki/
|
|
||||||
.. _`Distribute`:
|
.. _`Distribute`:
|
||||||
.. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions
|
.. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions
|
||||||
.. _`distribute installation`: http://pypi.python.org/pypi/distribute
|
.. _`distribute installation`: http://pypi.python.org/pypi/distribute
|
||||||
.. _checkout:
|
|
||||||
.. _tarball:
|
|
||||||
|
|
||||||
Working from version control or a tarball
|
|
||||||
=================================================
|
|
||||||
|
|
||||||
To follow development or start experiments, checkout the
|
|
||||||
complete code and documentation source with mercurial_::
|
|
||||||
|
|
||||||
hg clone https://bitbucket.org/hpk42/py-trunk/
|
|
||||||
|
|
||||||
Development takes place on the 'trunk' branch.
|
|
||||||
|
|
||||||
You can also go to the python package index and
|
|
||||||
download and unpack a TAR file::
|
|
||||||
|
|
||||||
http://pypi.python.org/pypi/py/
|
|
||||||
|
|
||||||
|
|
||||||
activating a checkout with setuptools
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
With a working `Distribute`_ or setuptools_ installation you can type::
|
|
||||||
|
|
||||||
python setup.py develop
|
|
||||||
|
|
||||||
in order to work inline with the tools and the lib of your checkout.
|
|
||||||
|
|
||||||
.. _`no-setuptools`:
|
|
||||||
|
|
||||||
.. _`directly use a checkout`:
|
|
||||||
|
|
||||||
directly use a checkout or tarball / avoid setuptools
|
|
||||||
-------------------------------------------------------------
|
|
||||||
|
|
||||||
Get a checkout_ or tarball_ and add paths to your environment variables:
|
|
||||||
|
|
||||||
* ``PYTHONPATH`` needs to contain the root directory (where ``py`` resides)
|
|
||||||
* ``PATH`` needs to contain ``ROOT/bin`` or ``ROOT\bin\win32`` respectively.
|
|
||||||
|
|
||||||
There also are helper scripts that set the variables accordingly. On windows
|
|
||||||
execute::
|
|
||||||
|
|
||||||
# inside autoexec.bat or shell startup
|
|
||||||
c:\\path\to\checkout\bin\env.cmd
|
|
||||||
|
|
||||||
on linux/OSX add this to your shell initialization::
|
|
||||||
|
|
||||||
# inside e.g. .bashrc
|
|
||||||
eval `python ~/path/to/checkout/bin/env.py`
|
|
||||||
|
|
||||||
both of which which will get you good settings. If you install
|
|
||||||
the pylib this way you can easily ``hg pull && hg up`` or download
|
|
||||||
a new tarball_ to follow the development tree.
|
|
||||||
|
|
||||||
|
|
||||||
Note that the scripts manually added like this will look for
|
|
||||||
py libs in the chain of parent directories of the current working dir.
|
|
||||||
For example, if you have a layout like this::
|
|
||||||
|
|
||||||
mypkg/
|
|
||||||
subpkg1/
|
|
||||||
tests/
|
|
||||||
tests/
|
|
||||||
py/
|
|
||||||
|
|
||||||
issuing ``py.test subpkg1`` will use the py lib
|
|
||||||
from that projects root directory. If in doubt over where
|
|
||||||
the pylib comes from you can always do::
|
|
||||||
|
|
||||||
py.test --version
|
|
||||||
|
|
||||||
to see where py.test is imported from.
|
|
||||||
|
|
||||||
.. _`command line scripts`: bin.html
|
|
||||||
.. _contact: contact.html
|
|
||||||
|
|
||||||
.. _`RPM`: http://translate.sourceforge.net/releases/testing/fedora/pylib-0.9.2-1.fc9.noarch.rpm
|
|
||||||
|
|
||||||
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
|
|
||||||
|
|
||||||
|
|
43
doc/io.txt
43
doc/io.txt
|
@ -1,43 +0,0 @@
|
||||||
=======
|
|
||||||
py.io
|
|
||||||
=======
|
|
||||||
|
|
||||||
|
|
||||||
The 'py' lib provides helper classes for capturing IO during
|
|
||||||
execution of a program.
|
|
||||||
|
|
||||||
IO Capturing examples
|
|
||||||
===============================================
|
|
||||||
|
|
||||||
``py.io.StdCapture``
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Basic Example::
|
|
||||||
|
|
||||||
>>> import py
|
|
||||||
>>> capture = py.io.StdCapture()
|
|
||||||
>>> print "hello"
|
|
||||||
>>> out,err = capture.reset()
|
|
||||||
>>> out.strip() == "hello"
|
|
||||||
True
|
|
||||||
|
|
||||||
For calling functions you may use a shortcut::
|
|
||||||
|
|
||||||
>>> import py
|
|
||||||
>>> def f(): print "hello"
|
|
||||||
>>> res, out, err = py.io.StdCapture.call(f)
|
|
||||||
>>> out.strip() == "hello"
|
|
||||||
True
|
|
||||||
|
|
||||||
``py.io.StdCaptureFD``
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
If you also want to capture writes to the stdout/stderr
|
|
||||||
filedescriptors you may invoke::
|
|
||||||
|
|
||||||
>>> import py, sys
|
|
||||||
>>> capture = py.io.StdCaptureFD(out=False, in_=False)
|
|
||||||
>>> sys.stderr.write("world")
|
|
||||||
>>> out,err = capture.reset()
|
|
||||||
>>> err
|
|
||||||
'world'
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
.. _`skipping plugin`: plugin/skipping.html
|
||||||
|
.. _`funcargs mechanism`: funcargs.html
|
||||||
|
.. _`doctest.py`: http://docs.python.org/library/doctest.html
|
||||||
|
.. _`xUnit style setup`: xunit_setup.html
|
||||||
|
.. _`pytest_nose`: plugin/nose.html
|
||||||
|
.. _`reStructured Text`: http://docutils.sourceforge.net
|
||||||
|
.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
|
||||||
|
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
|
||||||
|
.. _pytest: http://pypi.python.org/pypi/pytest
|
||||||
|
.. _mercurial: http://mercurial.selenic.com/wiki/
|
||||||
|
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
|
||||||
|
.. _`distribute`: http://pypi.python.org/pypi/distribute
|
208
doc/log.txt
208
doc/log.txt
|
@ -1,208 +0,0 @@
|
||||||
.. role:: code(literal)
|
|
||||||
.. role:: file(literal)
|
|
||||||
|
|
||||||
.. XXX figure out how the code literals should be dealt with in sphinx. There is probably something builtin.
|
|
||||||
|
|
||||||
========================================
|
|
||||||
py.log documentation and musings
|
|
||||||
========================================
|
|
||||||
|
|
||||||
|
|
||||||
Foreword
|
|
||||||
========
|
|
||||||
|
|
||||||
This document is an attempt to briefly state the actual specification of the
|
|
||||||
:code:`py.log` module. It was written by Francois Pinard and also contains
|
|
||||||
some ideas for enhancing the py.log facilities.
|
|
||||||
|
|
||||||
NOTE that :code:`py.log` is subject to refactorings, it may change with
|
|
||||||
the next release.
|
|
||||||
|
|
||||||
This document is meant to trigger or facilitate discussions. It shamelessly
|
|
||||||
steals from the `Agile Testing`__ comments, and from other sources as well,
|
|
||||||
without really trying to sort them out.
|
|
||||||
|
|
||||||
__ http://agiletesting.blogspot.com/2005/06/keyword-based-logging-with-py-library.html
|
|
||||||
|
|
||||||
|
|
||||||
Logging organisation
|
|
||||||
====================
|
|
||||||
|
|
||||||
The :code:`py.log` module aims a niche comparable to the one of the
|
|
||||||
`logging module`__ found within the standard Python distributions, yet
|
|
||||||
with much simpler paradigms for configuration and usage.
|
|
||||||
|
|
||||||
__ http://www.python.org/doc/2.4.2/lib/module-logging.html
|
|
||||||
|
|
||||||
Holger Krekel, the main :code:`py` library developer, introduced
|
|
||||||
the idea of keyword-based logging and the idea of logging *producers* and
|
|
||||||
*consumers*. A log producer is an object used by the application code
|
|
||||||
to send messages to various log consumers. When you create a log
|
|
||||||
producer, you define a set of keywords that are then used to both route
|
|
||||||
the logging messages to consumers, and to prefix those messages.
|
|
||||||
|
|
||||||
In fact, each log producer has a few keywords associated with it for
|
|
||||||
identification purposes. These keywords form a tuple of strings, and
|
|
||||||
may be used to later retrieve a particular log producer.
|
|
||||||
|
|
||||||
A log producer may (or may not) be associated with a log consumer, meant
|
|
||||||
to handle log messages in particular ways. The log consumers can be
|
|
||||||
``STDOUT``, ``STDERR``, log files, syslog, the Windows Event Log, user
|
|
||||||
defined functions, etc. (Yet, logging to syslog or to the Windows Event
|
|
||||||
Log is only future plans for now). A log producer has never more than
|
|
||||||
one consumer at a given time, but it is possible to dynamically switch
|
|
||||||
a producer to use another consumer. On the other hand, a single log
|
|
||||||
consumer may be associated with many producers.
|
|
||||||
|
|
||||||
Note that creating and associating a producer and a consumer is done
|
|
||||||
automatically when not otherwise overriden, so using :code:`py` logging
|
|
||||||
is quite comfortable even in the smallest programs. More typically,
|
|
||||||
the application programmer will likely design a hierarchy of producers,
|
|
||||||
and will select keywords appropriately for marking the hierarchy tree.
|
|
||||||
If a node of the hierarchical tree of producers has to be divided in
|
|
||||||
sub-trees, all producers in the sub-trees share, as a common prefix, the
|
|
||||||
keywords of the node being divided. In other words, we go further down
|
|
||||||
in the hierarchy of producers merely by adding keywords.
|
|
||||||
|
|
||||||
Using the py.log library
|
|
||||||
================================
|
|
||||||
|
|
||||||
To use the :code:`py.log` library, the user must import it into a Python
|
|
||||||
application, create at least one log producer and one log consumer, have
|
|
||||||
producers and consumers associated, and finally call the log producers
|
|
||||||
as needed, giving them log messages.
|
|
||||||
|
|
||||||
Importing
|
|
||||||
---------
|
|
||||||
|
|
||||||
Once the :code:`py` library is installed on your system, a mere::
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
holds enough magic for lazily importing the various facilities of the
|
|
||||||
:code:`py` library when they are first needed. This is really how
|
|
||||||
:code:`py.log` is made available to the application. For example, after
|
|
||||||
the above ``import py``, one may directly write ``py.log.Producer(...)``
|
|
||||||
and everything should work fine, the user does not have to worry about
|
|
||||||
specifically importing more modules.
|
|
||||||
|
|
||||||
Creating a producer
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
There are three ways for creating a log producer instance:
|
|
||||||
|
|
||||||
+ As soon as ``py.log`` is first evaluated within an application
|
|
||||||
program, a default log producer is created, and made available under
|
|
||||||
the name ``py.log.default``. The keyword ``default`` is associated
|
|
||||||
with that producer.
|
|
||||||
|
|
||||||
+ The ``py.log.Producer()`` constructor may be explicitly called
|
|
||||||
for creating a new instance of a log producer. That constructor
|
|
||||||
accepts, as an argument, the keywords that should be associated with
|
|
||||||
that producer. Keywords may be given either as a tuple of keyword
|
|
||||||
strings, or as a single space-separated string of keywords.
|
|
||||||
|
|
||||||
+ Whenever an attribute is *taken* out of a log producer instance,
|
|
||||||
for the first time that attribute is taken, a new log producer is
|
|
||||||
created. The keywords associated with that new producer are those
|
|
||||||
of the initial producer instance, to which is appended the name of
|
|
||||||
the attribute being taken.
|
|
||||||
|
|
||||||
The last point is especially useful, as it allows using log producers
|
|
||||||
without further declarations, merely creating them *on-the-fly*.
|
|
||||||
|
|
||||||
Creating a consumer
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
There are many ways for creating or denoting a log consumer:
|
|
||||||
|
|
||||||
+ A default consumer exists within the ``py.log`` facilities, which
|
|
||||||
has the effect of writing log messages on the Python standard output
|
|
||||||
stream. That consumer is associated at the very top of the producer
|
|
||||||
hierarchy, and as such, is called whenever no other consumer is
|
|
||||||
found.
|
|
||||||
|
|
||||||
+ The notation ``py.log.STDOUT`` accesses a log consumer which writes
|
|
||||||
log messages on the Python standard output stream.
|
|
||||||
|
|
||||||
+ The notation ``py.log.STDERR`` accesses a log consumer which writes
|
|
||||||
log messages on the Python standard error stream.
|
|
||||||
|
|
||||||
+ The ``py.log.File()`` constructor accepts, as argument, either a file
|
|
||||||
already opened in write mode or any similar file-like object, and
|
|
||||||
creates a log consumer able to write log messages onto that file.
|
|
||||||
|
|
||||||
+ The ``py.log.Path()`` constructor accepts a file name for its first
|
|
||||||
argument, and creates a log consumer able to write log messages into
|
|
||||||
that file. The constructor call accepts a few keyword parameters:
|
|
||||||
|
|
||||||
+ ``append``, which is ``False`` by default, may be used for
|
|
||||||
opening the file in append mode instead of write mode.
|
|
||||||
|
|
||||||
+ ``delayed_create``, which is ``False`` by default, maybe be used
|
|
||||||
for opening the file at the latest possible time. Consequently,
|
|
||||||
the file will not be created if it did not exist, and no actual
|
|
||||||
log message gets written to it.
|
|
||||||
|
|
||||||
+ ``buffering``, which is 1 by default, is used when opening the
|
|
||||||
file. Buffering can be turned off by specifying a 0 value. The
|
|
||||||
buffer size may also be selected through this argument.
|
|
||||||
|
|
||||||
+ Any user defined function may be used for a log consumer. Such a
|
|
||||||
function should accept a single argument, which is the message to
|
|
||||||
write, and do whatever is deemed appropriate by the programmer.
|
|
||||||
When the need arises, this may be an especially useful and flexible
|
|
||||||
feature.
|
|
||||||
|
|
||||||
+ The special value ``None`` means no consumer at all. This acts just
|
|
||||||
like if there was a consumer which would silently discard all log
|
|
||||||
messages sent to it.
|
|
||||||
|
|
||||||
Associating producers and consumers
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
Each log producer may have at most one log consumer associated with
|
|
||||||
it. A log producer gets associated with a log consumer through a
|
|
||||||
``py.log.set_consumer()`` call. That function accepts two arguments,
|
|
||||||
the first identifying a producer (a tuple of keyword strings or a single
|
|
||||||
space-separated string of keywords), the second specifying the precise
|
|
||||||
consumer to use for that producer. Until this function is called for a
|
|
||||||
producer, that producer does not have any explicit consumer associated
|
|
||||||
with it.
|
|
||||||
|
|
||||||
Now, the hierarchy of log producers establishes which consumer gets used
|
|
||||||
whenever a producer has no explicit consumer. When a log producer
|
|
||||||
has no consumer explicitly associated with it, it dynamically and
|
|
||||||
recursively inherits the consumer of its parent node, that is, that node
|
|
||||||
being a bit closer to the root of the hierarchy. In other words, the
|
|
||||||
rightmost keywords of that producer are dropped until another producer
|
|
||||||
is found which has an explicit consumer. A nice side-effect is that,
|
|
||||||
by explicitly associating a consumer with a producer, all consumer-less
|
|
||||||
producers which appear under that producer, in the hierarchy tree,
|
|
||||||
automatically *inherits* that consumer.
|
|
||||||
|
|
||||||
Writing log messages
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
All log producer instances are also functions, and this is by calling
|
|
||||||
them that log messages are generated. Each call to a producer object
|
|
||||||
produces the text for one log entry, which in turn, is sent to the log
|
|
||||||
consumer for that producer.
|
|
||||||
|
|
||||||
The log entry displays, after a prefix identifying the log producer
|
|
||||||
being used, all arguments given in the call, converted to strings and
|
|
||||||
space-separated. (This is meant by design to be fairly similar to what
|
|
||||||
the ``print`` statement does in Python). The prefix itself is made up
|
|
||||||
of a colon-separated list of keywords associated with the producer, the
|
|
||||||
whole being set within square brackets.
|
|
||||||
|
|
||||||
Note that the consumer is responsible for adding the newline at the end
|
|
||||||
of the log entry. That final newline is not part of the text for the
|
|
||||||
log entry.
|
|
||||||
|
|
||||||
.. Other details
|
|
||||||
.. -------------
|
|
||||||
.. XXX: fill in details
|
|
||||||
.. + Should speak about pickle-ability of :code:`py.log`.
|
|
||||||
..
|
|
||||||
.. + What is :code:`log.get` (in :file:`logger.py`)?
|
|
|
@ -1,11 +1,9 @@
|
||||||
|
|
||||||
|
.. _mark:
|
||||||
|
|
||||||
generic mechanism for marking python functions.
|
generic mechanism for marking python functions.
|
||||||
===============================================
|
===============================================
|
||||||
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
|
|
||||||
By using the ``py.test.mark`` helper you can instantiate
|
By using the ``py.test.mark`` helper you can instantiate
|
||||||
decorators that will set named meta data on test functions.
|
decorators that will set named meta data on test functions.
|
||||||
|
|
||||||
|
@ -86,14 +84,3 @@ tests::
|
||||||
|
|
||||||
py.test -k webtest # will only run tests marked as webtest
|
py.test -k webtest # will only run tests marked as webtest
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
1. Download `pytest_mark.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_mark.py`` into your import path
|
|
||||||
3. a subsequent ``py.test`` run will use your local version
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
93
doc/misc.txt
93
doc/misc.txt
|
@ -1,93 +0,0 @@
|
||||||
====================================
|
|
||||||
Miscellaneous features of the py lib
|
|
||||||
====================================
|
|
||||||
|
|
||||||
Mapping the standard python library into py
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
The ``py.std`` object allows lazy access to
|
|
||||||
standard library modules. For example, to get to the print-exception
|
|
||||||
functionality of the standard library you can write::
|
|
||||||
|
|
||||||
py.std.traceback.print_exc()
|
|
||||||
|
|
||||||
without having to do anything else than the usual ``import py``
|
|
||||||
at the beginning. You can access any other top-level standard
|
|
||||||
library module this way. This means that you will only trigger
|
|
||||||
imports of modules that are actually needed. Note that no attempt
|
|
||||||
is made to import submodules.
|
|
||||||
|
|
||||||
Support for interaction with system utilities/binaries
|
|
||||||
======================================================
|
|
||||||
|
|
||||||
Currently, the py lib offers two ways to interact with
|
|
||||||
system executables. ``py.process.cmdexec()`` invokes
|
|
||||||
the shell in order to execute a string. The other
|
|
||||||
one, ``py.path.local``'s 'sysexec()' method lets you
|
|
||||||
directly execute a binary.
|
|
||||||
|
|
||||||
Both approaches will raise an exception in case of a return-
|
|
||||||
code other than 0 and otherwise return the stdout-output
|
|
||||||
of the child process.
|
|
||||||
|
|
||||||
The shell based approach
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
You can execute a command via your system shell
|
|
||||||
by doing something like::
|
|
||||||
|
|
||||||
out = py.process.cmdexec('ls -v')
|
|
||||||
|
|
||||||
However, the ``cmdexec`` approach has a few shortcomings:
|
|
||||||
|
|
||||||
- it relies on the underlying system shell
|
|
||||||
- it neccessitates shell-escaping for expressing arguments
|
|
||||||
- it does not easily allow to "fix" the binary you want to run.
|
|
||||||
- it only allows to execute executables from the local
|
|
||||||
filesystem
|
|
||||||
|
|
||||||
.. _sysexec:
|
|
||||||
|
|
||||||
local paths have ``sysexec``
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
In order to synchronously execute an executable file you
|
|
||||||
can use ``sysexec``::
|
|
||||||
|
|
||||||
binsvn.sysexec('ls', 'http://codespeak.net/svn')
|
|
||||||
|
|
||||||
where ``binsvn`` is a path that points to the ``svn`` commandline
|
|
||||||
binary. Note that this function does not offer any shell-escaping
|
|
||||||
so you have to pass in already separated arguments.
|
|
||||||
|
|
||||||
finding an executable local path
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Finding an executable is quite different on multiple platforms.
|
|
||||||
Currently, the ``PATH`` environment variable based search on
|
|
||||||
unix platforms is supported::
|
|
||||||
|
|
||||||
py.path.local.sysfind('svn')
|
|
||||||
|
|
||||||
which returns the first path whose ``basename`` matches ``svn``.
|
|
||||||
In principle, `sysfind` deploys platform specific algorithms
|
|
||||||
to perform the search. On Windows, for example, it may look
|
|
||||||
at the registry (XXX).
|
|
||||||
|
|
||||||
To make the story complete, we allow to pass in a second ``checker``
|
|
||||||
argument that is called for each found executable. For example, if
|
|
||||||
you have multiple binaries available you may want to select the
|
|
||||||
right version::
|
|
||||||
|
|
||||||
def mysvn(p):
|
|
||||||
""" check that the given svn binary has version 1.1. """
|
|
||||||
line = p.execute('--version'').readlines()[0]
|
|
||||||
if line.find('version 1.1'):
|
|
||||||
return p
|
|
||||||
binsvn = py.path.local.sysfind('svn', checker=mysvn)
|
|
||||||
|
|
||||||
|
|
||||||
Cross-Python Version compatibility helpers
|
|
||||||
=============================================
|
|
||||||
|
|
||||||
The ``py.builtin`` namespace provides a number of helpers that help to write python code compatible across Python interpreters, mainly Python2 and Python3. Type ``help(py.builtin)`` on a Python prompt for a the selection of builtins.
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
monkeypatching/mocking modules and environments
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
.. currentmodule:: pytest.plugin.pytest_monkeypatch
|
||||||
|
|
||||||
|
Sometimes tests need to invoke functionality which depends
|
||||||
|
on global settings or which invokes code which cannot be easily
|
||||||
|
tested such as network access. The ``monkeypatch`` function argument
|
||||||
|
helps you to safely set/delete an attribute, dictionary item or
|
||||||
|
environment variable or to modify ``sys.path`` for importing.
|
||||||
|
See the `monkeypatch blog post`_ one some introduction material
|
||||||
|
and motivation.
|
||||||
|
|
||||||
|
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||||
|
|
||||||
|
|
||||||
|
Simple example: patching ``os.path.expanduser``
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If you e.g. want to pretend that ``os.expanduser`` returns a certain
|
||||||
|
directory, you can use the :py:meth:`monkeypatch.setattr` method to
|
||||||
|
patch this function before calling into a function which uses it::
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
def getssh(): # pseudo application code
|
||||||
|
return os.path.join(os.expanduser("~admin"), '.ssh')
|
||||||
|
|
||||||
|
def test_mytest(monkeypatch):
|
||||||
|
monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz')
|
||||||
|
x = getssh()
|
||||||
|
assert x == '/tmp/xyz/.ssh'
|
||||||
|
|
||||||
|
After the test function finishes the ``os.path.expanduser`` modification
|
||||||
|
will be undone.
|
||||||
|
|
||||||
|
Running the above example::
|
||||||
|
|
||||||
|
$ py.test
|
||||||
|
PASS
|
||||||
|
|
||||||
|
|
||||||
|
Method reference of the monkeypatch function argument
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
.. autoclass:: monkeypatch
|
||||||
|
:members: setattr, delattr, setitem, delitem, setenv, delenv, syspath_prepend, undo
|
||||||
|
|
||||||
|
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
||||||
|
by default raise an Exception if the target does not exist.
|
||||||
|
Pass ``raising=False`` if you want to skip this check.
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
==================================================
|
||||||
|
Overview and Introduction
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
features.txt
|
||||||
|
install.txt
|
||||||
|
|
273
doc/path.txt
273
doc/path.txt
|
@ -1,273 +0,0 @@
|
||||||
=======
|
|
||||||
py.path
|
|
||||||
=======
|
|
||||||
|
|
||||||
The 'py' lib provides a uniform high-level api to deal with filesystems
|
|
||||||
and filesystem-like interfaces: ``py.path``. It aims to offer a central
|
|
||||||
object to fs-like object trees (reading from and writing to files, adding
|
|
||||||
files/directories, examining the types and structure, etc.), and out-of-the-box
|
|
||||||
provides a number of implementations of this API.
|
|
||||||
|
|
||||||
Path implementations provided by ``py.path``
|
|
||||||
===============================================
|
|
||||||
|
|
||||||
.. _`local`:
|
|
||||||
|
|
||||||
``py.path.local``
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
The first and most obvious of the implementations is a wrapper around a local
|
|
||||||
filesystem. It's just a bit nicer in usage than the regular Python APIs, and
|
|
||||||
of course all the functionality is bundled together rather than spread over a
|
|
||||||
number of modules.
|
|
||||||
|
|
||||||
Example usage, here we use the ``py.test.ensuretemp()`` function to create
|
|
||||||
a ``py.path.local`` object for us (which wraps a directory):
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
>>> import py
|
|
||||||
>>> temppath = py.test.ensuretemp('py.path_documentation')
|
|
||||||
>>> foopath = temppath.join('foo') # get child 'foo' (lazily)
|
|
||||||
>>> foopath.check() # check if child 'foo' exists
|
|
||||||
False
|
|
||||||
>>> foopath.write('bar') # write some data to it
|
|
||||||
>>> foopath.check()
|
|
||||||
True
|
|
||||||
>>> foopath.read()
|
|
||||||
'bar'
|
|
||||||
>>> foofile = foopath.open() # return a 'real' file object
|
|
||||||
>>> foofile.read(1)
|
|
||||||
'b'
|
|
||||||
|
|
||||||
``py.path.svnurl`` and ``py.path.svnwc``
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
Two other ``py.path`` implementations that the py lib provides wrap the
|
|
||||||
popular `Subversion`_ revision control system: the first (called 'svnurl')
|
|
||||||
by interfacing with a remote server, the second by wrapping a local checkout.
|
|
||||||
Both allow you to access relatively advanced features such as metadata and
|
|
||||||
versioning, and both in a way more user-friendly manner than existing other
|
|
||||||
solutions.
|
|
||||||
|
|
||||||
Some example usage of ``py.path.svnurl``:
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
.. >>> import py
|
|
||||||
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
|
||||||
>>> url = py.path.svnurl('http://codespeak.net/svn/py')
|
|
||||||
>>> info = url.info()
|
|
||||||
>>> info.kind
|
|
||||||
'dir'
|
|
||||||
>>> firstentry = url.log()[-1]
|
|
||||||
>>> import time
|
|
||||||
>>> time.strftime('%Y-%m-%d', time.gmtime(firstentry.date))
|
|
||||||
'2004-10-02'
|
|
||||||
|
|
||||||
Example usage of ``py.path.svnwc``:
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
|
||||||
>>> temp = py.test.ensuretemp('py.path_documentation')
|
|
||||||
>>> wc = py.path.svnwc(temp.join('svnwc'))
|
|
||||||
>>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local')
|
|
||||||
>>> wc.join('local.py').check()
|
|
||||||
True
|
|
||||||
|
|
||||||
.. _`Subversion`: http://subversion.tigris.org/
|
|
||||||
|
|
||||||
Common vs. specific API
|
|
||||||
=======================
|
|
||||||
|
|
||||||
All Path objects support a common set of operations, suitable
|
|
||||||
for many use cases and allowing to transparently switch the
|
|
||||||
path object within an application (e.g. from "local" to "svnwc").
|
|
||||||
The common set includes functions such as `path.read()` to read all data
|
|
||||||
from a file, `path.write()` to write data, `path.listdir()` to get a list
|
|
||||||
of directory entries, `path.check()` to check if a node exists
|
|
||||||
and is of a particular type, `path.join()` to get
|
|
||||||
to a (grand)child, `path.visit()` to recursively walk through a node's
|
|
||||||
children, etc. Only things that are not common on 'normal' filesystems (yet),
|
|
||||||
such as handling metadata (e.g. the Subversion "properties") require
|
|
||||||
using specific APIs.
|
|
||||||
|
|
||||||
Examples
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
A quick 'cookbook' of small examples that will be useful 'in real life',
|
|
||||||
which also presents parts of the 'common' API, and shows some non-common
|
|
||||||
methods:
|
|
||||||
|
|
||||||
Searching `.txt` files
|
|
||||||
..........................
|
|
||||||
|
|
||||||
Search for a particular string inside all files with a .txt extension in a
|
|
||||||
specific directory.
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
>>> dirpath = temppath.ensure('testdir', dir=True)
|
|
||||||
>>> dirpath.join('textfile1.txt').write('foo bar baz')
|
|
||||||
>>> dirpath.join('textfile2.txt').write('frob bar spam eggs')
|
|
||||||
>>> subdir = dirpath.ensure('subdir', dir=True)
|
|
||||||
>>> subdir.join('textfile1.txt').write('foo baz')
|
|
||||||
>>> subdir.join('textfile2.txt').write('spam eggs spam foo bar spam')
|
|
||||||
>>> results = []
|
|
||||||
>>> for fpath in dirpath.visit('*.txt'):
|
|
||||||
... if 'bar' in fpath.read():
|
|
||||||
... results.append(fpath.basename)
|
|
||||||
>>> results.sort()
|
|
||||||
>>> results
|
|
||||||
['textfile1.txt', 'textfile2.txt', 'textfile2.txt']
|
|
||||||
|
|
||||||
Working with Paths
|
|
||||||
.......................
|
|
||||||
|
|
||||||
This example shows the ``py.path`` features to deal with
|
|
||||||
filesystem paths Note that the filesystem is never touched,
|
|
||||||
all operations are performed on a string level (so the paths
|
|
||||||
don't have to exist, either):
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
>>> p1 = py.path.local('/foo/bar')
|
|
||||||
>>> p2 = p1.join('baz/qux')
|
|
||||||
>>> p2 == py.path.local('/foo/bar/baz/qux')
|
|
||||||
True
|
|
||||||
>>> sep = py.path.local.sep
|
|
||||||
>>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
|
|
||||||
'baz/qux'
|
|
||||||
>>> p2.bestrelpath(p1).replace(sep, '/')
|
|
||||||
'../..'
|
|
||||||
>>> p2.join(p2.bestrelpath(p1)) == p1
|
|
||||||
True
|
|
||||||
>>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
|
|
||||||
>>> p2 == p3
|
|
||||||
True
|
|
||||||
>>> p4 = p1 + ".py"
|
|
||||||
>>> p4.basename == "bar.py"
|
|
||||||
True
|
|
||||||
>>> p4.ext == ".py"
|
|
||||||
True
|
|
||||||
>>> p4.purebasename == "bar"
|
|
||||||
True
|
|
||||||
|
|
||||||
This should be possible on every implementation of ``py.path``, so
|
|
||||||
regardless of whether the implementation wraps a UNIX filesystem, a Windows
|
|
||||||
one, or a database or object tree, these functions should be available (each
|
|
||||||
with their own notion of path seperators and dealing with conversions, etc.).
|
|
||||||
|
|
||||||
Checking path types
|
|
||||||
.......................
|
|
||||||
|
|
||||||
Now we will show a bit about the powerful 'check()' method on paths, which
|
|
||||||
allows you to check whether a file exists, what type it is, etc.:
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
>>> file1 = temppath.join('file1')
|
|
||||||
>>> file1.check() # does it exist?
|
|
||||||
False
|
|
||||||
>>> file1 = file1.ensure(file=True) # 'touch' the file
|
|
||||||
>>> file1.check()
|
|
||||||
True
|
|
||||||
>>> file1.check(dir=True) # is it a dir?
|
|
||||||
False
|
|
||||||
>>> file1.check(file=True) # or a file?
|
|
||||||
True
|
|
||||||
>>> file1.check(ext='.txt') # check the extension
|
|
||||||
False
|
|
||||||
>>> textfile = temppath.ensure('text.txt', file=True)
|
|
||||||
>>> textfile.check(ext='.txt')
|
|
||||||
True
|
|
||||||
>>> file1.check(basename='file1') # we can use all the path's properties here
|
|
||||||
True
|
|
||||||
|
|
||||||
Setting svn-properties
|
|
||||||
.......................
|
|
||||||
|
|
||||||
As an example of 'uncommon' methods, we'll show how to read and write
|
|
||||||
properties in an ``py.path.svnwc`` instance:
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
|
||||||
>>> wc.propget('foo')
|
|
||||||
''
|
|
||||||
>>> wc.propset('foo', 'bar')
|
|
||||||
>>> wc.propget('foo')
|
|
||||||
'bar'
|
|
||||||
>>> len(wc.status().prop_modified) # our own props
|
|
||||||
1
|
|
||||||
>>> msg = wc.revert() # roll back our changes
|
|
||||||
>>> len(wc.status().prop_modified)
|
|
||||||
0
|
|
||||||
|
|
||||||
SVN authentication
|
|
||||||
.......................
|
|
||||||
|
|
||||||
Some uncommon functionality can also be provided as extensions, such as SVN
|
|
||||||
authentication:
|
|
||||||
|
|
||||||
.. sourcecode:: pycon
|
|
||||||
|
|
||||||
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
|
||||||
>>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
|
|
||||||
... interactive=False)
|
|
||||||
>>> wc.auth = auth
|
|
||||||
>>> wc.update() # this should work
|
|
||||||
>>> path = wc.ensure('thisshouldnotexist.txt')
|
|
||||||
>>> try:
|
|
||||||
... path.commit('testing')
|
|
||||||
... except py.process.cmdexec.Error, e:
|
|
||||||
... pass
|
|
||||||
>>> 'authorization failed' in str(e)
|
|
||||||
True
|
|
||||||
|
|
||||||
Known problems / limitations
|
|
||||||
===================================
|
|
||||||
|
|
||||||
* The SVN path objects require the "svn" command line,
|
|
||||||
there is currently no support for python bindings.
|
|
||||||
Parsing the svn output can lead to problems, particularly
|
|
||||||
regarding if you have a non-english "locales" setting.
|
|
||||||
|
|
||||||
* While the path objects basically work on windows,
|
|
||||||
there is no attention yet on making unicode paths
|
|
||||||
work or deal with the famous "8.3" filename issues.
|
|
||||||
|
|
||||||
Future plans
|
|
||||||
============
|
|
||||||
|
|
||||||
The Subversion path implementations are based
|
|
||||||
on the `svn` command line, not on the bindings.
|
|
||||||
It makes sense now to directly use the bindings.
|
|
||||||
|
|
||||||
Moreover, it would be good, also considering
|
|
||||||
`execnet`_ distribution of programs, to
|
|
||||||
be able to manipulate Windows Paths on Linux
|
|
||||||
and vice versa. So we'd like to consider
|
|
||||||
refactoring the path implementations
|
|
||||||
to provide this choice (and getting rid
|
|
||||||
of platform-dependencies as much as possible).
|
|
||||||
|
|
||||||
There is some experimental small approach
|
|
||||||
(``py/path/gateway/``) aiming at having
|
|
||||||
a convenient Remote Path implementation.
|
|
||||||
|
|
||||||
There are various hacks out there to have
|
|
||||||
Memory-Filesystems and even path objects
|
|
||||||
being directly mountable under Linux (via `fuse`).
|
|
||||||
However, the Path object implementations
|
|
||||||
do not internally have a clean abstraction
|
|
||||||
of going to the filesystem - so with some
|
|
||||||
refactoring it should become easier to
|
|
||||||
have very custom Path objects, still offering
|
|
||||||
the quite full interface without requiring
|
|
||||||
to know about all details of the full path
|
|
||||||
implementation.
|
|
||||||
|
|
||||||
.. _`execnet`: execnet.html
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
helpers for asserting deprecation and other warnings.
|
||||||
|
=====================================================
|
||||||
|
|
||||||
|
|
||||||
|
recwarn function argument
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
You can use the ``recwarn`` funcarg to assert that code triggers
|
||||||
|
warnings through the Python warnings system. Here is a simple
|
||||||
|
self-contained test::
|
||||||
|
|
||||||
|
# content of test_recwarn.py
|
||||||
|
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
|
||||||
|
|
||||||
|
The ``recwarn`` function argument provides these methods:
|
||||||
|
|
||||||
|
* ``pop(category=None)``: return last warning matching the category.
|
||||||
|
* ``clear()``: clear list of warnings
|
||||||
|
|
||||||
|
ensuring a function triggers a deprecation warning
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
You can also call a global helper for checking
|
||||||
|
that a certain function call triggers a Deprecation
|
||||||
|
warning::
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
def test_global():
|
||||||
|
py.test.deprecated_call(myfunction, 17)
|
||||||
|
|
|
@ -2,21 +2,18 @@
|
||||||
advanced skipping for python test functions, classes or modules.
|
advanced skipping for python test functions, classes or modules.
|
||||||
================================================================
|
================================================================
|
||||||
|
|
||||||
|
You can mark test functions for a conditional *skip* or as *xfail*,
|
||||||
|
expected-to-fail. Skipping a test avoids running a test.
|
||||||
|
Whereas an xfail-marked test usually is run but if it fails it is
|
||||||
|
not reported in detail and counted separately. The latter allows
|
||||||
|
to keep track of real implementation problems whereas test skips
|
||||||
|
are normally tied to a condition, such as a platform or dependency
|
||||||
|
requirement without which considering or running the test does
|
||||||
|
not make sense. If a test fails under all conditions then it's
|
||||||
|
probably best to mark your test as 'xfail'.
|
||||||
|
|
||||||
.. contents::
|
By running ``py.test -rxs`` you will see extra reporting
|
||||||
:local:
|
information on skips and xfail-run tests at the end of a test run.
|
||||||
|
|
||||||
With this plugin you can mark test functions for conditional skipping
|
|
||||||
or as "xfail", expected-to-fail. Skipping a test will avoid running it
|
|
||||||
while xfail-marked tests will run and result in an inverted outcome:
|
|
||||||
a pass becomes a failure and a fail becomes a semi-passing one.
|
|
||||||
|
|
||||||
The need for skipping a test is usually connected to a condition.
|
|
||||||
If a test fails under all conditions then it's probably better
|
|
||||||
to mark your test as 'xfail'.
|
|
||||||
|
|
||||||
By passing ``-rxs`` to the terminal reporter you will see extra
|
|
||||||
summary information on skips and xfail-run tests at the end of a test run.
|
|
||||||
|
|
||||||
.. _skipif:
|
.. _skipif:
|
||||||
|
|
||||||
|
@ -53,7 +50,7 @@ at module level like this::
|
||||||
skip groups of test functions
|
skip groups of test functions
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
As with all metadata function marking you can do it at
|
As with all function :ref:`marking` you can do it at
|
||||||
`whole class- or module level`_. Here is an example
|
`whole class- or module level`_. Here is an example
|
||||||
for skipping all methods of a test class based on platform::
|
for skipping all methods of a test class based on platform::
|
||||||
|
|
||||||
|
@ -112,6 +109,12 @@ To specify an explicit reason to be shown with xfailure detail::
|
||||||
|
|
||||||
@py.test.mark.xfail(..., reason="my reason")
|
@py.test.mark.xfail(..., reason="my reason")
|
||||||
|
|
||||||
|
By specifying on the commandline::
|
||||||
|
|
||||||
|
py.test --runxfail
|
||||||
|
|
||||||
|
you can force the running and reporting of a runnable ``xfail`` marked test.
|
||||||
|
|
||||||
imperative xfail from within a test or setup function
|
imperative xfail from within a test or setup function
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
|
@ -151,21 +154,3 @@ within test or setup code. Example::
|
||||||
if not valid_config():
|
if not valid_config():
|
||||||
py.test.skip("unsuppored configuration")
|
py.test.skip("unsuppored configuration")
|
||||||
|
|
||||||
command line options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
|
|
||||||
``--runxfail``
|
|
||||||
run tests even if they are marked xfail
|
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
1. Download `pytest_skipping.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_skipping.py`` into your import path
|
|
||||||
3. a subsequent ``py.test`` run will use your local version
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
1044
doc/style.css
1044
doc/style.css
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
==========================
|
|
||||||
Talks and Tutorials
|
Talks and Tutorials
|
||||||
==========================
|
==========================
|
||||||
|
|
|
@ -1,553 +0,0 @@
|
||||||
==============================================================
|
|
||||||
**funcargs**: advanced test fixtures and parametrization
|
|
||||||
==============================================================
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
:depth: 2
|
|
||||||
|
|
||||||
what is a "funcarg"?
|
|
||||||
=================================================
|
|
||||||
|
|
||||||
A *funcarg* is the short name for "test function argument". Each python test function invocation may receive one or multiple function arguments. Function argument values can be created next to the test code or in separate test configuration files which allows test functions to remain ignorant of how its base test values are created. A test function can also be called multiple times with different sets of function arguments, allowing for arbitrary parametrization. A Funcarg parameter can be any value, a simple number or an application object.
|
|
||||||
|
|
||||||
.. _`contact possibilities`: ../contact.html
|
|
||||||
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
|
|
||||||
|
|
||||||
.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
|
||||||
.. _`xUnit style`: xunit_setup.html
|
|
||||||
|
|
||||||
|
|
||||||
.. _`funcarg factory`:
|
|
||||||
.. _factory:
|
|
||||||
|
|
||||||
funcarg factories: creating test function arguments
|
|
||||||
==============================================================
|
|
||||||
|
|
||||||
Test functions can specify one ore more arguments ("funcargs")
|
|
||||||
and a test module or plugin can define factory functions that provide
|
|
||||||
the function argument. Let's look at a simple self-contained
|
|
||||||
example that you can put into a test module:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./test_simplefactory.py
|
|
||||||
def pytest_funcarg__myfuncarg(request):
|
|
||||||
return 42
|
|
||||||
|
|
||||||
def test_function(myfuncarg):
|
|
||||||
assert myfuncarg == 17
|
|
||||||
|
|
||||||
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_simplefactory.py
|
|
||||||
|
|
||||||
test_simplefactory.py F
|
|
||||||
|
|
||||||
================================ FAILURES ==================================
|
|
||||||
______________________________ test_function _______________________________
|
|
||||||
|
|
||||||
myfuncarg = 42
|
|
||||||
|
|
||||||
def test_function(myfuncarg):
|
|
||||||
> assert myfuncarg == 17
|
|
||||||
E assert 42 == 17
|
|
||||||
|
|
||||||
test_simplefactory.py:6: AssertionError
|
|
||||||
======================== 1 failed in 0.11 seconds ==========================
|
|
||||||
|
|
||||||
|
|
||||||
This means that the test function was called with a ``myfuncarg`` value
|
|
||||||
of ``42`` and the assert fails accordingly. Here is how py.test
|
|
||||||
calls the 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 factory function is discovered by looking for the
|
|
||||||
name ``pytest_funcarg__myfuncarg``.
|
|
||||||
|
|
||||||
2. ``pytest_funcarg__myfuncarg(request)`` is called and
|
|
||||||
returns the value for ``myfuncarg``.
|
|
||||||
|
|
||||||
3. ``test_function(42)`` call is executed.
|
|
||||||
|
|
||||||
Note that if you misspell a function argument or want
|
|
||||||
to use one that isn't available, you'll see an error
|
|
||||||
with a list of available function arguments. You can
|
|
||||||
also issue::
|
|
||||||
|
|
||||||
py.test --funcargs test_simplefactory.py
|
|
||||||
|
|
||||||
to see available function arguments (which you can also
|
|
||||||
think of as "resources").
|
|
||||||
|
|
||||||
Factory functions receive a `request object`_
|
|
||||||
which they can use to register setup/teardown
|
|
||||||
functions or access meta data about a test.
|
|
||||||
|
|
||||||
.. _`request object`:
|
|
||||||
|
|
||||||
funcarg factory request objects
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
Request objects represents a handle on a specific python test function call. A request object is passed to a funcarg factory and provides access to test configuration and context as well as some `useful caching and finalization helpers`_. Here is a list of attributes:
|
|
||||||
|
|
||||||
``request.function``: python function object requesting the argument
|
|
||||||
|
|
||||||
``request.cls``: class object where the test function is defined in or None.
|
|
||||||
|
|
||||||
``request.module``: module object where the test function is defined in.
|
|
||||||
|
|
||||||
``request.config``: access to command line opts and general config
|
|
||||||
|
|
||||||
``request.param``: if exists was passed by a previous `metafunc.addcall`_
|
|
||||||
|
|
||||||
.. _`useful caching and finalization helpers`:
|
|
||||||
|
|
||||||
|
|
||||||
registering funcarg related finalizers/cleanup
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def addfinalizer(func):
|
|
||||||
""" call a finalizer function when test function finishes. """
|
|
||||||
|
|
||||||
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 execution of a
|
|
||||||
test function finishes.
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def pytest_funcarg__myfile(self, request):
|
|
||||||
# ... create and open a unique per-function "myfile" object ...
|
|
||||||
request.addfinalizer(lambda: myfile.close())
|
|
||||||
return myfile
|
|
||||||
|
|
||||||
|
|
||||||
managing fixtures across test modules and test runs
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def cached_setup(setup, teardown=None, scope="module", extrakey=None):
|
|
||||||
""" cache and return result of calling setup().
|
|
||||||
|
|
||||||
The requested argument name, 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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__database(request):
|
|
||||||
return request.cached_setup(
|
|
||||||
setup=lambda: Database("..."),
|
|
||||||
teardown=lambda val: val.close(),
|
|
||||||
scope="session"
|
|
||||||
)
|
|
||||||
|
|
||||||
dynamically applying a marker
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def applymarker(self, marker):
|
|
||||||
""" apply a marker to a test function invocation.
|
|
||||||
|
|
||||||
The 'marker' must be created with py.test.mark.* XYZ.
|
|
||||||
"""
|
|
||||||
|
|
||||||
``request.applymarker(marker)`` will mark the test invocation
|
|
||||||
with the given marker. For example, if your funcarg factory provides
|
|
||||||
values which may cause a test function to fail you can call
|
|
||||||
``request.applymarker(py.test.mark.xfail(reason='flaky config'))``
|
|
||||||
and this will cause the test to not show tracebacks. See xfail_
|
|
||||||
for details.
|
|
||||||
|
|
||||||
.. _`xfail`: plugin/skipping.html#xfail
|
|
||||||
|
|
||||||
requesting values of other funcargs
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def getfuncargvalue(name):
|
|
||||||
""" Lookup and call function argument factory for the given name.
|
|
||||||
Each function argument is only created once per function setup.
|
|
||||||
"""
|
|
||||||
|
|
||||||
``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-tests`:
|
|
||||||
|
|
||||||
generating parametrized tests
|
|
||||||
===========================================================
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./test_example.py
|
|
||||||
def pytest_generate_tests(metafunc):
|
|
||||||
if "numiter" in metafunc.funcargnames:
|
|
||||||
for i in range(10):
|
|
||||||
metafunc.addcall(funcargs=dict(numiter=i))
|
|
||||||
|
|
||||||
def test_func(numiter):
|
|
||||||
assert numiter < 9
|
|
||||||
|
|
||||||
If you run this with ``py.test test_example.py`` you'll get:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
============================= test session starts ==========================
|
|
||||||
python: platform linux2 -- Python 2.6.2
|
|
||||||
test object 1: /home/hpk/hg/py/trunk/test_example.py
|
|
||||||
|
|
||||||
test_example.py .........F
|
|
||||||
|
|
||||||
================================ FAILURES ==================================
|
|
||||||
__________________________ test_func.test_func[9] __________________________
|
|
||||||
|
|
||||||
numiter = 9
|
|
||||||
|
|
||||||
def test_func(numiter):
|
|
||||||
> assert numiter < 9
|
|
||||||
E assert 9 < 9
|
|
||||||
|
|
||||||
/home/hpk/hg/py/trunk/test_example.py:10: AssertionError
|
|
||||||
|
|
||||||
|
|
||||||
Here is what happens in detail:
|
|
||||||
|
|
||||||
1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
|
|
||||||
function. It adds ten new function calls with explicit function arguments.
|
|
||||||
|
|
||||||
2. **execute tests**: ``test_func(numiter)`` is called ten times with
|
|
||||||
ten different arguments.
|
|
||||||
|
|
||||||
.. _`metafunc object`:
|
|
||||||
|
|
||||||
test generators and metafunc objects
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
metafunc objects are passed to the ``pytest_generate_tests`` hook.
|
|
||||||
They help to inspect a testfunction and to generate tests
|
|
||||||
according to test configuration or values specified
|
|
||||||
in the class or module where a test function is defined:
|
|
||||||
|
|
||||||
``metafunc.funcargnames``: set of required function arguments for given function
|
|
||||||
|
|
||||||
``metafunc.function``: underlying python test function
|
|
||||||
|
|
||||||
``metafunc.cls``: class object where the test function is defined in or None.
|
|
||||||
|
|
||||||
``metafunc.module``: the module object where the test function is defined in.
|
|
||||||
|
|
||||||
``metafunc.config``: access to command line opts and general config
|
|
||||||
|
|
||||||
|
|
||||||
.. _`metafunc.addcall`:
|
|
||||||
|
|
||||||
the ``metafunc.addcall()`` method
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def addcall(funcargs={}, id=None, param=None):
|
|
||||||
""" trigger a new test function call. """
|
|
||||||
|
|
||||||
``funcargs`` can be a dictionary of argument names
|
|
||||||
mapped to values - providing it is called *direct parametrization*.
|
|
||||||
|
|
||||||
If you provide an `id`` it will be used for reporting
|
|
||||||
and identification purposes. If you don't supply an `id`
|
|
||||||
the stringified counter of the list of added calls will be used.
|
|
||||||
``id`` values needs to be unique between all
|
|
||||||
invocations for a given test function.
|
|
||||||
|
|
||||||
``param`` if specified will be seen by any
|
|
||||||
`funcarg factory`_ as a ``request.param`` attribute.
|
|
||||||
Setting it is called *indirect parametrization*.
|
|
||||||
|
|
||||||
Indirect parametrization is preferable if test values are
|
|
||||||
expensive to setup or can only be created in certain environments.
|
|
||||||
Test generators and thus ``addcall()`` invocations are performed
|
|
||||||
during test collection which is separate from the actual test
|
|
||||||
setup and test run phase. With distributed testing collection
|
|
||||||
and test setup/run happens in different process.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _`tutorial examples`:
|
|
||||||
|
|
||||||
Tutorial Examples
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
To see how you can implement custom paramtrization schemes,
|
|
||||||
see e.g. `parametrizing tests, generalized`_ (blog post).
|
|
||||||
|
|
||||||
To enable creation of test support code that can flexibly
|
|
||||||
register setup/teardown functions see the `blog post about
|
|
||||||
the monkeypatch funcarg`_.
|
|
||||||
|
|
||||||
If you find issues or have further suggestions for improving
|
|
||||||
the mechanism you are welcome to checkout `contact possibilities`_ page.
|
|
||||||
|
|
||||||
.. _`application setup tutorial example`:
|
|
||||||
.. _appsetup:
|
|
||||||
|
|
||||||
application specific test setup and fixtures
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
Here is a basic useful step-wise example for handling application
|
|
||||||
specific test setup. The goal is to have one place where we have the
|
|
||||||
glue and test support code for bootstrapping and configuring application objects and allow
|
|
||||||
test modules and test functions to stay ignorant of involved details.
|
|
||||||
|
|
||||||
step 1: use and implement a test/app-specific "mysetup"
|
|
||||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
Let's write a simple test function living in a test file
|
|
||||||
``test_sample.py`` that uses a ``mysetup`` funcarg for accessing test
|
|
||||||
specific setup.
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./test_sample.py
|
|
||||||
def test_answer(mysetup):
|
|
||||||
app = mysetup.myapp()
|
|
||||||
answer = app.question()
|
|
||||||
assert answer == 42
|
|
||||||
|
|
||||||
To run this test py.test needs to find and call a factory to
|
|
||||||
obtain the required ``mysetup`` function argument. The test
|
|
||||||
function interacts with the provided application specific setup.
|
|
||||||
|
|
||||||
To provide the ``mysetup`` function argument we write down
|
|
||||||
a factory method in a `local plugin`_ by putting the
|
|
||||||
following code into a local ``conftest.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./conftest.py
|
|
||||||
|
|
||||||
from myapp import MyApp
|
|
||||||
|
|
||||||
def pytest_funcarg__mysetup(request):
|
|
||||||
return MySetup()
|
|
||||||
|
|
||||||
class MySetup:
|
|
||||||
def myapp(self):
|
|
||||||
return MyApp()
|
|
||||||
|
|
||||||
To run the example we represent our application by putting a pseudo MyApp object into ``myapp.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./myapp.py
|
|
||||||
class MyApp:
|
|
||||||
def question(self):
|
|
||||||
return 6 * 9
|
|
||||||
|
|
||||||
You can now run the test with ``py.test test_sample.py`` which will
|
|
||||||
show this failure:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
========================= test session starts =========================
|
|
||||||
python: platform linux2 -- Python 2.6.2
|
|
||||||
test object 1: /home/hpk/hg/py/trunk/example/funcarg/mysetup
|
|
||||||
|
|
||||||
test_sample.py F
|
|
||||||
|
|
||||||
============================== FAILURES ===============================
|
|
||||||
_____________________________ test_answer _____________________________
|
|
||||||
|
|
||||||
mysetup = <mysetup.conftest.MySetup instance at 0xa020eac>
|
|
||||||
|
|
||||||
def test_answer(mysetup):
|
|
||||||
app = mysetup.myapp()
|
|
||||||
answer = app.question()
|
|
||||||
> assert answer == 42
|
|
||||||
E assert 54 == 42
|
|
||||||
|
|
||||||
test_sample.py:5: AssertionError
|
|
||||||
====================== 1 failed in 0.11 seconds =======================
|
|
||||||
|
|
||||||
This means that our ``mysetup`` object was successfully instantiated,
|
|
||||||
we asked it to provide an application instance and checking
|
|
||||||
its ``question`` method resulted in the wrong answer. If you are
|
|
||||||
confused as to what the concrete question or answers actually mean,
|
|
||||||
please see here_ :) Otherwise proceed to step 2.
|
|
||||||
|
|
||||||
.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy
|
|
||||||
.. _`local plugin`: customize.html#local-plugin
|
|
||||||
|
|
||||||
.. _`tut-cmdlineoption`:
|
|
||||||
|
|
||||||
step 2: adding a command line option
|
|
||||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
|
|
||||||
If you provide a "funcarg" from a plugin you can easily make methods
|
|
||||||
depend on command line options or environment settings.
|
|
||||||
To add a command line option we update the conftest.py of
|
|
||||||
the previous example to add a command line option
|
|
||||||
and to offer a new mysetup method:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./conftest.py
|
|
||||||
import py
|
|
||||||
from myapp import MyApp
|
|
||||||
|
|
||||||
def pytest_funcarg__mysetup(request):
|
|
||||||
return MySetup(request)
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
parser.addoption("--ssh", action="store", default=None,
|
|
||||||
help="specify ssh host to run tests with")
|
|
||||||
|
|
||||||
|
|
||||||
class MySetup:
|
|
||||||
def __init__(self, request):
|
|
||||||
self.config = request.config
|
|
||||||
|
|
||||||
def myapp(self):
|
|
||||||
return MyApp()
|
|
||||||
|
|
||||||
def getsshconnection(self):
|
|
||||||
host = self.config.option.ssh
|
|
||||||
if host is None:
|
|
||||||
py.test.skip("specify ssh host with --ssh")
|
|
||||||
return execnet.SshGateway(host)
|
|
||||||
|
|
||||||
|
|
||||||
Now any test function can use the ``mysetup.getsshconnection()`` method like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./test_ssh.py
|
|
||||||
class TestClass:
|
|
||||||
def test_function(self, mysetup):
|
|
||||||
conn = mysetup.getsshconnection()
|
|
||||||
# work with conn
|
|
||||||
|
|
||||||
Running ``py.test test_ssh.py`` without specifying a command line option will result in a skipped test_function:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
========================= test session starts =========================
|
|
||||||
python: platform linux2 -- Python 2.6.2
|
|
||||||
test object 1: test_ssh.py
|
|
||||||
|
|
||||||
test_ssh.py s
|
|
||||||
|
|
||||||
________________________ skipped test summary _________________________
|
|
||||||
conftest.py:23: [1] Skipped: 'specify ssh host with --ssh'
|
|
||||||
====================== 1 skipped in 0.11 seconds ======================
|
|
||||||
|
|
||||||
Note especially how the test function could stay clear knowing about how to construct test state values or when to skip and with what message. The test function can concentrate on actual test code and test state factories can interact with execution of tests.
|
|
||||||
|
|
||||||
If you specify a command line option like ``py.test --ssh=python.org`` the test will get un-skipped and actually execute.
|
|
||||||
|
|
||||||
.. _`accept example`:
|
|
||||||
|
|
||||||
example: specifying and selecting acceptance tests
|
|
||||||
--------------------------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./conftest.py
|
|
||||||
def pytest_option(parser):
|
|
||||||
group = parser.getgroup("myproject")
|
|
||||||
group.addoption("-A", dest="acceptance", action="store_true",
|
|
||||||
help="run (slow) acceptance tests")
|
|
||||||
|
|
||||||
def pytest_funcarg__accept(request):
|
|
||||||
return AcceptFuncarg(request)
|
|
||||||
|
|
||||||
class AcceptFuncarg:
|
|
||||||
def __init__(self, request):
|
|
||||||
if not request.config.option.acceptance:
|
|
||||||
py.test.skip("specify -A to run acceptance tests")
|
|
||||||
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
|
|
||||||
|
|
||||||
def run(self, cmd):
|
|
||||||
""" called by test code to execute an acceptance test. """
|
|
||||||
self.tmpdir.chdir()
|
|
||||||
return py.process.cmdexec(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
and the actual test function example:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_some_acceptance_aspect(accept):
|
|
||||||
accept.tmpdir.mkdir("somesub")
|
|
||||||
result = accept.run("ls -la")
|
|
||||||
assert "somesub" in result
|
|
||||||
|
|
||||||
If you run this test without specifying a command line option
|
|
||||||
the test will get skipped with an appropriate message. Otherwise
|
|
||||||
you can start to add convenience and test support methods
|
|
||||||
to your AcceptFuncarg and drive running of tools or
|
|
||||||
applications and provide ways to do assertions about
|
|
||||||
the output.
|
|
||||||
|
|
||||||
.. _`decorate a funcarg`:
|
|
||||||
|
|
||||||
example: decorating a funcarg in a test module
|
|
||||||
--------------------------------------------------------------
|
|
||||||
|
|
||||||
For larger scale setups it's sometimes useful to decorare
|
|
||||||
a funcarg just for a particular test module. We can
|
|
||||||
extend the `accept example`_ by putting this in our test module:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def pytest_funcarg__accept(request):
|
|
||||||
# call the next factory (living in our conftest.py)
|
|
||||||
arg = request.getfuncargvalue("accept")
|
|
||||||
# create a special layout in our tempdir
|
|
||||||
arg.tmpdir.mkdir("special")
|
|
||||||
return arg
|
|
||||||
|
|
||||||
class TestSpecialAcceptance:
|
|
||||||
def test_sometest(self, accept):
|
|
||||||
assert accept.tmpdir.join("special").check()
|
|
||||||
|
|
||||||
Our module level factory will be invoked first and it can
|
|
||||||
ask its request object to call the next factory and then
|
|
||||||
decorate its result. This mechanism allows us to stay
|
|
||||||
ignorant of how/where the function argument is provided -
|
|
||||||
in our example from a `conftest plugin`_.
|
|
||||||
|
|
||||||
sidenote: the temporary directory used here are instances of
|
|
||||||
the `py.path.local`_ class which provides many of the os.path
|
|
||||||
methods in a convenient way.
|
|
||||||
|
|
||||||
.. _`py.path.local`: ../path.html#local
|
|
||||||
.. _`conftest plugin`: customize.html#conftestplugin
|
|
|
@ -1,132 +0,0 @@
|
||||||
|
|
||||||
configurable per-test stdout/stderr capturing mechanisms.
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
|
|
||||||
This plugin captures stdout/stderr output for each test separately.
|
|
||||||
In case of test failures this captured output is shown grouped
|
|
||||||
togtther with the test.
|
|
||||||
|
|
||||||
The plugin also provides test function arguments that help to
|
|
||||||
assert stdout/stderr output from within your tests, see the
|
|
||||||
`funcarg example`_.
|
|
||||||
|
|
||||||
|
|
||||||
Capturing of input/output streams during tests
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
By default ``sys.stdout`` and ``sys.stderr`` are substituted with
|
|
||||||
temporary streams during the execution of tests and setup/teardown code.
|
|
||||||
During the whole testing process it will re-use the same temporary
|
|
||||||
streams allowing to play well with the logging module which easily
|
|
||||||
takes ownership on these streams.
|
|
||||||
|
|
||||||
Also, 'sys.stdin' is substituted with a file-like "null" object that
|
|
||||||
does not return any values. This is to immediately error out
|
|
||||||
on tests that wait on reading something from stdin.
|
|
||||||
|
|
||||||
You can influence output capturing mechanisms from the command line::
|
|
||||||
|
|
||||||
py.test -s # disable all capturing
|
|
||||||
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
|
|
||||||
py.test --capture=fd # point filedescriptors 1 and 2 to temp file
|
|
||||||
|
|
||||||
If you set capturing values in a conftest file like this::
|
|
||||||
|
|
||||||
# conftest.py
|
|
||||||
option_capture = 'fd'
|
|
||||||
|
|
||||||
then all tests in that directory will execute with "fd" style capturing.
|
|
||||||
|
|
||||||
sys-level capturing
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr``
|
|
||||||
will be replaced with in-memory files (``py.io.TextIO`` to be precise)
|
|
||||||
that capture writes and decode non-unicode strings to a unicode object
|
|
||||||
(using a default, usually, UTF-8, encoding).
|
|
||||||
|
|
||||||
FD-level capturing and subprocesses
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
The ``fd`` based method means that writes going to system level files
|
|
||||||
based on the standard file descriptors will be captured, for example
|
|
||||||
writes such as ``os.write(1, 'hello')`` will be captured properly.
|
|
||||||
Capturing on fd-level will include output generated from
|
|
||||||
any subprocesses created during a test.
|
|
||||||
|
|
||||||
.. _`funcarg example`:
|
|
||||||
|
|
||||||
Example Usage of the capturing Function arguments
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
You can use the `capsys funcarg`_ and `capfd funcarg`_ to
|
|
||||||
capture writes to stdout and stderr streams. Using the
|
|
||||||
funcargs frees your test from having to care about setting/resetting
|
|
||||||
the old streams and also interacts well with py.test's own
|
|
||||||
per-test capturing. Here is an example test function:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_myoutput(capsys):
|
|
||||||
print ("hello")
|
|
||||||
sys.stderr.write("world\n")
|
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert out == "hello\n"
|
|
||||||
assert err == "world\n"
|
|
||||||
print "next"
|
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert out == "next\n"
|
|
||||||
|
|
||||||
The ``readouterr()`` call snapshots the output so far -
|
|
||||||
and capturing will be continued. After the test
|
|
||||||
function finishes the original streams will
|
|
||||||
be restored. If you want to capture on
|
|
||||||
the filedescriptor level you can use the ``capfd`` function
|
|
||||||
argument which offers the same interface.
|
|
||||||
|
|
||||||
.. _`capsys funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'capsys' test function argument
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
captures writes to sys.stdout/sys.stderr and makes
|
|
||||||
them available successively via a ``capsys.readouterr()`` method
|
|
||||||
which returns a ``(out, err)`` tuple of captured snapshot strings.
|
|
||||||
|
|
||||||
.. _`capfd funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'capfd' test function argument
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
captures writes to file descriptors 1 and 2 and makes
|
|
||||||
snapshotted ``(out, err)`` string tuples available
|
|
||||||
via the ``capsys.readouterr()`` method. If the underlying
|
|
||||||
platform does not have ``os.dup`` (e.g. Jython) tests using
|
|
||||||
this funcarg will automatically skip.
|
|
||||||
|
|
||||||
command line options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
|
|
||||||
``--capture=method``
|
|
||||||
per-test capturing method: one of fd (default)|sys|no.
|
|
||||||
``-s``
|
|
||||||
shortcut for --capture=no.
|
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
1. Download `pytest_capture.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_capture.py`` into your import path
|
|
||||||
3. a subsequent ``py.test`` run will use your local version
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
|
@ -1,48 +0,0 @@
|
||||||
|
|
||||||
collect and execute doctests from modules and test files.
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-------------
|
|
||||||
|
|
||||||
By default all files matching the ``test*.txt`` pattern will
|
|
||||||
be run through the python standard ``doctest`` module. Issue::
|
|
||||||
|
|
||||||
py.test --doctest-glob='*.rst'
|
|
||||||
|
|
||||||
to change the pattern. Additionally you can trigger running of
|
|
||||||
tests in all python modules (including regular python test modules)::
|
|
||||||
|
|
||||||
py.test --doctest-modules
|
|
||||||
|
|
||||||
You can also make these changes permanent in your project by
|
|
||||||
putting them into a conftest.py file like this::
|
|
||||||
|
|
||||||
# content of conftest.py
|
|
||||||
option_doctestmodules = True
|
|
||||||
option_doctestglob = "*.rst"
|
|
||||||
|
|
||||||
command line options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
|
|
||||||
``--doctest-modules``
|
|
||||||
run doctests in all .py modules
|
|
||||||
``--doctest-glob=pat``
|
|
||||||
doctests file matching pattern, default: test*.txt
|
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
|
@ -1,92 +0,0 @@
|
||||||
|
|
||||||
safely patch object attributes, dicts and environment variables.
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
|
|
||||||
.. contents::
|
|
||||||
:local:
|
|
||||||
|
|
||||||
Usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Use the `monkeypatch funcarg`_ to tweak your global test environment
|
|
||||||
for running a particular test. You can safely set/del an attribute,
|
|
||||||
dictionary item or environment variable by respective methods
|
|
||||||
on the monkeypatch funcarg. If you want e.g. to set an ENV1 variable
|
|
||||||
and have os.path.expanduser return a particular directory, you can
|
|
||||||
write it down like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_mytest(monkeypatch):
|
|
||||||
monkeypatch.setenv('ENV1', 'myval')
|
|
||||||
monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz')
|
|
||||||
... # your test code that uses those patched values implicitely
|
|
||||||
|
|
||||||
After the test function finished all modifications will be undone,
|
|
||||||
because the ``monkeypatch.undo()`` method is registered as a finalizer.
|
|
||||||
|
|
||||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
|
||||||
by default raise an Exception if the target does not exist.
|
|
||||||
Pass ``raising=False`` if you want to skip this check.
|
|
||||||
|
|
||||||
prepending to PATH or other environment variables
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
To prepend a value to an already existing environment parameter:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_mypath_finding(monkeypatch):
|
|
||||||
monkeypatch.setenv('PATH', 'x/y', prepend=":")
|
|
||||||
# in bash language: export PATH=x/y:$PATH
|
|
||||||
|
|
||||||
calling "undo" finalization explicitely
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
At the end of function execution py.test invokes
|
|
||||||
a teardown hook which undoes all monkeypatch changes.
|
|
||||||
If you do not want to wait that long you can call
|
|
||||||
finalization explicitely::
|
|
||||||
|
|
||||||
monkeypatch.undo()
|
|
||||||
|
|
||||||
This will undo previous changes. This call consumes the
|
|
||||||
undo stack. Calling it a second time has no effect unless
|
|
||||||
you start monkeypatching after the undo call.
|
|
||||||
|
|
||||||
.. _`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 these
|
|
||||||
helper methods to modify objects, dictionaries or os.environ::
|
|
||||||
|
|
||||||
monkeypatch.setattr(obj, name, value, raising=True)
|
|
||||||
monkeypatch.delattr(obj, name, raising=True)
|
|
||||||
monkeypatch.setitem(mapping, name, value)
|
|
||||||
monkeypatch.delitem(obj, name, raising=True)
|
|
||||||
monkeypatch.setenv(name, value, prepend=False)
|
|
||||||
monkeypatch.delenv(name, value, raising=True)
|
|
||||||
monkeypatch.syspath_prepend(path)
|
|
||||||
|
|
||||||
All modifications will be undone when the requesting
|
|
||||||
test function finished its execution. The ``raising``
|
|
||||||
parameter determines if a KeyError or AttributeError
|
|
||||||
will be raised if the set/deletion operation has no target.
|
|
||||||
|
|
||||||
Start improving this plugin in 30 seconds
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
|
@ -1,58 +0,0 @@
|
||||||
|
|
||||||
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
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Checkout customize_, other plugins_ or `get in contact`_.
|
|
||||||
|
|
||||||
.. include:: links.txt
|
|
|
@ -1,81 +0,0 @@
|
||||||
====================================
|
|
||||||
extended xUnit style setup
|
|
||||||
====================================
|
|
||||||
|
|
||||||
.. _`funcargs`: funcargs.html
|
|
||||||
.. _`test parametrization`: funcargs.html#parametrizing-tests
|
|
||||||
.. _`unittest plugin`: plugin/unittest.html
|
|
||||||
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
|
||||||
|
|
||||||
Note:
|
|
||||||
|
|
||||||
Since version 1.0 funcargs_ present the new and
|
|
||||||
more powerful way to manage test setups with larger
|
|
||||||
test suites. *funcargs* also provide flexible
|
|
||||||
`test parametrization`_ which goes way beyond
|
|
||||||
what you can do with the xUnit setup/teardown-method
|
|
||||||
patter.
|
|
||||||
|
|
||||||
Python, Java and many other languages have a tradition
|
|
||||||
of using xUnit_ style testing. This typically
|
|
||||||
involves the call of a ``setup`` method before
|
|
||||||
a test function is run and ``teardown`` after
|
|
||||||
it finishes. With ``py.test`` there are three
|
|
||||||
scopes for which you can provide setup/teardown
|
|
||||||
hooks to provide test fixtures: per-module, per-class
|
|
||||||
and per-method/function. ``py.test`` will
|
|
||||||
discover and call according methods automatically.
|
|
||||||
|
|
||||||
The `unittest plugin`_ also will intregate ``unittest.TestCase``
|
|
||||||
instances into a test run and call respective setup/teardown methods.
|
|
||||||
|
|
||||||
All setup/teardown methods are optional.
|
|
||||||
|
|
||||||
The following methods are called at module level if they exist:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def setup_module(module):
|
|
||||||
""" setup up any state specific to the execution
|
|
||||||
of the given module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def teardown_module(module):
|
|
||||||
""" teardown any state that was previously setup
|
|
||||||
with a setup_module method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
The following hooks are available for test classes:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def setup_class(cls):
|
|
||||||
""" setup up any state specific to the execution
|
|
||||||
of the given class (which usually contains tests).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def teardown_class(cls):
|
|
||||||
""" teardown any state that was previously setup
|
|
||||||
with a call to setup_class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setup_method(self, method):
|
|
||||||
""" setup up any state tied to the execution of the given
|
|
||||||
method in a class. setup_method is invoked for every
|
|
||||||
test method of a class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def teardown_method(self, method):
|
|
||||||
""" teardown any state that was previously setup
|
|
||||||
with a setup_method call.
|
|
||||||
"""
|
|
||||||
|
|
||||||
The last two hooks, ``setup_method`` and ``teardown_method``, are
|
|
||||||
equivalent to ``setUp`` and ``tearDown`` in the Python standard
|
|
||||||
library's `unittest.py module`_.
|
|
||||||
|
|
||||||
Note that it possible that setup/teardown pairs are invoked multiple
|
|
||||||
times per testing process.
|
|
||||||
|
|
||||||
.. _`unittest.py module`: http://docs.python.org/library/unittest.html
|
|
||||||
|
|
168
doc/xml.txt
168
doc/xml.txt
|
@ -1,168 +0,0 @@
|
||||||
====================================================
|
|
||||||
py.xml: Lightweight and flexible xml/html generation
|
|
||||||
====================================================
|
|
||||||
|
|
||||||
|
|
||||||
Motivation
|
|
||||||
==========
|
|
||||||
|
|
||||||
There are a plethora of frameworks and libraries to generate
|
|
||||||
xml and html trees. However, many of them are large, have a
|
|
||||||
steep learning curve and are often hard to debug. Not to
|
|
||||||
speak of the fact that they are frameworks to begin with.
|
|
||||||
|
|
||||||
The py lib strives to offer enough functionality to represent
|
|
||||||
itself and especially its API in html or xml.
|
|
||||||
|
|
||||||
.. _xist: http://www.livinglogic.de/Python/xist/index.html
|
|
||||||
|
|
||||||
a pythonic object model , please
|
|
||||||
================================
|
|
||||||
|
|
||||||
The py lib offers a pythonic way to generate xml/html, based on
|
|
||||||
ideas from xist_ which `uses python class objects`_ to build
|
|
||||||
xml trees. However, xist_'s implementation is somewhat heavy
|
|
||||||
because it has additional goals like transformations and
|
|
||||||
supporting many namespaces. But its basic idea is very easy.
|
|
||||||
|
|
||||||
.. _`uses python class objects`: http://www.livinglogic.de/Python/xist/Howto.html
|
|
||||||
|
|
||||||
generating arbitrary xml structures
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
With ``py.xml.Namespace`` you have the basis
|
|
||||||
to generate custom xml-fragments on the fly::
|
|
||||||
|
|
||||||
class ns(py.xml.Namespace):
|
|
||||||
"my custom xml namespace"
|
|
||||||
doc = ns.books(
|
|
||||||
ns.book(
|
|
||||||
ns.author("May Day"),
|
|
||||||
ns.title("python for java programmers"),),
|
|
||||||
ns.book(
|
|
||||||
ns.author("why"),
|
|
||||||
ns.title("Java for Python programmers"),),
|
|
||||||
publisher="N.N",
|
|
||||||
)
|
|
||||||
print doc.unicode(indent=2).encode('utf8')
|
|
||||||
|
|
||||||
will give you this representation::
|
|
||||||
|
|
||||||
<books publisher="N.N">
|
|
||||||
<book>
|
|
||||||
<author>May Day</author>
|
|
||||||
<title>python for java programmers</title></book>
|
|
||||||
<book>
|
|
||||||
<author>why</author>
|
|
||||||
<title>Java for Python programmers</title></book></books>
|
|
||||||
|
|
||||||
In a sentence: positional arguments are child-tags and
|
|
||||||
keyword-arguments are attributes.
|
|
||||||
|
|
||||||
On a side note, you'll see that the unicode-serializer
|
|
||||||
supports a nice indentation style which keeps your generated
|
|
||||||
html readable, basically through emulating python's white
|
|
||||||
space significance by putting closing-tags rightmost and
|
|
||||||
almost invisible at first glance :-)
|
|
||||||
|
|
||||||
basic example for generating html
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
Consider this example::
|
|
||||||
|
|
||||||
from py.xml import html # html namespace
|
|
||||||
|
|
||||||
paras = "First Para", "Second para"
|
|
||||||
|
|
||||||
doc = html.html(
|
|
||||||
html.head(
|
|
||||||
html.meta(name="Content-Type", value="text/html; charset=latin1")),
|
|
||||||
html.body(
|
|
||||||
[html.p(p) for p in paras]))
|
|
||||||
|
|
||||||
print unicode(doc).encode('latin1')
|
|
||||||
|
|
||||||
Again, tags are objects which contain tags and have attributes.
|
|
||||||
More exactly, Tags inherit from the list type and thus can be
|
|
||||||
manipulated as list objects. They additionally support a default
|
|
||||||
way to represent themselves as a serialized unicode object.
|
|
||||||
|
|
||||||
If you happen to look at the py.xml implementation you'll
|
|
||||||
note that the tag/namespace implementation consumes some 50 lines
|
|
||||||
with another 50 lines for the unicode serialization code.
|
|
||||||
|
|
||||||
CSS-styling your html Tags
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
One aspect where many of the huge python xml/html generation
|
|
||||||
frameworks utterly fail is a clean and convenient integration
|
|
||||||
of CSS styling. Often, developers are left alone with keeping
|
|
||||||
CSS style definitions in sync with some style files
|
|
||||||
represented as strings (often in a separate .css file). Not
|
|
||||||
only is this hard to debug but the missing abstractions make
|
|
||||||
it hard to modify the styling of your tags or to choose custom
|
|
||||||
style representations (inline, html.head or external). Add the
|
|
||||||
Browers usual tolerance of messyness and errors in Style
|
|
||||||
references and welcome to hell, known as the domain of
|
|
||||||
developing web applications :-)
|
|
||||||
|
|
||||||
By contrast, consider this CSS styling example::
|
|
||||||
|
|
||||||
class my(html):
|
|
||||||
"my initial custom style"
|
|
||||||
class body(html.body):
|
|
||||||
style = html.Style(font_size = "120%")
|
|
||||||
|
|
||||||
class h2(html.h2):
|
|
||||||
style = html.Style(background = "grey")
|
|
||||||
|
|
||||||
class p(html.p):
|
|
||||||
style = html.Style(font_weight="bold")
|
|
||||||
|
|
||||||
doc = my.html(
|
|
||||||
my.head(),
|
|
||||||
my.body(
|
|
||||||
my.h2("hello world"),
|
|
||||||
my.p("bold as bold can")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
print doc.unicode(indent=2)
|
|
||||||
|
|
||||||
This will give you a small'n mean self contained
|
|
||||||
represenation by default::
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head/>
|
|
||||||
<body style="font-size: 120%">
|
|
||||||
<h2 style="background: grey">hello world</h2>
|
|
||||||
<p style="font-weight: bold">bold as bold can</p></body></html>
|
|
||||||
|
|
||||||
Most importantly, note that the inline-styling is just an
|
|
||||||
implementation detail of the unicode serialization code.
|
|
||||||
You can easily modify the serialization to put your styling into the
|
|
||||||
``html.head`` or in a separate file and autogenerate CSS-class
|
|
||||||
names or ids.
|
|
||||||
|
|
||||||
Hey, you could even write tests that you are using correct
|
|
||||||
styles suitable for specific browser requirements. Did i mention
|
|
||||||
that the ability to easily write tests for your generated
|
|
||||||
html and its serialization could help to develop _stable_ user
|
|
||||||
interfaces?
|
|
||||||
|
|
||||||
More to come ...
|
|
||||||
----------------
|
|
||||||
|
|
||||||
For now, i don't think we should strive to offer much more
|
|
||||||
than the above. However, it is probably not hard to offer
|
|
||||||
*partial serialization* to allow generating maybe hundreds of
|
|
||||||
complex html documents per second. Basically we would allow
|
|
||||||
putting callables both as Tag content and as values of
|
|
||||||
attributes. A slightly more advanced Serialization would then
|
|
||||||
produce a list of unicode objects intermingled with callables.
|
|
||||||
At HTTP-Request time the callables would get called to
|
|
||||||
complete the probably request-specific serialization of
|
|
||||||
your Tags. Hum, it's probably harder to explain this than to
|
|
||||||
actually code it :-)
|
|
||||||
|
|
||||||
.. _`py.test`: test/index.html
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
====================================
|
||||||
|
extended xUnit style setup fixtures
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. _`funcargs`: funcargs.html
|
||||||
|
.. _`test parametrization`: funcargs.html#parametrizing-tests
|
||||||
|
.. _`unittest plugin`: plugin/unittest.html
|
||||||
|
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
||||||
|
|
||||||
|
Python, Java and many other languages support xUnit_ style testing.
|
||||||
|
This typically involves the call of a ``setup`` ("fixture") method
|
||||||
|
before running a test function and ``teardown`` after it has finished.
|
||||||
|
``py.test`` supports a more fine-grained model of setup/teardown
|
||||||
|
handling by optionally calling per-module and per-class hooks.
|
||||||
|
|
||||||
|
|
||||||
|
module level setup/teardown
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
If you have multiple test functions and test classes in a single
|
||||||
|
module you can optionally implement the following fixture methods
|
||||||
|
which will usually be called once for all the functions::
|
||||||
|
|
||||||
|
def setup_module(module):
|
||||||
|
""" setup up any state specific to the execution
|
||||||
|
of the given module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def teardown_module(module):
|
||||||
|
""" teardown any state that was previously setup
|
||||||
|
with a setup_module method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class level setup/teardown
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Similarly, the following methods are called at class level before
|
||||||
|
and after all test methods of the class are called::
|
||||||
|
|
||||||
|
def setup_class(cls):
|
||||||
|
""" setup up any state specific to the execution
|
||||||
|
of the given class (which usually contains tests).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def teardown_class(cls):
|
||||||
|
""" teardown any state that was previously setup
|
||||||
|
with a call to setup_class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
method and function level setup/teardown
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Similarly, the following methods are called around each method invocation::
|
||||||
|
|
||||||
|
def setup_method(self, method):
|
||||||
|
""" setup up any state tied to the execution of the given
|
||||||
|
method in a class. setup_method is invoked for every
|
||||||
|
test method of a class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def teardown_method(self, method):
|
||||||
|
""" teardown any state that was previously setup
|
||||||
|
with a setup_method call.
|
||||||
|
"""
|
||||||
|
|
||||||
|
If you rather define test functions directly at module level
|
||||||
|
you can also use the following functions to implement fixtures::
|
||||||
|
|
||||||
|
def setup_function(function):
|
||||||
|
""" setup up any state tied to the execution of the given
|
||||||
|
function. Invoked for every test function in the module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def teardown_method(function):
|
||||||
|
""" teardown any state that was previously setup
|
||||||
|
with a setup_function call.
|
||||||
|
|
||||||
|
Note that it possible that setup/teardown pairs are invoked multiple
|
||||||
|
times per testing process.
|
||||||
|
|
||||||
|
.. _`unittest.py module`: http://docs.python.org/library/unittest.html
|
||||||
|
|
|
@ -1,87 +1,5 @@
|
||||||
"""
|
""" plugin for configurable per-test stdout/stderr capturing mechanisms and
|
||||||
configurable per-test stdout/stderr capturing mechanisms.
|
``capsys`` and ``capfd`` function arguments.
|
||||||
|
|
||||||
This plugin captures stdout/stderr output for each test separately.
|
|
||||||
In case of test failures this captured output is shown grouped
|
|
||||||
togtther with the test.
|
|
||||||
|
|
||||||
The plugin also provides test function arguments that help to
|
|
||||||
assert stdout/stderr output from within your tests, see the
|
|
||||||
`funcarg example`_.
|
|
||||||
|
|
||||||
|
|
||||||
Capturing of input/output streams during tests
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
By default ``sys.stdout`` and ``sys.stderr`` are substituted with
|
|
||||||
temporary streams during the execution of tests and setup/teardown code.
|
|
||||||
During the whole testing process it will re-use the same temporary
|
|
||||||
streams allowing to play well with the logging module which easily
|
|
||||||
takes ownership on these streams.
|
|
||||||
|
|
||||||
Also, 'sys.stdin' is substituted with a file-like "null" object that
|
|
||||||
does not return any values. This is to immediately error out
|
|
||||||
on tests that wait on reading something from stdin.
|
|
||||||
|
|
||||||
You can influence output capturing mechanisms from the command line::
|
|
||||||
|
|
||||||
py.test -s # disable all capturing
|
|
||||||
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
|
|
||||||
py.test --capture=fd # point filedescriptors 1 and 2 to temp file
|
|
||||||
|
|
||||||
If you set capturing values in a conftest file like this::
|
|
||||||
|
|
||||||
# conftest.py
|
|
||||||
option_capture = 'fd'
|
|
||||||
|
|
||||||
then all tests in that directory will execute with "fd" style capturing.
|
|
||||||
|
|
||||||
sys-level capturing
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr``
|
|
||||||
will be replaced with in-memory files (``py.io.TextIO`` to be precise)
|
|
||||||
that capture writes and decode non-unicode strings to a unicode object
|
|
||||||
(using a default, usually, UTF-8, encoding).
|
|
||||||
|
|
||||||
FD-level capturing and subprocesses
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
The ``fd`` based method means that writes going to system level files
|
|
||||||
based on the standard file descriptors will be captured, for example
|
|
||||||
writes such as ``os.write(1, 'hello')`` will be captured properly.
|
|
||||||
Capturing on fd-level will include output generated from
|
|
||||||
any subprocesses created during a test.
|
|
||||||
|
|
||||||
.. _`funcarg example`:
|
|
||||||
|
|
||||||
Example Usage of the capturing Function arguments
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
You can use the `capsys funcarg`_ and `capfd funcarg`_ to
|
|
||||||
capture writes to stdout and stderr streams. Using the
|
|
||||||
funcargs frees your test from having to care about setting/resetting
|
|
||||||
the old streams and also interacts well with py.test's own
|
|
||||||
per-test capturing. Here is an example test function:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_myoutput(capsys):
|
|
||||||
print ("hello")
|
|
||||||
sys.stderr.write("world\\n")
|
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert out == "hello\\n"
|
|
||||||
assert err == "world\\n"
|
|
||||||
print "next"
|
|
||||||
out, err = capsys.readouterr()
|
|
||||||
assert out == "next\\n"
|
|
||||||
|
|
||||||
The ``readouterr()`` call snapshots the output so far -
|
|
||||||
and capturing will be continued. After the test
|
|
||||||
function finishes the original streams will
|
|
||||||
be restored. If you want to capture on
|
|
||||||
the filedescriptor level you can use the ``capfd`` function
|
|
||||||
argument which offers the same interface.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
|
@ -1,57 +1,4 @@
|
||||||
"""
|
""" monkeypatching and mocking functionality. """
|
||||||
safely patch object attributes, dicts and environment variables.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Use the `monkeypatch funcarg`_ to tweak your global test environment
|
|
||||||
for running a particular test. You can safely set/del an attribute,
|
|
||||||
dictionary item or environment variable by respective methods
|
|
||||||
on the monkeypatch funcarg. If you want e.g. to set an ENV1 variable
|
|
||||||
and have os.path.expanduser return a particular directory, you can
|
|
||||||
write it down like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_mytest(monkeypatch):
|
|
||||||
monkeypatch.setenv('ENV1', 'myval')
|
|
||||||
monkeypatch.setattr(os.path, 'expanduser', lambda x: '/tmp/xyz')
|
|
||||||
... # your test code that uses those patched values implicitely
|
|
||||||
|
|
||||||
After the test function finished all modifications will be undone,
|
|
||||||
because the ``monkeypatch.undo()`` method is registered as a finalizer.
|
|
||||||
|
|
||||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
|
||||||
by default raise an Exception if the target does not exist.
|
|
||||||
Pass ``raising=False`` if you want to skip this check.
|
|
||||||
|
|
||||||
prepending to PATH or other environment variables
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
To prepend a value to an already existing environment parameter:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_mypath_finding(monkeypatch):
|
|
||||||
monkeypatch.setenv('PATH', 'x/y', prepend=":")
|
|
||||||
# in bash language: export PATH=x/y:$PATH
|
|
||||||
|
|
||||||
calling "undo" finalization explicitely
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
At the end of function execution py.test invokes
|
|
||||||
a teardown hook which undoes all monkeypatch changes.
|
|
||||||
If you do not want to wait that long you can call
|
|
||||||
finalization explicitely::
|
|
||||||
|
|
||||||
monkeypatch.undo()
|
|
||||||
|
|
||||||
This will undo previous changes. This call consumes the
|
|
||||||
undo stack. Calling it a second time has no effect unless
|
|
||||||
you start monkeypatching after the undo call.
|
|
||||||
|
|
||||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
|
@ -72,18 +19,21 @@ def pytest_funcarg__monkeypatch(request):
|
||||||
parameter determines if a KeyError or AttributeError
|
parameter determines if a KeyError or AttributeError
|
||||||
will be raised if the set/deletion operation has no target.
|
will be raised if the set/deletion operation has no target.
|
||||||
"""
|
"""
|
||||||
monkeypatch = MonkeyPatch()
|
mpatch = monkeypatch()
|
||||||
request.addfinalizer(monkeypatch.undo)
|
request.addfinalizer(mpatch.undo)
|
||||||
return monkeypatch
|
return mpatch
|
||||||
|
|
||||||
notset = object()
|
notset = object()
|
||||||
|
|
||||||
class MonkeyPatch:
|
class monkeypatch:
|
||||||
|
""" object keeping a record of setattr/item/env/syspath changes. """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._setattr = []
|
self._setattr = []
|
||||||
self._setitem = []
|
self._setitem = []
|
||||||
|
|
||||||
def setattr(self, obj, name, value, raising=True):
|
def setattr(self, obj, name, value, raising=True):
|
||||||
|
""" set attribute ``name`` on ``obj`` to ``value``, by default
|
||||||
|
raise AttributeEror if the attribute did not exist. """
|
||||||
oldval = getattr(obj, name, notset)
|
oldval = getattr(obj, name, notset)
|
||||||
if raising and oldval is notset:
|
if raising and oldval is notset:
|
||||||
raise AttributeError("%r has no attribute %r" %(obj, name))
|
raise AttributeError("%r has no attribute %r" %(obj, name))
|
||||||
|
@ -91,6 +41,8 @@ class MonkeyPatch:
|
||||||
setattr(obj, name, value)
|
setattr(obj, name, value)
|
||||||
|
|
||||||
def delattr(self, obj, name, raising=True):
|
def delattr(self, obj, name, raising=True):
|
||||||
|
""" delete attribute ``name`` from ``obj``, by default raise
|
||||||
|
AttributeError it the attribute did not previously exist. """
|
||||||
if not hasattr(obj, name):
|
if not hasattr(obj, name):
|
||||||
if raising:
|
if raising:
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
@ -99,10 +51,12 @@ class MonkeyPatch:
|
||||||
delattr(obj, name)
|
delattr(obj, name)
|
||||||
|
|
||||||
def setitem(self, dic, name, value):
|
def setitem(self, dic, name, value):
|
||||||
|
""" set dictionary entry ``name`` to value. """
|
||||||
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
|
||||||
dic[name] = value
|
dic[name] = value
|
||||||
|
|
||||||
def delitem(self, dic, name, raising=True):
|
def delitem(self, dic, name, raising=True):
|
||||||
|
""" delete ``name`` from dict, raise KeyError if it doesn't exist."""
|
||||||
if name not in dic:
|
if name not in dic:
|
||||||
if raising:
|
if raising:
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
|
@ -111,20 +65,28 @@ class MonkeyPatch:
|
||||||
del dic[name]
|
del dic[name]
|
||||||
|
|
||||||
def setenv(self, name, value, prepend=None):
|
def setenv(self, name, value, prepend=None):
|
||||||
|
""" set environment variable ``name`` to ``value``. if ``prepend``
|
||||||
|
is a character, read the current environment variable value
|
||||||
|
and prepend the ``value`` adjoined with the ``prepend`` character."""
|
||||||
value = str(value)
|
value = str(value)
|
||||||
if prepend and name in os.environ:
|
if prepend and name in os.environ:
|
||||||
value = value + prepend + os.environ[name]
|
value = value + prepend + os.environ[name]
|
||||||
self.setitem(os.environ, name, value)
|
self.setitem(os.environ, name, value)
|
||||||
|
|
||||||
def delenv(self, name, raising=True):
|
def delenv(self, name, raising=True):
|
||||||
|
""" delete ``name`` from environment, raise KeyError it not exists."""
|
||||||
self.delitem(os.environ, name, raising=raising)
|
self.delitem(os.environ, name, raising=raising)
|
||||||
|
|
||||||
def syspath_prepend(self, path):
|
def syspath_prepend(self, path):
|
||||||
|
""" prepend ``path`` to ``sys.path`` list of import locations. """
|
||||||
if not hasattr(self, '_savesyspath'):
|
if not hasattr(self, '_savesyspath'):
|
||||||
self._savesyspath = sys.path[:]
|
self._savesyspath = sys.path[:]
|
||||||
sys.path.insert(0, str(path))
|
sys.path.insert(0, str(path))
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
|
""" undo previous changes. This call consumes the
|
||||||
|
undo stack. Calling it a second time has no effect unless
|
||||||
|
you do more monkeypatching after the undo call."""
|
||||||
for obj, name, value in self._setattr:
|
for obj, name, value in self._setattr:
|
||||||
if value is not notset:
|
if value is not notset:
|
||||||
setattr(obj, name, value)
|
setattr(obj, name, value)
|
||||||
|
|
|
@ -527,6 +527,24 @@ class Metafunc:
|
||||||
self._ids = py.builtin.set()
|
self._ids = py.builtin.set()
|
||||||
|
|
||||||
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
|
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
|
||||||
|
""" add a new call to the underlying test function during the
|
||||||
|
collection phase of a test run.
|
||||||
|
|
||||||
|
:arg funcargs: argument keyword dictionary used when invoking
|
||||||
|
the test function.
|
||||||
|
|
||||||
|
:arg id: used for reporting and identification purposes. If you
|
||||||
|
don't supply an `id` the length of the currently
|
||||||
|
list of calls to the test function will be used.
|
||||||
|
|
||||||
|
:arg param: will be exposed to a later funcarg factory invocation
|
||||||
|
through the ``request.param`` attribute. Setting it (instead of
|
||||||
|
directly providing a ``funcargs`` ditionary) is called
|
||||||
|
*indirect parametrization*. Indirect parametrization is
|
||||||
|
preferable if test values are expensive to setup or can
|
||||||
|
only be created after certain fixtures or test-run related
|
||||||
|
initialization code has been run.
|
||||||
|
"""
|
||||||
assert funcargs is None or isinstance(funcargs, dict)
|
assert funcargs is None or isinstance(funcargs, dict)
|
||||||
if id is None:
|
if id is None:
|
||||||
raise ValueError("id=None not allowed")
|
raise ValueError("id=None not allowed")
|
||||||
|
@ -573,22 +591,29 @@ class FuncargRequest:
|
||||||
|
|
||||||
def applymarker(self, marker):
|
def applymarker(self, marker):
|
||||||
""" apply a marker to a test function invocation.
|
""" apply a marker to a test function invocation.
|
||||||
|
Usually markers can be used as decorators for test functions or
|
||||||
|
classes. However, with parametrized testing a single
|
||||||
|
test function may be called multiple times and ``applymarker``
|
||||||
|
allows to mark only a single invocation.
|
||||||
|
|
||||||
|
:param marker: The ``pytest.mark.*`` object to be applied to the test invocation.
|
||||||
|
|
||||||
The 'marker' must be created with pytest.mark.* XYZ.
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(marker, py.test.mark.XYZ.__class__):
|
if not isinstance(marker, py.test.mark.XYZ.__class__):
|
||||||
raise ValueError("%r is not a py.test.mark.* object")
|
raise ValueError("%r is not a py.test.mark.* object")
|
||||||
self._pyfuncitem.keywords[marker.markname] = marker
|
self._pyfuncitem.keywords[marker.markname] = marker
|
||||||
|
|
||||||
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
||||||
""" cache and return result of calling setup().
|
""" return a testing resource managed by ``setup`` &
|
||||||
|
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
|
||||||
|
``teardown`` function will be called so that subsequent calls to
|
||||||
|
``setup`` would recreate the resource.
|
||||||
|
|
||||||
The requested argument name, the scope and the ``extrakey``
|
:arg teardown: function receiving a previously setup resource.
|
||||||
determine the cache key. The scope also determines when
|
:arg setup: a no-argument function creating a resource.
|
||||||
teardown(result) will be called. valid scopes are:
|
:arg scope: a string value out of ``function``, ``module`` or
|
||||||
scope == 'function': when the single test function run finishes.
|
``session`` indicating the caching lifecycle of the resource.
|
||||||
scope == 'module': when tests in a different module are run
|
:arg extrakey: added to internal caching key of (funcargname, scope).
|
||||||
scope == 'session': when tests of the session have run.
|
|
||||||
"""
|
"""
|
||||||
if not hasattr(self.config, '_setupcache'):
|
if not hasattr(self.config, '_setupcache'):
|
||||||
self.config._setupcache = {} # XXX weakref?
|
self.config._setupcache = {} # XXX weakref?
|
||||||
|
@ -607,6 +632,13 @@ class FuncargRequest:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def getfuncargvalue(self, argname):
|
def getfuncargvalue(self, argname):
|
||||||
|
""" Retrieve a function argument by name for this test
|
||||||
|
function invocation. This allows one function argument factory
|
||||||
|
to call another function argument factory. If there are two
|
||||||
|
funcarg factories for the same test function argument the first
|
||||||
|
factory may use ``getfuncargvalue`` to call the second one and
|
||||||
|
do something additional with the resource.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return self._funcargs[argname]
|
return self._funcargs[argname]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -637,15 +669,16 @@ class FuncargRequest:
|
||||||
return None
|
return None
|
||||||
raise ValueError("unknown finalization scope %r" %(scope,))
|
raise ValueError("unknown finalization scope %r" %(scope,))
|
||||||
|
|
||||||
|
def addfinalizer(self, finalizer):
|
||||||
|
"""add finalizer function to be called after test function
|
||||||
|
finished execution. """
|
||||||
|
self._addfinalizer(finalizer, scope="function")
|
||||||
|
|
||||||
def _addfinalizer(self, finalizer, scope):
|
def _addfinalizer(self, finalizer, scope):
|
||||||
colitem = self._getscopeitem(scope)
|
colitem = self._getscopeitem(scope)
|
||||||
self.config._setupstate.addfinalizer(
|
self.config._setupstate.addfinalizer(
|
||||||
finalizer=finalizer, colitem=colitem)
|
finalizer=finalizer, colitem=colitem)
|
||||||
|
|
||||||
def addfinalizer(self, finalizer):
|
|
||||||
""" call the given finalizer after test function finished execution. """
|
|
||||||
self._addfinalizer(finalizer, scope="function")
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<FuncargRequest for %r>" %(self._pyfuncitem)
|
return "<FuncargRequest for %r>" %(self._pyfuncitem)
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,4 @@
|
||||||
"""
|
""" record warnings to allow assertions about them. """
|
||||||
helpers for asserting deprecation and other warnings.
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os, sys
|
import os, sys
|
||||||
import py
|
import py
|
||||||
from pytest.plugin.pytest_monkeypatch import MonkeyPatch
|
from pytest.plugin.pytest_monkeypatch import monkeypatch as MonkeyPatch
|
||||||
|
|
||||||
def test_setattr():
|
def test_setattr():
|
||||||
class A:
|
class A:
|
||||||
|
@ -115,9 +115,8 @@ def test_setenv_prepend():
|
||||||
|
|
||||||
def test_monkeypatch_plugin(testdir):
|
def test_monkeypatch_plugin(testdir):
|
||||||
reprec = testdir.inline_runsource("""
|
reprec = testdir.inline_runsource("""
|
||||||
pytest_plugins = 'pytest_monkeypatch',
|
|
||||||
def test_method(monkeypatch):
|
def test_method(monkeypatch):
|
||||||
assert monkeypatch.__class__.__name__ == "MonkeyPatch"
|
assert monkeypatch.__class__.__name__ == "monkeypatch"
|
||||||
""")
|
""")
|
||||||
res = reprec.countoutcomes()
|
res = reprec.countoutcomes()
|
||||||
assert tuple(res) == (1, 0, 0), res
|
assert tuple(res) == (1, 0, 0), res
|
||||||
|
|
Loading…
Reference in New Issue