update docs, leave out internal plugins
--HG-- branch : 1.0.x
This commit is contained in:
parent
3e226f9392
commit
7fabb3df69
|
@ -633,6 +633,10 @@ div.heading, h1 {
|
||||||
border-bottom: 1px solid #8CACBB;
|
border-bottom: 1px solid #8CACBB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
border-bottom: 1px dotted #8CACBB;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
color: Black;
|
color: Black;
|
||||||
|
@ -648,11 +652,10 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
|
|
||||||
|
|
||||||
h1 { font-size: 145%; }
|
h1 { font-size: 145%; }
|
||||||
h2 { font-size: 135%; }
|
h2 { font-size: 115%; }
|
||||||
h3 { font-size: 125%; }
|
h3 { font-size: 105%; }
|
||||||
h4 { font-size: 120%; }
|
h4 { font-size: 100%; }
|
||||||
h5 { font-size: 110%; }
|
h5 { font-size: 100%; }
|
||||||
h6 { font-size: 80%; }
|
|
||||||
|
|
||||||
h1 a { text-decoration: None;}
|
h1 a { text-decoration: None;}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@ pytest_doctest plugin
|
||||||
|
|
||||||
collect and execute doctests from modules and test files.
|
collect and execute doctests from modules and test files.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -22,218 +25,19 @@ command line options
|
||||||
``--doctest-modules``
|
``--doctest-modules``
|
||||||
search all python files for doctests
|
search all python files for doctests
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_doctest.py`_ plugin source code
|
1. Download `pytest_doctest.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_doctest.py`` into your import path
|
2. put it somewhere as ``pytest_doctest.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_doctest.py``:
|
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_doctest.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
collect and execute doctests from modules and test files.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-------------
|
|
||||||
|
|
||||||
By default all files matching the ``test_*.txt`` pattern will
|
|
||||||
be run with the ``doctest`` module. If you issue::
|
|
||||||
|
|
||||||
py.test --doctest-modules
|
|
||||||
|
|
||||||
all python files in your projects will be doctest-run
|
|
||||||
as well.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
from py.__.code.excinfo import Repr, ReprFileLocation
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.addgroup("doctest options")
|
|
||||||
group.addoption("--doctest-modules",
|
|
||||||
action="store_true", default=False,
|
|
||||||
help="search all python files for doctests",
|
|
||||||
dest="doctestmodules")
|
|
||||||
|
|
||||||
def pytest_collect_file(path, parent):
|
|
||||||
if path.ext == ".py":
|
|
||||||
if parent.config.getvalue("doctestmodules"):
|
|
||||||
return DoctestModule(path, parent)
|
|
||||||
if path.check(fnmatch="test_*.txt"):
|
|
||||||
return DoctestTextfile(path, parent)
|
|
||||||
|
|
||||||
class ReprFailDoctest(Repr):
|
|
||||||
def __init__(self, reprlocation, lines):
|
|
||||||
self.reprlocation = reprlocation
|
|
||||||
self.lines = lines
|
|
||||||
def toterminal(self, tw):
|
|
||||||
for line in self.lines:
|
|
||||||
tw.line(line)
|
|
||||||
self.reprlocation.toterminal(tw)
|
|
||||||
|
|
||||||
class DoctestItem(py.test.collect.Item):
|
|
||||||
def __init__(self, path, parent):
|
|
||||||
name = self.__class__.__name__ + ":" + path.basename
|
|
||||||
super(DoctestItem, self).__init__(name=name, parent=parent)
|
|
||||||
self.fspath = path
|
|
||||||
|
|
||||||
def repr_failure(self, excinfo, outerr):
|
|
||||||
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
|
|
||||||
doctestfailure = excinfo.value
|
|
||||||
example = doctestfailure.example
|
|
||||||
test = doctestfailure.test
|
|
||||||
filename = test.filename
|
|
||||||
lineno = test.lineno + example.lineno + 1
|
|
||||||
message = excinfo.type.__name__
|
|
||||||
reprlocation = ReprFileLocation(filename, lineno, message)
|
|
||||||
checker = py.compat.doctest.OutputChecker()
|
|
||||||
REPORT_UDIFF = py.compat.doctest.REPORT_UDIFF
|
|
||||||
filelines = py.path.local(filename).readlines(cr=0)
|
|
||||||
i = max(test.lineno, max(0, lineno - 10)) # XXX?
|
|
||||||
lines = []
|
|
||||||
for line in filelines[i:lineno]:
|
|
||||||
lines.append("%03d %s" % (i+1, line))
|
|
||||||
i += 1
|
|
||||||
lines += checker.output_difference(example,
|
|
||||||
doctestfailure.got, REPORT_UDIFF).split("\n")
|
|
||||||
return ReprFailDoctest(reprlocation, lines)
|
|
||||||
elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
|
|
||||||
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
|
||||||
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
|
||||||
else:
|
|
||||||
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
|
||||||
|
|
||||||
class DoctestTextfile(DoctestItem):
|
|
||||||
def runtest(self):
|
|
||||||
if not self._deprecated_testexecution():
|
|
||||||
failed, tot = py.compat.doctest.testfile(
|
|
||||||
str(self.fspath), module_relative=False,
|
|
||||||
raise_on_error=True, verbose=0)
|
|
||||||
|
|
||||||
class DoctestModule(DoctestItem):
|
|
||||||
def runtest(self):
|
|
||||||
module = self.fspath.pyimport()
|
|
||||||
failed, tot = py.compat.doctest.testmod(
|
|
||||||
module, raise_on_error=True, verbose=0)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Plugin tests
|
|
||||||
#
|
|
||||||
|
|
||||||
class TestDoctests:
|
|
||||||
|
|
||||||
def test_collect_testtextfile(self, testdir):
|
|
||||||
testdir.maketxtfile(whatever="")
|
|
||||||
checkfile = testdir.maketxtfile(test_something="""
|
|
||||||
alskdjalsdk
|
|
||||||
>>> i = 5
|
|
||||||
>>> i-1
|
|
||||||
4
|
|
||||||
""")
|
|
||||||
for x in (testdir.tmpdir, checkfile):
|
|
||||||
#print "checking that %s returns custom items" % (x,)
|
|
||||||
items, reprec = testdir.inline_genitems(x)
|
|
||||||
assert len(items) == 1
|
|
||||||
assert isinstance(items[0], DoctestTextfile)
|
|
||||||
|
|
||||||
def test_collect_module(self, testdir):
|
|
||||||
path = testdir.makepyfile(whatever="#")
|
|
||||||
for p in (path, testdir.tmpdir):
|
|
||||||
items, reprec = testdir.inline_genitems(p, '--doctest-modules')
|
|
||||||
assert len(items) == 1
|
|
||||||
assert isinstance(items[0], DoctestModule)
|
|
||||||
|
|
||||||
def test_simple_doctestfile(self, testdir):
|
|
||||||
p = testdir.maketxtfile(test_doc="""
|
|
||||||
>>> x = 1
|
|
||||||
>>> x == 1
|
|
||||||
False
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(p)
|
|
||||||
reprec.assertoutcome(failed=1)
|
|
||||||
|
|
||||||
def test_doctest_unexpected_exception(self, testdir):
|
|
||||||
from py.__.test.outcome import Failed
|
|
||||||
|
|
||||||
p = testdir.maketxtfile("""
|
|
||||||
>>> i = 0
|
|
||||||
>>> i = 1
|
|
||||||
>>> x
|
|
||||||
2
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(p)
|
|
||||||
call = reprec.getcall("pytest_runtest_logreport")
|
|
||||||
assert call.rep.failed
|
|
||||||
assert call.rep.longrepr
|
|
||||||
# XXX
|
|
||||||
#testitem, = items
|
|
||||||
#excinfo = py.test.raises(Failed, "testitem.runtest()")
|
|
||||||
#repr = testitem.repr_failure(excinfo, ("", ""))
|
|
||||||
#assert repr.reprlocation
|
|
||||||
|
|
||||||
def test_doctestmodule(self, testdir):
|
|
||||||
p = testdir.makepyfile("""
|
|
||||||
'''
|
|
||||||
>>> x = 1
|
|
||||||
>>> x == 1
|
|
||||||
False
|
|
||||||
|
|
||||||
'''
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(p, "--doctest-modules")
|
|
||||||
reprec.assertoutcome(failed=1)
|
|
||||||
|
|
||||||
def test_doctestmodule_external(self, testdir):
|
|
||||||
p = testdir.makepyfile("""
|
|
||||||
#
|
|
||||||
def somefunc():
|
|
||||||
'''
|
|
||||||
>>> i = 0
|
|
||||||
>>> i + 1
|
|
||||||
2
|
|
||||||
'''
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p, "--doctest-modules")
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
'004 *>>> i = 0',
|
|
||||||
'005 *>>> i + 1',
|
|
||||||
'*Expected:',
|
|
||||||
"* 2",
|
|
||||||
"*Got:",
|
|
||||||
"* 1",
|
|
||||||
"*:5: DocTestFailure"
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def test_txtfile_failing(self, testdir):
|
|
||||||
p = testdir.maketxtfile("""
|
|
||||||
>>> i = 0
|
|
||||||
>>> i + 1
|
|
||||||
2
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p)
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
'001 >>> i = 0',
|
|
||||||
'002 >>> i + 1',
|
|
||||||
'Expected:',
|
|
||||||
" 2",
|
|
||||||
"Got:",
|
|
||||||
" 1",
|
|
||||||
"*test_txtfile_failing.txt:2: DocTestFailure"
|
|
||||||
])
|
|
||||||
|
|
||||||
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_doctest.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
|
|
||||||
pytest_execnetcleanup plugin
|
|
||||||
============================
|
|
||||||
|
|
||||||
cleanup execnet gateways during test function runs.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_execnetcleanup.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_execnetcleanup.py`` into your import path
|
|
||||||
3. a subsequent test run will now use your local version!
|
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_execnetcleanup.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
cleanup execnet gateways during test function runs.
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
|
|
||||||
pytest_plugins = "xfail"
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
config.pluginmanager.register(Execnetcleanup())
|
|
||||||
|
|
||||||
class Execnetcleanup:
|
|
||||||
_gateways = None
|
|
||||||
def __init__(self, debug=False):
|
|
||||||
self._debug = debug
|
|
||||||
|
|
||||||
def pyexecnet_gateway_init(self, gateway):
|
|
||||||
if self._gateways is not None:
|
|
||||||
self._gateways.append(gateway)
|
|
||||||
|
|
||||||
def pyexecnet_gateway_exit(self, gateway):
|
|
||||||
if self._gateways is not None:
|
|
||||||
self._gateways.remove(gateway)
|
|
||||||
|
|
||||||
def pytest_sessionstart(self, session):
|
|
||||||
self._gateways = []
|
|
||||||
|
|
||||||
def pytest_sessionfinish(self, session, exitstatus):
|
|
||||||
l = []
|
|
||||||
for gw in self._gateways:
|
|
||||||
gw.exit()
|
|
||||||
l.append(gw)
|
|
||||||
#for gw in l:
|
|
||||||
# gw.join()
|
|
||||||
|
|
||||||
def pytest_pyfunc_call(self, __call__, pyfuncitem):
|
|
||||||
if self._gateways is not None:
|
|
||||||
gateways = self._gateways[:]
|
|
||||||
res = __call__.execute(firstresult=True)
|
|
||||||
while len(self._gateways) > len(gateways):
|
|
||||||
self._gateways[-1].exit()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def test_execnetplugin(testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
def test_hello():
|
|
||||||
sys._gw = py.execnet.PopenGateway()
|
|
||||||
def test_world():
|
|
||||||
assert hasattr(sys, '_gw')
|
|
||||||
py.test.raises(KeyError, "sys._gw.exit()") # already closed
|
|
||||||
|
|
||||||
""", "-s", "--debug")
|
|
||||||
reprec.assertoutcome(passed=2)
|
|
||||||
|
|
||||||
.. _`pytest_execnetcleanup.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_execnetcleanup.py
|
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
|
@ -4,6 +4,9 @@ pytest_figleaf plugin
|
||||||
|
|
||||||
write and report coverage data with 'figleaf'.
|
write and report coverage data with 'figleaf'.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command line options
|
command line options
|
||||||
|
@ -17,93 +20,19 @@ command line options
|
||||||
``--figleaf-html=FIGLEAFHTML``
|
``--figleaf-html=FIGLEAFHTML``
|
||||||
path to the coverage html dir.
|
path to the coverage html dir.
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_figleaf.py`_ plugin source code
|
1. Download `pytest_figleaf.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_figleaf.py`` into your import path
|
2. put it somewhere as ``pytest_figleaf.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_figleaf.py``:
|
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_figleaf.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
write and report coverage data with 'figleaf'.
|
|
||||||
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
|
|
||||||
figleaf = py.test.importorskip("figleaf.annotate_html")
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.addgroup('figleaf options')
|
|
||||||
group.addoption('-F', action='store_true', default=False,
|
|
||||||
dest = 'figleaf',
|
|
||||||
help=('trace python coverage with figleaf and write HTML '
|
|
||||||
'for files below the current working dir'))
|
|
||||||
group.addoption('--figleaf-data', action='store', default='.figleaf',
|
|
||||||
dest='figleafdata',
|
|
||||||
help='path to coverage tracing file.')
|
|
||||||
group.addoption('--figleaf-html', action='store', default='html',
|
|
||||||
dest='figleafhtml',
|
|
||||||
help='path to the coverage html dir.')
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
figleaf.start()
|
|
||||||
|
|
||||||
def pytest_terminal_summary(terminalreporter):
|
|
||||||
config = terminalreporter.config
|
|
||||||
datafile = py.path.local(config.getvalue('figleafdata'))
|
|
||||||
tw = terminalreporter._tw
|
|
||||||
tw.sep('-', 'figleaf')
|
|
||||||
tw.line('Writing figleaf data to %s' % (datafile))
|
|
||||||
figleaf.stop()
|
|
||||||
figleaf.write_coverage(str(datafile))
|
|
||||||
coverage = get_coverage(datafile, config)
|
|
||||||
reportdir = py.path.local(config.getvalue('figleafhtml'))
|
|
||||||
tw.line('Writing figleaf html to file://%s' % (reportdir))
|
|
||||||
figleaf.annotate_html.prepare_reportdir(str(reportdir))
|
|
||||||
exclude = []
|
|
||||||
figleaf.annotate_html.report_as_html(coverage,
|
|
||||||
str(reportdir), exclude, {})
|
|
||||||
|
|
||||||
def get_coverage(datafile, config):
|
|
||||||
# basepath = config.topdir
|
|
||||||
basepath = py.path.local()
|
|
||||||
data = figleaf.read_coverage(str(datafile))
|
|
||||||
d = {}
|
|
||||||
coverage = figleaf.combine_coverage(d, data)
|
|
||||||
for path in coverage.keys():
|
|
||||||
if not py.path.local(path).relto(basepath):
|
|
||||||
del coverage[path]
|
|
||||||
return coverage
|
|
||||||
|
|
||||||
|
|
||||||
def test_functional(testdir):
|
|
||||||
py.test.importorskip("figleaf")
|
|
||||||
testdir.plugins.append("figleaf")
|
|
||||||
testdir.makepyfile("""
|
|
||||||
def f():
|
|
||||||
x = 42
|
|
||||||
def test_whatever():
|
|
||||||
pass
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest('-F')
|
|
||||||
assert result.ret == 0
|
|
||||||
assert result.stdout.fnmatch_lines([
|
|
||||||
'*figleaf html*'
|
|
||||||
])
|
|
||||||
#print result.stdout.str()
|
|
||||||
|
|
||||||
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_figleaf.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
|
|
||||||
pytest_hooklog plugin
|
|
||||||
=====================
|
|
||||||
|
|
||||||
log invocations of extension hooks to a file.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command line options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
|
|
||||||
``--hooklog=HOOKLOG``
|
|
||||||
write hook calls to the given file.
|
|
||||||
|
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_hooklog.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_hooklog.py`` into your import path
|
|
||||||
3. a subsequent test run will now use your local version!
|
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_hooklog.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
""" log invocations of extension hooks to a file. """
|
|
||||||
import py
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
parser.addoption("--hooklog", dest="hooklog", default=None,
|
|
||||||
help="write hook calls to the given file.")
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
hooklog = config.getvalue("hooklog")
|
|
||||||
if hooklog:
|
|
||||||
assert not config.pluginmanager.comregistry.logfile
|
|
||||||
config.pluginmanager.comregistry.logfile = open(hooklog, 'w')
|
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
|
||||||
f = config.pluginmanager.comregistry.logfile
|
|
||||||
if f:
|
|
||||||
f.close()
|
|
||||||
config.pluginmanager.comregistry.logfile = None
|
|
||||||
|
|
||||||
# ===============================================================================
|
|
||||||
# plugin tests
|
|
||||||
# ===============================================================================
|
|
||||||
|
|
||||||
def test_functional(testdir):
|
|
||||||
testdir.makepyfile("""
|
|
||||||
def test_pass():
|
|
||||||
pass
|
|
||||||
""")
|
|
||||||
testdir.runpytest("--hooklog=hook.log")
|
|
||||||
s = testdir.tmpdir.join("hook.log").read()
|
|
||||||
assert s.find("pytest_sessionstart") != -1
|
|
||||||
assert s.find("ItemTestReport") != -1
|
|
||||||
assert s.find("sessionfinish") != -1
|
|
||||||
|
|
||||||
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_hooklog.py
|
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
|
@ -8,7 +8,7 @@ figleaf_ write and report coverage data with 'figleaf'.
|
||||||
|
|
||||||
monkeypatch_ safely patch object attributes, dicts and environment variables.
|
monkeypatch_ safely patch object attributes, dicts and environment variables.
|
||||||
|
|
||||||
iocapture_ convenient capturing of writes to stdout/stderror streams
|
iocapture_ convenient capturing of writes to stdout/stderror streams and file descriptors.
|
||||||
|
|
||||||
recwarn_ helpers for asserting deprecation and other warnings.
|
recwarn_ helpers for asserting deprecation and other warnings.
|
||||||
|
|
||||||
|
@ -32,23 +32,7 @@ pocoo_ submit failure information to paste.pocoo.org
|
||||||
|
|
||||||
resultlog_ resultlog plugin for machine-readable logging of test results.
|
resultlog_ resultlog plugin for machine-readable logging of test results.
|
||||||
|
|
||||||
terminal_ terminal reporting of the full testing process.
|
terminal_ Implements terminal reporting of the full testing process.
|
||||||
|
|
||||||
|
|
||||||
internal plugins / core functionality
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
pdb_ interactive debugging with the Python Debugger.
|
|
||||||
|
|
||||||
keyword_ py.test.mark / keyword plugin
|
|
||||||
|
|
||||||
hooklog_ log invocations of extension hooks to a file.
|
|
||||||
|
|
||||||
runner_ collect and run test items and create reports.
|
|
||||||
|
|
||||||
execnetcleanup_ cleanup execnet gateways during test function runs.
|
|
||||||
|
|
||||||
pytester_ funcargs and support code for testing py.test's own functionality.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`xfail`: xfail.html
|
.. _`xfail`: xfail.html
|
||||||
|
@ -63,9 +47,3 @@ pytester_ funcargs and support code for testing py.test's own functionality.
|
||||||
.. _`pocoo`: pocoo.html
|
.. _`pocoo`: pocoo.html
|
||||||
.. _`resultlog`: resultlog.html
|
.. _`resultlog`: resultlog.html
|
||||||
.. _`terminal`: terminal.html
|
.. _`terminal`: terminal.html
|
||||||
.. _`pdb`: pdb.html
|
|
||||||
.. _`keyword`: keyword.html
|
|
||||||
.. _`hooklog`: hooklog.html
|
|
||||||
.. _`runner`: runner.html
|
|
||||||
.. _`execnetcleanup`: execnetcleanup.html
|
|
||||||
.. _`pytester`: pytester.html
|
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
pytest_iocapture plugin
|
pytest_iocapture plugin
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
convenient capturing of writes to stdout/stderror streams
|
convenient capturing of writes to stdout/stderror streams and file descriptors.
|
||||||
|
|
||||||
and file descriptors.
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
Example Usage
|
Example Usage
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -29,6 +30,7 @@ The ``reset()`` call returns a tuple and will restart
|
||||||
capturing so that you can successively check for output.
|
capturing so that you can successively check for output.
|
||||||
After the test function finishes the original streams
|
After the test function finishes the original streams
|
||||||
will be restored.
|
will be restored.
|
||||||
|
|
||||||
.. _`capsys funcarg`:
|
.. _`capsys funcarg`:
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ the 'capsys' test function argument
|
||||||
captures writes to sys.stdout/sys.stderr and makes
|
captures writes to sys.stdout/sys.stderr and makes
|
||||||
them available successively via a ``capsys.reset()`` method
|
them available successively via a ``capsys.reset()`` method
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
which returns a ``(out, err)`` tuple of captured strings.
|
||||||
|
|
||||||
.. _`capfd funcarg`:
|
.. _`capfd funcarg`:
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,123 +51,19 @@ captures writes to file descriptors 1 and 2 and makes
|
||||||
them available successively via a ``capsys.reset()`` method
|
them available successively via a ``capsys.reset()`` method
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
which returns a ``(out, err)`` tuple of captured strings.
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_iocapture.py`_ plugin source code
|
1. Download `pytest_iocapture.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_iocapture.py`` into your import path
|
2. put it somewhere as ``pytest_iocapture.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_iocapture.py``:
|
.. _`pytest_iocapture.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_iocapture.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
convenient capturing of writes to stdout/stderror streams
|
|
||||||
and file descriptors.
|
|
||||||
|
|
||||||
Example Usage
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
You can use the `capsys funcarg`_ to capture writes
|
|
||||||
to stdout and stderr streams by using it in a test
|
|
||||||
likes this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_myoutput(capsys):
|
|
||||||
print "hello"
|
|
||||||
print >>sys.stderr, "world"
|
|
||||||
out, err = capsys.reset()
|
|
||||||
assert out == "hello\\n"
|
|
||||||
assert err == "world\\n"
|
|
||||||
print "next"
|
|
||||||
out, err = capsys.reset()
|
|
||||||
assert out == "next\\n"
|
|
||||||
|
|
||||||
The ``reset()`` call returns a tuple and will restart
|
|
||||||
capturing so that you can successively check for output.
|
|
||||||
After the test function finishes the original streams
|
|
||||||
will be restored.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
def pytest_funcarg__capsys(request):
|
|
||||||
"""captures writes to sys.stdout/sys.stderr and makes
|
|
||||||
them available successively via a ``capsys.reset()`` method
|
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
|
||||||
"""
|
|
||||||
capture = Capture(py.io.StdCapture)
|
|
||||||
request.addfinalizer(capture.finalize)
|
|
||||||
return capture
|
|
||||||
|
|
||||||
def pytest_funcarg__capfd(request):
|
|
||||||
"""captures writes to file descriptors 1 and 2 and makes
|
|
||||||
them available successively via a ``capsys.reset()`` method
|
|
||||||
which returns a ``(out, err)`` tuple of captured strings.
|
|
||||||
"""
|
|
||||||
capture = Capture(py.io.StdCaptureFD)
|
|
||||||
request.addfinalizer(capture.finalize)
|
|
||||||
return capture
|
|
||||||
|
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
|
||||||
if hasattr(pyfuncitem, 'funcargs'):
|
|
||||||
for funcarg, value in pyfuncitem.funcargs.items():
|
|
||||||
if funcarg == "capsys" or funcarg == "capfd":
|
|
||||||
value.reset()
|
|
||||||
|
|
||||||
class Capture:
|
|
||||||
_capture = None
|
|
||||||
def __init__(self, captureclass):
|
|
||||||
self._captureclass = captureclass
|
|
||||||
|
|
||||||
def finalize(self):
|
|
||||||
if self._capture:
|
|
||||||
self._capture.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
res = None
|
|
||||||
if self._capture:
|
|
||||||
res = self._capture.reset()
|
|
||||||
self._capture = self._captureclass()
|
|
||||||
return res
|
|
||||||
|
|
||||||
class TestCapture:
|
|
||||||
def test_std_functional(self, testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
def test_hello(capsys):
|
|
||||||
print 42
|
|
||||||
out, err = capsys.reset()
|
|
||||||
assert out.startswith("42")
|
|
||||||
""")
|
|
||||||
reprec.assertoutcome(passed=1)
|
|
||||||
|
|
||||||
def test_stdfd_functional(self, testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
def test_hello(capfd):
|
|
||||||
import os
|
|
||||||
os.write(1, "42")
|
|
||||||
out, err = capfd.reset()
|
|
||||||
assert out.startswith("42")
|
|
||||||
""")
|
|
||||||
reprec.assertoutcome(passed=1)
|
|
||||||
|
|
||||||
def test_funcall_yielded_no_funcargs(self, testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
def test_hello():
|
|
||||||
yield lambda: None
|
|
||||||
""")
|
|
||||||
reprec.assertoutcome(passed=1)
|
|
||||||
|
|
||||||
.. _`pytest_iocapture.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_iocapture.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
|
|
||||||
pytest_keyword plugin
|
|
||||||
=====================
|
|
||||||
|
|
||||||
py.test.mark / keyword plugin
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_keyword.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_keyword.py`` into your import path
|
|
||||||
3. a subsequent test run will now use your local version!
|
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_keyword.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
py.test.mark / keyword plugin
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
|
|
||||||
def pytest_namespace():
|
|
||||||
mark = KeywordDecorator({})
|
|
||||||
return {'mark': mark}
|
|
||||||
|
|
||||||
class KeywordDecorator:
|
|
||||||
""" decorator for setting function attributes. """
|
|
||||||
def __init__(self, keywords, lastname=None):
|
|
||||||
self._keywords = keywords
|
|
||||||
self._lastname = lastname
|
|
||||||
|
|
||||||
def __call__(self, func=None, **kwargs):
|
|
||||||
if func is None:
|
|
||||||
kw = self._keywords.copy()
|
|
||||||
kw.update(kwargs)
|
|
||||||
return KeywordDecorator(kw)
|
|
||||||
elif not hasattr(func, 'func_dict'):
|
|
||||||
kw = self._keywords.copy()
|
|
||||||
name = self._lastname
|
|
||||||
if name is None:
|
|
||||||
name = "mark"
|
|
||||||
kw[name] = func
|
|
||||||
return KeywordDecorator(kw)
|
|
||||||
func.func_dict.update(self._keywords)
|
|
||||||
return func
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name[0] == "_":
|
|
||||||
raise AttributeError(name)
|
|
||||||
kw = self._keywords.copy()
|
|
||||||
kw[name] = True
|
|
||||||
return self.__class__(kw, lastname=name)
|
|
||||||
|
|
||||||
def test_pytest_mark_getattr():
|
|
||||||
mark = KeywordDecorator({})
|
|
||||||
def f(): pass
|
|
||||||
|
|
||||||
mark.hello(f)
|
|
||||||
assert f.hello == True
|
|
||||||
|
|
||||||
mark.hello("test")(f)
|
|
||||||
assert f.hello == "test"
|
|
||||||
|
|
||||||
py.test.raises(AttributeError, "mark._hello")
|
|
||||||
py.test.raises(AttributeError, "mark.__str__")
|
|
||||||
|
|
||||||
def test_pytest_mark_call():
|
|
||||||
mark = KeywordDecorator({})
|
|
||||||
def f(): pass
|
|
||||||
mark(x=3)(f)
|
|
||||||
assert f.x == 3
|
|
||||||
def g(): pass
|
|
||||||
mark(g)
|
|
||||||
assert not g.func_dict
|
|
||||||
|
|
||||||
mark.hello(f)
|
|
||||||
assert f.hello == True
|
|
||||||
|
|
||||||
mark.hello("test")(f)
|
|
||||||
assert f.hello == "test"
|
|
||||||
|
|
||||||
mark("x1")(f)
|
|
||||||
assert f.mark == "x1"
|
|
||||||
|
|
||||||
def test_mark_plugin(testdir):
|
|
||||||
p = testdir.makepyfile("""
|
|
||||||
import py
|
|
||||||
pytest_plugins = "keyword"
|
|
||||||
@py.test.mark.hello
|
|
||||||
def test_hello():
|
|
||||||
assert hasattr(test_hello, 'hello')
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p)
|
|
||||||
assert result.stdout.fnmatch_lines(["*passed*"])
|
|
||||||
|
|
||||||
.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_keyword.py
|
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
|
@ -4,6 +4,9 @@ pytest_monkeypatch plugin
|
||||||
|
|
||||||
safely patch object attributes, dicts and environment variables.
|
safely patch object attributes, dicts and environment variables.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -26,6 +29,7 @@ modifications will be reverted. See the `monkeypatch blog post`_
|
||||||
for an extensive discussion.
|
for an extensive discussion.
|
||||||
|
|
||||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||||
|
|
||||||
.. _`monkeypatch funcarg`:
|
.. _`monkeypatch funcarg`:
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,147 +46,19 @@ helper methods to modify objects, dictionaries or os.environ::
|
||||||
All such modifications will be undone when the requesting
|
All such modifications will be undone when the requesting
|
||||||
test function finished its execution.
|
test function finished its execution.
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_monkeypatch.py`_ plugin source code
|
1. Download `pytest_monkeypatch.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_monkeypatch.py`` into your import path
|
2. put it somewhere as ``pytest_monkeypatch.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_monkeypatch.py``:
|
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_monkeypatch.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
safely patch object attributes, dicts and environment variables.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Use the `monkeypatch funcarg`_ to safely patch the environment
|
|
||||||
variables, object attributes or dictionaries. For example, if you want
|
|
||||||
to set the environment variable ``ENV1`` and patch the
|
|
||||||
``os.path.abspath`` function to return a particular value during a test
|
|
||||||
function execution you can write it down like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def test_mytest(monkeypatch):
|
|
||||||
monkeypatch.setenv('ENV1', 'myval')
|
|
||||||
monkeypatch.setattr(os.path, 'abspath', lambda x: '/')
|
|
||||||
... # your test code
|
|
||||||
|
|
||||||
The function argument will do the modifications and memorize the
|
|
||||||
old state. After the test function finished execution all
|
|
||||||
modifications will be reverted. See the `monkeypatch blog post`_
|
|
||||||
for an extensive discussion.
|
|
||||||
|
|
||||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
def pytest_funcarg__monkeypatch(request):
|
|
||||||
"""The returned ``monkeypatch`` funcarg provides three
|
|
||||||
helper methods to modify objects, dictionaries or os.environ::
|
|
||||||
|
|
||||||
monkeypatch.setattr(obj, name, value)
|
|
||||||
monkeypatch.setitem(mapping, name, value)
|
|
||||||
monkeypatch.setenv(name, value)
|
|
||||||
|
|
||||||
All such modifications will be undone when the requesting
|
|
||||||
test function finished its execution.
|
|
||||||
"""
|
|
||||||
monkeypatch = MonkeyPatch()
|
|
||||||
request.addfinalizer(monkeypatch.finalize)
|
|
||||||
return monkeypatch
|
|
||||||
|
|
||||||
notset = object()
|
|
||||||
|
|
||||||
class MonkeyPatch:
|
|
||||||
def __init__(self):
|
|
||||||
self._setattr = []
|
|
||||||
self._setitem = []
|
|
||||||
|
|
||||||
def setattr(self, obj, name, value):
|
|
||||||
self._setattr.insert(0, (obj, name, getattr(obj, name, notset)))
|
|
||||||
setattr(obj, name, value)
|
|
||||||
|
|
||||||
def setitem(self, dictionary, name, value):
|
|
||||||
self._setitem.insert(0, (dictionary, name, dictionary.get(name, notset)))
|
|
||||||
dictionary[name] = value
|
|
||||||
|
|
||||||
def setenv(self, name, value):
|
|
||||||
self.setitem(os.environ, name, str(value))
|
|
||||||
|
|
||||||
def finalize(self):
|
|
||||||
for obj, name, value in self._setattr:
|
|
||||||
if value is not notset:
|
|
||||||
setattr(obj, name, value)
|
|
||||||
else:
|
|
||||||
delattr(obj, name)
|
|
||||||
for dictionary, name, value in self._setitem:
|
|
||||||
if value is notset:
|
|
||||||
del dictionary[name]
|
|
||||||
else:
|
|
||||||
dictionary[name] = value
|
|
||||||
|
|
||||||
|
|
||||||
def test_setattr():
|
|
||||||
class A:
|
|
||||||
x = 1
|
|
||||||
monkeypatch = MonkeyPatch()
|
|
||||||
monkeypatch.setattr(A, 'x', 2)
|
|
||||||
assert A.x == 2
|
|
||||||
monkeypatch.setattr(A, 'x', 3)
|
|
||||||
assert A.x == 3
|
|
||||||
monkeypatch.finalize()
|
|
||||||
assert A.x == 1
|
|
||||||
|
|
||||||
monkeypatch.setattr(A, 'y', 3)
|
|
||||||
assert A.y == 3
|
|
||||||
monkeypatch.finalize()
|
|
||||||
assert not hasattr(A, 'y')
|
|
||||||
|
|
||||||
|
|
||||||
def test_setitem():
|
|
||||||
d = {'x': 1}
|
|
||||||
monkeypatch = MonkeyPatch()
|
|
||||||
monkeypatch.setitem(d, 'x', 2)
|
|
||||||
monkeypatch.setitem(d, 'y', 1700)
|
|
||||||
assert d['x'] == 2
|
|
||||||
assert d['y'] == 1700
|
|
||||||
monkeypatch.setitem(d, 'x', 3)
|
|
||||||
assert d['x'] == 3
|
|
||||||
monkeypatch.finalize()
|
|
||||||
assert d['x'] == 1
|
|
||||||
assert 'y' not in d
|
|
||||||
|
|
||||||
def test_setenv():
|
|
||||||
monkeypatch = MonkeyPatch()
|
|
||||||
monkeypatch.setenv('XYZ123', 2)
|
|
||||||
import os
|
|
||||||
assert os.environ['XYZ123'] == "2"
|
|
||||||
monkeypatch.finalize()
|
|
||||||
assert 'XYZ123' not in os.environ
|
|
||||||
|
|
||||||
def test_monkeypatch_plugin(testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
pytest_plugins = 'pytest_monkeypatch',
|
|
||||||
def test_method(monkeypatch):
|
|
||||||
assert monkeypatch.__class__.__name__ == "MonkeyPatch"
|
|
||||||
""")
|
|
||||||
res = reprec.countoutcomes()
|
|
||||||
assert tuple(res) == (1, 0, 0), res
|
|
||||||
|
|
||||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_monkeypatch.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
|
|
||||||
pytest_pdb plugin
|
|
||||||
=================
|
|
||||||
|
|
||||||
interactive debugging with the Python Debugger.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command line options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
|
|
||||||
``--pdb``
|
|
||||||
start pdb (the Python debugger) on errors.
|
|
||||||
|
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_pdb.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_pdb.py`` into your import path
|
|
||||||
3. a subsequent test run will now use your local version!
|
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_pdb.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
interactive debugging with the Python Debugger.
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
import pdb, sys, linecache
|
|
||||||
from py.__.test.outcome import Skipped
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.getgroup("general")
|
|
||||||
group._addoption('--pdb',
|
|
||||||
action="store_true", dest="usepdb", default=False,
|
|
||||||
help="start pdb (the Python debugger) on errors.")
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
if config.option.usepdb:
|
|
||||||
if config.getvalue("looponfail"):
|
|
||||||
raise config.Error("--pdb incompatible with --looponfail.")
|
|
||||||
if config.option.dist != "no":
|
|
||||||
raise config.Error("--pdb incomptaible with distributing tests.")
|
|
||||||
config.pluginmanager.register(PdbInvoke())
|
|
||||||
|
|
||||||
class PdbInvoke:
|
|
||||||
def pytest_runtest_makereport(self, item, call):
|
|
||||||
if call.excinfo and not call.excinfo.errisinstance(Skipped):
|
|
||||||
tw = py.io.TerminalWriter()
|
|
||||||
repr = call.excinfo.getrepr()
|
|
||||||
repr.toterminal(tw)
|
|
||||||
post_mortem(call.excinfo._excinfo[2])
|
|
||||||
|
|
||||||
class Pdb(py.std.pdb.Pdb):
|
|
||||||
def do_list(self, arg):
|
|
||||||
self.lastcmd = 'list'
|
|
||||||
last = None
|
|
||||||
if arg:
|
|
||||||
try:
|
|
||||||
x = eval(arg, {}, {})
|
|
||||||
if type(x) == type(()):
|
|
||||||
first, last = x
|
|
||||||
first = int(first)
|
|
||||||
last = int(last)
|
|
||||||
if last < first:
|
|
||||||
# Assume it's a count
|
|
||||||
last = first + last
|
|
||||||
else:
|
|
||||||
first = max(1, int(x) - 5)
|
|
||||||
except:
|
|
||||||
print '*** Error in argument:', repr(arg)
|
|
||||||
return
|
|
||||||
elif self.lineno is None:
|
|
||||||
first = max(1, self.curframe.f_lineno - 5)
|
|
||||||
else:
|
|
||||||
first = self.lineno + 1
|
|
||||||
if last is None:
|
|
||||||
last = first + 10
|
|
||||||
filename = self.curframe.f_code.co_filename
|
|
||||||
breaklist = self.get_file_breaks(filename)
|
|
||||||
try:
|
|
||||||
for lineno in range(first, last+1):
|
|
||||||
# start difference from normal do_line
|
|
||||||
line = self._getline(filename, lineno)
|
|
||||||
# end difference from normal do_line
|
|
||||||
if not line:
|
|
||||||
print '[EOF]'
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
s = repr(lineno).rjust(3)
|
|
||||||
if len(s) < 4: s = s + ' '
|
|
||||||
if lineno in breaklist: s = s + 'B'
|
|
||||||
else: s = s + ' '
|
|
||||||
if lineno == self.curframe.f_lineno:
|
|
||||||
s = s + '->'
|
|
||||||
print s + '\t' + line,
|
|
||||||
self.lineno = lineno
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
do_l = do_list
|
|
||||||
|
|
||||||
def _getline(self, filename, lineno):
|
|
||||||
if hasattr(filename, "__source__"):
|
|
||||||
try:
|
|
||||||
return filename.__source__.lines[lineno - 1] + "\n"
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
return linecache.getline(filename, lineno)
|
|
||||||
|
|
||||||
def get_stack(self, f, t):
|
|
||||||
# Modified from bdb.py to be able to walk the stack beyond generators,
|
|
||||||
# which does not work in the normal pdb :-(
|
|
||||||
stack, i = pdb.Pdb.get_stack(self, f, t)
|
|
||||||
if f is None:
|
|
||||||
i = max(0, len(stack) - 1)
|
|
||||||
return stack, i
|
|
||||||
|
|
||||||
def post_mortem(t):
|
|
||||||
# modified from pdb.py for the new get_stack() implementation
|
|
||||||
p = Pdb()
|
|
||||||
p.reset()
|
|
||||||
p.interaction(None, t)
|
|
||||||
|
|
||||||
def set_trace():
|
|
||||||
# again, a copy of the version in pdb.py
|
|
||||||
Pdb().set_trace(sys._getframe().f_back)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPDB:
|
|
||||||
def pytest_funcarg__pdblist(self, request):
|
|
||||||
monkeypatch = request.getfuncargvalue("monkeypatch")
|
|
||||||
pdblist = []
|
|
||||||
def mypdb(*args):
|
|
||||||
pdblist.append(args)
|
|
||||||
monkeypatch.setitem(globals(), 'post_mortem', mypdb)
|
|
||||||
return pdblist
|
|
||||||
|
|
||||||
def test_incompatibility_messages(self, testdir):
|
|
||||||
Error = py.test.config.Error
|
|
||||||
py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')")
|
|
||||||
py.test.raises(Error, "testdir.parseconfigure('--pdb', '-n 3')")
|
|
||||||
py.test.raises(Error, "testdir.parseconfigure('--pdb', '-d')")
|
|
||||||
|
|
||||||
def test_pdb_on_fail(self, testdir, pdblist):
|
|
||||||
rep = testdir.inline_runsource1('--pdb', """
|
|
||||||
def test_func():
|
|
||||||
assert 0
|
|
||||||
""")
|
|
||||||
assert rep.failed
|
|
||||||
assert len(pdblist) == 1
|
|
||||||
tb = py.code.Traceback(pdblist[0][0])
|
|
||||||
assert tb[-1].name == "test_func"
|
|
||||||
|
|
||||||
def test_pdb_on_skip(self, testdir, pdblist):
|
|
||||||
rep = testdir.inline_runsource1('--pdb', """
|
|
||||||
import py
|
|
||||||
def test_func():
|
|
||||||
py.test.skip("hello")
|
|
||||||
""")
|
|
||||||
assert rep.skipped
|
|
||||||
assert len(pdblist) == 0
|
|
||||||
|
|
||||||
def test_pdb_interaction(self, testdir):
|
|
||||||
p1 = testdir.makepyfile("""
|
|
||||||
def test_1():
|
|
||||||
i = 0
|
|
||||||
assert i == 1
|
|
||||||
""")
|
|
||||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
|
||||||
#child.expect(".*def test_1.*")
|
|
||||||
child.expect(".*i = 0.*")
|
|
||||||
child.expect("(Pdb)")
|
|
||||||
child.sendeof()
|
|
||||||
child.expect("1 failed")
|
|
||||||
if child.isalive():
|
|
||||||
child.wait()
|
|
||||||
|
|
||||||
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_pdb.py
|
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
Binary file not shown.
|
@ -1,683 +0,0 @@
|
||||||
|
|
||||||
pytest_pytester plugin
|
|
||||||
======================
|
|
||||||
|
|
||||||
funcargs and support code for testing py.test's own functionality.
|
|
||||||
|
|
||||||
|
|
||||||
.. _`testdir funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'testdir' test function argument
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
XXX missing docstring
|
|
||||||
.. _`reportrecorder funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'reportrecorder' test function argument
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
XXX missing docstring
|
|
||||||
.. _`venv funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'venv' test function argument
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
XXX missing docstring
|
|
||||||
.. _`linecomp funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'linecomp' test function argument
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
XXX missing docstring
|
|
||||||
.. _`py_setup funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'py_setup' test function argument
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
XXX missing docstring
|
|
||||||
.. _`LineMatcher funcarg`:
|
|
||||||
|
|
||||||
|
|
||||||
the 'LineMatcher' test function argument
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
XXX missing docstring
|
|
||||||
|
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_pytester.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_pytester.py`` into your import path
|
|
||||||
3. a subsequent test run will now use your local version!
|
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_pytester.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
funcargs and support code for testing py.test's own functionality.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
import sys, os
|
|
||||||
import inspect
|
|
||||||
from py.__.test.config import Config as pytestConfig
|
|
||||||
import hookspec
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
pytest_plugins = '_pytest'
|
|
||||||
|
|
||||||
def pytest_funcarg__linecomp(request):
|
|
||||||
return LineComp()
|
|
||||||
|
|
||||||
def pytest_funcarg__LineMatcher(request):
|
|
||||||
return LineMatcher
|
|
||||||
|
|
||||||
def pytest_funcarg__testdir(request):
|
|
||||||
tmptestdir = TmpTestdir(request)
|
|
||||||
return tmptestdir
|
|
||||||
|
|
||||||
def pytest_funcarg__reportrecorder(request):
|
|
||||||
reprec = ReportRecorder(py._com.comregistry)
|
|
||||||
request.addfinalizer(lambda: reprec.comregistry.unregister(reprec))
|
|
||||||
return reprec
|
|
||||||
|
|
||||||
class RunResult:
|
|
||||||
def __init__(self, ret, outlines, errlines):
|
|
||||||
self.ret = ret
|
|
||||||
self.outlines = outlines
|
|
||||||
self.errlines = errlines
|
|
||||||
self.stdout = LineMatcher(outlines)
|
|
||||||
self.stderr = LineMatcher(errlines)
|
|
||||||
|
|
||||||
class TmpTestdir:
|
|
||||||
def __init__(self, request):
|
|
||||||
self.request = request
|
|
||||||
self._pytest = request.getfuncargvalue("_pytest")
|
|
||||||
# XXX remove duplication with tmpdir plugin
|
|
||||||
basetmp = request.config.ensuretemp("testdir")
|
|
||||||
name = request.function.__name__
|
|
||||||
for i in range(100):
|
|
||||||
try:
|
|
||||||
tmpdir = basetmp.mkdir(name + str(i))
|
|
||||||
except py.error.EEXIST:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
# we need to create another subdir
|
|
||||||
# because Directory.collect() currently loads
|
|
||||||
# conftest.py from sibling directories
|
|
||||||
self.tmpdir = tmpdir.mkdir(name)
|
|
||||||
self.plugins = []
|
|
||||||
self._syspathremove = []
|
|
||||||
self.chdir() # always chdir
|
|
||||||
assert hasattr(self, '_olddir')
|
|
||||||
self.request.addfinalizer(self.finalize)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<TmpTestdir %r>" % (self.tmpdir,)
|
|
||||||
|
|
||||||
def Config(self, comregistry=None, topdir=None):
|
|
||||||
if topdir is None:
|
|
||||||
topdir = self.tmpdir.dirpath()
|
|
||||||
return pytestConfig(comregistry, topdir=topdir)
|
|
||||||
|
|
||||||
def finalize(self):
|
|
||||||
for p in self._syspathremove:
|
|
||||||
py.std.sys.path.remove(p)
|
|
||||||
if hasattr(self, '_olddir'):
|
|
||||||
self._olddir.chdir()
|
|
||||||
|
|
||||||
def getreportrecorder(self, obj):
|
|
||||||
if isinstance(obj, py._com.Registry):
|
|
||||||
registry = obj
|
|
||||||
elif hasattr(obj, 'comregistry'):
|
|
||||||
registry = obj.comregistry
|
|
||||||
elif hasattr(obj, 'pluginmanager'):
|
|
||||||
registry = obj.pluginmanager.comregistry
|
|
||||||
elif hasattr(obj, 'config'):
|
|
||||||
registry = obj.config.pluginmanager.comregistry
|
|
||||||
else:
|
|
||||||
raise ValueError("obj %r provides no comregistry" %(obj,))
|
|
||||||
assert isinstance(registry, py._com.Registry)
|
|
||||||
reprec = ReportRecorder(registry)
|
|
||||||
reprec.hookrecorder = self._pytest.gethookrecorder(hookspec, registry)
|
|
||||||
reprec.hook = reprec.hookrecorder.hook
|
|
||||||
return reprec
|
|
||||||
|
|
||||||
def chdir(self):
|
|
||||||
old = self.tmpdir.chdir()
|
|
||||||
if not hasattr(self, '_olddir'):
|
|
||||||
self._olddir = old
|
|
||||||
|
|
||||||
def _makefile(self, ext, args, kwargs):
|
|
||||||
items = kwargs.items()
|
|
||||||
if args:
|
|
||||||
source = "\n".join(map(str, args))
|
|
||||||
basename = self.request.function.__name__
|
|
||||||
items.insert(0, (basename, source))
|
|
||||||
ret = None
|
|
||||||
for name, value in items:
|
|
||||||
p = self.tmpdir.join(name).new(ext=ext)
|
|
||||||
source = py.code.Source(value)
|
|
||||||
p.write(str(py.code.Source(value)).lstrip())
|
|
||||||
if ret is None:
|
|
||||||
ret = p
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def makefile(self, ext, *args, **kwargs):
|
|
||||||
return self._makefile(ext, args, kwargs)
|
|
||||||
|
|
||||||
def makeconftest(self, source):
|
|
||||||
return self.makepyfile(conftest=source)
|
|
||||||
|
|
||||||
def makepyfile(self, *args, **kwargs):
|
|
||||||
return self._makefile('.py', args, kwargs)
|
|
||||||
|
|
||||||
def maketxtfile(self, *args, **kwargs):
|
|
||||||
return self._makefile('.txt', args, kwargs)
|
|
||||||
|
|
||||||
def syspathinsert(self, path=None):
|
|
||||||
if path is None:
|
|
||||||
path = self.tmpdir
|
|
||||||
py.std.sys.path.insert(0, str(path))
|
|
||||||
self._syspathremove.append(str(path))
|
|
||||||
|
|
||||||
def mkdir(self, name):
|
|
||||||
return self.tmpdir.mkdir(name)
|
|
||||||
|
|
||||||
def genitems(self, colitems):
|
|
||||||
return list(self.session.genitems(colitems))
|
|
||||||
|
|
||||||
def inline_genitems(self, *args):
|
|
||||||
#config = self.parseconfig(*args)
|
|
||||||
config = self.parseconfig(*args)
|
|
||||||
session = config.initsession()
|
|
||||||
rec = self.getreportrecorder(config)
|
|
||||||
colitems = [config.getfsnode(arg) for arg in config.args]
|
|
||||||
items = list(session.genitems(colitems))
|
|
||||||
return items, rec
|
|
||||||
|
|
||||||
def runitem(self, source):
|
|
||||||
# used from runner functional tests
|
|
||||||
item = self.getitem(source)
|
|
||||||
# the test class where we are called from wants to provide the runner
|
|
||||||
testclassinstance = self.request.function.im_self
|
|
||||||
runner = testclassinstance.getrunner()
|
|
||||||
return runner(item)
|
|
||||||
|
|
||||||
def inline_runsource(self, source, *cmdlineargs):
|
|
||||||
p = self.makepyfile(source)
|
|
||||||
l = list(cmdlineargs) + [p]
|
|
||||||
return self.inline_run(*l)
|
|
||||||
|
|
||||||
def inline_runsource1(self, *args):
|
|
||||||
args = list(args)
|
|
||||||
source = args.pop()
|
|
||||||
p = self.makepyfile(source)
|
|
||||||
l = list(args) + [p]
|
|
||||||
reprec = self.inline_run(*l)
|
|
||||||
reports = reprec.getreports("pytest_runtest_logreport")
|
|
||||||
assert len(reports) == 1, reports
|
|
||||||
return reports[0]
|
|
||||||
|
|
||||||
def inline_run(self, *args):
|
|
||||||
config = self.parseconfig(*args)
|
|
||||||
config.pluginmanager.do_configure(config)
|
|
||||||
session = config.initsession()
|
|
||||||
reprec = self.getreportrecorder(config)
|
|
||||||
session.main()
|
|
||||||
config.pluginmanager.do_unconfigure(config)
|
|
||||||
return reprec
|
|
||||||
|
|
||||||
def config_preparse(self):
|
|
||||||
config = self.Config()
|
|
||||||
for plugin in self.plugins:
|
|
||||||
if isinstance(plugin, str):
|
|
||||||
config.pluginmanager.import_plugin(plugin)
|
|
||||||
else:
|
|
||||||
if isinstance(plugin, dict):
|
|
||||||
plugin = PseudoPlugin(plugin)
|
|
||||||
if not config.pluginmanager.isregistered(plugin):
|
|
||||||
config.pluginmanager.register(plugin)
|
|
||||||
#print "config.pluginmanager.impname2plugin", config.pluginmanager.impname2plugin
|
|
||||||
return config
|
|
||||||
|
|
||||||
def parseconfig(self, *args):
|
|
||||||
if not args:
|
|
||||||
args = (self.tmpdir,)
|
|
||||||
config = self.config_preparse()
|
|
||||||
args = list(args) + ["--basetemp=%s" % self.tmpdir.dirpath('basetemp')]
|
|
||||||
config.parse(args)
|
|
||||||
return config
|
|
||||||
|
|
||||||
def parseconfigure(self, *args):
|
|
||||||
config = self.parseconfig(*args)
|
|
||||||
config.pluginmanager.do_configure(config)
|
|
||||||
return config
|
|
||||||
|
|
||||||
def getitem(self, source, funcname="test_func"):
|
|
||||||
modcol = self.getmodulecol(source)
|
|
||||||
moditems = modcol.collect()
|
|
||||||
for item in modcol.collect():
|
|
||||||
if item.name == funcname:
|
|
||||||
return item
|
|
||||||
else:
|
|
||||||
assert 0, "%r item not found in module:\n%s" %(funcname, source)
|
|
||||||
|
|
||||||
def getitems(self, source):
|
|
||||||
modcol = self.getmodulecol(source)
|
|
||||||
return list(modcol.config.initsession().genitems([modcol]))
|
|
||||||
#assert item is not None, "%r item not found in module:\n%s" %(funcname, source)
|
|
||||||
#return item
|
|
||||||
|
|
||||||
def getfscol(self, path, configargs=()):
|
|
||||||
self.config = self.parseconfig(path, *configargs)
|
|
||||||
self.session = self.config.initsession()
|
|
||||||
return self.config.getfsnode(path)
|
|
||||||
|
|
||||||
def getmodulecol(self, source, configargs=(), withinit=False):
|
|
||||||
kw = {self.request.function.__name__: py.code.Source(source).strip()}
|
|
||||||
path = self.makepyfile(**kw)
|
|
||||||
if withinit:
|
|
||||||
self.makepyfile(__init__ = "#")
|
|
||||||
self.config = self.parseconfig(path, *configargs)
|
|
||||||
self.session = self.config.initsession()
|
|
||||||
#self.config.pluginmanager.do_configure(config=self.config)
|
|
||||||
# XXX
|
|
||||||
self.config.pluginmanager.import_plugin("runner")
|
|
||||||
plugin = self.config.pluginmanager.getplugin("runner")
|
|
||||||
plugin.pytest_configure(config=self.config)
|
|
||||||
|
|
||||||
return self.config.getfsnode(path)
|
|
||||||
|
|
||||||
def prepare(self):
|
|
||||||
p = self.tmpdir.join("conftest.py")
|
|
||||||
if not p.check():
|
|
||||||
plugins = [x for x in self.plugins if isinstance(x, str)]
|
|
||||||
if not plugins:
|
|
||||||
return
|
|
||||||
p.write("import py ; pytest_plugins = %r" % plugins)
|
|
||||||
else:
|
|
||||||
if self.plugins:
|
|
||||||
print "warning, ignoring reusing existing con", p
|
|
||||||
|
|
||||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
|
||||||
if not hasattr(py.std, 'subprocess'):
|
|
||||||
py.test.skip("no subprocess module")
|
|
||||||
env = os.environ.copy()
|
|
||||||
env['PYTHONPATH'] = ":".join(filter(None, [
|
|
||||||
str(os.getcwd()), env.get('PYTHONPATH', '')]))
|
|
||||||
kw['env'] = env
|
|
||||||
#print "env", env
|
|
||||||
return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
|
|
||||||
|
|
||||||
def run(self, *cmdargs):
|
|
||||||
self.prepare()
|
|
||||||
old = self.tmpdir.chdir()
|
|
||||||
#print "chdir", self.tmpdir
|
|
||||||
try:
|
|
||||||
return self._run(*cmdargs)
|
|
||||||
finally:
|
|
||||||
old.chdir()
|
|
||||||
|
|
||||||
def _run(self, *cmdargs):
|
|
||||||
cmdargs = map(str, cmdargs)
|
|
||||||
p1 = py.path.local("stdout")
|
|
||||||
p2 = py.path.local("stderr")
|
|
||||||
print "running", cmdargs, "curdir=", py.path.local()
|
|
||||||
f1 = p1.open("w")
|
|
||||||
f2 = p2.open("w")
|
|
||||||
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
|
|
||||||
close_fds=(sys.platform != "win32"))
|
|
||||||
ret = popen.wait()
|
|
||||||
f1.close()
|
|
||||||
f2.close()
|
|
||||||
out, err = p1.readlines(cr=0), p2.readlines(cr=0)
|
|
||||||
if err:
|
|
||||||
for line in err:
|
|
||||||
print >>py.std.sys.stderr, line
|
|
||||||
if out:
|
|
||||||
for line in out:
|
|
||||||
print >>py.std.sys.stdout, line
|
|
||||||
return RunResult(ret, out, err)
|
|
||||||
|
|
||||||
def runpybin(self, scriptname, *args):
|
|
||||||
fullargs = self._getpybinargs(scriptname) + args
|
|
||||||
return self.run(*fullargs)
|
|
||||||
|
|
||||||
def _getpybinargs(self, scriptname):
|
|
||||||
bindir = py.path.local(py.__file__).dirpath("bin")
|
|
||||||
script = bindir.join(scriptname)
|
|
||||||
assert script.check()
|
|
||||||
return py.std.sys.executable, script
|
|
||||||
|
|
||||||
def runpytest(self, *args):
|
|
||||||
p = py.path.local.make_numbered_dir(prefix="runpytest-",
|
|
||||||
keep=None, rootdir=self.tmpdir)
|
|
||||||
args = ('--basetemp=%s' % p, ) + args
|
|
||||||
return self.runpybin("py.test", *args)
|
|
||||||
|
|
||||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
|
||||||
pexpect = py.test.importorskip("pexpect", "2.3")
|
|
||||||
basetemp = self.tmpdir.mkdir("pexpect")
|
|
||||||
invoke = "%s %s" % self._getpybinargs("py.test")
|
|
||||||
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
|
||||||
child = pexpect.spawn(cmd, logfile=basetemp.join("spawn.out").open("w"))
|
|
||||||
child.timeout = expect_timeout
|
|
||||||
return child
|
|
||||||
|
|
||||||
class PseudoPlugin:
|
|
||||||
def __init__(self, vars):
|
|
||||||
self.__dict__.update(vars)
|
|
||||||
|
|
||||||
class ReportRecorder(object):
|
|
||||||
def __init__(self, comregistry):
|
|
||||||
self.comregistry = comregistry
|
|
||||||
comregistry.register(self)
|
|
||||||
|
|
||||||
def getcall(self, name):
|
|
||||||
return self.hookrecorder.getcall(name)
|
|
||||||
|
|
||||||
def popcall(self, name):
|
|
||||||
return self.hookrecorder.popcall(name)
|
|
||||||
|
|
||||||
def getcalls(self, names):
|
|
||||||
""" return list of ParsedCall instances matching the given eventname. """
|
|
||||||
return self.hookrecorder.getcalls(names)
|
|
||||||
|
|
||||||
# functionality for test reports
|
|
||||||
|
|
||||||
def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
|
|
||||||
return [x.rep for x in self.getcalls(names)]
|
|
||||||
|
|
||||||
def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport"):
|
|
||||||
""" return a testreport whose dotted import path matches """
|
|
||||||
l = []
|
|
||||||
for rep in self.getreports(names=names):
|
|
||||||
colitem = rep.getnode()
|
|
||||||
if not inamepart or inamepart in colitem.listnames():
|
|
||||||
l.append(rep)
|
|
||||||
if not l:
|
|
||||||
raise ValueError("could not find test report matching %r: no test reports at all!" %
|
|
||||||
(inamepart,))
|
|
||||||
if len(l) > 1:
|
|
||||||
raise ValueError("found more than one testreport matching %r: %s" %(
|
|
||||||
inamepart, l))
|
|
||||||
return l[0]
|
|
||||||
|
|
||||||
def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'):
|
|
||||||
return [rep for rep in self.getreports(names) if rep.failed]
|
|
||||||
|
|
||||||
def getfailedcollections(self):
|
|
||||||
return self.getfailures('pytest_collectreport')
|
|
||||||
|
|
||||||
def listoutcomes(self):
|
|
||||||
passed = []
|
|
||||||
skipped = []
|
|
||||||
failed = []
|
|
||||||
for rep in self.getreports("pytest_runtest_logreport"):
|
|
||||||
if rep.passed:
|
|
||||||
if rep.when == "call":
|
|
||||||
passed.append(rep)
|
|
||||||
elif rep.skipped:
|
|
||||||
skipped.append(rep)
|
|
||||||
elif rep.failed:
|
|
||||||
failed.append(rep)
|
|
||||||
return passed, skipped, failed
|
|
||||||
|
|
||||||
def countoutcomes(self):
|
|
||||||
return map(len, self.listoutcomes())
|
|
||||||
|
|
||||||
def assertoutcome(self, passed=0, skipped=0, failed=0):
|
|
||||||
realpassed, realskipped, realfailed = self.listoutcomes()
|
|
||||||
assert passed == len(realpassed)
|
|
||||||
assert skipped == len(realskipped)
|
|
||||||
assert failed == len(realfailed)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.hookrecorder.calls[:] = []
|
|
||||||
|
|
||||||
def unregister(self):
|
|
||||||
self.comregistry.unregister(self)
|
|
||||||
self.hookrecorder.finish_recording()
|
|
||||||
|
|
||||||
def test_reportrecorder(testdir):
|
|
||||||
registry = py._com.Registry()
|
|
||||||
recorder = testdir.getreportrecorder(registry)
|
|
||||||
assert not recorder.getfailures()
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
class rep:
|
|
||||||
excinfo = None
|
|
||||||
passed = False
|
|
||||||
failed = True
|
|
||||||
skipped = False
|
|
||||||
when = "call"
|
|
||||||
|
|
||||||
recorder.hook.pytest_runtest_logreport(rep=rep)
|
|
||||||
failures = recorder.getfailures()
|
|
||||||
assert failures == [rep]
|
|
||||||
failures = recorder.getfailures()
|
|
||||||
assert failures == [rep]
|
|
||||||
|
|
||||||
class rep:
|
|
||||||
excinfo = None
|
|
||||||
passed = False
|
|
||||||
failed = False
|
|
||||||
skipped = True
|
|
||||||
when = "call"
|
|
||||||
rep.passed = False
|
|
||||||
rep.skipped = True
|
|
||||||
recorder.hook.pytest_runtest_logreport(rep=rep)
|
|
||||||
|
|
||||||
modcol = testdir.getmodulecol("")
|
|
||||||
rep = modcol.config.hook.pytest_make_collect_report(collector=modcol)
|
|
||||||
rep.passed = False
|
|
||||||
rep.failed = True
|
|
||||||
rep.skipped = False
|
|
||||||
recorder.hook.pytest_collectreport(rep=rep)
|
|
||||||
|
|
||||||
passed, skipped, failed = recorder.listoutcomes()
|
|
||||||
assert not passed and skipped and failed
|
|
||||||
|
|
||||||
numpassed, numskipped, numfailed = recorder.countoutcomes()
|
|
||||||
assert numpassed == 0
|
|
||||||
assert numskipped == 1
|
|
||||||
assert numfailed == 1
|
|
||||||
assert len(recorder.getfailedcollections()) == 1
|
|
||||||
|
|
||||||
recorder.unregister()
|
|
||||||
recorder.clear()
|
|
||||||
recorder.hook.pytest_runtest_logreport(rep=rep)
|
|
||||||
py.test.raises(ValueError, "recorder.getfailures()")
|
|
||||||
|
|
||||||
class LineComp:
|
|
||||||
def __init__(self):
|
|
||||||
self.stringio = py.std.StringIO.StringIO()
|
|
||||||
|
|
||||||
def assert_contains_lines(self, lines2):
|
|
||||||
""" assert that lines2 are contained (linearly) in lines1.
|
|
||||||
return a list of extralines found.
|
|
||||||
"""
|
|
||||||
__tracebackhide__ = True
|
|
||||||
val = self.stringio.getvalue()
|
|
||||||
self.stringio.truncate(0) # remove what we got
|
|
||||||
lines1 = val.split("\n")
|
|
||||||
return LineMatcher(lines1).fnmatch_lines(lines2)
|
|
||||||
|
|
||||||
class LineMatcher:
|
|
||||||
def __init__(self, lines):
|
|
||||||
self.lines = lines
|
|
||||||
|
|
||||||
def str(self):
|
|
||||||
return "\n".join(self.lines)
|
|
||||||
|
|
||||||
def fnmatch_lines(self, lines2):
|
|
||||||
if isinstance(lines2, str):
|
|
||||||
lines2 = py.code.Source(lines2)
|
|
||||||
if isinstance(lines2, py.code.Source):
|
|
||||||
lines2 = lines2.strip().lines
|
|
||||||
|
|
||||||
from fnmatch import fnmatch
|
|
||||||
__tracebackhide__ = True
|
|
||||||
lines1 = self.lines[:]
|
|
||||||
nextline = None
|
|
||||||
extralines = []
|
|
||||||
for line in lines2:
|
|
||||||
nomatchprinted = False
|
|
||||||
while lines1:
|
|
||||||
nextline = lines1.pop(0)
|
|
||||||
if line == nextline:
|
|
||||||
print "exact match:", repr(line)
|
|
||||||
break
|
|
||||||
elif fnmatch(nextline, line):
|
|
||||||
print "fnmatch:", repr(line)
|
|
||||||
print " with:", repr(nextline)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if not nomatchprinted:
|
|
||||||
print "nomatch:", repr(line)
|
|
||||||
nomatchprinted = True
|
|
||||||
print " and:", repr(nextline)
|
|
||||||
extralines.append(nextline)
|
|
||||||
else:
|
|
||||||
if line != nextline:
|
|
||||||
#__tracebackhide__ = True
|
|
||||||
raise AssertionError("expected line not found: %r" % line)
|
|
||||||
extralines.extend(lines1)
|
|
||||||
return extralines
|
|
||||||
|
|
||||||
def test_parseconfig(testdir):
|
|
||||||
config1 = testdir.parseconfig()
|
|
||||||
config2 = testdir.parseconfig()
|
|
||||||
assert config2 != config1
|
|
||||||
assert config1 != py.test.config
|
|
||||||
|
|
||||||
def test_testdir_runs_with_plugin(testdir):
|
|
||||||
testdir.makepyfile("""
|
|
||||||
pytest_plugins = "pytest_pytester"
|
|
||||||
def test_hello(testdir):
|
|
||||||
assert 1
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest()
|
|
||||||
assert result.stdout.fnmatch_lines([
|
|
||||||
"*1 passed*"
|
|
||||||
])
|
|
||||||
|
|
||||||
#
|
|
||||||
# experimental funcargs for venv/install-tests
|
|
||||||
#
|
|
||||||
|
|
||||||
def pytest_funcarg__venv(request):
|
|
||||||
p = request.config.mktemp(request.function.__name__, numbered=True)
|
|
||||||
venv = VirtualEnv(str(p))
|
|
||||||
venv.create()
|
|
||||||
return venv
|
|
||||||
|
|
||||||
def pytest_funcarg__py_setup(request):
|
|
||||||
rootdir = py.path.local(py.__file__).dirpath().dirpath()
|
|
||||||
setup = rootdir.join('setup.py')
|
|
||||||
if not setup.check():
|
|
||||||
py.test.skip("not found: %r" % setup)
|
|
||||||
return SetupBuilder(setup)
|
|
||||||
|
|
||||||
class SetupBuilder:
|
|
||||||
def __init__(self, setup_path):
|
|
||||||
self.setup_path = setup_path
|
|
||||||
|
|
||||||
def make_sdist(self, destdir=None):
|
|
||||||
temp = py.path.local.mkdtemp()
|
|
||||||
try:
|
|
||||||
args = ['python', str(self.setup_path), 'sdist',
|
|
||||||
'--dist-dir', str(temp)]
|
|
||||||
subprocess.check_call(args)
|
|
||||||
l = temp.listdir('py-*')
|
|
||||||
assert len(l) == 1
|
|
||||||
sdist = l[0]
|
|
||||||
if destdir is None:
|
|
||||||
destdir = self.setup_path.dirpath('build')
|
|
||||||
assert destdir.check()
|
|
||||||
else:
|
|
||||||
destdir = py.path.local(destdir)
|
|
||||||
target = destdir.join(sdist.basename)
|
|
||||||
sdist.copy(target)
|
|
||||||
return target
|
|
||||||
finally:
|
|
||||||
temp.remove()
|
|
||||||
|
|
||||||
# code taken from Ronny Pfannenschmidt's virtualenvmanager
|
|
||||||
|
|
||||||
class VirtualEnv(object):
|
|
||||||
def __init__(self, path):
|
|
||||||
#XXX: supply the python executable
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<VirtualEnv at %r>" %(self.path)
|
|
||||||
|
|
||||||
def _cmd(self, name):
|
|
||||||
return os.path.join(self.path, 'bin', name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def valid(self):
|
|
||||||
return os.path.exists(self._cmd('python'))
|
|
||||||
|
|
||||||
def create(self, sitepackages=False):
|
|
||||||
args = ['virtualenv', self.path]
|
|
||||||
if not sitepackages:
|
|
||||||
args.append('--no-site-packages')
|
|
||||||
subprocess.check_call(args)
|
|
||||||
|
|
||||||
def makegateway(self):
|
|
||||||
python = self._cmd('python')
|
|
||||||
return py.execnet.makegateway("popen//python=%s" %(python,))
|
|
||||||
|
|
||||||
def pcall(self, cmd, *args, **kw):
|
|
||||||
assert self.valid
|
|
||||||
return subprocess.call([
|
|
||||||
self._cmd(cmd)
|
|
||||||
] + list(args),
|
|
||||||
**kw)
|
|
||||||
|
|
||||||
|
|
||||||
def easy_install(self, *packages, **kw):
|
|
||||||
args = []
|
|
||||||
if 'index' in kw:
|
|
||||||
index = kw['index']
|
|
||||||
if isinstance(index, (list, tuple)):
|
|
||||||
for i in index:
|
|
||||||
args.extend(['-i', i])
|
|
||||||
else:
|
|
||||||
args.extend(['-i', index])
|
|
||||||
|
|
||||||
args.extend(packages)
|
|
||||||
self.pcall('easy_install', *args)
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def has_pip(self):
|
|
||||||
return os.path.exists(self._cmd('pip'))
|
|
||||||
|
|
||||||
def pip_install(self, *packages):
|
|
||||||
if not self.has_pip:
|
|
||||||
self.easy_install('pip')
|
|
||||||
|
|
||||||
self.pcall('pip', *packages)
|
|
||||||
|
|
||||||
.. _`pytest_pytester.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_pytester.py
|
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
|
@ -4,207 +4,61 @@ pytest_recwarn plugin
|
||||||
|
|
||||||
helpers for asserting deprecation and other warnings.
|
helpers for asserting deprecation and other warnings.
|
||||||
|
|
||||||
**recwarn**: function argument where one can call recwarn.pop() to get
|
.. contents::
|
||||||
the last warning that would have been shown.
|
: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)
|
||||||
|
|
||||||
**py.test.deprecated_call(func, *args, **kwargs)**: assert that the given function call triggers a deprecation warning.
|
|
||||||
.. _`recwarn funcarg`:
|
.. _`recwarn funcarg`:
|
||||||
|
|
||||||
|
|
||||||
the 'recwarn' test function argument
|
the 'recwarn' test function argument
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
check that warnings have been raised.
|
Return a WarningsRecorder instance that provides these methods:
|
||||||
|
|
||||||
Getting and improving this plugin
|
* ``pop(category=None)``: return last warning matching the category.
|
||||||
---------------------------------
|
* ``clear()``: clear list of warnings
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_recwarn.py`_ plugin source code
|
1. Download `pytest_recwarn.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_recwarn.py`` into your import path
|
2. put it somewhere as ``pytest_recwarn.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_recwarn.py``:
|
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_recwarn.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
helpers for asserting deprecation and other warnings.
|
|
||||||
|
|
||||||
**recwarn**: function argument where one can call recwarn.pop() to get
|
|
||||||
the last warning that would have been shown.
|
|
||||||
|
|
||||||
**py.test.deprecated_call(func, *args, **kwargs)**: assert that the given function call triggers a deprecation warning.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
import os
|
|
||||||
|
|
||||||
def pytest_funcarg__recwarn(request):
|
|
||||||
""" check that warnings have been raised. """
|
|
||||||
warnings = WarningsRecorder()
|
|
||||||
request.addfinalizer(warnings.finalize)
|
|
||||||
return warnings
|
|
||||||
|
|
||||||
def pytest_namespace():
|
|
||||||
return {'deprecated_call': deprecated_call}
|
|
||||||
|
|
||||||
def deprecated_call(func, *args, **kwargs):
|
|
||||||
""" assert that calling func(*args, **kwargs)
|
|
||||||
triggers a DeprecationWarning.
|
|
||||||
"""
|
|
||||||
warningmodule = py.std.warnings
|
|
||||||
l = []
|
|
||||||
oldwarn_explicit = getattr(warningmodule, 'warn_explicit')
|
|
||||||
def warn_explicit(*args, **kwargs):
|
|
||||||
l.append(args)
|
|
||||||
oldwarn_explicit(*args, **kwargs)
|
|
||||||
oldwarn = getattr(warningmodule, 'warn')
|
|
||||||
def warn(*args, **kwargs):
|
|
||||||
l.append(args)
|
|
||||||
oldwarn(*args, **kwargs)
|
|
||||||
|
|
||||||
warningmodule.warn_explicit = warn_explicit
|
|
||||||
warningmodule.warn = warn
|
|
||||||
try:
|
|
||||||
ret = func(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
warningmodule.warn_explicit = warn_explicit
|
|
||||||
warningmodule.warn = warn
|
|
||||||
if not l:
|
|
||||||
#print warningmodule
|
|
||||||
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class RecordedWarning:
|
|
||||||
def __init__(self, message, category, filename, lineno, line):
|
|
||||||
self.message = message
|
|
||||||
self.category = category
|
|
||||||
self.filename = filename
|
|
||||||
self.lineno = lineno
|
|
||||||
self.line = line
|
|
||||||
|
|
||||||
class WarningsRecorder:
|
|
||||||
def __init__(self):
|
|
||||||
warningmodule = py.std.warnings
|
|
||||||
self.list = []
|
|
||||||
def showwarning(message, category, filename, lineno, line=0):
|
|
||||||
self.list.append(RecordedWarning(
|
|
||||||
message, category, filename, lineno, line))
|
|
||||||
try:
|
|
||||||
self.old_showwarning(message, category,
|
|
||||||
filename, lineno, line=line)
|
|
||||||
except TypeError:
|
|
||||||
# < python2.6
|
|
||||||
self.old_showwarning(message, category, filename, lineno)
|
|
||||||
self.old_showwarning = warningmodule.showwarning
|
|
||||||
warningmodule.showwarning = showwarning
|
|
||||||
|
|
||||||
def pop(self, cls=Warning):
|
|
||||||
""" pop the first recorded warning, raise exception if not exists."""
|
|
||||||
for i, w in py.builtin.enumerate(self.list):
|
|
||||||
if issubclass(w.category, cls):
|
|
||||||
return self.list.pop(i)
|
|
||||||
__tracebackhide__ = True
|
|
||||||
assert 0, "%r not found in %r" %(cls, self.list)
|
|
||||||
|
|
||||||
#def resetregistry(self):
|
|
||||||
# import warnings
|
|
||||||
# warnings.onceregistry.clear()
|
|
||||||
# warnings.__warningregistry__.clear()
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.list[:] = []
|
|
||||||
|
|
||||||
def finalize(self):
|
|
||||||
py.std.warnings.showwarning = self.old_showwarning
|
|
||||||
|
|
||||||
def test_WarningRecorder():
|
|
||||||
showwarning = py.std.warnings.showwarning
|
|
||||||
rec = WarningsRecorder()
|
|
||||||
assert py.std.warnings.showwarning != showwarning
|
|
||||||
assert not rec.list
|
|
||||||
py.std.warnings.warn_explicit("hello", UserWarning, "xyz", 13)
|
|
||||||
assert len(rec.list) == 1
|
|
||||||
py.std.warnings.warn(DeprecationWarning("hello"))
|
|
||||||
assert len(rec.list) == 2
|
|
||||||
warn = rec.pop()
|
|
||||||
assert str(warn.message) == "hello"
|
|
||||||
l = rec.list
|
|
||||||
rec.clear()
|
|
||||||
assert len(rec.list) == 0
|
|
||||||
assert l is rec.list
|
|
||||||
py.test.raises(AssertionError, "rec.pop()")
|
|
||||||
rec.finalize()
|
|
||||||
assert showwarning == py.std.warnings.showwarning
|
|
||||||
|
|
||||||
def test_recwarn_functional(testdir):
|
|
||||||
reprec = testdir.inline_runsource("""
|
|
||||||
pytest_plugins = 'pytest_recwarn',
|
|
||||||
import warnings
|
|
||||||
oldwarn = warnings.showwarning
|
|
||||||
def test_method(recwarn):
|
|
||||||
assert warnings.showwarning != oldwarn
|
|
||||||
warnings.warn("hello")
|
|
||||||
warn = recwarn.pop()
|
|
||||||
assert isinstance(warn.message, UserWarning)
|
|
||||||
def test_finalized():
|
|
||||||
assert warnings.showwarning == oldwarn
|
|
||||||
""")
|
|
||||||
res = reprec.countoutcomes()
|
|
||||||
assert tuple(res) == (2, 0, 0), res
|
|
||||||
|
|
||||||
#
|
|
||||||
# ============ test py.test.deprecated_call() ==============
|
|
||||||
#
|
|
||||||
|
|
||||||
def dep(i):
|
|
||||||
if i == 0:
|
|
||||||
py.std.warnings.warn("is deprecated", DeprecationWarning)
|
|
||||||
return 42
|
|
||||||
|
|
||||||
reg = {}
|
|
||||||
def dep_explicit(i):
|
|
||||||
if i == 0:
|
|
||||||
py.std.warnings.warn_explicit("dep_explicit", category=DeprecationWarning,
|
|
||||||
filename="hello", lineno=3)
|
|
||||||
|
|
||||||
def test_deprecated_call_raises():
|
|
||||||
excinfo = py.test.raises(AssertionError,
|
|
||||||
"py.test.deprecated_call(dep, 3)")
|
|
||||||
assert str(excinfo).find("did not produce") != -1
|
|
||||||
|
|
||||||
def test_deprecated_call():
|
|
||||||
py.test.deprecated_call(dep, 0)
|
|
||||||
|
|
||||||
def test_deprecated_call_ret():
|
|
||||||
ret = py.test.deprecated_call(dep, 0)
|
|
||||||
assert ret == 42
|
|
||||||
|
|
||||||
def test_deprecated_call_preserves():
|
|
||||||
r = py.std.warnings.onceregistry.copy()
|
|
||||||
f = py.std.warnings.filters[:]
|
|
||||||
test_deprecated_call_raises()
|
|
||||||
test_deprecated_call()
|
|
||||||
assert r == py.std.warnings.onceregistry
|
|
||||||
assert f == py.std.warnings.filters
|
|
||||||
|
|
||||||
def test_deprecated_explicit_call_raises():
|
|
||||||
py.test.raises(AssertionError,
|
|
||||||
"py.test.deprecated_call(dep_explicit, 3)")
|
|
||||||
|
|
||||||
def test_deprecated_explicit_call():
|
|
||||||
py.test.deprecated_call(dep_explicit, 0)
|
|
||||||
py.test.deprecated_call(dep_explicit, 0)
|
|
||||||
|
|
||||||
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_recwarn.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -4,6 +4,9 @@ pytest_restdoc plugin
|
||||||
|
|
||||||
perform ReST syntax, local and remote reference tests on .rst/.txt files.
|
perform ReST syntax, local and remote reference tests on .rst/.txt files.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command line options
|
command line options
|
||||||
|
@ -17,514 +20,19 @@ command line options
|
||||||
``--forcegen``
|
``--forcegen``
|
||||||
force generation of html files.
|
force generation of html files.
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_restdoc.py`_ plugin source code
|
1. Download `pytest_restdoc.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_restdoc.py`` into your import path
|
2. put it somewhere as ``pytest_restdoc.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_restdoc.py``:
|
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_restdoc.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
perform ReST syntax, local and remote reference tests on .rst/.txt files.
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.addgroup("ReST", "ReST documentation check options")
|
|
||||||
group.addoption('-R', '--urlcheck',
|
|
||||||
action="store_true", dest="urlcheck", default=False,
|
|
||||||
help="urlopen() remote links found in ReST text files.")
|
|
||||||
group.addoption('--urltimeout', action="store", metavar="secs",
|
|
||||||
type="int", dest="urlcheck_timeout", default=5,
|
|
||||||
help="timeout in seconds for remote urlchecks")
|
|
||||||
group.addoption('--forcegen',
|
|
||||||
action="store_true", dest="forcegen", default=False,
|
|
||||||
help="force generation of html files.")
|
|
||||||
|
|
||||||
def pytest_collect_file(path, parent):
|
|
||||||
if path.ext in (".txt", ".rst"):
|
|
||||||
project = getproject(path)
|
|
||||||
if project is not None:
|
|
||||||
return ReSTFile(path, parent=parent, project=project)
|
|
||||||
|
|
||||||
def getproject(path):
|
|
||||||
for parent in path.parts(reverse=True):
|
|
||||||
confrest = parent.join("confrest.py")
|
|
||||||
if confrest.check():
|
|
||||||
Project = confrest.pyimport().Project
|
|
||||||
return Project(parent)
|
|
||||||
|
|
||||||
class ReSTFile(py.test.collect.File):
|
|
||||||
def __init__(self, fspath, parent, project=None):
|
|
||||||
super(ReSTFile, self).__init__(fspath=fspath, parent=parent)
|
|
||||||
if project is None:
|
|
||||||
project = getproject(fspath)
|
|
||||||
assert project is not None
|
|
||||||
self.project = project
|
|
||||||
|
|
||||||
def collect(self):
|
|
||||||
return [
|
|
||||||
ReSTSyntaxTest(self.project, "ReSTSyntax", parent=self),
|
|
||||||
LinkCheckerMaker("checklinks", parent=self),
|
|
||||||
DoctestText("doctest", parent=self),
|
|
||||||
]
|
|
||||||
|
|
||||||
def deindent(s, sep='\n'):
|
|
||||||
leastspaces = -1
|
|
||||||
lines = s.split(sep)
|
|
||||||
for line in lines:
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
spaces = len(line) - len(line.lstrip())
|
|
||||||
if leastspaces == -1 or spaces < leastspaces:
|
|
||||||
leastspaces = spaces
|
|
||||||
if leastspaces == -1:
|
|
||||||
return s
|
|
||||||
for i, line in py.builtin.enumerate(lines):
|
|
||||||
if not line.strip():
|
|
||||||
lines[i] = ''
|
|
||||||
else:
|
|
||||||
lines[i] = line[leastspaces:]
|
|
||||||
return sep.join(lines)
|
|
||||||
|
|
||||||
class ReSTSyntaxTest(py.test.collect.Item):
|
|
||||||
def __init__(self, project, *args, **kwargs):
|
|
||||||
super(ReSTSyntaxTest, self).__init__(*args, **kwargs)
|
|
||||||
self.project = project
|
|
||||||
|
|
||||||
def reportinfo(self):
|
|
||||||
return self.fspath, None, "syntax check"
|
|
||||||
|
|
||||||
def runtest(self):
|
|
||||||
self.restcheck(py.path.svnwc(self.fspath))
|
|
||||||
|
|
||||||
def restcheck(self, path):
|
|
||||||
py.test.importorskip("docutils")
|
|
||||||
self.register_linkrole()
|
|
||||||
from docutils.utils import SystemMessage
|
|
||||||
try:
|
|
||||||
self._checkskip(path, self.project.get_htmloutputpath(path))
|
|
||||||
self.project.process(path)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except SystemMessage:
|
|
||||||
# we assume docutils printed info on stdout
|
|
||||||
py.test.fail("docutils processing failed, see captured stderr")
|
|
||||||
|
|
||||||
def register_linkrole(self):
|
|
||||||
from py.__.rest import directive
|
|
||||||
directive.register_linkrole('api', self.resolve_linkrole)
|
|
||||||
directive.register_linkrole('source', self.resolve_linkrole)
|
|
||||||
|
|
||||||
# XXX fake sphinx' "toctree" and refs
|
|
||||||
directive.register_linkrole('ref', self.resolve_linkrole)
|
|
||||||
|
|
||||||
from docutils.parsers.rst import directives
|
|
||||||
def toctree_directive(name, arguments, options, content, lineno,
|
|
||||||
content_offset, block_text, state, state_machine):
|
|
||||||
return []
|
|
||||||
toctree_directive.content = 1
|
|
||||||
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag,
|
|
||||||
'hidden': directives.flag}
|
|
||||||
directives.register_directive('toctree', toctree_directive)
|
|
||||||
self.register_pygments()
|
|
||||||
|
|
||||||
def register_pygments(self):
|
|
||||||
# taken from pygments-main/external/rst-directive.py
|
|
||||||
try:
|
|
||||||
from pygments.formatters import HtmlFormatter
|
|
||||||
except ImportError:
|
|
||||||
def pygments_directive(name, arguments, options, content, lineno,
|
|
||||||
content_offset, block_text, state, state_machine):
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
# The default formatter
|
|
||||||
DEFAULT = HtmlFormatter(noclasses=True)
|
|
||||||
# Add name -> formatter pairs for every variant you want to use
|
|
||||||
VARIANTS = {
|
|
||||||
# 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
from docutils import nodes
|
|
||||||
from docutils.parsers.rst import directives
|
|
||||||
|
|
||||||
from pygments import highlight
|
|
||||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
|
||||||
|
|
||||||
def pygments_directive(name, arguments, options, content, lineno,
|
|
||||||
content_offset, block_text, state, state_machine):
|
|
||||||
try:
|
|
||||||
lexer = get_lexer_by_name(arguments[0])
|
|
||||||
except ValueError:
|
|
||||||
# no lexer found - use the text one instead of an exception
|
|
||||||
lexer = TextLexer()
|
|
||||||
# take an arbitrary option if more than one is given
|
|
||||||
formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
|
|
||||||
parsed = highlight(u'\n'.join(content), lexer, formatter)
|
|
||||||
return [nodes.raw('', parsed, format='html')]
|
|
||||||
|
|
||||||
pygments_directive.arguments = (1, 0, 1)
|
|
||||||
pygments_directive.content = 1
|
|
||||||
pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
|
|
||||||
|
|
||||||
directives.register_directive('sourcecode', pygments_directive)
|
|
||||||
|
|
||||||
def resolve_linkrole(self, name, text, check=True):
|
|
||||||
apigen_relpath = self.project.apigen_relpath
|
|
||||||
|
|
||||||
if name == 'api':
|
|
||||||
if text == 'py':
|
|
||||||
return ('py', apigen_relpath + 'api/index.html')
|
|
||||||
else:
|
|
||||||
assert text.startswith('py.'), (
|
|
||||||
'api link "%s" does not point to the py package') % (text,)
|
|
||||||
dotted_name = text
|
|
||||||
if dotted_name.find('(') > -1:
|
|
||||||
dotted_name = dotted_name[:text.find('(')]
|
|
||||||
# remove pkg root
|
|
||||||
path = dotted_name.split('.')[1:]
|
|
||||||
dotted_name = '.'.join(path)
|
|
||||||
obj = py
|
|
||||||
if check:
|
|
||||||
for chunk in path:
|
|
||||||
try:
|
|
||||||
obj = getattr(obj, chunk)
|
|
||||||
except AttributeError:
|
|
||||||
raise AssertionError(
|
|
||||||
'problem with linkrole :api:`%s`: can not resolve '
|
|
||||||
'dotted name %s' % (text, dotted_name,))
|
|
||||||
return (text, apigen_relpath + 'api/%s.html' % (dotted_name,))
|
|
||||||
elif name == 'source':
|
|
||||||
assert text.startswith('py/'), ('source link "%s" does not point '
|
|
||||||
'to the py package') % (text,)
|
|
||||||
relpath = '/'.join(text.split('/')[1:])
|
|
||||||
if check:
|
|
||||||
pkgroot = py.__pkg__.getpath()
|
|
||||||
abspath = pkgroot.join(relpath)
|
|
||||||
assert pkgroot.join(relpath).check(), (
|
|
||||||
'problem with linkrole :source:`%s`: '
|
|
||||||
'path %s does not exist' % (text, relpath))
|
|
||||||
if relpath.endswith('/') or not relpath:
|
|
||||||
relpath += 'index.html'
|
|
||||||
else:
|
|
||||||
relpath += '.html'
|
|
||||||
return (text, apigen_relpath + 'source/%s' % (relpath,))
|
|
||||||
elif name == 'ref':
|
|
||||||
return ("", "")
|
|
||||||
|
|
||||||
def _checkskip(self, lpath, htmlpath=None):
|
|
||||||
if not self.config.getvalue("forcegen"):
|
|
||||||
lpath = py.path.local(lpath)
|
|
||||||
if htmlpath is not None:
|
|
||||||
htmlpath = py.path.local(htmlpath)
|
|
||||||
if lpath.ext == '.txt':
|
|
||||||
htmlpath = htmlpath or lpath.new(ext='.html')
|
|
||||||
if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime():
|
|
||||||
py.test.skip("html file is up to date, use --forcegen to regenerate")
|
|
||||||
#return [] # no need to rebuild
|
|
||||||
|
|
||||||
class DoctestText(py.test.collect.Item):
|
|
||||||
def reportinfo(self):
|
|
||||||
return self.fspath, None, "doctest"
|
|
||||||
|
|
||||||
def runtest(self):
|
|
||||||
content = self._normalize_linesep()
|
|
||||||
newcontent = self.config.hook.pytest_doctest_prepare_content(content=content)
|
|
||||||
if newcontent is not None:
|
|
||||||
content = newcontent
|
|
||||||
s = content
|
|
||||||
l = []
|
|
||||||
prefix = '.. >>> '
|
|
||||||
mod = py.std.types.ModuleType(self.fspath.purebasename)
|
|
||||||
skipchunk = False
|
|
||||||
for line in deindent(s).split('\n'):
|
|
||||||
stripped = line.strip()
|
|
||||||
if skipchunk and line.startswith(skipchunk):
|
|
||||||
print "skipping", line
|
|
||||||
continue
|
|
||||||
skipchunk = False
|
|
||||||
if stripped.startswith(prefix):
|
|
||||||
try:
|
|
||||||
exec py.code.Source(stripped[len(prefix):]).compile() in \
|
|
||||||
mod.__dict__
|
|
||||||
except ValueError, e:
|
|
||||||
if e.args and e.args[0] == "skipchunk":
|
|
||||||
skipchunk = " " * (len(line) - len(line.lstrip()))
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
l.append(line)
|
|
||||||
docstring = "\n".join(l)
|
|
||||||
mod.__doc__ = docstring
|
|
||||||
failed, tot = py.compat.doctest.testmod(mod, verbose=1)
|
|
||||||
if failed:
|
|
||||||
py.test.fail("doctest %s: %s failed out of %s" %(
|
|
||||||
self.fspath, failed, tot))
|
|
||||||
|
|
||||||
def _normalize_linesep(self):
|
|
||||||
# XXX quite nasty... but it works (fixes win32 issues)
|
|
||||||
s = self.fspath.read()
|
|
||||||
linesep = '\n'
|
|
||||||
if '\r' in s:
|
|
||||||
if '\n' not in s:
|
|
||||||
linesep = '\r'
|
|
||||||
else:
|
|
||||||
linesep = '\r\n'
|
|
||||||
s = s.replace(linesep, '\n')
|
|
||||||
return s
|
|
||||||
|
|
||||||
class LinkCheckerMaker(py.test.collect.Collector):
|
|
||||||
def collect(self):
|
|
||||||
return list(self.genlinkchecks())
|
|
||||||
|
|
||||||
def genlinkchecks(self):
|
|
||||||
path = self.fspath
|
|
||||||
# generating functions + args as single tests
|
|
||||||
timeout = self.config.getvalue("urlcheck_timeout")
|
|
||||||
for lineno, line in py.builtin.enumerate(path.readlines()):
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith('.. _'):
|
|
||||||
if line.startswith('.. _`'):
|
|
||||||
delim = '`:'
|
|
||||||
else:
|
|
||||||
delim = ':'
|
|
||||||
l = line.split(delim, 1)
|
|
||||||
if len(l) != 2:
|
|
||||||
continue
|
|
||||||
tryfn = l[1].strip()
|
|
||||||
name = "%s:%d" %(tryfn, lineno)
|
|
||||||
if tryfn.startswith('http:') or tryfn.startswith('https'):
|
|
||||||
if self.config.getvalue("urlcheck"):
|
|
||||||
yield CheckLink(name, parent=self,
|
|
||||||
args=(tryfn, path, lineno, timeout), checkfunc=urlcheck)
|
|
||||||
elif tryfn.startswith('webcal:'):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
i = tryfn.find('#')
|
|
||||||
if i != -1:
|
|
||||||
checkfn = tryfn[:i]
|
|
||||||
else:
|
|
||||||
checkfn = tryfn
|
|
||||||
if checkfn.strip() and (1 or checkfn.endswith('.html')):
|
|
||||||
yield CheckLink(name, parent=self,
|
|
||||||
args=(tryfn, path, lineno), checkfunc=localrefcheck)
|
|
||||||
|
|
||||||
class CheckLink(py.test.collect.Item):
|
|
||||||
def __init__(self, name, parent, args, checkfunc):
|
|
||||||
super(CheckLink, self).__init__(name, parent)
|
|
||||||
self.args = args
|
|
||||||
self.checkfunc = checkfunc
|
|
||||||
|
|
||||||
def runtest(self):
|
|
||||||
return self.checkfunc(*self.args)
|
|
||||||
|
|
||||||
def reportinfo(self, basedir=None):
|
|
||||||
return (self.fspath, self.args[2], "checklink: %s" % self.args[0])
|
|
||||||
|
|
||||||
def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN):
|
|
||||||
old = py.std.socket.getdefaulttimeout()
|
|
||||||
py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN)
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
print "trying remote", tryfn
|
|
||||||
py.std.urllib2.urlopen(tryfn)
|
|
||||||
finally:
|
|
||||||
py.std.socket.setdefaulttimeout(old)
|
|
||||||
except (py.std.urllib2.URLError, py.std.urllib2.HTTPError), e:
|
|
||||||
if getattr(e, 'code', None) in (401, 403): # authorization required, forbidden
|
|
||||||
py.test.skip("%s: %s" %(tryfn, str(e)))
|
|
||||||
else:
|
|
||||||
py.test.fail("remote reference error %r in %s:%d\n%s" %(
|
|
||||||
tryfn, path.basename, lineno+1, e))
|
|
||||||
|
|
||||||
def localrefcheck(tryfn, path, lineno):
|
|
||||||
# assume it should be a file
|
|
||||||
i = tryfn.find('#')
|
|
||||||
if tryfn.startswith('javascript:'):
|
|
||||||
return # don't check JS refs
|
|
||||||
if i != -1:
|
|
||||||
anchor = tryfn[i+1:]
|
|
||||||
tryfn = tryfn[:i]
|
|
||||||
else:
|
|
||||||
anchor = ''
|
|
||||||
fn = path.dirpath(tryfn)
|
|
||||||
ishtml = fn.ext == '.html'
|
|
||||||
fn = ishtml and fn.new(ext='.txt') or fn
|
|
||||||
print "filename is", fn
|
|
||||||
if not fn.check(): # not ishtml or not fn.check():
|
|
||||||
if not py.path.local(tryfn).check(): # the html could be there
|
|
||||||
py.test.fail("reference error %r in %s:%d" %(
|
|
||||||
tryfn, path.basename, lineno+1))
|
|
||||||
if anchor:
|
|
||||||
source = unicode(fn.read(), 'latin1')
|
|
||||||
source = source.lower().replace('-', ' ') # aehem
|
|
||||||
|
|
||||||
anchor = anchor.replace('-', ' ')
|
|
||||||
match2 = ".. _`%s`:" % anchor
|
|
||||||
match3 = ".. _%s:" % anchor
|
|
||||||
candidates = (anchor, match2, match3)
|
|
||||||
print "candidates", repr(candidates)
|
|
||||||
for line in source.split('\n'):
|
|
||||||
line = line.strip()
|
|
||||||
if line in candidates:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
py.test.fail("anchor reference error %s#%s in %s:%d" %(
|
|
||||||
tryfn, anchor, path.basename, lineno+1))
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# PLUGIN tests
|
|
||||||
#
|
|
||||||
|
|
||||||
def test_deindent():
|
|
||||||
assert deindent('foo') == 'foo'
|
|
||||||
assert deindent('foo\n bar') == 'foo\n bar'
|
|
||||||
assert deindent(' foo\n bar\n') == 'foo\nbar\n'
|
|
||||||
assert deindent(' foo\n\n bar\n') == 'foo\n\nbar\n'
|
|
||||||
assert deindent(' foo\n bar\n') == 'foo\n bar\n'
|
|
||||||
assert deindent(' foo\n bar\n') == ' foo\nbar\n'
|
|
||||||
|
|
||||||
class TestApigenLinkRole:
|
|
||||||
disabled = True
|
|
||||||
|
|
||||||
# these tests are moved here from the former py/doc/conftest.py
|
|
||||||
def test_resolve_linkrole(self):
|
|
||||||
from py.__.doc.conftest import get_apigen_relpath
|
|
||||||
apigen_relpath = get_apigen_relpath()
|
|
||||||
|
|
||||||
assert resolve_linkrole('api', 'py.foo.bar', False) == (
|
|
||||||
'py.foo.bar', apigen_relpath + 'api/foo.bar.html')
|
|
||||||
assert resolve_linkrole('api', 'py.foo.bar()', False) == (
|
|
||||||
'py.foo.bar()', apigen_relpath + 'api/foo.bar.html')
|
|
||||||
assert resolve_linkrole('api', 'py', False) == (
|
|
||||||
'py', apigen_relpath + 'api/index.html')
|
|
||||||
py.test.raises(AssertionError, 'resolve_linkrole("api", "foo.bar")')
|
|
||||||
assert resolve_linkrole('source', 'py/foo/bar.py', False) == (
|
|
||||||
'py/foo/bar.py', apigen_relpath + 'source/foo/bar.py.html')
|
|
||||||
assert resolve_linkrole('source', 'py/foo/', False) == (
|
|
||||||
'py/foo/', apigen_relpath + 'source/foo/index.html')
|
|
||||||
assert resolve_linkrole('source', 'py/', False) == (
|
|
||||||
'py/', apigen_relpath + 'source/index.html')
|
|
||||||
py.test.raises(AssertionError, 'resolve_linkrole("source", "/foo/bar/")')
|
|
||||||
|
|
||||||
def test_resolve_linkrole_check_api(self):
|
|
||||||
assert resolve_linkrole('api', 'py.test.ensuretemp')
|
|
||||||
py.test.raises(AssertionError, "resolve_linkrole('api', 'py.foo.baz')")
|
|
||||||
|
|
||||||
def test_resolve_linkrole_check_source(self):
|
|
||||||
assert resolve_linkrole('source', 'py/path/common.py')
|
|
||||||
py.test.raises(AssertionError,
|
|
||||||
"resolve_linkrole('source', 'py/foo/bar.py')")
|
|
||||||
|
|
||||||
|
|
||||||
class TestDoctest:
|
|
||||||
def pytest_funcarg__testdir(self, request):
|
|
||||||
testdir = request.getfuncargvalue("testdir")
|
|
||||||
assert request.module.__name__ == __name__
|
|
||||||
testdir.makepyfile(confrest="from py.__.misc.rest import Project")
|
|
||||||
for p in testdir.plugins:
|
|
||||||
if p == globals():
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
testdir.plugins.append(globals())
|
|
||||||
return testdir
|
|
||||||
|
|
||||||
def test_doctest_extra_exec(self, testdir):
|
|
||||||
xtxt = testdir.maketxtfile(x="""
|
|
||||||
hello::
|
|
||||||
.. >>> raise ValueError
|
|
||||||
>>> None
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(xtxt)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert failed == 1
|
|
||||||
|
|
||||||
def test_doctest_basic(self, testdir):
|
|
||||||
xtxt = testdir.maketxtfile(x="""
|
|
||||||
..
|
|
||||||
>>> from os.path import abspath
|
|
||||||
|
|
||||||
hello world
|
|
||||||
|
|
||||||
>>> assert abspath
|
|
||||||
>>> i=3
|
|
||||||
>>> print i
|
|
||||||
3
|
|
||||||
|
|
||||||
yes yes
|
|
||||||
|
|
||||||
>>> i
|
|
||||||
3
|
|
||||||
|
|
||||||
end
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(xtxt)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert passed + skipped == 2
|
|
||||||
|
|
||||||
def test_doctest_eol(self, testdir):
|
|
||||||
ytxt = testdir.maketxtfile(y=".. >>> 1 + 1\r\n 2\r\n\r\n")
|
|
||||||
reprec = testdir.inline_run(ytxt)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert passed + skipped == 2
|
|
||||||
|
|
||||||
def test_doctest_indentation(self, testdir):
|
|
||||||
footxt = testdir.maketxtfile(foo=
|
|
||||||
'..\n >>> print "foo\\n bar"\n foo\n bar\n')
|
|
||||||
reprec = testdir.inline_run(footxt)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert skipped + passed == 2
|
|
||||||
|
|
||||||
def test_js_ignore(self, testdir):
|
|
||||||
xtxt = testdir.maketxtfile(xtxt="""
|
|
||||||
`blah`_
|
|
||||||
|
|
||||||
.. _`blah`: javascript:some_function()
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(xtxt)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert skipped + passed == 3
|
|
||||||
|
|
||||||
def test_pytest_doctest_prepare_content(self, testdir):
|
|
||||||
l = []
|
|
||||||
class MyPlugin:
|
|
||||||
def pytest_doctest_prepare_content(self, content):
|
|
||||||
l.append(content)
|
|
||||||
return content.replace("False", "True")
|
|
||||||
|
|
||||||
testdir.plugins.append(MyPlugin())
|
|
||||||
|
|
||||||
xtxt = testdir.maketxtfile(x="""
|
|
||||||
hello:
|
|
||||||
|
|
||||||
>>> 2 == 2
|
|
||||||
False
|
|
||||||
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(xtxt)
|
|
||||||
assert len(l) == 1
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert passed >= 1
|
|
||||||
assert not failed
|
|
||||||
assert skipped <= 1
|
|
||||||
|
|
||||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_restdoc.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -4,6 +4,9 @@ pytest_resultlog plugin
|
||||||
|
|
||||||
resultlog plugin for machine-readable logging of test results.
|
resultlog plugin for machine-readable logging of test results.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
Useful for buildbot integration code.
|
Useful for buildbot integration code.
|
||||||
|
|
||||||
command line options
|
command line options
|
||||||
|
@ -13,268 +16,19 @@ command line options
|
||||||
``--resultlog=path``
|
``--resultlog=path``
|
||||||
path for machine-readable result log.
|
path for machine-readable result log.
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_resultlog.py`_ plugin source code
|
1. Download `pytest_resultlog.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_resultlog.py`` into your import path
|
2. put it somewhere as ``pytest_resultlog.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_resultlog.py``:
|
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_resultlog.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""resultlog plugin for machine-readable logging of test results.
|
|
||||||
Useful for buildbot integration code.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.addgroup("resultlog", "resultlog plugin options")
|
|
||||||
group.addoption('--resultlog', action="store", dest="resultlog", metavar="path", default=None,
|
|
||||||
help="path for machine-readable result log.")
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
resultlog = config.option.resultlog
|
|
||||||
if resultlog:
|
|
||||||
logfile = open(resultlog, 'w', 1) # line buffered
|
|
||||||
config._resultlog = ResultLog(logfile)
|
|
||||||
config.pluginmanager.register(config._resultlog)
|
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
|
||||||
resultlog = getattr(config, '_resultlog', None)
|
|
||||||
if resultlog:
|
|
||||||
resultlog.logfile.close()
|
|
||||||
del config._resultlog
|
|
||||||
config.pluginmanager.unregister(resultlog)
|
|
||||||
|
|
||||||
def generic_path(item):
|
|
||||||
chain = item.listchain()
|
|
||||||
gpath = [chain[0].name]
|
|
||||||
fspath = chain[0].fspath
|
|
||||||
fspart = False
|
|
||||||
for node in chain[1:]:
|
|
||||||
newfspath = node.fspath
|
|
||||||
if newfspath == fspath:
|
|
||||||
if fspart:
|
|
||||||
gpath.append(':')
|
|
||||||
fspart = False
|
|
||||||
else:
|
|
||||||
gpath.append('.')
|
|
||||||
else:
|
|
||||||
gpath.append('/')
|
|
||||||
fspart = True
|
|
||||||
name = node.name
|
|
||||||
if name[0] in '([':
|
|
||||||
gpath.pop()
|
|
||||||
gpath.append(name)
|
|
||||||
fspath = newfspath
|
|
||||||
return ''.join(gpath)
|
|
||||||
|
|
||||||
class ResultLog(object):
|
|
||||||
def __init__(self, logfile):
|
|
||||||
self.logfile = logfile # preferably line buffered
|
|
||||||
|
|
||||||
def write_log_entry(self, testpath, shortrepr, longrepr):
|
|
||||||
print >>self.logfile, "%s %s" % (shortrepr, testpath)
|
|
||||||
for line in longrepr.splitlines():
|
|
||||||
print >>self.logfile, " %s" % line
|
|
||||||
|
|
||||||
def log_outcome(self, node, shortrepr, longrepr):
|
|
||||||
testpath = generic_path(node)
|
|
||||||
self.write_log_entry(testpath, shortrepr, longrepr)
|
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, rep):
|
|
||||||
code = rep.shortrepr
|
|
||||||
if rep.passed:
|
|
||||||
longrepr = ""
|
|
||||||
elif rep.failed:
|
|
||||||
longrepr = str(rep.longrepr)
|
|
||||||
elif rep.skipped:
|
|
||||||
longrepr = str(rep.longrepr.reprcrash.message)
|
|
||||||
self.log_outcome(rep.item, code, longrepr)
|
|
||||||
|
|
||||||
def pytest_collectreport(self, rep):
|
|
||||||
if not rep.passed:
|
|
||||||
if rep.failed:
|
|
||||||
code = "F"
|
|
||||||
else:
|
|
||||||
assert rep.skipped
|
|
||||||
code = "S"
|
|
||||||
longrepr = str(rep.longrepr.reprcrash)
|
|
||||||
self.log_outcome(rep.collector, code, longrepr)
|
|
||||||
|
|
||||||
def pytest_internalerror(self, excrepr):
|
|
||||||
path = excrepr.reprcrash.path
|
|
||||||
self.write_log_entry(path, '!', str(excrepr))
|
|
||||||
|
|
||||||
|
|
||||||
# ===============================================================================
|
|
||||||
#
|
|
||||||
# plugin tests
|
|
||||||
#
|
|
||||||
# ===============================================================================
|
|
||||||
|
|
||||||
import os, StringIO
|
|
||||||
|
|
||||||
def test_generic_path():
|
|
||||||
from py.__.test.collect import Node, Item, FSCollector
|
|
||||||
p1 = Node('a')
|
|
||||||
assert p1.fspath is None
|
|
||||||
p2 = Node('B', parent=p1)
|
|
||||||
p3 = Node('()', parent = p2)
|
|
||||||
item = Item('c', parent = p3)
|
|
||||||
|
|
||||||
res = generic_path(item)
|
|
||||||
assert res == 'a.B().c'
|
|
||||||
|
|
||||||
p0 = FSCollector('proj/test')
|
|
||||||
p1 = FSCollector('proj/test/a', parent=p0)
|
|
||||||
p2 = Node('B', parent=p1)
|
|
||||||
p3 = Node('()', parent = p2)
|
|
||||||
p4 = Node('c', parent=p3)
|
|
||||||
item = Item('[1]', parent = p4)
|
|
||||||
|
|
||||||
res = generic_path(item)
|
|
||||||
assert res == 'test/a:B().c[1]'
|
|
||||||
|
|
||||||
def test_write_log_entry():
|
|
||||||
reslog = ResultLog(None)
|
|
||||||
reslog.logfile = StringIO.StringIO()
|
|
||||||
reslog.write_log_entry('name', '.', '')
|
|
||||||
entry = reslog.logfile.getvalue()
|
|
||||||
assert entry[-1] == '\n'
|
|
||||||
entry_lines = entry.splitlines()
|
|
||||||
assert len(entry_lines) == 1
|
|
||||||
assert entry_lines[0] == '. name'
|
|
||||||
|
|
||||||
reslog.logfile = StringIO.StringIO()
|
|
||||||
reslog.write_log_entry('name', 's', 'Skipped')
|
|
||||||
entry = reslog.logfile.getvalue()
|
|
||||||
assert entry[-1] == '\n'
|
|
||||||
entry_lines = entry.splitlines()
|
|
||||||
assert len(entry_lines) == 2
|
|
||||||
assert entry_lines[0] == 's name'
|
|
||||||
assert entry_lines[1] == ' Skipped'
|
|
||||||
|
|
||||||
reslog.logfile = StringIO.StringIO()
|
|
||||||
reslog.write_log_entry('name', 's', 'Skipped\n')
|
|
||||||
entry = reslog.logfile.getvalue()
|
|
||||||
assert entry[-1] == '\n'
|
|
||||||
entry_lines = entry.splitlines()
|
|
||||||
assert len(entry_lines) == 2
|
|
||||||
assert entry_lines[0] == 's name'
|
|
||||||
assert entry_lines[1] == ' Skipped'
|
|
||||||
|
|
||||||
reslog.logfile = StringIO.StringIO()
|
|
||||||
longrepr = ' tb1\n tb 2\nE tb3\nSome Error'
|
|
||||||
reslog.write_log_entry('name', 'F', longrepr)
|
|
||||||
entry = reslog.logfile.getvalue()
|
|
||||||
assert entry[-1] == '\n'
|
|
||||||
entry_lines = entry.splitlines()
|
|
||||||
assert len(entry_lines) == 5
|
|
||||||
assert entry_lines[0] == 'F name'
|
|
||||||
assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()]
|
|
||||||
|
|
||||||
|
|
||||||
class TestWithFunctionIntegration:
|
|
||||||
# XXX (hpk) i think that the resultlog plugin should
|
|
||||||
# provide a Parser object so that one can remain
|
|
||||||
# ignorant regarding formatting details.
|
|
||||||
def getresultlog(self, testdir, arg):
|
|
||||||
resultlog = testdir.tmpdir.join("resultlog")
|
|
||||||
testdir.plugins.append("resultlog")
|
|
||||||
args = ["--resultlog=%s" % resultlog] + [arg]
|
|
||||||
testdir.runpytest(*args)
|
|
||||||
return filter(None, resultlog.readlines(cr=0))
|
|
||||||
|
|
||||||
def test_collection_report(self, testdir):
|
|
||||||
ok = testdir.makepyfile(test_collection_ok="")
|
|
||||||
skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
|
|
||||||
fail = testdir.makepyfile(test_collection_fail="XXX")
|
|
||||||
lines = self.getresultlog(testdir, ok)
|
|
||||||
assert not lines
|
|
||||||
|
|
||||||
lines = self.getresultlog(testdir, skip)
|
|
||||||
assert len(lines) == 2
|
|
||||||
assert lines[0].startswith("S ")
|
|
||||||
assert lines[0].endswith("test_collection_skip.py")
|
|
||||||
assert lines[1].startswith(" ")
|
|
||||||
assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'")
|
|
||||||
|
|
||||||
lines = self.getresultlog(testdir, fail)
|
|
||||||
assert lines
|
|
||||||
assert lines[0].startswith("F ")
|
|
||||||
assert lines[0].endswith("test_collection_fail.py"), lines[0]
|
|
||||||
for x in lines[1:]:
|
|
||||||
assert x.startswith(" ")
|
|
||||||
assert "XXX" in "".join(lines[1:])
|
|
||||||
|
|
||||||
def test_log_test_outcomes(self, testdir):
|
|
||||||
mod = testdir.makepyfile(test_mod="""
|
|
||||||
import py
|
|
||||||
def test_pass(): pass
|
|
||||||
def test_skip(): py.test.skip("hello")
|
|
||||||
def test_fail(): raise ValueError("val")
|
|
||||||
""")
|
|
||||||
lines = self.getresultlog(testdir, mod)
|
|
||||||
assert len(lines) >= 3
|
|
||||||
assert lines[0].startswith(". ")
|
|
||||||
assert lines[0].endswith("test_pass")
|
|
||||||
assert lines[1].startswith("s "), lines[1]
|
|
||||||
assert lines[1].endswith("test_skip")
|
|
||||||
assert lines[2].find("hello") != -1
|
|
||||||
|
|
||||||
assert lines[3].startswith("F ")
|
|
||||||
assert lines[3].endswith("test_fail")
|
|
||||||
tb = "".join(lines[4:])
|
|
||||||
assert tb.find("ValueError") != -1
|
|
||||||
|
|
||||||
def test_internal_exception(self):
|
|
||||||
# they are produced for example by a teardown failing
|
|
||||||
# at the end of the run
|
|
||||||
try:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
excinfo = py.code.ExceptionInfo()
|
|
||||||
reslog = ResultLog(StringIO.StringIO())
|
|
||||||
reslog.pytest_internalerror(excinfo.getrepr())
|
|
||||||
entry = reslog.logfile.getvalue()
|
|
||||||
entry_lines = entry.splitlines()
|
|
||||||
|
|
||||||
assert entry_lines[0].startswith('! ')
|
|
||||||
assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc
|
|
||||||
assert entry_lines[-1][0] == ' '
|
|
||||||
assert 'ValueError' in entry
|
|
||||||
|
|
||||||
def test_generic(testdir, LineMatcher):
|
|
||||||
testdir.plugins.append("resultlog")
|
|
||||||
testdir.makepyfile("""
|
|
||||||
import py
|
|
||||||
def test_pass():
|
|
||||||
pass
|
|
||||||
def test_fail():
|
|
||||||
assert 0
|
|
||||||
def test_skip():
|
|
||||||
py.test.skip("")
|
|
||||||
""")
|
|
||||||
testdir.runpytest("--resultlog=result.log")
|
|
||||||
lines = testdir.tmpdir.join("result.log").readlines(cr=0)
|
|
||||||
LineMatcher(lines).fnmatch_lines([
|
|
||||||
". *:test_pass",
|
|
||||||
"F *:test_fail",
|
|
||||||
"s *:test_skip",
|
|
||||||
])
|
|
||||||
|
|
||||||
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_resultlog.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
|
|
||||||
pytest_runner plugin
|
|
||||||
====================
|
|
||||||
|
|
||||||
collect and run test items and create reports.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command line options
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
|
|
||||||
``--boxed``
|
|
||||||
box each test run in a separate process
|
|
||||||
|
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_runner.py`_ plugin source code
|
|
||||||
2. put it somewhere as ``pytest_runner.py`` into your import path
|
|
||||||
3. a subsequent test run will now use your local version!
|
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_runner.py``:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
collect and run test items and create reports.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
from py.__.test.outcome import Skipped
|
|
||||||
|
|
||||||
#
|
|
||||||
# pytest plugin hooks
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.getgroup("general")
|
|
||||||
group.addoption('--boxed',
|
|
||||||
action="store_true", dest="boxed", default=False,
|
|
||||||
help="box each test run in a separate process")
|
|
||||||
|
|
||||||
# XXX move to pytest_sessionstart and fix py.test owns tests
|
|
||||||
def pytest_configure(config):
|
|
||||||
config._setupstate = SetupState()
|
|
||||||
|
|
||||||
def pytest_sessionfinish(session, exitstatus):
|
|
||||||
# XXX see above
|
|
||||||
if hasattr(session.config, '_setupstate'):
|
|
||||||
session.config._setupstate.teardown_all()
|
|
||||||
# prevent logging module atexit handler from choking on
|
|
||||||
# its attempt to close already closed streams
|
|
||||||
# see http://bugs.python.org/issue6333
|
|
||||||
mod = py.std.sys.modules.get("logging", None)
|
|
||||||
if mod is not None:
|
|
||||||
mod.raiseExceptions = False
|
|
||||||
|
|
||||||
def pytest_make_collect_report(collector):
|
|
||||||
call = collector.config.guardedcall(
|
|
||||||
lambda: collector._memocollect()
|
|
||||||
)
|
|
||||||
result = None
|
|
||||||
if not call.excinfo:
|
|
||||||
result = call.result
|
|
||||||
return CollectReport(collector, result, call.excinfo, call.outerr)
|
|
||||||
|
|
||||||
return report
|
|
||||||
|
|
||||||
def pytest_runtest_protocol(item):
|
|
||||||
if item.config.getvalue("boxed"):
|
|
||||||
reports = forked_run_report(item)
|
|
||||||
for rep in reports:
|
|
||||||
item.config.hook.pytest_runtest_logreport(rep=rep)
|
|
||||||
else:
|
|
||||||
runtestprotocol(item)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def runtestprotocol(item, log=True):
|
|
||||||
rep = call_and_report(item, "setup", log)
|
|
||||||
reports = [rep]
|
|
||||||
if rep.passed:
|
|
||||||
reports.append(call_and_report(item, "call", log))
|
|
||||||
reports.append(call_and_report(item, "teardown", log))
|
|
||||||
return reports
|
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
|
||||||
item.config._setupstate.prepare(item)
|
|
||||||
|
|
||||||
def pytest_runtest_call(item):
|
|
||||||
if not item._deprecated_testexecution():
|
|
||||||
item.runtest()
|
|
||||||
|
|
||||||
def pytest_runtest_makereport(item, call):
|
|
||||||
return ItemTestReport(item, call.excinfo, call.when, call.outerr)
|
|
||||||
|
|
||||||
def pytest_runtest_teardown(item):
|
|
||||||
item.config._setupstate.teardown_exact(item)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Implementation
|
|
||||||
|
|
||||||
def call_and_report(item, when, log=True):
|
|
||||||
call = RuntestHookCall(item, when)
|
|
||||||
hook = item.config.hook
|
|
||||||
report = hook.pytest_runtest_makereport(item=item, call=call)
|
|
||||||
if log and (when == "call" or not report.passed):
|
|
||||||
hook.pytest_runtest_logreport(rep=report)
|
|
||||||
return report
|
|
||||||
|
|
||||||
|
|
||||||
class RuntestHookCall:
|
|
||||||
excinfo = None
|
|
||||||
_prefix = "pytest_runtest_"
|
|
||||||
def __init__(self, item, when):
|
|
||||||
self.when = when
|
|
||||||
hookname = self._prefix + when
|
|
||||||
hook = getattr(item.config.hook, hookname)
|
|
||||||
capture = item.config._getcapture()
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self.result = hook(item=item)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.excinfo = py.code.ExceptionInfo()
|
|
||||||
finally:
|
|
||||||
self.outerr = capture.reset()
|
|
||||||
|
|
||||||
def forked_run_report(item):
|
|
||||||
# for now, we run setup/teardown in the subprocess
|
|
||||||
# XXX optionally allow sharing of setup/teardown
|
|
||||||
EXITSTATUS_TESTEXIT = 4
|
|
||||||
from py.__.test.dist.mypickle import ImmutablePickler
|
|
||||||
ipickle = ImmutablePickler(uneven=0)
|
|
||||||
ipickle.selfmemoize(item.config)
|
|
||||||
# XXX workaround the issue that 2.6 cannot pickle
|
|
||||||
# instances of classes defined in global conftest.py files
|
|
||||||
ipickle.selfmemoize(item)
|
|
||||||
def runforked():
|
|
||||||
try:
|
|
||||||
reports = runtestprotocol(item, log=False)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
py.std.os._exit(EXITSTATUS_TESTEXIT)
|
|
||||||
return ipickle.dumps(reports)
|
|
||||||
|
|
||||||
ff = py.process.ForkedFunc(runforked)
|
|
||||||
result = ff.waitfinish()
|
|
||||||
if result.retval is not None:
|
|
||||||
return ipickle.loads(result.retval)
|
|
||||||
else:
|
|
||||||
if result.exitstatus == EXITSTATUS_TESTEXIT:
|
|
||||||
py.test.exit("forked test item %s raised Exit" %(item,))
|
|
||||||
return [report_process_crash(item, result)]
|
|
||||||
|
|
||||||
def report_process_crash(item, result):
|
|
||||||
path, lineno = item._getfslineno()
|
|
||||||
info = "%s:%s: running the test CRASHED with signal %d" %(
|
|
||||||
path, lineno, result.signal)
|
|
||||||
return ItemTestReport(item, excinfo=info, when="???")
|
|
||||||
|
|
||||||
class BaseReport(object):
|
|
||||||
def __repr__(self):
|
|
||||||
l = ["%s=%s" %(key, value)
|
|
||||||
for key, value in self.__dict__.items()]
|
|
||||||
return "<%s %s>" %(self.__class__.__name__, " ".join(l),)
|
|
||||||
|
|
||||||
def toterminal(self, out):
|
|
||||||
longrepr = self.longrepr
|
|
||||||
if hasattr(longrepr, 'toterminal'):
|
|
||||||
longrepr.toterminal(out)
|
|
||||||
else:
|
|
||||||
out.line(str(longrepr))
|
|
||||||
|
|
||||||
class ItemTestReport(BaseReport):
|
|
||||||
failed = passed = skipped = False
|
|
||||||
|
|
||||||
def __init__(self, item, excinfo=None, when=None, outerr=None):
|
|
||||||
self.item = item
|
|
||||||
self.when = when
|
|
||||||
self.outerr = outerr
|
|
||||||
if item and when != "setup":
|
|
||||||
self.keywords = item.readkeywords()
|
|
||||||
else:
|
|
||||||
# if we fail during setup it might mean
|
|
||||||
# we are not able to access the underlying object
|
|
||||||
# this might e.g. happen if we are unpickled
|
|
||||||
# and our parent collector did not collect us
|
|
||||||
# (because it e.g. skipped for platform reasons)
|
|
||||||
self.keywords = {}
|
|
||||||
if not excinfo:
|
|
||||||
self.passed = True
|
|
||||||
self.shortrepr = "."
|
|
||||||
else:
|
|
||||||
if not isinstance(excinfo, py.code.ExceptionInfo):
|
|
||||||
self.failed = True
|
|
||||||
shortrepr = "?"
|
|
||||||
longrepr = excinfo
|
|
||||||
elif excinfo.errisinstance(Skipped):
|
|
||||||
self.skipped = True
|
|
||||||
shortrepr = "s"
|
|
||||||
longrepr = self.item._repr_failure_py(excinfo, outerr)
|
|
||||||
else:
|
|
||||||
self.failed = True
|
|
||||||
shortrepr = self.item.shortfailurerepr
|
|
||||||
if self.when == "call":
|
|
||||||
longrepr = self.item.repr_failure(excinfo, outerr)
|
|
||||||
else: # exception in setup or teardown
|
|
||||||
longrepr = self.item._repr_failure_py(excinfo, outerr)
|
|
||||||
shortrepr = shortrepr.lower()
|
|
||||||
self.shortrepr = shortrepr
|
|
||||||
self.longrepr = longrepr
|
|
||||||
|
|
||||||
def getnode(self):
|
|
||||||
return self.item
|
|
||||||
|
|
||||||
class CollectReport(BaseReport):
|
|
||||||
skipped = failed = passed = False
|
|
||||||
|
|
||||||
def __init__(self, collector, result, excinfo=None, outerr=None):
|
|
||||||
self.collector = collector
|
|
||||||
if not excinfo:
|
|
||||||
self.passed = True
|
|
||||||
self.result = result
|
|
||||||
else:
|
|
||||||
self.outerr = outerr
|
|
||||||
self.longrepr = self.collector._repr_failure_py(excinfo, outerr)
|
|
||||||
if excinfo.errisinstance(Skipped):
|
|
||||||
self.skipped = True
|
|
||||||
self.reason = str(excinfo.value)
|
|
||||||
else:
|
|
||||||
self.failed = True
|
|
||||||
|
|
||||||
def getnode(self):
|
|
||||||
return self.collector
|
|
||||||
|
|
||||||
class SetupState(object):
|
|
||||||
""" shared state for setting up/tearing down test items or collectors. """
|
|
||||||
def __init__(self):
|
|
||||||
self.stack = []
|
|
||||||
self._finalizers = {}
|
|
||||||
|
|
||||||
def addfinalizer(self, finalizer, colitem):
|
|
||||||
""" attach a finalizer to the given colitem.
|
|
||||||
if colitem is None, this will add a finalizer that
|
|
||||||
is called at the end of teardown_all().
|
|
||||||
"""
|
|
||||||
assert callable(finalizer)
|
|
||||||
#assert colitem in self.stack
|
|
||||||
self._finalizers.setdefault(colitem, []).append(finalizer)
|
|
||||||
|
|
||||||
def _pop_and_teardown(self):
|
|
||||||
colitem = self.stack.pop()
|
|
||||||
self._teardown_with_finalization(colitem)
|
|
||||||
|
|
||||||
def _callfinalizers(self, colitem):
|
|
||||||
finalizers = self._finalizers.pop(colitem, None)
|
|
||||||
while finalizers:
|
|
||||||
fin = finalizers.pop()
|
|
||||||
fin()
|
|
||||||
|
|
||||||
def _teardown_with_finalization(self, colitem):
|
|
||||||
self._callfinalizers(colitem)
|
|
||||||
if colitem:
|
|
||||||
colitem.teardown()
|
|
||||||
for colitem in self._finalizers:
|
|
||||||
assert colitem is None or colitem in self.stack
|
|
||||||
|
|
||||||
def teardown_all(self):
|
|
||||||
while self.stack:
|
|
||||||
self._pop_and_teardown()
|
|
||||||
self._teardown_with_finalization(None)
|
|
||||||
assert not self._finalizers
|
|
||||||
|
|
||||||
def teardown_exact(self, item):
|
|
||||||
if item == self.stack[-1]:
|
|
||||||
self._pop_and_teardown()
|
|
||||||
else:
|
|
||||||
self._callfinalizers(item)
|
|
||||||
|
|
||||||
def prepare(self, colitem):
|
|
||||||
""" setup objects along the collector chain to the test-method
|
|
||||||
and teardown previously setup objects."""
|
|
||||||
needed_collectors = colitem.listchain()
|
|
||||||
while self.stack:
|
|
||||||
if self.stack == needed_collectors[:len(self.stack)]:
|
|
||||||
break
|
|
||||||
self._pop_and_teardown()
|
|
||||||
for col in needed_collectors[len(self.stack):]:
|
|
||||||
col.setup()
|
|
||||||
self.stack.append(col)
|
|
||||||
|
|
||||||
.. _`pytest_runner.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_runner.py
|
|
||||||
.. _`extend`: ../extend.html
|
|
||||||
.. _`plugins`: index.html
|
|
||||||
.. _`contact`: ../../contact.html
|
|
||||||
.. _`checkout the py.test development version`: ../../download.html#checkout
|
|
|
@ -2,430 +2,26 @@
|
||||||
pytest_terminal plugin
|
pytest_terminal plugin
|
||||||
======================
|
======================
|
||||||
|
|
||||||
terminal reporting of the full testing process.
|
Implements terminal reporting of the full testing process.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
This is a good source for looking at the various reporting hooks.
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
Getting and improving this plugin
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_terminal.py`_ plugin source code
|
1. Download `pytest_terminal.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_terminal.py`` into your import path
|
2. put it somewhere as ``pytest_terminal.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_terminal.py``:
|
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_terminal.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
terminal reporting of the full testing process.
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
|
||||||
if config.option.collectonly:
|
|
||||||
reporter = CollectonlyReporter(config)
|
|
||||||
else:
|
|
||||||
reporter = TerminalReporter(config)
|
|
||||||
# XXX see remote.py's XXX
|
|
||||||
for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
|
|
||||||
if hasattr(config, attr):
|
|
||||||
#print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
|
|
||||||
name = attr.split("_")[-1]
|
|
||||||
assert hasattr(self.reporter._tw, name), name
|
|
||||||
setattr(reporter._tw, name, getattr(config, attr))
|
|
||||||
config.pluginmanager.register(reporter)
|
|
||||||
|
|
||||||
class TerminalReporter:
|
|
||||||
def __init__(self, config, file=None):
|
|
||||||
self.config = config
|
|
||||||
self.stats = {}
|
|
||||||
self.curdir = py.path.local()
|
|
||||||
if file is None:
|
|
||||||
file = py.std.sys.stdout
|
|
||||||
self._tw = py.io.TerminalWriter(file)
|
|
||||||
self.currentfspath = None
|
|
||||||
self.gateway2info = {}
|
|
||||||
|
|
||||||
def write_fspath_result(self, fspath, res):
|
|
||||||
fspath = self.curdir.bestrelpath(fspath)
|
|
||||||
if fspath != self.currentfspath:
|
|
||||||
self._tw.line()
|
|
||||||
relpath = self.curdir.bestrelpath(fspath)
|
|
||||||
self._tw.write(relpath + " ")
|
|
||||||
self.currentfspath = fspath
|
|
||||||
self._tw.write(res)
|
|
||||||
|
|
||||||
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
|
||||||
if self.currentfspath != prefix:
|
|
||||||
self._tw.line()
|
|
||||||
self.currentfspath = prefix
|
|
||||||
self._tw.write(prefix)
|
|
||||||
if extra:
|
|
||||||
self._tw.write(extra, **kwargs)
|
|
||||||
self.currentfspath = -2
|
|
||||||
|
|
||||||
def ensure_newline(self):
|
|
||||||
if self.currentfspath:
|
|
||||||
self._tw.line()
|
|
||||||
self.currentfspath = None
|
|
||||||
|
|
||||||
def write_line(self, line, **markup):
|
|
||||||
line = str(line)
|
|
||||||
self.ensure_newline()
|
|
||||||
self._tw.line(line, **markup)
|
|
||||||
|
|
||||||
def write_sep(self, sep, title=None, **markup):
|
|
||||||
self.ensure_newline()
|
|
||||||
self._tw.sep(sep, title, **markup)
|
|
||||||
|
|
||||||
def getcategoryletterword(self, rep):
|
|
||||||
res = self.config.hook.pytest_report_teststatus(rep=rep)
|
|
||||||
if res:
|
|
||||||
return res
|
|
||||||
for cat in 'skipped failed passed ???'.split():
|
|
||||||
if getattr(rep, cat, None):
|
|
||||||
break
|
|
||||||
return cat, self.getoutcomeletter(rep), self.getoutcomeword(rep)
|
|
||||||
|
|
||||||
def getoutcomeletter(self, rep):
|
|
||||||
return rep.shortrepr
|
|
||||||
|
|
||||||
def getoutcomeword(self, rep):
|
|
||||||
if rep.passed:
|
|
||||||
return "PASS", dict(green=True)
|
|
||||||
elif rep.failed:
|
|
||||||
return "FAIL", dict(red=True)
|
|
||||||
elif rep.skipped:
|
|
||||||
return "SKIP"
|
|
||||||
else:
|
|
||||||
return "???", dict(red=True)
|
|
||||||
|
|
||||||
def pytest_internalerror(self, excrepr):
|
|
||||||
for line in str(excrepr).split("\n"):
|
|
||||||
self.write_line("INTERNALERROR> " + line)
|
|
||||||
|
|
||||||
def pyexecnet_gwmanage_newgateway(self, gateway, platinfo):
|
|
||||||
#self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec))
|
|
||||||
d = {}
|
|
||||||
d['version'] = repr_pythonversion(platinfo.version_info)
|
|
||||||
d['id'] = gateway.id
|
|
||||||
d['spec'] = gateway.spec._spec
|
|
||||||
d['platform'] = platinfo.platform
|
|
||||||
if self.config.option.verbose:
|
|
||||||
d['extra'] = "- " + platinfo.executable
|
|
||||||
else:
|
|
||||||
d['extra'] = ""
|
|
||||||
d['cwd'] = platinfo.cwd
|
|
||||||
infoline = ("%(id)s %(spec)s -- platform %(platform)s, "
|
|
||||||
"Python %(version)s "
|
|
||||||
"cwd: %(cwd)s"
|
|
||||||
"%(extra)s" % d)
|
|
||||||
self.write_line(infoline)
|
|
||||||
self.gateway2info[gateway] = infoline
|
|
||||||
|
|
||||||
def pyexecnet_gwmanage_rsyncstart(self, source, gateways):
|
|
||||||
targets = ", ".join([gw.id for gw in gateways])
|
|
||||||
msg = "rsyncstart: %s -> %s" %(source, targets)
|
|
||||||
if not self.config.option.verbose:
|
|
||||||
msg += " # use --verbose to see rsync progress"
|
|
||||||
self.write_line(msg)
|
|
||||||
|
|
||||||
def pyexecnet_gwmanage_rsyncfinish(self, source, gateways):
|
|
||||||
targets = ", ".join([gw.id for gw in gateways])
|
|
||||||
self.write_line("rsyncfinish: %s -> %s" %(source, targets))
|
|
||||||
|
|
||||||
def pytest_plugin_registered(self, plugin):
|
|
||||||
if self.config.option.traceconfig:
|
|
||||||
msg = "PLUGIN registered: %s" %(plugin,)
|
|
||||||
# XXX this event may happen during setup/teardown time
|
|
||||||
# which unfortunately captures our output here
|
|
||||||
# which garbles our output if we use self.write_line
|
|
||||||
self.write_line(msg)
|
|
||||||
|
|
||||||
def pytest_testnodeready(self, node):
|
|
||||||
self.write_line("%s txnode ready to receive tests" %(node.gateway.id,))
|
|
||||||
|
|
||||||
def pytest_testnodedown(self, node, error):
|
|
||||||
if error:
|
|
||||||
self.write_line("%s node down, error: %s" %(node.gateway.id, error))
|
|
||||||
|
|
||||||
def pytest_trace(self, category, msg):
|
|
||||||
if self.config.option.debug or \
|
|
||||||
self.config.option.traceconfig and category.find("config") != -1:
|
|
||||||
self.write_line("[%s] %s" %(category, msg))
|
|
||||||
|
|
||||||
def pytest_rescheduleitems(self, items):
|
|
||||||
if self.config.option.debug:
|
|
||||||
self.write_sep("!", "RESCHEDULING %s " %(items,))
|
|
||||||
|
|
||||||
def pytest_deselected(self, items):
|
|
||||||
self.stats.setdefault('deselected', []).append(items)
|
|
||||||
|
|
||||||
def pytest_itemstart(self, item, node=None):
|
|
||||||
if self.config.option.dist != "no":
|
|
||||||
# for dist-testing situations itemstart means we
|
|
||||||
# queued the item for sending, not interesting (unless debugging)
|
|
||||||
if self.config.option.debug:
|
|
||||||
line = self._reportinfoline(item)
|
|
||||||
extra = ""
|
|
||||||
if node:
|
|
||||||
extra = "-> " + str(node.gateway.id)
|
|
||||||
self.write_ensure_prefix(line, extra)
|
|
||||||
else:
|
|
||||||
if self.config.option.verbose:
|
|
||||||
line = self._reportinfoline(item)
|
|
||||||
self.write_ensure_prefix(line, "")
|
|
||||||
else:
|
|
||||||
# ensure that the path is printed before the
|
|
||||||
# 1st test of a module starts running
|
|
||||||
fspath, lineno, msg = self._getreportinfo(item)
|
|
||||||
self.write_fspath_result(fspath, "")
|
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, rep):
|
|
||||||
if rep.passed and rep.when in ("setup", "teardown"):
|
|
||||||
return
|
|
||||||
fspath = rep.item.fspath
|
|
||||||
cat, letter, word = self.getcategoryletterword(rep)
|
|
||||||
if isinstance(word, tuple):
|
|
||||||
word, markup = word
|
|
||||||
else:
|
|
||||||
markup = {}
|
|
||||||
self.stats.setdefault(cat, []).append(rep)
|
|
||||||
if not self.config.option.verbose:
|
|
||||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
|
||||||
self.write_fspath_result(fspath, letter)
|
|
||||||
else:
|
|
||||||
line = self._reportinfoline(rep.item)
|
|
||||||
if not hasattr(rep, 'node'):
|
|
||||||
self.write_ensure_prefix(line, word, **markup)
|
|
||||||
else:
|
|
||||||
self.ensure_newline()
|
|
||||||
if hasattr(rep, 'node'):
|
|
||||||
self._tw.write("%s " % rep.node.gateway.id)
|
|
||||||
self._tw.write(word, **markup)
|
|
||||||
self._tw.write(" " + line)
|
|
||||||
self.currentfspath = -2
|
|
||||||
|
|
||||||
def pytest_collectreport(self, rep):
|
|
||||||
if not rep.passed:
|
|
||||||
if rep.failed:
|
|
||||||
self.stats.setdefault("failed", []).append(rep)
|
|
||||||
msg = rep.longrepr.reprcrash.message
|
|
||||||
self.write_fspath_result(rep.collector.fspath, "F")
|
|
||||||
elif rep.skipped:
|
|
||||||
self.stats.setdefault("skipped", []).append(rep)
|
|
||||||
self.write_fspath_result(rep.collector.fspath, "S")
|
|
||||||
|
|
||||||
def pytest_sessionstart(self, session):
|
|
||||||
self.write_sep("=", "test session starts", bold=True)
|
|
||||||
self._sessionstarttime = py.std.time.time()
|
|
||||||
|
|
||||||
verinfo = ".".join(map(str, sys.version_info[:3]))
|
|
||||||
msg = "python: platform %s -- Python %s" % (sys.platform, verinfo)
|
|
||||||
if self.config.option.verbose or self.config.option.debug:
|
|
||||||
msg += " -- " + str(sys.executable)
|
|
||||||
msg += " -- pytest-%s" % (py.__version__)
|
|
||||||
self.write_line(msg)
|
|
||||||
|
|
||||||
if self.config.option.debug or self.config.option.traceconfig:
|
|
||||||
rev = py.__pkg__.getrev()
|
|
||||||
self.write_line("using py lib: %s <rev %s>" % (
|
|
||||||
py.path.local(py.__file__).dirpath(), rev))
|
|
||||||
if self.config.option.traceconfig:
|
|
||||||
plugins = []
|
|
||||||
for plugin in self.config.pluginmanager.comregistry:
|
|
||||||
name = plugin.__class__.__name__
|
|
||||||
if name.endswith("Plugin"):
|
|
||||||
name = name[:-6]
|
|
||||||
#if name == "Conftest":
|
|
||||||
# XXX get filename
|
|
||||||
plugins.append(name)
|
|
||||||
else:
|
|
||||||
plugins.append(str(plugin))
|
|
||||||
|
|
||||||
plugins = ", ".join(plugins)
|
|
||||||
self.write_line("active plugins: %s" %(plugins,))
|
|
||||||
for i, testarg in py.builtin.enumerate(self.config.args):
|
|
||||||
self.write_line("test object %d: %s" %(i+1, testarg))
|
|
||||||
|
|
||||||
def pytest_sessionfinish(self, __call__, session, exitstatus):
|
|
||||||
__call__.execute()
|
|
||||||
self._tw.line("")
|
|
||||||
if exitstatus in (0, 1, 2):
|
|
||||||
self.summary_failures()
|
|
||||||
self.summary_skips()
|
|
||||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
|
||||||
if exitstatus == 2:
|
|
||||||
self._report_keyboardinterrupt()
|
|
||||||
self.summary_deselected()
|
|
||||||
self.summary_stats()
|
|
||||||
|
|
||||||
def pytest_keyboard_interrupt(self, excinfo):
|
|
||||||
self._keyboardinterrupt_memo = excinfo.getrepr()
|
|
||||||
|
|
||||||
def _report_keyboardinterrupt(self):
|
|
||||||
self.write_sep("!", "KEYBOARD INTERRUPT")
|
|
||||||
excrepr = self._keyboardinterrupt_memo
|
|
||||||
if self.config.option.verbose:
|
|
||||||
excrepr.toterminal(self._tw)
|
|
||||||
else:
|
|
||||||
excrepr.reprcrash.toterminal(self._tw)
|
|
||||||
|
|
||||||
def pytest_looponfailinfo(self, failreports, rootdirs):
|
|
||||||
if failreports:
|
|
||||||
self.write_sep("#", "LOOPONFAILING", red=True)
|
|
||||||
for report in failreports:
|
|
||||||
try:
|
|
||||||
loc = report.longrepr.reprcrash
|
|
||||||
except AttributeError:
|
|
||||||
loc = str(report.longrepr)[:50]
|
|
||||||
self.write_line(loc, red=True)
|
|
||||||
self.write_sep("#", "waiting for changes")
|
|
||||||
for rootdir in rootdirs:
|
|
||||||
self.write_line("### Watching: %s" %(rootdir,), bold=True)
|
|
||||||
|
|
||||||
def _reportinfoline(self, item):
|
|
||||||
fspath, lineno, msg = self._getreportinfo(item)
|
|
||||||
if fspath:
|
|
||||||
fspath = self.curdir.bestrelpath(fspath)
|
|
||||||
if lineno is not None:
|
|
||||||
lineno += 1
|
|
||||||
if fspath and lineno and msg:
|
|
||||||
line = "%(fspath)s:%(lineno)s: %(msg)s"
|
|
||||||
elif fspath and msg:
|
|
||||||
line = "%(fspath)s: %(msg)s"
|
|
||||||
elif fspath and lineno:
|
|
||||||
line = "%(fspath)s:%(lineno)s"
|
|
||||||
else:
|
|
||||||
line = "[noreportinfo]"
|
|
||||||
return line % locals() + " "
|
|
||||||
|
|
||||||
def _getfailureheadline(self, rep):
|
|
||||||
if hasattr(rep, "collector"):
|
|
||||||
return str(rep.collector.fspath)
|
|
||||||
else:
|
|
||||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def _getreportinfo(self, item):
|
|
||||||
try:
|
|
||||||
return item.__reportinfo
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
reportinfo = item.config.hook.pytest_report_iteminfo(item=item)
|
|
||||||
# cache on item
|
|
||||||
item.__reportinfo = reportinfo
|
|
||||||
return reportinfo
|
|
||||||
|
|
||||||
#
|
|
||||||
# summaries for sessionfinish
|
|
||||||
#
|
|
||||||
|
|
||||||
def summary_failures(self):
|
|
||||||
if 'failed' in self.stats and self.config.option.tbstyle != "no":
|
|
||||||
self.write_sep("=", "FAILURES")
|
|
||||||
for rep in self.stats['failed']:
|
|
||||||
msg = self._getfailureheadline(rep)
|
|
||||||
self.write_sep("_", msg)
|
|
||||||
if hasattr(rep, 'node'):
|
|
||||||
self.write_line(self.gateway2info.get(
|
|
||||||
rep.node.gateway, "node %r (platinfo not found? strange)")
|
|
||||||
[:self._tw.fullwidth-1])
|
|
||||||
rep.toterminal(self._tw)
|
|
||||||
|
|
||||||
def summary_stats(self):
|
|
||||||
session_duration = py.std.time.time() - self._sessionstarttime
|
|
||||||
|
|
||||||
keys = "failed passed skipped deselected".split()
|
|
||||||
parts = []
|
|
||||||
for key in keys:
|
|
||||||
val = self.stats.get(key, None)
|
|
||||||
if val:
|
|
||||||
parts.append("%d %s" %(len(val), key))
|
|
||||||
line = ", ".join(parts)
|
|
||||||
# XXX coloring
|
|
||||||
self.write_sep("=", "%s in %.2f seconds" %(line, session_duration))
|
|
||||||
|
|
||||||
def summary_deselected(self):
|
|
||||||
if 'deselected' in self.stats:
|
|
||||||
self.write_sep("=", "%d tests deselected by %r" %(
|
|
||||||
len(self.stats['deselected']), self.config.option.keyword), bold=True)
|
|
||||||
|
|
||||||
def summary_skips(self):
|
|
||||||
if 'skipped' in self.stats:
|
|
||||||
if 'failed' not in self.stats: # or self.config.option.showskipsummary:
|
|
||||||
fskips = folded_skips(self.stats['skipped'])
|
|
||||||
if fskips:
|
|
||||||
self.write_sep("_", "skipped test summary")
|
|
||||||
for num, fspath, lineno, reason in fskips:
|
|
||||||
self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason))
|
|
||||||
|
|
||||||
class CollectonlyReporter:
|
|
||||||
INDENT = " "
|
|
||||||
|
|
||||||
def __init__(self, config, out=None):
|
|
||||||
self.config = config
|
|
||||||
if out is None:
|
|
||||||
out = py.std.sys.stdout
|
|
||||||
self.out = py.io.TerminalWriter(out)
|
|
||||||
self.indent = ""
|
|
||||||
self._failed = []
|
|
||||||
|
|
||||||
def outindent(self, line):
|
|
||||||
self.out.line(self.indent + str(line))
|
|
||||||
|
|
||||||
def pytest_internalerror(self, excrepr):
|
|
||||||
for line in str(excrepr).split("\n"):
|
|
||||||
self.out.line("INTERNALERROR> " + line)
|
|
||||||
|
|
||||||
def pytest_collectstart(self, collector):
|
|
||||||
self.outindent(collector)
|
|
||||||
self.indent += self.INDENT
|
|
||||||
|
|
||||||
def pytest_itemstart(self, item, node=None):
|
|
||||||
self.outindent(item)
|
|
||||||
|
|
||||||
def pytest_collectreport(self, rep):
|
|
||||||
if not rep.passed:
|
|
||||||
self.outindent("!!! %s !!!" % rep.longrepr.reprcrash.message)
|
|
||||||
self._failed.append(rep)
|
|
||||||
self.indent = self.indent[:-len(self.INDENT)]
|
|
||||||
|
|
||||||
def pytest_sessionfinish(self, session, exitstatus):
|
|
||||||
if self._failed:
|
|
||||||
self.out.sep("!", "collection failures")
|
|
||||||
for rep in self._failed:
|
|
||||||
rep.toterminal(self.out)
|
|
||||||
|
|
||||||
def folded_skips(skipped):
|
|
||||||
d = {}
|
|
||||||
for event in skipped:
|
|
||||||
entry = event.longrepr.reprcrash
|
|
||||||
key = entry.path, entry.lineno, entry.message
|
|
||||||
d.setdefault(key, []).append(event)
|
|
||||||
l = []
|
|
||||||
for key, events in d.iteritems():
|
|
||||||
l.append((len(events),) + key)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def repr_pythonversion(v=None):
|
|
||||||
if v is None:
|
|
||||||
v = sys.version_info
|
|
||||||
try:
|
|
||||||
return "%s.%s.%s-%s-%s" % v
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return str(v)
|
|
||||||
|
|
||||||
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_terminal.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -4,6 +4,9 @@ pytest_unittest plugin
|
||||||
|
|
||||||
automatically discover and run traditional "unittest.py" style tests.
|
automatically discover and run traditional "unittest.py" style tests.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -16,146 +19,19 @@ This plugin is enabled by default.
|
||||||
|
|
||||||
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
|
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_unittest.py`_ plugin source code
|
1. Download `pytest_unittest.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_unittest.py`` into your import path
|
2. put it somewhere as ``pytest_unittest.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_unittest.py``:
|
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_unittest.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
automatically discover and run traditional "unittest.py" style tests.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
----------------
|
|
||||||
|
|
||||||
This plugin collects and runs Python `unittest.py style`_ tests.
|
|
||||||
It will automatically collect ``unittest.TestCase`` subclasses
|
|
||||||
and their ``test`` methods from the test modules of a project
|
|
||||||
(usually following the ``test_*.py`` pattern).
|
|
||||||
|
|
||||||
This plugin is enabled by default.
|
|
||||||
|
|
||||||
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
|
||||||
if 'unittest' not in sys.modules:
|
|
||||||
return # nobody could have possibly derived a subclass
|
|
||||||
if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
|
|
||||||
return UnitTestCase(name, parent=collector)
|
|
||||||
|
|
||||||
class UnitTestCase(py.test.collect.Class):
|
|
||||||
def collect(self):
|
|
||||||
return [UnitTestCaseInstance("()", self)]
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
_dummy = object()
|
|
||||||
class UnitTestCaseInstance(py.test.collect.Instance):
|
|
||||||
def collect(self):
|
|
||||||
loader = py.std.unittest.TestLoader()
|
|
||||||
names = loader.getTestCaseNames(self.obj.__class__)
|
|
||||||
l = []
|
|
||||||
for name in names:
|
|
||||||
callobj = getattr(self.obj, name)
|
|
||||||
if callable(callobj):
|
|
||||||
l.append(UnitTestFunction(name, parent=self))
|
|
||||||
return l
|
|
||||||
|
|
||||||
def _getobj(self):
|
|
||||||
x = self.parent.obj
|
|
||||||
return self.parent.obj(methodName='run')
|
|
||||||
|
|
||||||
class UnitTestFunction(py.test.collect.Function):
|
|
||||||
def __init__(self, name, parent, args=(), obj=_dummy, sort_value=None):
|
|
||||||
super(UnitTestFunction, self).__init__(name, parent)
|
|
||||||
self._args = args
|
|
||||||
if obj is not _dummy:
|
|
||||||
self._obj = obj
|
|
||||||
self._sort_value = sort_value
|
|
||||||
|
|
||||||
def runtest(self):
|
|
||||||
target = self.obj
|
|
||||||
args = self._args
|
|
||||||
target(*args)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
instance = self.obj.im_self
|
|
||||||
instance.setUp()
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
instance = self.obj.im_self
|
|
||||||
instance.tearDown()
|
|
||||||
|
|
||||||
|
|
||||||
def test_simple_unittest(testdir):
|
|
||||||
testpath = testdir.makepyfile("""
|
|
||||||
import unittest
|
|
||||||
pytest_plugins = "pytest_unittest"
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
def testpassing(self):
|
|
||||||
self.assertEquals('foo', 'foo')
|
|
||||||
def test_failing(self):
|
|
||||||
self.assertEquals('foo', 'bar')
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(testpath)
|
|
||||||
assert reprec.matchreport("testpassing").passed
|
|
||||||
assert reprec.matchreport("test_failing").failed
|
|
||||||
|
|
||||||
def test_setup(testdir):
|
|
||||||
testpath = testdir.makepyfile(test_two="""
|
|
||||||
import unittest
|
|
||||||
pytest_plugins = "pytest_unittest" # XXX
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.foo = 1
|
|
||||||
def test_setUp(self):
|
|
||||||
self.assertEquals(1, self.foo)
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(testpath)
|
|
||||||
rep = reprec.matchreport("test_setUp")
|
|
||||||
assert rep.passed
|
|
||||||
|
|
||||||
def test_teardown(testdir):
|
|
||||||
testpath = testdir.makepyfile(test_three="""
|
|
||||||
import unittest
|
|
||||||
pytest_plugins = "pytest_unittest" # XXX
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
l = []
|
|
||||||
def test_one(self):
|
|
||||||
pass
|
|
||||||
def tearDown(self):
|
|
||||||
self.l.append(None)
|
|
||||||
class Second(unittest.TestCase):
|
|
||||||
def test_check(self):
|
|
||||||
self.assertEquals(MyTestCase.l, [None])
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(testpath)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
print "COUNTS", passed, skipped, failed
|
|
||||||
assert failed == 0, failed
|
|
||||||
assert passed == 2
|
|
||||||
assert passed + skipped + failed == 2
|
|
||||||
|
|
||||||
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_unittest.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -4,6 +4,9 @@ pytest_xfail plugin
|
||||||
|
|
||||||
mark python tests as expected-to-fail and report them separately.
|
mark python tests as expected-to-fail and report them separately.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
usage
|
usage
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -18,116 +21,19 @@ This test will be executed but no traceback will be reported
|
||||||
when it fails. Instead terminal reporting will list it in the
|
when it fails. Instead terminal reporting will list it in the
|
||||||
"expected to fail" section or "unexpectedly passing" section.
|
"expected to fail" section or "unexpectedly passing" section.
|
||||||
|
|
||||||
Getting and improving this plugin
|
Start improving this plugin in 30 seconds
|
||||||
---------------------------------
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `pytest_xfail.py`_ plugin source code
|
1. Download `pytest_xfail.py`_ plugin source code
|
||||||
2. put it somewhere as ``pytest_xfail.py`` into your import path
|
2. put it somewhere as ``pytest_xfail.py`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
|
|
||||||
For your convenience here is also an inlined version of ``pytest_xfail.py``:
|
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/85fe614ab05f301f206935d11a477df184cbbce6/py/test/plugin/pytest_xfail.py
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
mark python tests as expected-to-fail and report them separately.
|
|
||||||
|
|
||||||
usage
|
|
||||||
------------
|
|
||||||
|
|
||||||
Use the generic mark decorator to add the 'xfail' keyword to your
|
|
||||||
test function::
|
|
||||||
|
|
||||||
@py.test.mark.xfail
|
|
||||||
def test_hello():
|
|
||||||
...
|
|
||||||
|
|
||||||
This test will be executed but no traceback will be reported
|
|
||||||
when it fails. Instead terminal reporting will list it in the
|
|
||||||
"expected to fail" section or "unexpectedly passing" section.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
pytest_plugins = ['keyword']
|
|
||||||
|
|
||||||
def pytest_runtest_makereport(__call__, item, call):
|
|
||||||
if call.when != "call":
|
|
||||||
return
|
|
||||||
if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'):
|
|
||||||
if 'xfail' in item.obj.func_dict:
|
|
||||||
res = __call__.execute(firstresult=True)
|
|
||||||
if call.excinfo:
|
|
||||||
res.skipped = True
|
|
||||||
res.failed = res.passed = False
|
|
||||||
else:
|
|
||||||
res.skipped = res.passed = False
|
|
||||||
res.failed = True
|
|
||||||
return res
|
|
||||||
|
|
||||||
def pytest_report_teststatus(rep):
|
|
||||||
""" return shortletter and verbose word. """
|
|
||||||
if 'xfail' in rep.keywords:
|
|
||||||
if rep.skipped:
|
|
||||||
return "xfailed", "x", "xfail"
|
|
||||||
elif rep.failed:
|
|
||||||
return "xpassed", "P", "xpass"
|
|
||||||
|
|
||||||
# called by the terminalreporter instance/plugin
|
|
||||||
def pytest_terminal_summary(terminalreporter):
|
|
||||||
tr = terminalreporter
|
|
||||||
xfailed = tr.stats.get("xfailed")
|
|
||||||
if xfailed:
|
|
||||||
tr.write_sep("_", "expected failures")
|
|
||||||
for event in xfailed:
|
|
||||||
entry = event.longrepr.reprcrash
|
|
||||||
key = entry.path, entry.lineno, entry.message
|
|
||||||
reason = event.longrepr.reprcrash.message
|
|
||||||
modpath = event.item.getmodpath(includemodule=True)
|
|
||||||
#tr._tw.line("%s %s:%d: %s" %(modpath, entry.path, entry.lineno, entry.message))
|
|
||||||
tr._tw.line("%s %s:%d: " %(modpath, entry.path, entry.lineno))
|
|
||||||
|
|
||||||
xpassed = terminalreporter.stats.get("xpassed")
|
|
||||||
if xpassed:
|
|
||||||
tr.write_sep("_", "UNEXPECTEDLY PASSING TESTS")
|
|
||||||
for event in xpassed:
|
|
||||||
tr._tw.line("%s: xpassed" %(event.item,))
|
|
||||||
|
|
||||||
|
|
||||||
# ===============================================================================
|
|
||||||
#
|
|
||||||
# plugin tests
|
|
||||||
#
|
|
||||||
# ===============================================================================
|
|
||||||
|
|
||||||
def test_xfail(testdir, linecomp):
|
|
||||||
p = testdir.makepyfile(test_one="""
|
|
||||||
import py
|
|
||||||
@py.test.mark.xfail
|
|
||||||
def test_this():
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
@py.test.mark.xfail
|
|
||||||
def test_that():
|
|
||||||
assert 1
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p)
|
|
||||||
extra = result.stdout.fnmatch_lines([
|
|
||||||
"*expected failures*",
|
|
||||||
"*test_one.test_this*test_one.py:4*",
|
|
||||||
"*UNEXPECTEDLY PASSING*",
|
|
||||||
"*test_that*",
|
|
||||||
])
|
|
||||||
assert result.ret == 1
|
|
||||||
|
|
||||||
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/ea1f958813ebbff45161fdb468a6204be5396112/py/test/plugin/pytest_xfail.py
|
|
||||||
.. _`extend`: ../extend.html
|
.. _`extend`: ../extend.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`contact`: ../../contact.html
|
.. _`contact`: ../../contact.html
|
||||||
|
|
|
@ -10,9 +10,10 @@ plugins = [
|
||||||
'unittest doctest oejskit restdoc'),
|
'unittest doctest oejskit restdoc'),
|
||||||
('Plugins for generic reporting and failure logging',
|
('Plugins for generic reporting and failure logging',
|
||||||
'pocoo resultlog terminal',),
|
'pocoo resultlog terminal',),
|
||||||
('internal plugins / core functionality',
|
#('internal plugins / core functionality',
|
||||||
'pdb keyword hooklog runner execnetcleanup pytester',
|
# #'pdb keyword hooklog runner execnetcleanup # pytester',
|
||||||
)
|
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
|
||||||
|
#)
|
||||||
]
|
]
|
||||||
|
|
||||||
externals = {
|
externals = {
|
||||||
|
@ -152,6 +153,9 @@ class PluginDoc(RestWriter):
|
||||||
self.h1("%s plugin" % self.name) # : %s" %(self.name, self.oneliner))
|
self.h1("%s plugin" % self.name) # : %s" %(self.name, self.oneliner))
|
||||||
self.Print(self.oneliner)
|
self.Print(self.oneliner)
|
||||||
self.Print()
|
self.Print()
|
||||||
|
self.Print(".. contents::")
|
||||||
|
self.Print(" :local:")
|
||||||
|
self.Print()
|
||||||
|
|
||||||
self.Print(moduledoc)
|
self.Print(moduledoc)
|
||||||
|
|
||||||
|
@ -170,15 +174,13 @@ class PluginDoc(RestWriter):
|
||||||
#self.links.append((basename,
|
#self.links.append((basename,
|
||||||
# "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
|
# "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
|
||||||
# basename))
|
# basename))
|
||||||
self.h2("Getting and improving this plugin")
|
self.h1("Start improving this plugin in 30 seconds")
|
||||||
self.para(py.code.Source("""
|
self.para(py.code.Source("""
|
||||||
Do you find the above documentation or the plugin itself lacking,
|
Do you find the above documentation or the plugin itself lacking?
|
||||||
not fit for what you need? Here is a **30 seconds guide**
|
|
||||||
to get you started on improving the plugin:
|
|
||||||
|
|
||||||
1. Download `%s`_ plugin source code
|
1. Download `%s`_ plugin source code
|
||||||
2. put it somewhere as ``%s`` into your import path
|
2. put it somewhere as ``%s`` into your import path
|
||||||
3. a subsequent test run will now use your local version!
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
Further information: extend_ documentation, other plugins_ or contact_.
|
Further information: extend_ documentation, other plugins_ or contact_.
|
||||||
""" % (basename, basename)))
|
""" % (basename, basename)))
|
||||||
|
@ -194,14 +196,15 @@ class PluginDoc(RestWriter):
|
||||||
self.links.append(('contact', '../../contact.html'))
|
self.links.append(('contact', '../../contact.html'))
|
||||||
self.links.append(('checkout the py.test development version',
|
self.links.append(('checkout the py.test development version',
|
||||||
'../../download.html#checkout'))
|
'../../download.html#checkout'))
|
||||||
|
|
||||||
#self.h2("plugin source code")
|
if 0: # this breaks the page layout and makes large doc files
|
||||||
self.Print()
|
#self.h2("plugin source code")
|
||||||
self.para("For your convenience here is also an inlined version "
|
self.Print()
|
||||||
"of ``%s``:" %basename)
|
self.para("For your convenience here is also an inlined version "
|
||||||
#self(or copy-paste from below)
|
"of ``%s``:" %basename)
|
||||||
self.Print()
|
#self(or copy-paste from below)
|
||||||
self.sourcecode(py.code.Source(plugin))
|
self.Print()
|
||||||
|
self.sourcecode(py.code.Source(plugin))
|
||||||
|
|
||||||
def emit_funcargs(self, plugin):
|
def emit_funcargs(self, plugin):
|
||||||
funcargfuncs = []
|
funcargfuncs = []
|
||||||
|
@ -213,6 +216,7 @@ class PluginDoc(RestWriter):
|
||||||
return
|
return
|
||||||
for func in funcargfuncs:
|
for func in funcargfuncs:
|
||||||
argname = func.__name__[len(prefix):]
|
argname = func.__name__[len(prefix):]
|
||||||
|
self.Print()
|
||||||
self.Print(".. _`%s funcarg`:" % argname)
|
self.Print(".. _`%s funcarg`:" % argname)
|
||||||
self.Print()
|
self.Print()
|
||||||
self.h2("the %r test function argument" % argname)
|
self.h2("the %r test function argument" % argname)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
"""
|
"""
|
||||||
convenient capturing of writes to stdout/stderror streams
|
convenient capturing of writes to stdout/stderror streams and file descriptors.
|
||||||
and file descriptors.
|
|
||||||
|
|
||||||
Example Usage
|
Example Usage
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -1,17 +1,46 @@
|
||||||
"""
|
"""
|
||||||
helpers for asserting deprecation and other warnings.
|
helpers for asserting deprecation and other warnings.
|
||||||
|
|
||||||
**recwarn**: function argument where one can call recwarn.pop() to get
|
Example usage
|
||||||
the last warning that would have been shown.
|
---------------------
|
||||||
|
|
||||||
**py.test.deprecated_call(func, *args, **kwargs)**: assert that the given function call triggers a deprecation warning.
|
You can use the ``recwarn`` funcarg to track
|
||||||
|
warnings within a test function:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def test_hello(recwarn):
|
||||||
|
from warnings import warn
|
||||||
|
warn("hello", DeprecationWarning)
|
||||||
|
w = recwarn.pop(DeprecationWarning)
|
||||||
|
assert issubclass(w.category, DeprecationWarning)
|
||||||
|
assert 'hello' in str(w.message)
|
||||||
|
assert w.filename
|
||||||
|
assert w.lineno
|
||||||
|
|
||||||
|
You can also call a global helper for checking
|
||||||
|
taht a certain function call yields a Deprecation
|
||||||
|
warning:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
def test_global():
|
||||||
|
py.test.deprecated_call(myfunction, 17)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def pytest_funcarg__recwarn(request):
|
def pytest_funcarg__recwarn(request):
|
||||||
""" check that warnings have been raised. """
|
"""Return a WarningsRecorder instance that provides these methods:
|
||||||
|
|
||||||
|
* ``pop(category=None)``: return last warning matching the category.
|
||||||
|
* ``clear()``: clear list of warnings
|
||||||
|
"""
|
||||||
warnings = WarningsRecorder()
|
warnings = WarningsRecorder()
|
||||||
request.addfinalizer(warnings.finalize)
|
request.addfinalizer(warnings.finalize)
|
||||||
return warnings
|
return warnings
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
"""
|
"""
|
||||||
terminal reporting of the full testing process.
|
Implements terminal reporting of the full testing process.
|
||||||
|
|
||||||
|
This is a good source for looking at the various reporting hooks.
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
import sys
|
import sys
|
||||||
|
|
Loading…
Reference in New Issue