largely improve and reshuffle docs, heading strongly towards a 1.1.0
--HG-- branch : trunk
This commit is contained in:
parent
b04a04cabd
commit
a5a94c4e8f
|
@ -3,28 +3,29 @@ import os, sys
|
||||||
WIDTH = 75
|
WIDTH = 75
|
||||||
|
|
||||||
plugins = [
|
plugins = [
|
||||||
('plugins for Python test functions',
|
('advanced python testing',
|
||||||
'skipping figleaf monkeypatch capture recwarn',),
|
'skipping mark pdb figleaf coverage '
|
||||||
('plugins for other testing styles and languages',
|
'monkeypatch capture recwarn tmpdir',),
|
||||||
'oejskit unittest nose django doctest restdoc'),
|
('testing domains',
|
||||||
('plugins for generic reporting and failure logging',
|
'oejskit django'),
|
||||||
'pastebin resultlog terminal',),
|
('reporting and failure logging',
|
||||||
('plugins for generic reporting and failure logging',
|
'pastebin xmlresult resultlog terminal',),
|
||||||
'pastebin resultlog terminal',),
|
('other testing conventions',
|
||||||
('misc plugins / core functionality',
|
'unittest nose doctest restdoc'),
|
||||||
'helpconfig pdb mark hooklog')
|
('core debugging / help functionality',
|
||||||
|
'helpconfig hooklog')
|
||||||
#('internal plugins / core functionality',
|
#('internal plugins / core functionality',
|
||||||
# #'pdb keyword hooklog runner execnetcleanup # pytester',
|
# #'runner execnetcleanup # pytester',
|
||||||
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
|
# 'runner execnetcleanup' # pytester',
|
||||||
#)
|
#)
|
||||||
]
|
]
|
||||||
|
|
||||||
externals = {
|
externals = {
|
||||||
'oejskit': "run javascript tests in real life browsers",
|
'oejskit': "run javascript tests in real life browsers",
|
||||||
'django': "support for testing django applications",
|
'django': "for testing django applications",
|
||||||
# 'coverage': "support for using Ned's coverage module",
|
'coverage': "for testing with Ned's coverage module ",
|
||||||
# 'xmlresult': "support for generating xml reports "
|
'xmlresult': "for generating xml reports "
|
||||||
# "and CruiseControl integration",
|
"and CruiseControl integration",
|
||||||
}
|
}
|
||||||
|
|
||||||
def warn(*args):
|
def warn(*args):
|
||||||
|
@ -136,7 +137,7 @@ class PluginOverview(RestWriter):
|
||||||
docpath = self.target.dirpath(name).new(ext=".txt")
|
docpath = self.target.dirpath(name).new(ext=".txt")
|
||||||
if oneliner is not None:
|
if oneliner is not None:
|
||||||
htmlpath = docpath.new(ext='.html')
|
htmlpath = docpath.new(ext='.html')
|
||||||
self.para("%s_ %s" %(name, oneliner))
|
self.para("%s_ (3rd) %s" %(name, oneliner))
|
||||||
self.add_internal_link(name, htmlpath)
|
self.add_internal_link(name, htmlpath)
|
||||||
else:
|
else:
|
||||||
doc = PluginDoc(docpath)
|
doc = PluginDoc(docpath)
|
||||||
|
@ -212,7 +213,7 @@ class PluginDoc(RestWriter):
|
||||||
# "py/test/plugin/%s" %(hg_changeset, basename)))
|
# "py/test/plugin/%s" %(hg_changeset, basename)))
|
||||||
self.links.append((basename,
|
self.links.append((basename,
|
||||||
"http://bitbucket.org/hpk42/py-trunk/raw/%s/"
|
"http://bitbucket.org/hpk42/py-trunk/raw/%s/"
|
||||||
"_py/test/plugin/%s" %(pyversion, basename)))
|
"py/plugin/%s" %(pyversion, basename)))
|
||||||
self.links.append(('customize', '../customize.html'))
|
self.links.append(('customize', '../customize.html'))
|
||||||
self.links.append(('plugins', 'index.html'))
|
self.links.append(('plugins', 'index.html'))
|
||||||
self.links.append(('get in contact', '../../contact.html'))
|
self.links.append(('get in contact', '../../contact.html'))
|
||||||
|
|
|
@ -1,329 +0,0 @@
|
||||||
"""
|
|
||||||
Tested with coverage 2.85 and pygments 1.0
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
+ 'html-output/*,cover' should be deleted
|
|
||||||
+ credits for coverage
|
|
||||||
+ credits for pygments
|
|
||||||
+ 'Install pygments' after ImportError is to less
|
|
||||||
+ is the way of determining DIR_CSS_RESOURCE ok?
|
|
||||||
+ write plugin test
|
|
||||||
+ '.coverage' still exists in py.test execution dir
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pygments import highlight
|
|
||||||
from pygments.lexers import get_lexer_by_name
|
|
||||||
from pygments.formatters import HtmlFormatter
|
|
||||||
except ImportError:
|
|
||||||
print "Install pygments" # XXX
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
DIR_CUR = str(py.path.local())
|
|
||||||
REPORT_FILE = os.path.join(DIR_CUR, '.coverage')
|
|
||||||
DIR_ANNOTATE_OUTPUT = os.path.join(DIR_CUR, '.coverage_annotate')
|
|
||||||
COVERAGE_MODULES = set()
|
|
||||||
# coverage output parsing
|
|
||||||
REG_COVERAGE_SUMMARY = re.compile('([a-z_\.]+) +([0-9]+) +([0-9]+) +([0-9]+%)')
|
|
||||||
REG_COVERAGE_SUMMARY_TOTAL = re.compile('(TOTAL) +([0-9]+) +([0-9]+) +([0-9]+%)')
|
|
||||||
DEFAULT_COVERAGE_OUTPUT = '.coverage_annotation'
|
|
||||||
# HTML output specific
|
|
||||||
DIR_CSS_RESOURCE = os.path.dirname(__import__('pytest_coverage').__file__)
|
|
||||||
CSS_RESOURCE_FILES = ['header_bg.jpg', 'links.gif']
|
|
||||||
|
|
||||||
COVERAGE_TERM_HEADER = "\nCOVERAGE INFORMATION\n" \
|
|
||||||
"====================\n"
|
|
||||||
HTML_INDEX_HEADER = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>py.test - Coverage Index</title>
|
|
||||||
|
|
||||||
<style type="text/css">
|
|
||||||
table {
|
|
||||||
font-size:0.9em;
|
|
||||||
font-family: Arial, Helvetica, verdana sans-serif;
|
|
||||||
background-color:#fff;
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
caption {
|
|
||||||
font-size: 25px;
|
|
||||||
color: #1ba6b2;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: left;
|
|
||||||
background: url(header_bg.jpg) no-repeat top left;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
thead th {
|
|
||||||
border-right: 1px solid #fff;
|
|
||||||
color:#fff;
|
|
||||||
text-align:center;
|
|
||||||
padding:2px;
|
|
||||||
text-transform:uppercase;
|
|
||||||
height:25px;
|
|
||||||
background-color: #a3c159;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
tfoot {
|
|
||||||
color:#1ba6b2;
|
|
||||||
padding:2px;
|
|
||||||
text-transform:uppercase;
|
|
||||||
font-size:1.2em;
|
|
||||||
font-weigth: bold;
|
|
||||||
margin-top:6px;
|
|
||||||
border-top: 6px solid #e9f7f6;
|
|
||||||
}
|
|
||||||
tfoot td {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
tbody tr {
|
|
||||||
background-color:#fff;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
tbody td {
|
|
||||||
color:#414141;
|
|
||||||
padding:5px;
|
|
||||||
text-align:left;
|
|
||||||
}
|
|
||||||
tbody th {
|
|
||||||
text-align:left;
|
|
||||||
padding:2px;
|
|
||||||
}
|
|
||||||
tbody td a, tbody th a {
|
|
||||||
color:#6C8C37;
|
|
||||||
text-decoration:none;
|
|
||||||
font-weight:normal;
|
|
||||||
display:block;
|
|
||||||
background: transparent url(links.gif) no-repeat 0% 50%;
|
|
||||||
padding-left:15px;
|
|
||||||
}
|
|
||||||
tbody td a:hover, tbody th a:hover {
|
|
||||||
color:#009193;
|
|
||||||
text-decoration:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body
|
|
||||||
<table >
|
|
||||||
<caption>Module Coverage</caption>
|
|
||||||
<tbody>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Module</th>
|
|
||||||
<th>Statements</th>
|
|
||||||
<th>Executed</th>
|
|
||||||
<th>Coverage</th>
|
|
||||||
</tr>
|
|
||||||
</thead>'''
|
|
||||||
HTML_INDEX_FOOTER = ''' </tbody>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>'''
|
|
||||||
|
|
||||||
|
|
||||||
class CoverageHtmlFormatter(HtmlFormatter):
|
|
||||||
"""XXX: doc"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
HtmlFormatter.__init__(self,*args, **kwargs)
|
|
||||||
self.annotation_infos = kwargs.get('annotation_infos')
|
|
||||||
|
|
||||||
def _highlight_lines(self, tokensource):
|
|
||||||
"""
|
|
||||||
XXX: doc
|
|
||||||
"""
|
|
||||||
|
|
||||||
hls = self.hl_lines
|
|
||||||
self.annotation_infos = [None] + self.annotation_infos
|
|
||||||
hls = [l for l, i in enumerate(self.annotation_infos) if i]
|
|
||||||
for i, (t, value) in enumerate(tokensource):
|
|
||||||
if t != 1:
|
|
||||||
yield t, value
|
|
||||||
if i + 1 in hls: # i + 1 because Python indexes start at 0
|
|
||||||
if self.annotation_infos[i+1] == "!":
|
|
||||||
yield 1, '<span style="background-color:#FFE5E5">%s</span>' \
|
|
||||||
% value
|
|
||||||
elif self.annotation_infos[i+1] == ">":
|
|
||||||
yield 1, '<span style="background-color:#CCFFEB">%s</span>' \
|
|
||||||
% value
|
|
||||||
else:
|
|
||||||
raise ValueError("HHAHA: %s" % self.annotation_infos[i+1])
|
|
||||||
else:
|
|
||||||
yield 1, value
|
|
||||||
|
|
||||||
|
|
||||||
def _rename_annotation_files(module_list, dir_annotate_output):
|
|
||||||
for m in module_list:
|
|
||||||
mod_fpath = os.path.basename(m.__file__)
|
|
||||||
if mod_fpath.endswith('pyc'):
|
|
||||||
mod_fpath = mod_fpath[:-1]
|
|
||||||
old = os.path.join(dir_annotate_output, '%s,cover'% mod_fpath)
|
|
||||||
new = os.path.join(dir_annotate_output, '%s,cover'% m.__name__)
|
|
||||||
if os.path.isfile(old):
|
|
||||||
shutil.move(old, new)
|
|
||||||
yield new
|
|
||||||
|
|
||||||
def _generate_module_coverage(mc_path, anotation_infos, src_lines):
|
|
||||||
#XXX: doc
|
|
||||||
|
|
||||||
code = "".join(src_lines)
|
|
||||||
mc_path = "%s.html" % mc_path
|
|
||||||
lexer = get_lexer_by_name("python", stripall=True)
|
|
||||||
formatter = CoverageHtmlFormatter(linenos=True, noclasses=True,
|
|
||||||
hl_lines=[1], annotation_infos=anotation_infos)
|
|
||||||
result = highlight(code, lexer, formatter)
|
|
||||||
fp = open(mc_path, 'w')
|
|
||||||
fp.write(result)
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
def _parse_modulecoverage(mc_fpath):
|
|
||||||
#XXX: doc
|
|
||||||
|
|
||||||
fd = open(mc_fpath, 'r')
|
|
||||||
anotate_infos = []
|
|
||||||
src_lines = []
|
|
||||||
for line in fd.readlines():
|
|
||||||
anotate_info = line[0:2].strip()
|
|
||||||
if not anotate_info:
|
|
||||||
anotate_info = None
|
|
||||||
src_line = line[2:]
|
|
||||||
anotate_infos.append(anotate_info)
|
|
||||||
src_lines.append(src_line)
|
|
||||||
return mc_fpath, anotate_infos, src_lines
|
|
||||||
|
|
||||||
def _parse_coverage_summary(fd):
|
|
||||||
"""Parses coverage summary output."""
|
|
||||||
|
|
||||||
if hasattr(fd, 'readlines'):
|
|
||||||
fd.seek(0)
|
|
||||||
for l in fd.readlines():
|
|
||||||
m = REG_COVERAGE_SUMMARY.match(l)
|
|
||||||
if m:
|
|
||||||
# yield name, stmts, execs, cover
|
|
||||||
yield m.group(1), m.group(2), m.group(3), m.group(4)
|
|
||||||
else:
|
|
||||||
m = REG_COVERAGE_SUMMARY_TOTAL.match(l)
|
|
||||||
if m:
|
|
||||||
# yield name, stmts, execs, cover
|
|
||||||
yield m.group(1), m.group(2), m.group(3), m.group(4)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_coverage_index(mod_name, stmts, execs, cover, annotation_dir):
|
|
||||||
"""
|
|
||||||
Generates the index page where are all modulare coverage reports are
|
|
||||||
linked.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if mod_name == 'TOTAL':
|
|
||||||
return '<tfoot><tr style="text-align: center;font-weigth:bold;font-size:1.2em; "><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr></tfoot>\n' % (mod_name, stmts, execs, cover)
|
|
||||||
covrep_fpath = os.path.join(annotation_dir, '%s,cover.html' % mod_name)
|
|
||||||
assert os.path.isfile(covrep_fpath) == True
|
|
||||||
fname = os.path.basename(covrep_fpath)
|
|
||||||
modlink = '<a href="%s">%s</a>' % (fname, mod_name)
|
|
||||||
return '<tr style="text-align: center;"><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (modlink, stmts, execs, cover)
|
|
||||||
|
|
||||||
|
|
||||||
class CoveragePlugin:
|
|
||||||
def pytest_addoption(self, parser):
|
|
||||||
group = parser.addgroup('coverage options')
|
|
||||||
group.addoption('-C', action='store_true', default=False,
|
|
||||||
dest = 'coverage',
|
|
||||||
help=('displays coverage information.'))
|
|
||||||
group.addoption('--coverage-html', action='store', default=False,
|
|
||||||
dest='coverage_annotation',
|
|
||||||
help='path to the coverage HTML output dir.')
|
|
||||||
group.addoption('--coverage-css-resourcesdir', action='store',
|
|
||||||
default=DIR_CSS_RESOURCE,
|
|
||||||
dest='coverage_css_ressourcedir',
|
|
||||||
help='path to dir with css-resources (%s) for '
|
|
||||||
'being copied to the HTML output dir.' % \
|
|
||||||
", ".join(CSS_RESOURCE_FILES))
|
|
||||||
|
|
||||||
def pytest_configure(self, config):
|
|
||||||
if config.getvalue('coverage'):
|
|
||||||
try:
|
|
||||||
import coverage
|
|
||||||
except ImportError:
|
|
||||||
raise config.Error("To run use the coverage option you have to install " \
|
|
||||||
"Ned Batchelder's coverage: "\
|
|
||||||
"http://nedbatchelder.com/code/modules/coverage.html")
|
|
||||||
self.coverage = coverage
|
|
||||||
self.summary = None
|
|
||||||
|
|
||||||
def pytest_terminal_summary(self, terminalreporter):
|
|
||||||
if hasattr(self, 'coverage'):
|
|
||||||
self.coverage.stop()
|
|
||||||
module_list = [sys.modules[mod] for mod in COVERAGE_MODULES]
|
|
||||||
module_list.sort()
|
|
||||||
summary_fd = StringIO()
|
|
||||||
# get coverage reports by module list
|
|
||||||
self.coverage.report(module_list, file=summary_fd)
|
|
||||||
summary = COVERAGE_TERM_HEADER + summary_fd.getvalue()
|
|
||||||
terminalreporter._tw.write(summary)
|
|
||||||
|
|
||||||
config = terminalreporter.config
|
|
||||||
dir_annotate_output = config.getvalue('coverage_annotation')
|
|
||||||
if dir_annotate_output:
|
|
||||||
if dir_annotate_output == "":
|
|
||||||
dir_annotate_output = DIR_ANNOTATE_OUTPUT
|
|
||||||
# create dir
|
|
||||||
if os.path.isdir(dir_annotate_output):
|
|
||||||
shutil.rmtree(dir_annotate_output)
|
|
||||||
os.mkdir(dir_annotate_output)
|
|
||||||
# generate annotation text files for later parsing
|
|
||||||
self.coverage.annotate(module_list, dir_annotate_output)
|
|
||||||
# generate the separate module coverage reports
|
|
||||||
for mc_fpath in _rename_annotation_files(module_list, \
|
|
||||||
dir_annotate_output):
|
|
||||||
# mc_fpath, anotate_infos, src_lines from _parse_do
|
|
||||||
_generate_module_coverage(*_parse_modulecoverage(mc_fpath))
|
|
||||||
# creating contents for the index pagee for coverage report
|
|
||||||
idxpage_html = StringIO()
|
|
||||||
idxpage_html.write(HTML_INDEX_HEADER)
|
|
||||||
total_sum = None
|
|
||||||
for args in _parse_coverage_summary(summary_fd):
|
|
||||||
# mod_name, stmts, execs, cover = args
|
|
||||||
idxpage_html.write(_get_coverage_index(*args, \
|
|
||||||
**dict(annotation_dir=dir_annotate_output)))
|
|
||||||
idxpage_html.write(HTML_INDEX_FOOTER)
|
|
||||||
idx_fpath = os.path.join(dir_annotate_output, 'index.html')
|
|
||||||
idx_fd = open(idx_fpath, 'w')
|
|
||||||
idx_fd.write(idxpage_html.getvalue())
|
|
||||||
idx_fd.close()
|
|
||||||
|
|
||||||
dir_css_resource_dir = config.getvalue('coverage_css_ressourcedir')
|
|
||||||
if dir_annotate_output and dir_css_resource_dir != "":
|
|
||||||
if not os.path.isdir(dir_css_resource_dir):
|
|
||||||
raise config.Error("CSS resource dir not found: '%s'" % \
|
|
||||||
dir_css_resource_dir)
|
|
||||||
for r in CSS_RESOURCE_FILES:
|
|
||||||
src = os.path.join(dir_css_resource_dir, r)
|
|
||||||
if os.path.isfile(src):
|
|
||||||
dest = os.path.join(dir_annotate_output, r)
|
|
||||||
shutil.copy(src, dest)
|
|
||||||
|
|
||||||
def pytest_collectstart(self, collector):
|
|
||||||
if isinstance(collector, py.__.test.pycollect.Module):
|
|
||||||
COVERAGE_MODULES.update(getattr(collector.obj,
|
|
||||||
'COVERAGE_MODULES', []))
|
|
||||||
|
|
||||||
def pytest_testrunstart(self):
|
|
||||||
print "self.coverage", self.coverage
|
|
||||||
if hasattr(self, 'coverage'):
|
|
||||||
print "START coverage"
|
|
||||||
self.coverage.erase()
|
|
||||||
self.coverage.start()
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 75 B |
|
@ -0,0 +1,115 @@
|
||||||
|
py.test/pylib 1.1.0: Python3, Jython, advanced skipping, cleanups ...
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* compatible to Python3 (single py2/py3 source), works with Distribute
|
||||||
|
* generalized marking_: mark tests one a whole-class or whole-module basis
|
||||||
|
* conditional skipping_: skip/xfail based on platform/dependencies
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* code reduction and "de-magification" (e.g. 23 KLoc -> 11 KLOC)
|
||||||
|
* distribute testing requires the now separately released 'execnet' package
|
||||||
|
* funcarg-setup/caching, "same-name" test modules now cause an exlicit error
|
||||||
|
* de-cluttered reporting, --report option for skipped/xfail details
|
||||||
|
|
||||||
|
Compatibilities
|
||||||
|
|
||||||
|
1.1.0 should allow running test code that already worked well with 1.0.2
|
||||||
|
plus some more due to improved unittest/nose compatibility.
|
||||||
|
|
||||||
|
More information:
|
||||||
|
|
||||||
|
http://pytest.org
|
||||||
|
|
||||||
|
thanks and have fun,
|
||||||
|
|
||||||
|
holger (http://twitter.com/hpk42)
|
||||||
|
|
||||||
|
.. _marking: ../test/plugin/mark.html
|
||||||
|
.. _skipping: ../test/plugin/skipping.html
|
||||||
|
|
||||||
|
|
||||||
|
Changelog 1.0.2 -> 1.1.0
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
* remove py.rest tool and internal namespace - it was
|
||||||
|
never really advertised and can still be used with
|
||||||
|
the old release if needed. If there is interest
|
||||||
|
it could be revived into its own tool i guess.
|
||||||
|
|
||||||
|
* fix issue48 and issue59: raise an Error if the module
|
||||||
|
from an imported test file does not seem to come from
|
||||||
|
the filepath - avoids "same-name" confusion that has
|
||||||
|
been reported repeatedly
|
||||||
|
|
||||||
|
* merged Ronny's nose-compatibility hacks: now
|
||||||
|
nose-style setup_module() and setup() functions are
|
||||||
|
supported
|
||||||
|
|
||||||
|
* introduce generalized py.test.mark function marking
|
||||||
|
|
||||||
|
* reshuffle / refine command line grouping
|
||||||
|
|
||||||
|
* deprecate parser.addgroup in favour of getgroup which creates option group
|
||||||
|
|
||||||
|
* add --report command line option that allows to control showing of skipped/xfailed sections
|
||||||
|
|
||||||
|
* generalized skipping: a new way to mark python functions with skipif or xfail
|
||||||
|
at function, class and modules level based on platform or sys-module attributes.
|
||||||
|
|
||||||
|
* extend py.test.mark decorator to allow for positional args
|
||||||
|
|
||||||
|
* introduce and test "py.cleanup -d" to remove empty directories
|
||||||
|
|
||||||
|
* fix issue #59 - robustify unittest test collection
|
||||||
|
|
||||||
|
* make bpython/help interaction work by adding an __all__ attribute
|
||||||
|
to ApiModule, cleanup initpkg
|
||||||
|
|
||||||
|
* use MIT license for pylib, add some contributors
|
||||||
|
|
||||||
|
* remove py.execnet code and substitute all usages with 'execnet' proper
|
||||||
|
|
||||||
|
* fix issue50 - cached_setup now caches more to expectations
|
||||||
|
for test functions with multiple arguments.
|
||||||
|
|
||||||
|
* merge Jarko's fixes, issue #45 and #46
|
||||||
|
|
||||||
|
* add the ability to specify a path for py.lookup to search in
|
||||||
|
|
||||||
|
* fix a funcarg cached_setup bug probably only occuring
|
||||||
|
in distributed testing and "module" scope with teardown.
|
||||||
|
|
||||||
|
* many fixes and changes for making the code base python3 compatible,
|
||||||
|
many thanks to Benjamin Peterson for helping with this.
|
||||||
|
|
||||||
|
* consolidate builtins implementation to be compatible with >=2.3,
|
||||||
|
add helpers to ease keeping 2 and 3k compatible code
|
||||||
|
|
||||||
|
* deprecate py.compat.doctest|subprocess|textwrap|optparse
|
||||||
|
|
||||||
|
* deprecate py.magic.autopath, remove py/magic directory
|
||||||
|
|
||||||
|
* move pytest assertion handling to py/code and a pytest_assertion
|
||||||
|
plugin, add "--no-assert" option, deprecate py.magic namespaces
|
||||||
|
in favour of (less) py.code ones.
|
||||||
|
|
||||||
|
* consolidate and cleanup py/code classes and files
|
||||||
|
|
||||||
|
* cleanup py/misc, move tests to bin-for-dist
|
||||||
|
|
||||||
|
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
|
||||||
|
|
||||||
|
* consolidate py.log implementation, remove old approach.
|
||||||
|
|
||||||
|
* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
|
||||||
|
text/unicode and byte-streams (uses underlying standard lib io.*
|
||||||
|
if available)
|
||||||
|
|
||||||
|
* make py.unittest_convert helper script available which converts "unittest.py"
|
||||||
|
style files into the simpler assert/direct-test-classes py.test/nosetests
|
||||||
|
style. The script was written by Laura Creighton.
|
||||||
|
|
||||||
|
* simplified internal localpath implementation
|
|
@ -5,10 +5,12 @@ Release notes
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 2
|
||||||
|
|
||||||
announce/release-1.0.2
|
.. include: release-1.1.0
|
||||||
announce/release-1.0.1
|
.. include: release-1.0.2
|
||||||
announce/release-1.0.0
|
|
||||||
announce/release-0.9.2
|
release-1.0.1
|
||||||
announce/release-0.9.0
|
release-1.0.0
|
||||||
|
release-0.9.2
|
||||||
|
release-0.9.0
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
Changes between 1.0.2 and '1.1.0b1'
|
Changes between 1.1.0 and 1.0.2
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
* adjust and improve docs
|
||||||
|
|
||||||
* remove py.rest tool and internal namespace - it was
|
* remove py.rest tool and internal namespace - it was
|
||||||
never really advertised and can still be used with
|
never really advertised and can still be used with
|
||||||
the old release if needed. If there is interest
|
the old release if needed. If there is interest
|
||||||
|
@ -49,6 +51,9 @@ Changes between 1.0.2 and '1.1.0b1'
|
||||||
* fix a funcarg cached_setup bug probably only occuring
|
* fix a funcarg cached_setup bug probably only occuring
|
||||||
in distributed testing and "module" scope with teardown.
|
in distributed testing and "module" scope with teardown.
|
||||||
|
|
||||||
|
* many fixes and changes for making the code base python3 compatible,
|
||||||
|
many thanks to Benjamin Peterson for helping with this.
|
||||||
|
|
||||||
* consolidate builtins implementation to be compatible with >=2.3,
|
* consolidate builtins implementation to be compatible with >=2.3,
|
||||||
add helpers to ease keeping 2 and 3k compatible code
|
add helpers to ease keeping 2 and 3k compatible code
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ Contents of the library
|
||||||
Every object in the ``py.code`` library wraps a code Python object related
|
Every object in the ``py.code`` library wraps a code Python object related
|
||||||
to code objects, source code, frames and tracebacks: the ``py.code.Code``
|
to code objects, source code, frames and tracebacks: the ``py.code.Code``
|
||||||
class wraps code objects, ``py.code.Source`` source snippets,
|
class wraps code objects, ``py.code.Source`` source snippets,
|
||||||
``py.code.Traceback` exception tracebacks, :api:`py.code.Frame`` frame
|
``py.code.Traceback` exception tracebacks, ``py.code.Frame`` frame
|
||||||
objects (as found in e.g. tracebacks) and ``py.code.ExceptionInfo`` the
|
objects (as found in e.g. tracebacks) and ``py.code.ExceptionInfo`` the
|
||||||
tuple provided by sys.exc_info() (containing exception and traceback
|
tuple provided by sys.exc_info() (containing exception and traceback
|
||||||
information when an exception occurs). Also in the library is a helper function
|
information when an exception occurs). Also in the library is a helper function
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from _py.test.plugin.pytest_restdoc import convert_rest_html, strip_html_header
|
from py.plugin.pytest_restdoc import convert_rest_html, strip_html_header
|
||||||
|
|
||||||
html = py.xml.html
|
html = py.xml.html
|
||||||
|
|
||||||
|
@ -57,23 +57,23 @@ pageTracker._trackPageview();
|
||||||
|
|
||||||
def fill_menubar(self):
|
def fill_menubar(self):
|
||||||
items = [
|
items = [
|
||||||
self.a_docref("install", "install.html"),
|
self.a_docref("INSTALL", "install.html"),
|
||||||
self.a_docref("contact", "contact.html"),
|
self.a_docref("CONTACT", "contact.html"),
|
||||||
self.a_docref("changelog", "changelog.html"),
|
self.a_docref("CHANGELOG", "changelog.html"),
|
||||||
self.a_docref("faq", "faq.html"),
|
self.a_docref("FAQ", "faq.html"),
|
||||||
html.div(
|
html.div(
|
||||||
html.h3("py.test:"),
|
html.h3("py.test:"),
|
||||||
self.a_docref("doc index", "test/index.html"),
|
self.a_docref("Index", "test/index.html"),
|
||||||
self.a_docref("features", "test/features.html"),
|
self.a_docref("Quickstart", "test/quickstart.html"),
|
||||||
self.a_docref("quickstart", "test/quickstart.html"),
|
self.a_docref("Features", "test/features.html"),
|
||||||
self.a_docref("tutorials", "test/talks.html"),
|
self.a_docref("Plugins", "test/plugin/index.html"),
|
||||||
self.a_docref("plugins", "test/plugin/index.html"),
|
self.a_docref("Funcargs", "test/funcargs.html"),
|
||||||
self.a_docref("funcargs", "test/funcargs.html"),
|
self.a_docref("Customize", "test/customize.html"),
|
||||||
self.a_docref("customize", "test/customize.html"),
|
self.a_docref("Tutorials", "test/talks.html"),
|
||||||
),
|
),
|
||||||
html.div(
|
html.div(
|
||||||
html.h3("supporting APIs:"),
|
html.h3("supporting APIs:"),
|
||||||
self.a_docref("pylib index", "index.html"),
|
self.a_docref("Index", "index.html"),
|
||||||
self.a_docref("py.path", "path.html"),
|
self.a_docref("py.path", "path.html"),
|
||||||
self.a_docref("py.code", "code.html"),
|
self.a_docref("py.code", "code.html"),
|
||||||
)
|
)
|
||||||
|
@ -85,9 +85,10 @@ pageTracker._trackPageview();
|
||||||
self.menubar = html.div(id=css.menubar, *[
|
self.menubar = html.div(id=css.menubar, *[
|
||||||
html.div(item) for item in items])
|
html.div(item) for item in items])
|
||||||
version = py.version
|
version = py.version
|
||||||
|
announcelink = self.a_docref("%s ANN" % version,
|
||||||
|
"announce/release-%s.html" %(version,))
|
||||||
self.menubar.insert(0,
|
self.menubar.insert(0,
|
||||||
html.div("%s" % (py.version), style="font-style: italic;")
|
html.div(announcelink))
|
||||||
)
|
|
||||||
#self.a_href("%s-%s" % (self.title, py.version),
|
#self.a_href("%s-%s" % (self.title, py.version),
|
||||||
# "http://pypi.python.org/pypi/py/%s" % version,
|
# "http://pypi.python.org/pypi/py/%s" % version,
|
||||||
#id="versioninfo",
|
#id="versioninfo",
|
||||||
|
|
84
doc/faq.txt
84
doc/faq.txt
|
@ -13,75 +13,79 @@ On naming, nosetests, licensing and magic
|
||||||
Why the ``py`` naming? what is it?
|
Why the ``py`` naming? what is it?
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
Because the name was kind of available and there was the
|
Because the name was available and there was the
|
||||||
idea to have the package evolve into a "standard" library
|
idea to have the package evolve into a "standard" library
|
||||||
kind of thing that works cross-python versions and is
|
kind of thing that works cross-python versions and is
|
||||||
not tied to a particular CPython revision or its release
|
not tied to a particular CPython revision or its release
|
||||||
cycle. Clearly, this was ambitious and the naming
|
cycle. Clearly, this was ambitious and the naming
|
||||||
has maybe haunted the project rather than helping it.
|
has maybe haunted the project rather than helping it.
|
||||||
There may be a project name change and possibly a
|
|
||||||
split up into different projects sometime.
|
|
||||||
|
|
||||||
Why the ``py.test`` naming?
|
Why the ``py.test`` naming?
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
the py lib contains other command line tools that
|
because of TAB-completion under Bash/Shells. If you hit
|
||||||
all share the ``py.`` prefix which makes it easy
|
``py.<TAB>`` you'll get a list of available development
|
||||||
to use TAB-completion on the shell. Another motivation
|
tools that all share the ``py.`` prefix. Another motivation
|
||||||
was to make it obvious where testing functionality
|
was to unify the package ("py.test") and tool filename.
|
||||||
for the ``py.test`` command line tool is: in the
|
|
||||||
``py.test`` package name space.
|
|
||||||
|
|
||||||
What's py.test's relation to ``nosetests``?
|
What's py.test's relation to ``nosetests``?
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
py.test and nose_ share basic philosophy when it comes
|
py.test and nose_ share basic philosophy when it comes
|
||||||
to running Python tests. In fact,
|
to running Python tests. In fact,
|
||||||
with py.test-1.0.1 it is easy to run many test suites
|
with py.test-1.1.0 it is ever easier to run many test suites
|
||||||
that currently work with ``nosetests``. nose_ was created
|
that currently work with ``nosetests``. nose_ was created
|
||||||
as a clone of ``py.test`` when it was in the ``0.8`` release
|
as a clone of ``py.test`` when py.test was in the ``0.8`` release
|
||||||
cycle so some of the newer features_ introduced with py.test-1.0
|
cycle so some of the newer features_ introduced with py.test-1.0
|
||||||
have no counterpart in nose_.
|
and py.test-1.1 have no counterpart in nose_.
|
||||||
|
|
||||||
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/
|
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/
|
||||||
.. _features: test/features.html
|
.. _features: test/features.html
|
||||||
|
.. _apipkg: http://pypi.python.org/pypi/apipkg
|
||||||
|
|
||||||
What's all this "magic" with py.test?
|
|
||||||
|
What's this "magic" with py.test?
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
"All this magic" usually boils down to two issues:
|
issues where people have used the term "magic" in the past:
|
||||||
|
|
||||||
* There is a special tweak to importing: `py/__init__.py`_ contains
|
* `py/__init__.py`_ uses the apipkg_ mechanism for lazy-importing
|
||||||
a dictionary which maps the importable ``py.*`` namespaces to
|
and full control on what API you get when importing "import py".
|
||||||
objects in files. When looking at the project source code
|
|
||||||
you see imports like ``from py.__.test.session import Session``. The
|
|
||||||
the double ``__`` underscore indicates the "normal" python
|
|
||||||
filesystem/namespace coupled import, i.e. it points to
|
|
||||||
``py/test/session.py``'s ``Session`` object. However,
|
|
||||||
from the outside you use the "non-underscore" `py namespaces`_
|
|
||||||
so this distinction usually only shows up if you hack
|
|
||||||
on internal code or see internal tracebacks.
|
|
||||||
|
|
||||||
* when an ``assert`` fails, py.test re-interprets the expression
|
* when an ``assert`` statement fails, py.test re-interprets the expression
|
||||||
to show intermediate values. This allows to use the plain ``assert``
|
to show intermediate values if a test fails. If your expression
|
||||||
statement instead of the many methods that you otherwise need
|
has side effects the intermediate values may not be the same, obfuscating
|
||||||
to mimick this behaviour. This means that in case of a failing
|
the initial error (this is also explained at the command line if it happens).
|
||||||
assert, your expressions gets evaluated *twice*. If your expression
|
``py.test --no-assert`` turns off assert re-intepretation.
|
||||||
has side effects the outcome may be different. If the test suddenly
|
Sidenote: it is good practise to avoid asserts with side effects.
|
||||||
passes you will get a detailed message. It is good practise, anyway,
|
|
||||||
to not have asserts with side effects. ``py.test --nomagic`` turns
|
|
||||||
off assert re-intepretation.
|
|
||||||
|
|
||||||
Other than that, ``py.test`` has bugs or quirks like any other computer
|
|
||||||
software. In fact, it has a *strong* focus on running robustly and has
|
|
||||||
over a thousand automated tests for its own code base.
|
|
||||||
|
|
||||||
.. _`py namespaces`: index.html
|
.. _`py namespaces`: index.html
|
||||||
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/1.0.x/py/__init__.py
|
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
|
||||||
|
|
||||||
|
|
||||||
function arguments and parametrized tests
|
function arguments, parametrized tests and setup
|
||||||
===============================================
|
====================================================
|
||||||
|
|
||||||
|
.. _funcargs: test/funcargs.html
|
||||||
|
|
||||||
|
Is using funcarg- versus xUnit-based setup a style question?
|
||||||
|
---------------------------------------------------------------
|
||||||
|
|
||||||
|
It depends. For simple applications or for people experienced
|
||||||
|
with nose_ or unittest-style test setup using `xUnit style setup`_
|
||||||
|
make some sense. For larger test suites, parametrized testing
|
||||||
|
or setup of complex test resources using funcargs_ is recommended.
|
||||||
|
Moreover, funcargs are ideal for writing advanced test support
|
||||||
|
code (like e.g. the monkeypatch_, the tmpdir_ or capture_ funcargs)
|
||||||
|
because the support code can register setup/teardown functions
|
||||||
|
in a managed class/module/function scope.
|
||||||
|
|
||||||
|
.. _monkeypatch: test/plugin/monkeypatch.html
|
||||||
|
.. _tmpdir: test/plugin/tmpdir.html
|
||||||
|
.. _capture: test/plugin/capture.html
|
||||||
|
.. _`xUnit style setup`: test/xunit_setup.html
|
||||||
|
.. _`pytest_nose`: test/plugin/nose.html
|
||||||
|
|
||||||
.. _`why pytest_pyfuncarg__ methods?`:
|
.. _`why pytest_pyfuncarg__ methods?`:
|
||||||
|
|
||||||
|
@ -94,7 +98,7 @@ flexibility we decided to go for `Convention over Configuration`_ and
|
||||||
allow to directly specify the factory. Besides removing the need
|
allow to directly specify the factory. Besides removing the need
|
||||||
for an indirection it allows to "grep" for ``pytest_funcarg__MYARG``
|
for an indirection it allows to "grep" for ``pytest_funcarg__MYARG``
|
||||||
and will safely find all factory functions for the ``MYARG`` function
|
and will safely find all factory functions for the ``MYARG`` function
|
||||||
argument. It helps to alleviates the de-coupling of function
|
argument. It helps to alleviate the de-coupling of function
|
||||||
argument usage and creation.
|
argument usage and creation.
|
||||||
|
|
||||||
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
|
||||||
|
|
|
@ -29,7 +29,6 @@ Other (minor) support functionality
|
||||||
|
|
||||||
For the latest Release, see `PyPI project page`_
|
For the latest Release, see `PyPI project page`_
|
||||||
|
|
||||||
.. _`download and installation`: download.html
|
|
||||||
.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev
|
.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev
|
||||||
.. _`py.log`: log.html
|
.. _`py.log`: log.html
|
||||||
.. _`py.io`: io.html
|
.. _`py.io`: io.html
|
||||||
|
|
|
@ -25,7 +25,7 @@ on Windows you might need to write down the full path to ``easy_install``.
|
||||||
|
|
||||||
The py lib and its tools are expected to work well on Linux,
|
The py lib and its tools are expected to work well on Linux,
|
||||||
Windows and OSX, Python versions 2.4, 2.5, 2.6 through to
|
Windows and OSX, Python versions 2.4, 2.5, 2.6 through to
|
||||||
the Python3 versions 3.0 and 3.1. Jython
|
the Python3 versions 3.0 and 3.1 and Jython
|
||||||
|
|
||||||
.. _mercurial: http://mercurial.selenic.com/wiki/
|
.. _mercurial: http://mercurial.selenic.com/wiki/
|
||||||
.. _`Distribute`:
|
.. _`Distribute`:
|
||||||
|
@ -43,15 +43,13 @@ and documentation source with mercurial_::
|
||||||
|
|
||||||
hg clone https://bitbucket.org/hpk42/py-trunk/
|
hg clone https://bitbucket.org/hpk42/py-trunk/
|
||||||
|
|
||||||
This currrently contains a 1.0.x branch and the
|
Development usually takes place on the 'trunk' branch.
|
||||||
default 'trunk' branch where mainline development
|
|
||||||
takes place.
|
|
||||||
|
|
||||||
.. There also is a readonly subversion
|
.. There also is a readonly subversion
|
||||||
checkout available which contains the latest release::
|
checkout available which contains the latest release::
|
||||||
svn co https://codespeak.net/svn/py/dist
|
svn co https://codespeak.net/svn/py/dist
|
||||||
|
|
||||||
You can go to the python package index and
|
You can also go to the python package index and
|
||||||
download and unpack a TAR file::
|
download and unpack a TAR file::
|
||||||
|
|
||||||
http://pypi.python.org/pypi/py/
|
http://pypi.python.org/pypi/py/
|
||||||
|
@ -64,7 +62,7 @@ With a working `Distribute`_ or setuptools_ installation you can type::
|
||||||
|
|
||||||
python setup.py develop
|
python setup.py develop
|
||||||
|
|
||||||
in order to work with the tools and the lib of your checkout.
|
in order to work inline with the tools and the lib of your checkout.
|
||||||
|
|
||||||
.. _`no-setuptools`:
|
.. _`no-setuptools`:
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ a ``py.path.local`` object for us (which wraps a directory):
|
||||||
>>> foofile.read(1)
|
>>> foofile.read(1)
|
||||||
'b'
|
'b'
|
||||||
|
|
||||||
``py.path.svnurl` and :api:`py.path.svnwc``
|
``py.path.svnurl` and ``py.path.svnwc``
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
Two other ``py.path`` implementations that the py lib provides wrap the
|
Two other ``py.path`` implementations that the py lib provides wrap the
|
||||||
|
|
|
@ -14,6 +14,8 @@ specify different Python versions and interpreters.
|
||||||
**Requirements**: you need to install the `execnet`_ package
|
**Requirements**: you need to install the `execnet`_ package
|
||||||
to perform distributed test runs.
|
to perform distributed test runs.
|
||||||
|
|
||||||
|
**NOTE**: Version 1.1.0 is not able to distribute tests across Python3/Python2 barriers.
|
||||||
|
|
||||||
Speed up test runs by sending tests to multiple CPUs
|
Speed up test runs by sending tests to multiple CPUs
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ naming patterns. As ``py.test`` operates as a separate
|
||||||
cmdline tool you can easily have a command line utility and
|
cmdline tool you can easily have a command line utility and
|
||||||
some tests in the same file.
|
some tests in the same file.
|
||||||
|
|
||||||
supports many testing practises and methods
|
supports several testing practises and methods
|
||||||
==================================================================
|
==================================================================
|
||||||
|
|
||||||
py.test supports many testing methods conventionally used in
|
py.test supports many testing methods conventionally used in
|
||||||
|
|
|
@ -1,43 +1,301 @@
|
||||||
==========================================================
|
==============================================================
|
||||||
**funcargs**: test function arguments FTW
|
**funcargs**: advanced test setup and parametrization
|
||||||
==========================================================
|
==============================================================
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
:local:
|
||||||
:depth: 2
|
:depth: 2
|
||||||
|
|
||||||
Goals of the "funcarg" mechanism
|
what are "funcargs" and what are they good for?
|
||||||
==========================================
|
=================================================
|
||||||
|
|
||||||
Since version 1.0 py.test features the "funcarg" mechanism which
|
Named parameters of a test function are called *funcargs* for short.
|
||||||
allows a Python test function to take arguments independently provided
|
A Funcarg can be a simple number of a complex object. To perform a
|
||||||
by factory functions. Factory functions allow to encapsulate
|
test function call each parameter is setup by a factory function.
|
||||||
all setup and fixture glue code into nicely separated objects
|
To call a test function repeatedly with different funcargs sets
|
||||||
and provide a natural way for writing python test functions.
|
test parameters can be generated.
|
||||||
Compared to `xUnit style`_ the new mechanism is meant to:
|
|
||||||
|
|
||||||
* make test functions easier to write and to read
|
|
||||||
* isolate test fixture creation to a single place
|
|
||||||
* bring new flexibility and power to test state management
|
|
||||||
* naturally extend towards parametrizing test functions
|
|
||||||
with multiple argument sets
|
|
||||||
* enable creation of zero-boilerplate test helper objects that
|
|
||||||
interact with the execution of a test function, see the
|
|
||||||
`blog post about the monkeypatch funcarg`_.
|
|
||||||
|
|
||||||
If you find issues or have further suggestions for improving
|
|
||||||
the mechanism you are welcome to checkout `contact possibilities`_ page.
|
|
||||||
|
|
||||||
.. _`contact possibilities`: ../contact.html
|
.. _`contact possibilities`: ../contact.html
|
||||||
|
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
|
||||||
|
|
||||||
.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
.. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||||
.. _`xUnit style`: xunit_setup.html
|
.. _`xUnit style`: xunit_setup.html
|
||||||
|
|
||||||
|
|
||||||
|
.. _`funcarg factory`:
|
||||||
|
.. _factory:
|
||||||
|
|
||||||
|
funcarg factories: setting up test function arguments
|
||||||
|
==============================================================
|
||||||
|
|
||||||
|
Test functions can specify one ore more arguments ("funcargs")
|
||||||
|
and a test module or plugin can define factory functions that provide
|
||||||
|
the function argument. Let's look at a simple self-contained
|
||||||
|
example that you can put into a test module:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
# ./test_simplefactory.py
|
||||||
|
def pytest_funcarg__myfuncarg(request):
|
||||||
|
return 42
|
||||||
|
|
||||||
|
def test_function(myfuncarg):
|
||||||
|
assert myfuncarg == 17
|
||||||
|
|
||||||
|
If you run this with ``py.test test_simplefactory.py`` you see something like this:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
=========================== test session starts ============================
|
||||||
|
python: platform linux2 -- Python 2.6.2
|
||||||
|
test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py
|
||||||
|
|
||||||
|
test_simplefactory.py F
|
||||||
|
|
||||||
|
================================ FAILURES ==================================
|
||||||
|
______________________________ test_function _______________________________
|
||||||
|
|
||||||
|
myfuncarg = 42
|
||||||
|
|
||||||
|
def test_function(myfuncarg):
|
||||||
|
> assert myfuncarg == 17
|
||||||
|
E assert 42 == 17
|
||||||
|
|
||||||
|
test_simplefactory.py:6: AssertionError
|
||||||
|
======================== 1 failed in 0.11 seconds ==========================
|
||||||
|
|
||||||
|
|
||||||
|
This means that the test function was called with a ``myfuncarg`` value
|
||||||
|
of ``42`` and the assert fails accordingly. Here is how py.test
|
||||||
|
calls the test function:
|
||||||
|
|
||||||
|
1. py.test discovers the ``test_function`` because of the ``test_`` prefix.
|
||||||
|
The test function needs a function argument named ``myfuncarg``.
|
||||||
|
A matching factory function is discovered by looking for the
|
||||||
|
name ``pytest_funcarg__myfuncarg``.
|
||||||
|
|
||||||
|
2. ``pytest_funcarg__myfuncarg(request)`` is called and
|
||||||
|
returns the value for ``myfuncarg``.
|
||||||
|
|
||||||
|
3. ``test_function(42)`` call is executed.
|
||||||
|
|
||||||
|
Note that if you misspell a function argument or want
|
||||||
|
to use one that isn't available, you'll see an error
|
||||||
|
with a list of available function arguments.
|
||||||
|
|
||||||
|
factory functions receive a `request object`_
|
||||||
|
which they can use to register setup/teardown
|
||||||
|
functions or access meta data about a test.
|
||||||
|
|
||||||
|
.. _`request object`:
|
||||||
|
|
||||||
|
funcarg factory request objects
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Request objects are passed to funcarg factories and allow
|
||||||
|
to access test configuration, test context and `useful caching
|
||||||
|
and finalization helpers`_. Here is a list of attributes:
|
||||||
|
|
||||||
|
``request.function``: python function object requesting the argument
|
||||||
|
|
||||||
|
``request.cls``: class object where the test function is defined in or None.
|
||||||
|
|
||||||
|
``request.module``: module object where the test function is defined in.
|
||||||
|
|
||||||
|
``request.config``: access to command line opts and general config
|
||||||
|
|
||||||
|
``request.param``: if exists was passed by a previous `metafunc.addcall`_
|
||||||
|
|
||||||
|
.. _`useful caching and finalization helpers`:
|
||||||
|
|
||||||
|
|
||||||
|
registering funcarg related finalizers/cleanup
|
||||||
|
----------------------------------------------------
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def addfinalizer(func):
|
||||||
|
""" call a finalizer function when test function finishes. """
|
||||||
|
|
||||||
|
Calling ``request.addfinalizer()`` is useful for scheduling teardown
|
||||||
|
functions. Here is an example for providing a ``myfile``
|
||||||
|
object that is to be closed when the execution of a
|
||||||
|
test function finishes.
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def pytest_funcarg__myfile(self, request):
|
||||||
|
# ... create and open a unique per-function "myfile" object ...
|
||||||
|
request.addfinalizer(lambda: myfile.close())
|
||||||
|
return myfile
|
||||||
|
|
||||||
|
|
||||||
|
managing fixtures across test modules and test runs
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def cached_setup(setup, teardown=None, scope="module", extrakey=None):
|
||||||
|
""" cache and return result of calling setup().
|
||||||
|
|
||||||
|
The requested argument name, the scope and the ``extrakey``
|
||||||
|
determine the cache key. The scope also determines when
|
||||||
|
teardown(result) will be called. valid scopes are:
|
||||||
|
scope == 'function': when the single test function run finishes.
|
||||||
|
scope == 'module': when tests in a different module are run
|
||||||
|
scope == 'session': when tests of the session have run.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Calling ``request.cached_setup()`` helps you to manage fixture
|
||||||
|
objects across several scopes. For example, for creating a Database object
|
||||||
|
that is to be setup only once during a test session you can use the helper
|
||||||
|
like this:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def pytest_funcarg__database(request):
|
||||||
|
return request.cached_setup(
|
||||||
|
setup=lambda: Database("..."),
|
||||||
|
teardown=lambda val: val.close(),
|
||||||
|
scope="session"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
requesting values of other funcargs
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def getfuncargvalue(name):
|
||||||
|
""" Lookup and call function argument factory for the given name.
|
||||||
|
Each function argument is only created once per function setup.
|
||||||
|
"""
|
||||||
|
|
||||||
|
``request.getfuncargvalue(name)`` calls another funcarg factory function.
|
||||||
|
You can use this function if you want to `decorate a funcarg`_, i.e.
|
||||||
|
you want to provide the "normal" value but add something
|
||||||
|
extra. If a factory cannot be found a ``request.Error``
|
||||||
|
exception will be raised.
|
||||||
|
|
||||||
|
.. _`test generators`:
|
||||||
|
.. _`parametrizing-tests`:
|
||||||
|
|
||||||
|
generating parametrized tests
|
||||||
|
===========================================================
|
||||||
|
|
||||||
|
You can parametrize multiple runs of the same test
|
||||||
|
function by adding new test function calls with different
|
||||||
|
function argument values. Let's look at a simple self-contained
|
||||||
|
example:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
# ./test_example.py
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
if "numiter" in metafunc.funcargnames:
|
||||||
|
for i in range(10):
|
||||||
|
metafunc.addcall(funcargs=dict(numiter=i))
|
||||||
|
|
||||||
|
def test_func(numiter):
|
||||||
|
assert numiter < 9
|
||||||
|
|
||||||
|
If you run this with ``py.test test_example.py`` you'll get:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
============================= test session starts ==========================
|
||||||
|
python: platform linux2 -- Python 2.6.2
|
||||||
|
test object 1: /home/hpk/hg/py/trunk/test_example.py
|
||||||
|
|
||||||
|
test_example.py .........F
|
||||||
|
|
||||||
|
================================ FAILURES ==================================
|
||||||
|
__________________________ test_func.test_func[9] __________________________
|
||||||
|
|
||||||
|
numiter = 9
|
||||||
|
|
||||||
|
def test_func(numiter):
|
||||||
|
> assert numiter < 9
|
||||||
|
E assert 9 < 9
|
||||||
|
|
||||||
|
/home/hpk/hg/py/trunk/test_example.py:10: AssertionError
|
||||||
|
|
||||||
|
|
||||||
|
Here is what happens in detail:
|
||||||
|
|
||||||
|
1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
|
||||||
|
function. It adds ten new function calls with explicit function arguments.
|
||||||
|
|
||||||
|
2. **execute tests**: ``test_func(numiter)`` is called ten times with
|
||||||
|
ten different arguments.
|
||||||
|
|
||||||
|
.. _`metafunc object`:
|
||||||
|
|
||||||
|
test generators and metafunc objects
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
metafunc objects are passed to the ``pytest_generate_tests`` hook.
|
||||||
|
They help to inspect a testfunction and to generate tests
|
||||||
|
according to test configuration or values specified
|
||||||
|
in the class or module where a test function is defined:
|
||||||
|
|
||||||
|
``metafunc.funcargnames``: set of required function arguments for given function
|
||||||
|
|
||||||
|
``metafunc.function``: underlying python test function
|
||||||
|
|
||||||
|
``metafunc.cls``: class object where the test function is defined in or None.
|
||||||
|
|
||||||
|
``metafunc.module``: the module object where the test function is defined in.
|
||||||
|
|
||||||
|
``metafunc.config``: access to command line opts and general config
|
||||||
|
|
||||||
|
|
||||||
|
.. _`metafunc.addcall`:
|
||||||
|
|
||||||
|
the ``metafunc.addcall()`` method
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def addcall(funcargs={}, id=None, param=None):
|
||||||
|
""" trigger a new test function call. """
|
||||||
|
|
||||||
|
``funcargs`` can be a dictionary of argument names
|
||||||
|
mapped to values - providing it is called *direct parametrization*.
|
||||||
|
|
||||||
|
If you provide an `id`` it will be used for reporting
|
||||||
|
and identification purposes. If you don't supply an `id`
|
||||||
|
the stringified counter of the list of added calls will be used.
|
||||||
|
``id`` values needs to be unique between all
|
||||||
|
invocations for a given test function.
|
||||||
|
|
||||||
|
``param`` if specified will be seen by any
|
||||||
|
`funcarg factory`_ as a ``request.param`` attribute.
|
||||||
|
Setting it is called *indirect parametrization*.
|
||||||
|
|
||||||
|
Indirect parametrization is preferable if test values are
|
||||||
|
expensive to setup or can only be created in certain environments.
|
||||||
|
Test generators and thus ``addcall()`` invocations are performed
|
||||||
|
during test collection which is separate from the actual test
|
||||||
|
setup and test run phase. With distributed testing collection
|
||||||
|
and test setup/run happens in different process.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _`tutorial examples`:
|
.. _`tutorial examples`:
|
||||||
|
|
||||||
Tutorial Examples
|
Tutorial Examples
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
|
To see how you can implement custom paramtrization schemes,
|
||||||
|
see e.g. `parametrizing tests, generalized`_ (blog post).
|
||||||
|
|
||||||
|
To enable creation of test support code that can flexibly
|
||||||
|
register setup/teardown functions see the `blog post about
|
||||||
|
the monkeypatch funcarg`_.
|
||||||
|
|
||||||
|
If you find issues or have further suggestions for improving
|
||||||
|
the mechanism you are welcome to checkout `contact possibilities`_ page.
|
||||||
|
|
||||||
.. _`application setup tutorial example`:
|
.. _`application setup tutorial example`:
|
||||||
.. _appsetup:
|
.. _appsetup:
|
||||||
|
@ -274,262 +532,3 @@ methods in a convenient way.
|
||||||
|
|
||||||
.. _`py.path.local`: ../path.html#local
|
.. _`py.path.local`: ../path.html#local
|
||||||
.. _`conftest plugin`: customize.html#conftestplugin
|
.. _`conftest plugin`: customize.html#conftestplugin
|
||||||
|
|
||||||
.. _`funcarg factory`:
|
|
||||||
.. _factory:
|
|
||||||
|
|
||||||
funcarg factories: setting up test function arguments
|
|
||||||
==============================================================
|
|
||||||
|
|
||||||
Test functions can specify one ore more arguments ("funcargs")
|
|
||||||
and a test module or plugin can define functions that provide
|
|
||||||
the function argument. Let's look at a simple self-contained
|
|
||||||
example that you can put into a test module:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./test_simplefactory.py
|
|
||||||
def pytest_funcarg__myfuncarg(request):
|
|
||||||
return 42
|
|
||||||
|
|
||||||
def test_function(myfuncarg):
|
|
||||||
assert myfuncarg == 17
|
|
||||||
|
|
||||||
If you run this with ``py.test test_simplefactory.py`` you see something like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
=========================== test session starts ============================
|
|
||||||
python: platform linux2 -- Python 2.6.2
|
|
||||||
test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py
|
|
||||||
|
|
||||||
test_simplefactory.py F
|
|
||||||
|
|
||||||
================================ FAILURES ==================================
|
|
||||||
______________________________ test_function _______________________________
|
|
||||||
|
|
||||||
myfuncarg = 42
|
|
||||||
|
|
||||||
def test_function(myfuncarg):
|
|
||||||
> assert myfuncarg == 17
|
|
||||||
E assert 42 == 17
|
|
||||||
|
|
||||||
test_simplefactory.py:6: AssertionError
|
|
||||||
======================== 1 failed in 0.11 seconds ==========================
|
|
||||||
|
|
||||||
|
|
||||||
This means that the test function got executed and the assertion failed.
|
|
||||||
Here is how py.test comes to execute this test function:
|
|
||||||
|
|
||||||
1. py.test discovers the ``test_function`` because of the ``test_`` prefix.
|
|
||||||
The test function needs a function argument named ``myfuncarg``.
|
|
||||||
A matching factory function is discovered by looking for the special
|
|
||||||
name ``pytest_funcarg__myfuncarg``.
|
|
||||||
|
|
||||||
2. ``pytest_funcarg__myfuncarg(request)`` is called and
|
|
||||||
returns the value for ``myfuncarg``.
|
|
||||||
|
|
||||||
3. ``test_function(42)`` call is executed.
|
|
||||||
|
|
||||||
Note that if you misspell a function argument or want
|
|
||||||
to use one that isn't available, an error with a list of
|
|
||||||
available function argument is provided.
|
|
||||||
|
|
||||||
For more interesting factory functions that make good use of the
|
|
||||||
`request object`_ please see the `application setup tutorial example`_.
|
|
||||||
|
|
||||||
.. _`request object`:
|
|
||||||
|
|
||||||
funcarg factory request objects
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
Request objects are passed to funcarg factories and allow
|
|
||||||
to access test configuration, test context and `useful caching
|
|
||||||
and finalization helpers`_. Here is a list of attributes:
|
|
||||||
|
|
||||||
``request.function``: python function object requesting the argument
|
|
||||||
|
|
||||||
``request.cls``: class object where the test function is defined in or None.
|
|
||||||
|
|
||||||
``request.module``: module object where the test function is defined in.
|
|
||||||
|
|
||||||
``request.config``: access to command line opts and general config
|
|
||||||
|
|
||||||
``request.param``: if exists was passed by a previous `metafunc.addcall`_
|
|
||||||
|
|
||||||
.. _`useful caching and finalization helpers`:
|
|
||||||
|
|
||||||
|
|
||||||
registering funcarg related finalizers/cleanup
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def addfinalizer(func):
|
|
||||||
""" call a finalizer function when test function finishes. """
|
|
||||||
|
|
||||||
Calling ``request.addfinalizer()`` is useful for scheduling teardown
|
|
||||||
functions. Here is an example for providing a ``myfile``
|
|
||||||
object that is to be closed when the execution of a
|
|
||||||
test function finishes.
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def pytest_funcarg__myfile(self, request):
|
|
||||||
# ... create and open a unique per-function "myfile" object ...
|
|
||||||
request.addfinalizer(lambda: myfile.close())
|
|
||||||
return myfile
|
|
||||||
|
|
||||||
|
|
||||||
managing fixtures across test modules and test runs
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def cached_setup(setup, teardown=None, scope="module", extrakey=None):
|
|
||||||
""" cache and return result of calling setup().
|
|
||||||
|
|
||||||
The requested argument name, the scope and the ``extrakey``
|
|
||||||
determine the cache key. The scope also determines when
|
|
||||||
teardown(result) will be called. valid scopes are:
|
|
||||||
scope == 'function': when the single test function run finishes.
|
|
||||||
scope == 'module': when tests in a different module are run
|
|
||||||
scope == 'session': when tests of the session have run.
|
|
||||||
"""
|
|
||||||
|
|
||||||
Calling ``request.cached_setup()`` helps you to manage fixture
|
|
||||||
objects across several scopes. For example, for creating a Database object
|
|
||||||
that is to be setup only once during a test session you can use the helper
|
|
||||||
like this:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def pytest_funcarg__database(request):
|
|
||||||
return request.cached_setup(
|
|
||||||
setup=lambda: Database("..."),
|
|
||||||
teardown=lambda val: val.close(),
|
|
||||||
scope="session"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
requesting values of other funcargs
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def getfuncargvalue(name):
|
|
||||||
""" Lookup and call function argument factory for the given name.
|
|
||||||
Each function argument is only created once per function setup.
|
|
||||||
"""
|
|
||||||
|
|
||||||
``request.getfuncargvalue(name)`` calls another funcarg factory function.
|
|
||||||
You can use this function if you want to `decorate a funcarg`_, i.e.
|
|
||||||
you want to provide the "normal" value but add something
|
|
||||||
extra. If a factory cannot be found a ``request.Error``
|
|
||||||
exception will be raised.
|
|
||||||
|
|
||||||
.. _`test generators`:
|
|
||||||
.. _`parametrizing-tests`:
|
|
||||||
|
|
||||||
generating parametrized tests
|
|
||||||
===========================================================
|
|
||||||
|
|
||||||
You can parametrize multiple runs of the same test
|
|
||||||
function by adding new test function calls with different
|
|
||||||
function argument values. Let's look at a simple self-contained
|
|
||||||
example:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
# ./test_example.py
|
|
||||||
def pytest_generate_tests(metafunc):
|
|
||||||
if "numiter" in metafunc.funcargnames:
|
|
||||||
for i in range(10):
|
|
||||||
metafunc.addcall(funcargs=dict(numiter=i))
|
|
||||||
|
|
||||||
def test_func(numiter):
|
|
||||||
assert numiter < 9
|
|
||||||
|
|
||||||
If you run this with ``py.test test_example.py`` you'll get:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
============================= test session starts ==========================
|
|
||||||
python: platform linux2 -- Python 2.6.2
|
|
||||||
test object 1: /home/hpk/hg/py/trunk/test_example.py
|
|
||||||
|
|
||||||
test_example.py .........F
|
|
||||||
|
|
||||||
================================ FAILURES ==================================
|
|
||||||
__________________________ test_func.test_func[9] __________________________
|
|
||||||
|
|
||||||
numiter = 9
|
|
||||||
|
|
||||||
def test_func(numiter):
|
|
||||||
> assert numiter < 9
|
|
||||||
E assert 9 < 9
|
|
||||||
|
|
||||||
/home/hpk/hg/py/trunk/test_example.py:10: AssertionError
|
|
||||||
|
|
||||||
|
|
||||||
Here is what happens in detail:
|
|
||||||
|
|
||||||
1. ``pytest_generate_tests(metafunc)`` hook is called once for each test
|
|
||||||
function. It adds ten new function calls with explicit function arguments.
|
|
||||||
|
|
||||||
2. **execute tests**: ``test_func(numiter)`` is called ten times with
|
|
||||||
ten different arguments.
|
|
||||||
|
|
||||||
.. _`metafunc object`:
|
|
||||||
|
|
||||||
test generators and metafunc objects
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
metafunc objects are passed to the ``pytest_generate_tests`` hook.
|
|
||||||
They help to inspect a testfunction and to generate tests
|
|
||||||
according to test configuration or values specified
|
|
||||||
in the class or module where a test function is defined:
|
|
||||||
|
|
||||||
``metafunc.funcargnames``: set of required function arguments for given function
|
|
||||||
|
|
||||||
``metafunc.function``: underlying python test function
|
|
||||||
|
|
||||||
``metafunc.cls``: class object where the test function is defined in or None.
|
|
||||||
|
|
||||||
``metafunc.module``: the module object where the test function is defined in.
|
|
||||||
|
|
||||||
``metafunc.config``: access to command line opts and general config
|
|
||||||
|
|
||||||
|
|
||||||
.. _`metafunc.addcall`:
|
|
||||||
|
|
||||||
the ``metafunc.addcall()`` method
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
def addcall(funcargs={}, id=None, param=None):
|
|
||||||
""" trigger a new test function call. """
|
|
||||||
|
|
||||||
``funcargs`` can be a dictionary of argument names
|
|
||||||
mapped to values - providing it is called *direct parametrization*.
|
|
||||||
|
|
||||||
If you provide an `id`` it will be used for reporting
|
|
||||||
and identification purposes. If you don't supply an `id`
|
|
||||||
the stringified counter of the list of added calls will be used.
|
|
||||||
``id`` values needs to be unique between all
|
|
||||||
invocations for a given test function.
|
|
||||||
|
|
||||||
``param`` if specified will be seen by any
|
|
||||||
`funcarg factory`_ as a ``request.param`` attribute.
|
|
||||||
Setting it is called *indirect parametrization*.
|
|
||||||
|
|
||||||
Indirect parametrization is preferable if test values are
|
|
||||||
expensive to setup or can only be created in certain environments.
|
|
||||||
Test generators and thus ``addcall()`` invocations are performed
|
|
||||||
during test collection which is separate from the actual test
|
|
||||||
setup and test run phase. With distributed testing collection
|
|
||||||
and test setup/run happens in different process.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
pytest_xmlresult plugin (EXTERNAL)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
This plugin allows to write results in an XML format
|
||||||
|
compatible to CruiseControl_, see here for download:
|
||||||
|
|
||||||
|
http://github.com/rozza/py.test-plugins
|
||||||
|
|
||||||
|
.. _CruiseControl: http://cruisecontrol.sourceforge.net/
|
||||||
|
|
|
@ -1,63 +1,63 @@
|
||||||
|
|
||||||
plugins for Python test functions
|
advanced python testing
|
||||||
=================================
|
=======================
|
||||||
|
|
||||||
skipping_ advanced skipping for python test functions, classes or modules.
|
skipping_ advanced skipping for python test functions, classes or modules.
|
||||||
|
|
||||||
|
mark_ generic mechanism for marking python functions.
|
||||||
|
|
||||||
|
pdb_ interactive debugging with the Python Debugger.
|
||||||
|
|
||||||
figleaf_ write and report coverage data with 'figleaf'.
|
figleaf_ write and report coverage data with 'figleaf'.
|
||||||
|
|
||||||
|
coverage_ (3rd) for testing with Ned's coverage module
|
||||||
|
|
||||||
monkeypatch_ safely patch object attributes, dicts and environment variables.
|
monkeypatch_ safely patch object attributes, dicts and environment variables.
|
||||||
|
|
||||||
capture_ configurable per-test stdout/stderr capturing mechanisms.
|
capture_ configurable per-test stdout/stderr capturing mechanisms.
|
||||||
|
|
||||||
recwarn_ helpers for asserting deprecation and other warnings.
|
recwarn_ helpers for asserting deprecation and other warnings.
|
||||||
|
|
||||||
|
tmpdir_ provide temporary directories to test functions.
|
||||||
|
|
||||||
plugins for other testing styles and languages
|
|
||||||
==============================================
|
|
||||||
|
|
||||||
oejskit_ run javascript tests in real life browsers
|
testing domains
|
||||||
|
===============
|
||||||
|
|
||||||
|
oejskit_ (3rd) run javascript tests in real life browsers
|
||||||
|
|
||||||
|
django_ (3rd) for testing django applications
|
||||||
|
|
||||||
|
|
||||||
|
reporting and failure logging
|
||||||
|
=============================
|
||||||
|
|
||||||
|
pastebin_ submit failure or test session information to a pastebin service.
|
||||||
|
|
||||||
|
xmlresult_ (3rd) for generating xml reports and CruiseControl integration
|
||||||
|
|
||||||
|
resultlog_ resultlog plugin for machine-readable logging of test results.
|
||||||
|
|
||||||
|
terminal_ Implements terminal reporting of the full testing process.
|
||||||
|
|
||||||
|
|
||||||
|
other testing conventions
|
||||||
|
=========================
|
||||||
|
|
||||||
unittest_ automatically discover and run traditional "unittest.py" style tests.
|
unittest_ automatically discover and run traditional "unittest.py" style tests.
|
||||||
|
|
||||||
nose_ nose-compatibility plugin: allow to run nose test suites natively.
|
nose_ nose-compatibility plugin: allow to run nose test suites natively.
|
||||||
|
|
||||||
django_ support for testing django applications
|
|
||||||
|
|
||||||
doctest_ collect and execute doctests from modules and test files.
|
doctest_ collect and execute doctests from modules and test files.
|
||||||
|
|
||||||
restdoc_ perform ReST syntax, local and remote reference tests on .rst/.txt files.
|
restdoc_ perform ReST syntax, local and remote reference tests on .rst/.txt files.
|
||||||
|
|
||||||
|
|
||||||
plugins for generic reporting and failure logging
|
core debugging / help functionality
|
||||||
=================================================
|
===================================
|
||||||
|
|
||||||
pastebin_ submit failure or test session information to a pastebin service.
|
|
||||||
|
|
||||||
resultlog_ resultlog plugin for machine-readable logging of test results.
|
|
||||||
|
|
||||||
terminal_ Implements terminal reporting of the full testing process.
|
|
||||||
|
|
||||||
|
|
||||||
plugins for generic reporting and failure logging
|
|
||||||
=================================================
|
|
||||||
|
|
||||||
pastebin_ submit failure or test session information to a pastebin service.
|
|
||||||
|
|
||||||
resultlog_ resultlog plugin for machine-readable logging of test results.
|
|
||||||
|
|
||||||
terminal_ Implements terminal reporting of the full testing process.
|
|
||||||
|
|
||||||
|
|
||||||
misc plugins / core functionality
|
|
||||||
=================================
|
|
||||||
|
|
||||||
helpconfig_ provide version info, conftest/environment config names.
|
helpconfig_ provide version info, conftest/environment config names.
|
||||||
|
|
||||||
pdb_ interactive debugging with the Python Debugger.
|
|
||||||
|
|
||||||
mark_ generic mechanism for marking python functions.
|
|
||||||
|
|
||||||
hooklog_ log invocations of extension hooks to a file.
|
hooklog_ log invocations of extension hooks to a file.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,42 @@
|
||||||
.. _`helpconfig`: helpconfig.html
|
.. _`helpconfig`: helpconfig.html
|
||||||
.. _`terminal`: terminal.html
|
.. _`terminal`: terminal.html
|
||||||
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_recwarn.py
|
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_recwarn.py
|
||||||
.. _`unittest`: unittest.html
|
.. _`unittest`: unittest.html
|
||||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_monkeypatch.py
|
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_monkeypatch.py
|
||||||
.. _`pastebin`: pastebin.html
|
.. _`pastebin`: pastebin.html
|
||||||
.. _`skipping`: skipping.html
|
.. _`skipping`: skipping.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_doctest.py
|
|
||||||
.. _`capture`: capture.html
|
|
||||||
.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_nose.py
|
|
||||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_restdoc.py
|
|
||||||
.. _`restdoc`: restdoc.html
|
|
||||||
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pastebin.py
|
|
||||||
.. _`mark`: mark.html
|
.. _`mark`: mark.html
|
||||||
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_figleaf.py
|
.. _`tmpdir`: tmpdir.html
|
||||||
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_hooklog.py
|
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_doctest.py
|
||||||
.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_skipping.py
|
.. _`capture`: capture.html
|
||||||
|
.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_nose.py
|
||||||
|
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_restdoc.py
|
||||||
|
.. _`restdoc`: restdoc.html
|
||||||
|
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_pastebin.py
|
||||||
|
.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_tmpdir.py
|
||||||
|
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_figleaf.py
|
||||||
|
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_hooklog.py
|
||||||
|
.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_skipping.py
|
||||||
.. _`checkout the py.test development version`: ../../install.html#checkout
|
.. _`checkout the py.test development version`: ../../install.html#checkout
|
||||||
.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_helpconfig.py
|
.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_helpconfig.py
|
||||||
.. _`oejskit`: oejskit.html
|
.. _`oejskit`: oejskit.html
|
||||||
.. _`doctest`: doctest.html
|
.. _`doctest`: doctest.html
|
||||||
.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_mark.py
|
.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_mark.py
|
||||||
.. _`get in contact`: ../../contact.html
|
.. _`get in contact`: ../../contact.html
|
||||||
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_capture.py
|
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_capture.py
|
||||||
.. _`figleaf`: figleaf.html
|
.. _`figleaf`: figleaf.html
|
||||||
.. _`customize`: ../customize.html
|
.. _`customize`: ../customize.html
|
||||||
.. _`hooklog`: hooklog.html
|
.. _`hooklog`: hooklog.html
|
||||||
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_terminal.py
|
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_terminal.py
|
||||||
.. _`recwarn`: recwarn.html
|
.. _`recwarn`: recwarn.html
|
||||||
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pdb.py
|
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_pdb.py
|
||||||
.. _`monkeypatch`: monkeypatch.html
|
.. _`monkeypatch`: monkeypatch.html
|
||||||
|
.. _`coverage`: coverage.html
|
||||||
.. _`resultlog`: resultlog.html
|
.. _`resultlog`: resultlog.html
|
||||||
.. _`django`: django.html
|
.. _`django`: django.html
|
||||||
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_unittest.py
|
.. _`xmlresult`: xmlresult.html
|
||||||
|
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_unittest.py
|
||||||
.. _`nose`: nose.html
|
.. _`nose`: nose.html
|
||||||
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_resultlog.py
|
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_resultlog.py
|
||||||
.. _`pdb`: pdb.html
|
.. _`pdb`: pdb.html
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
pytest_tmpdir plugin
|
||||||
|
====================
|
||||||
|
|
||||||
|
provide temporary directories to test functions.
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
usage example::
|
||||||
|
|
||||||
|
def test_plugin(tmpdir):
|
||||||
|
tmpdir.join("hello").write("hello")
|
||||||
|
|
||||||
|
.. _`py.path.local`: ../../path.html
|
||||||
|
|
||||||
|
.. _`tmpdir funcarg`:
|
||||||
|
|
||||||
|
|
||||||
|
the 'tmpdir' test function argument
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
return a temporary directory path object
|
||||||
|
unique to each test function invocation,
|
||||||
|
created as a sub directory of the base temporary
|
||||||
|
directory. The returned object is a `py.path.local`_
|
||||||
|
path object.
|
||||||
|
|
||||||
|
Start improving this plugin in 30 seconds
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
|
||||||
|
1. Download `pytest_tmpdir.py`_ plugin source code
|
||||||
|
2. put it somewhere as ``pytest_tmpdir.py`` into your import path
|
||||||
|
3. a subsequent ``py.test`` run will use your local version
|
||||||
|
|
||||||
|
Checkout customize_, other plugins_ or `get in contact`_.
|
||||||
|
|
||||||
|
.. include:: links.txt
|
|
@ -0,0 +1,6 @@
|
||||||
|
pytest_coverage plugin (EXTERNAL)
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
This plugin allows to use Ned's coverage package, see
|
||||||
|
|
||||||
|
http://github.com/rozza/py.test-plugins
|
|
@ -7,7 +7,7 @@ Quickstart
|
||||||
|
|
||||||
.. _here: ../install.html
|
.. _here: ../install.html
|
||||||
|
|
||||||
If you have a version of ``easy_install`` (otherwise see here_) just type::
|
If you have any ``easy_install`` (otherwise see here_) just type::
|
||||||
|
|
||||||
easy_install -U py
|
easy_install -U py
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
====================================
|
====================================
|
||||||
xUnit style setup
|
extended xUnit style setup
|
||||||
====================================
|
====================================
|
||||||
|
|
||||||
.. _`funcargs`: funcargs.html
|
.. _`funcargs`: funcargs.html
|
||||||
|
.. _`test parametrization`: funcargs.html#parametrizing-tests
|
||||||
|
.. _`unittest plugin`: plugin/unittest.html
|
||||||
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
Since version 1.0 funcargs_ present the recommended way
|
Since version 1.0 funcargs_ present the new and
|
||||||
to manage flexible and scalable test setups.
|
more powerful way to manage test setups with larger
|
||||||
|
test suites. *funcargs* also provide flexible
|
||||||
|
`test parametrization`_ which goes way beyond
|
||||||
|
what you can do with the xUnit setup/teardown-method
|
||||||
|
patter.
|
||||||
|
|
||||||
Python, Java and many other languages have a tradition
|
Python, Java and many other languages have a tradition
|
||||||
of using xUnit_ style testing. This typically
|
of using xUnit_ style testing. This typically
|
||||||
|
@ -19,6 +25,10 @@ scopes for which you can provide setup/teardown
|
||||||
hooks to provide test fixtures: per-module, per-class
|
hooks to provide test fixtures: per-module, per-class
|
||||||
and per-method/function. ``py.test`` will
|
and per-method/function. ``py.test`` will
|
||||||
discover and call according methods automatically.
|
discover and call according methods automatically.
|
||||||
|
|
||||||
|
The `unittest plugin`_ also will intregate ``unittest.TestCase``
|
||||||
|
instances into a test run and call respective setup/teardown methods.
|
||||||
|
|
||||||
All setup/teardown methods are optional.
|
All setup/teardown methods are optional.
|
||||||
|
|
||||||
The following methods are called at module level if they exist:
|
The following methods are called at module level if they exist:
|
||||||
|
|
|
@ -2,20 +2,14 @@
|
||||||
"""
|
"""
|
||||||
py.test and pylib: rapid testing and development utils
|
py.test and pylib: rapid testing and development utils
|
||||||
|
|
||||||
- `py.test`_: cross-project testing tool with many advanced features
|
this module uses apipkg.py for lazy-loading sub modules
|
||||||
- `py.path`_: path abstractions over local and subversion files
|
and classes. The initpkg-dictionary below specifies
|
||||||
- `py.code`_: dynamic code compile and traceback printing support
|
name->value mappings where value can be another namespace
|
||||||
|
dictionary or an import path.
|
||||||
Compatibility: Linux, Win32, OSX, Python versions 2.4 through to 3.1.
|
|
||||||
For questions please check out http://pylib.org/contact.html
|
|
||||||
|
|
||||||
.. _`py.test`: http://pylib.org/test.html
|
|
||||||
.. _`py.path`: http://pylib.org/path.html
|
|
||||||
.. _`py.code`: http://pylib.org/html
|
|
||||||
|
|
||||||
(c) Holger Krekel and others, 2009
|
(c) Holger Krekel and others, 2009
|
||||||
"""
|
"""
|
||||||
version = "trunk"
|
version = "1.1.0"
|
||||||
|
|
||||||
__version__ = version = version or "1.1.x"
|
__version__ = version = version or "1.1.x"
|
||||||
import py.apipkg
|
import py.apipkg
|
||||||
|
|
|
@ -46,6 +46,7 @@ class Parser:
|
||||||
self._groups.insert(i+1, group)
|
self._groups.insert(i+1, group)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
addgroup = getgroup
|
||||||
def addgroup(self, name, description=""):
|
def addgroup(self, name, description=""):
|
||||||
py.log._apiwarn("1.1", "use getgroup() which gets-or-creates")
|
py.log._apiwarn("1.1", "use getgroup() which gets-or-creates")
|
||||||
return self.getgroup(name, description)
|
return self.getgroup(name, description)
|
||||||
|
|
|
@ -70,7 +70,7 @@ def pytest_addoption(parser):
|
||||||
add_dist_options(parser)
|
add_dist_options(parser)
|
||||||
else:
|
else:
|
||||||
parser.epilog = (
|
parser.epilog = (
|
||||||
"execnet missing: --looponfailing and distributed testing not available.")
|
"'execnet' package required for --looponfailing / distributed testing.")
|
||||||
|
|
||||||
def add_dist_options(parser):
|
def add_dist_options(parser):
|
||||||
# see http://pytest.org/help/dist")
|
# see http://pytest.org/help/dist")
|
||||||
|
|
|
@ -83,7 +83,7 @@ skipping on a missing import dependency
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
You can use the following import helper at module level
|
You can use the following import helper at module level
|
||||||
or within a test or setup function.
|
or within a test or test setup function::
|
||||||
|
|
||||||
docutils = py.test.importorskip("docutils")
|
docutils = py.test.importorskip("docutils")
|
||||||
|
|
||||||
|
|
|
@ -259,8 +259,8 @@ class TerminalReporter:
|
||||||
|
|
||||||
verinfo = ".".join(map(str, sys.version_info[:3]))
|
verinfo = ".".join(map(str, sys.version_info[:3]))
|
||||||
msg = "python: platform %s -- Python %s" % (sys.platform, verinfo)
|
msg = "python: platform %s -- Python %s" % (sys.platform, verinfo)
|
||||||
|
msg += " -- pytest-%s" % (py.__version__)
|
||||||
if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None):
|
if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None):
|
||||||
msg += " -- pytest-%s" % (py.__version__)
|
|
||||||
msg += " -- " + str(sys.executable)
|
msg += " -- " + str(sys.executable)
|
||||||
self.write_line(msg)
|
self.write_line(msg)
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
"""
|
"""provide temporary directories to test functions.
|
||||||
provide temporary directories to test functions and methods.
|
|
||||||
|
|
||||||
example:
|
usage example::
|
||||||
|
|
||||||
pytest_plugins = "pytest_tmpdir"
|
|
||||||
|
|
||||||
def test_plugin(tmpdir):
|
def test_plugin(tmpdir):
|
||||||
tmpdir.join("hello").write("hello")
|
tmpdir.join("hello").write("hello")
|
||||||
|
|
||||||
|
.. _`py.path.local`: ../../path.html
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
|
|
||||||
def pytest_funcarg__tmpdir(request):
|
def pytest_funcarg__tmpdir(request):
|
||||||
|
"""return a temporary directory path object
|
||||||
|
unique to each test function invocation,
|
||||||
|
created as a sub directory of the base temporary
|
||||||
|
directory. The returned object is a `py.path.local`_
|
||||||
|
path object.
|
||||||
|
"""
|
||||||
name = request.function.__name__
|
name = request.function.__name__
|
||||||
return request.config.mktemp(name, numbered=True)
|
return request.config.mktemp(name, numbered=True)
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -28,7 +28,7 @@ def main():
|
||||||
name='py',
|
name='py',
|
||||||
description='py.test and pylib: rapid testing and development utils.',
|
description='py.test and pylib: rapid testing and development utils.',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version= trunk or '1.1.0b1',
|
version= trunk or '1.1.0',
|
||||||
url='http://pylib.org',
|
url='http://pylib.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
@ -42,7 +42,7 @@ def main():
|
||||||
'py.svnwcrevert = py.cmdline:pysvnwcrevert',
|
'py.svnwcrevert = py.cmdline:pysvnwcrevert',
|
||||||
'py.test = py.cmdline:pytest',
|
'py.test = py.cmdline:pytest',
|
||||||
'py.which = py.cmdline:pywhich']},
|
'py.which = py.cmdline:pywhich']},
|
||||||
classifiers=['Development Status :: 4 - Beta',
|
classifiers=['Development Status :: 5 - Production/Stable',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Operating System :: POSIX',
|
'Operating System :: POSIX',
|
||||||
|
@ -50,7 +50,6 @@ def main():
|
||||||
'Operating System :: MacOS :: MacOS X',
|
'Operating System :: MacOS :: MacOS X',
|
||||||
'Topic :: Software Development :: Testing',
|
'Topic :: Software Development :: Testing',
|
||||||
'Topic :: Software Development :: Libraries',
|
'Topic :: Software Development :: Libraries',
|
||||||
'Topic :: System :: Distributed Computing',
|
|
||||||
'Topic :: Utilities',
|
'Topic :: Utilities',
|
||||||
'Programming Language :: Python'],
|
'Programming Language :: Python'],
|
||||||
packages=['py',
|
packages=['py',
|
||||||
|
|
|
@ -118,7 +118,7 @@ class TestLogConsumer:
|
||||||
|
|
||||||
def test_log_file(self):
|
def test_log_file(self):
|
||||||
customlog = tempdir.join('log.out')
|
customlog = tempdir.join('log.out')
|
||||||
py.log.setconsumer("default", open(str(customlog), 'w', buffering=0))
|
py.log.setconsumer("default", open(str(customlog), 'w', buffering=1))
|
||||||
py.log.Producer("default")("hello world #1")
|
py.log.Producer("default")("hello world #1")
|
||||||
assert customlog.readlines() == ['[default] hello world #1\n']
|
assert customlog.readlines() == ['[default] hello world #1\n']
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue