fix issue27 - --collectonly and -k keyword selection now work together.
internally, collectonly and terminal reporting has been unified.
This commit is contained in:
parent
18e784c9c9
commit
c552b58dc5
|
@ -1,12 +1,12 @@
|
||||||
Changes between 2.0.1 and 2.0.2
|
Changes between 2.0.1 and 2.0.2
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
- fix issue30 - extended xfail/skipif handling and better reporting.
|
- fix issue30 - extended xfail/skipif handling and improved reporting.
|
||||||
If you have a syntax error in your skip/xfail
|
If you have a syntax error in your skip/xfail
|
||||||
expressions you now get nice error reports.
|
expressions you now get nice error reports.
|
||||||
|
|
||||||
Also you can now access module globals from xfail/skipif
|
Also you can now access module globals from xfail/skipif
|
||||||
expressions so that this works now::
|
expressions so that this for example works now::
|
||||||
|
|
||||||
import mymodule
|
import mymodule
|
||||||
@pytest.mark.skipif("mymodule.__version__[0] == "1")
|
@pytest.mark.skipif("mymodule.__version__[0] == "1")
|
||||||
|
@ -24,6 +24,11 @@ Changes between 2.0.1 and 2.0.2
|
||||||
test function invocations generated from the pytest_generate_tests
|
test function invocations generated from the pytest_generate_tests
|
||||||
hook.
|
hook.
|
||||||
|
|
||||||
|
- fix issue27 - collectonly and keyword-selection (-k) now work together
|
||||||
|
Also, if you do "py.test --collectonly -q" you now get a flat list
|
||||||
|
of test ids that you can use to paste to the py.test commandline
|
||||||
|
in order to execute a particular test.
|
||||||
|
|
||||||
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
|
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
|
||||||
Starting with Python3.2 os.symlink may be supported. By requiring
|
Starting with Python3.2 os.symlink may be supported. By requiring
|
||||||
a newer py lib version the py.path.local() implementation acknowledges
|
a newer py lib version the py.path.local() implementation acknowledges
|
||||||
|
|
|
@ -32,22 +32,19 @@ def pytest_addoption(parser):
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
config.option.verbose -= config.option.quiet
|
config.option.verbose -= config.option.quiet
|
||||||
if config.option.collectonly:
|
# we try hard to make printing resilient against
|
||||||
reporter = CollectonlyReporter(config)
|
# later changes on FD level.
|
||||||
else:
|
stdout = py.std.sys.stdout
|
||||||
# we try hard to make printing resilient against
|
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
|
||||||
# later changes on FD level.
|
try:
|
||||||
stdout = py.std.sys.stdout
|
newfd = os.dup(stdout.fileno())
|
||||||
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
|
#print "got newfd", newfd
|
||||||
try:
|
except ValueError:
|
||||||
newfd = os.dup(stdout.fileno())
|
pass
|
||||||
#print "got newfd", newfd
|
else:
|
||||||
except ValueError:
|
stdout = os.fdopen(newfd, stdout.mode, 1)
|
||||||
pass
|
config._toclose = stdout
|
||||||
else:
|
reporter = TerminalReporter(config, stdout)
|
||||||
stdout = os.fdopen(newfd, stdout.mode, 1)
|
|
||||||
config._toclose = stdout
|
|
||||||
reporter = TerminalReporter(config, stdout)
|
|
||||||
config.pluginmanager.register(reporter, 'terminalreporter')
|
config.pluginmanager.register(reporter, 'terminalreporter')
|
||||||
if config.option.debug or config.option.traceconfig:
|
if config.option.debug or config.option.traceconfig:
|
||||||
def mywriter(tags, args):
|
def mywriter(tags, args):
|
||||||
|
@ -273,11 +270,44 @@ class TerminalReporter:
|
||||||
for line in flatten(lines):
|
for line in flatten(lines):
|
||||||
self.write_line(line)
|
self.write_line(line)
|
||||||
|
|
||||||
def pytest_collection_finish(self):
|
def pytest_collection_finish(self, session):
|
||||||
|
if self.config.option.collectonly:
|
||||||
|
self._printcollecteditems(session.items)
|
||||||
|
if self.stats.get('failed'):
|
||||||
|
self._tw.sep("!", "collection failures")
|
||||||
|
for rep in self.stats.get('failed'):
|
||||||
|
rep.toterminal(self._tw)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
if not self.showheader:
|
if not self.showheader:
|
||||||
return
|
return
|
||||||
#for i, testarg in enumerate(self.config.args):
|
#for i, testarg in enumerate(self.config.args):
|
||||||
# self.write_line("test path %d: %s" %(i+1, testarg))
|
# self.write_line("test path %d: %s" %(i+1, testarg))
|
||||||
|
|
||||||
|
def _printcollecteditems(self, items):
|
||||||
|
# to print out items and their parent collectors
|
||||||
|
# we take care to leave out Instances aka ()
|
||||||
|
# because later versions are going to get rid of them anyway
|
||||||
|
if self.config.option.verbose < 0:
|
||||||
|
for item in items:
|
||||||
|
nodeid = item.nodeid
|
||||||
|
nodeid = nodeid.replace("::()::", "::")
|
||||||
|
self._tw.line(nodeid)
|
||||||
|
return
|
||||||
|
stack = []
|
||||||
|
indent = ""
|
||||||
|
for item in items:
|
||||||
|
needed_collectors = item.listchain()[1:] # strip root node
|
||||||
|
while stack:
|
||||||
|
if stack == needed_collectors[:len(stack)]:
|
||||||
|
break
|
||||||
|
stack.pop()
|
||||||
|
for col in needed_collectors[len(stack):]:
|
||||||
|
stack.append(col)
|
||||||
|
#if col.name == "()":
|
||||||
|
# continue
|
||||||
|
indent = (len(stack)-1) * " "
|
||||||
|
self._tw.line("%s%s" %(indent, col))
|
||||||
|
|
||||||
def pytest_sessionfinish(self, exitstatus, __multicall__):
|
def pytest_sessionfinish(self, exitstatus, __multicall__):
|
||||||
__multicall__.execute()
|
__multicall__.execute()
|
||||||
|
@ -403,52 +433,6 @@ class TerminalReporter:
|
||||||
self.write_sep("=", "%d tests deselected by %r" %(
|
self.write_sep("=", "%d tests deselected by %r" %(
|
||||||
len(self.stats['deselected']), self.config.option.keyword), bold=True)
|
len(self.stats['deselected']), self.config.option.keyword), bold=True)
|
||||||
|
|
||||||
|
|
||||||
class CollectonlyReporter:
|
|
||||||
INDENT = " "
|
|
||||||
|
|
||||||
def __init__(self, config, out=None):
|
|
||||||
self.config = config
|
|
||||||
if out is None:
|
|
||||||
out = py.std.sys.stdout
|
|
||||||
self._tw = py.io.TerminalWriter(out)
|
|
||||||
self.indent = ""
|
|
||||||
self._failed = []
|
|
||||||
|
|
||||||
def outindent(self, line):
|
|
||||||
self._tw.line(self.indent + str(line))
|
|
||||||
|
|
||||||
def pytest_internalerror(self, excrepr):
|
|
||||||
for line in str(excrepr).split("\n"):
|
|
||||||
self._tw.line("INTERNALERROR> " + line)
|
|
||||||
|
|
||||||
def pytest_collectstart(self, collector):
|
|
||||||
if collector.session != collector:
|
|
||||||
self.outindent(collector)
|
|
||||||
self.indent += self.INDENT
|
|
||||||
|
|
||||||
def pytest_itemcollected(self, item):
|
|
||||||
self.outindent(item)
|
|
||||||
|
|
||||||
def pytest_collectreport(self, report):
|
|
||||||
if not report.passed:
|
|
||||||
if hasattr(report.longrepr, 'reprcrash'):
|
|
||||||
msg = report.longrepr.reprcrash.message
|
|
||||||
else:
|
|
||||||
# XXX unify (we have CollectErrorRepr here)
|
|
||||||
msg = str(report.longrepr[2])
|
|
||||||
self.outindent("!!! %s !!!" % msg)
|
|
||||||
#self.outindent("!!! error !!!")
|
|
||||||
self._failed.append(report)
|
|
||||||
self.indent = self.indent[:-len(self.INDENT)]
|
|
||||||
|
|
||||||
def pytest_collection_finish(self):
|
|
||||||
if self._failed:
|
|
||||||
self._tw.sep("!", "collection failures")
|
|
||||||
for rep in self._failed:
|
|
||||||
rep.toterminal(self._tw)
|
|
||||||
return self._failed and 1 or 0
|
|
||||||
|
|
||||||
def repr_pythonversion(v=None):
|
def repr_pythonversion(v=None):
|
||||||
if v is None:
|
if v is None:
|
||||||
v = sys.version_info
|
v = sys.version_info
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
unit and functional testing with Python.
|
unit and functional testing with Python.
|
||||||
"""
|
"""
|
||||||
__version__ = '2.0.2.dev4'
|
__version__ = '2.0.2.dev5'
|
||||||
__all__ = ['main']
|
__all__ = ['main']
|
||||||
|
|
||||||
from _pytest.core import main, UsageError, _preloadplugins
|
from _pytest.core import main, UsageError, _preloadplugins
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -22,7 +22,7 @@ def main():
|
||||||
name='pytest',
|
name='pytest',
|
||||||
description='py.test: simple powerful testing with Python',
|
description='py.test: simple powerful testing with Python',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version='2.0.2.dev4',
|
version='2.0.2.dev5',
|
||||||
url='http://pytest.org',
|
url='http://pytest.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
|
|
|
@ -89,9 +89,11 @@ class TestGeneralUsage:
|
||||||
import pytest
|
import pytest
|
||||||
class MyFile(pytest.File):
|
class MyFile(pytest.File):
|
||||||
def collect(self):
|
def collect(self):
|
||||||
return
|
return [MyItem("hello", parent=self)]
|
||||||
def pytest_collect_file(path, parent):
|
def pytest_collect_file(path, parent):
|
||||||
return MyFile(path, parent)
|
return MyFile(path, parent)
|
||||||
|
class MyItem(pytest.Item):
|
||||||
|
pass
|
||||||
""")
|
""")
|
||||||
p = testdir.makepyfile("def test_hello(): pass")
|
p = testdir.makepyfile("def test_hello(): pass")
|
||||||
result = testdir.runpytest(p, "--collectonly")
|
result = testdir.runpytest(p, "--collectonly")
|
||||||
|
|
|
@ -94,6 +94,8 @@ class TestCollectFS:
|
||||||
tmpdir.ensure(".whatever", 'test_notfound.py')
|
tmpdir.ensure(".whatever", 'test_notfound.py')
|
||||||
tmpdir.ensure(".bzr", 'test_notfound.py')
|
tmpdir.ensure(".bzr", 'test_notfound.py')
|
||||||
tmpdir.ensure("normal", 'test_found.py')
|
tmpdir.ensure("normal", 'test_found.py')
|
||||||
|
for x in tmpdir.visit("test_*.py"):
|
||||||
|
x.write("def test_hello(): pass")
|
||||||
|
|
||||||
result = testdir.runpytest("--collectonly")
|
result = testdir.runpytest("--collectonly")
|
||||||
s = result.stdout.str()
|
s = result.stdout.str()
|
||||||
|
|
|
@ -359,8 +359,8 @@ class TestConftestCustomization:
|
||||||
if path.basename == "test_xyz.py":
|
if path.basename == "test_xyz.py":
|
||||||
return MyModule(path, parent)
|
return MyModule(path, parent)
|
||||||
""")
|
""")
|
||||||
testdir.makepyfile("def some(): pass")
|
testdir.makepyfile("def test_some(): pass")
|
||||||
testdir.makepyfile(test_xyz="")
|
testdir.makepyfile(test_xyz="def test_func(): pass")
|
||||||
result = testdir.runpytest("--collectonly")
|
result = testdir.runpytest("--collectonly")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*<Module*test_pytest*",
|
"*<Module*test_pytest*",
|
||||||
|
|
|
@ -4,8 +4,7 @@ terminal reporting of the full testing process.
|
||||||
import pytest,py
|
import pytest,py
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from _pytest.terminal import TerminalReporter, \
|
from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
|
||||||
CollectonlyReporter, repr_pythonversion, getreportopt
|
|
||||||
from _pytest import runner
|
from _pytest import runner
|
||||||
|
|
||||||
def basic_run_report(item):
|
def basic_run_report(item):
|
||||||
|
@ -157,53 +156,35 @@ class TestTerminal:
|
||||||
|
|
||||||
|
|
||||||
class TestCollectonly:
|
class TestCollectonly:
|
||||||
def test_collectonly_basic(self, testdir, linecomp):
|
def test_collectonly_basic(self, testdir):
|
||||||
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
|
testdir.makepyfile("""
|
||||||
def test_func():
|
def test_func():
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
|
result = testdir.runpytest("--collectonly",)
|
||||||
modcol.config.pluginmanager.register(rep)
|
result.stdout.fnmatch_lines([
|
||||||
indent = rep.indent
|
"<Module 'test_collectonly_basic.py'>",
|
||||||
rep.config.hook.pytest_collectstart(collector=modcol)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
"<Module 'test_collectonly_basic.py'>"
|
|
||||||
])
|
|
||||||
item = modcol.collect()[0]
|
|
||||||
rep.config.hook.pytest_itemcollected(item=item)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
" <Function 'test_func'>",
|
" <Function 'test_func'>",
|
||||||
])
|
])
|
||||||
report = rep.config.hook.pytest_make_collect_report(collector=modcol)
|
|
||||||
rep.config.hook.pytest_collectreport(report=report)
|
|
||||||
assert rep.indent == indent
|
|
||||||
|
|
||||||
def test_collectonly_skipped_module(self, testdir, linecomp):
|
def test_collectonly_skipped_module(self, testdir):
|
||||||
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
pytest.skip("nomod")
|
pytest.skip("hello")
|
||||||
""")
|
|
||||||
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
|
|
||||||
modcol.config.pluginmanager.register(rep)
|
|
||||||
cols = list(testdir.genitems([modcol]))
|
|
||||||
assert len(cols) == 0
|
|
||||||
linecomp.assert_contains_lines("""
|
|
||||||
<Module 'test_collectonly_skipped_module.py'>
|
|
||||||
!!! Skipped: nomod !!!
|
|
||||||
""")
|
""")
|
||||||
|
result = testdir.runpytest("--collectonly", "-rs")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"SKIP*hello*",
|
||||||
|
"*1 skip*",
|
||||||
|
])
|
||||||
|
|
||||||
def test_collectonly_failed_module(self, testdir, linecomp):
|
def test_collectonly_failed_module(self, testdir):
|
||||||
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
|
testdir.makepyfile("""raise ValueError(0)""")
|
||||||
raise ValueError(0)
|
result = testdir.runpytest("--collectonly")
|
||||||
""")
|
result.stdout.fnmatch_lines([
|
||||||
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
|
"*raise ValueError*",
|
||||||
modcol.config.pluginmanager.register(rep)
|
"*1 error*",
|
||||||
cols = list(testdir.genitems([modcol]))
|
])
|
||||||
assert len(cols) == 0
|
|
||||||
linecomp.assert_contains_lines("""
|
|
||||||
<Module 'test_collectonly_failed_module.py'>
|
|
||||||
!!! ValueError: 0 !!!
|
|
||||||
""")
|
|
||||||
|
|
||||||
def test_collectonly_fatal(self, testdir):
|
def test_collectonly_fatal(self, testdir):
|
||||||
p1 = testdir.makeconftest("""
|
p1 = testdir.makeconftest("""
|
||||||
|
@ -228,11 +209,11 @@ class TestCollectonly:
|
||||||
stderr = result.stderr.str().strip()
|
stderr = result.stderr.str().strip()
|
||||||
#assert stderr.startswith("inserting into sys.path")
|
#assert stderr.startswith("inserting into sys.path")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
extra = result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*<Module '*.py'>",
|
"*<Module '*.py'>",
|
||||||
"* <Function 'test_func1'*>",
|
"* <Function 'test_func1'*>",
|
||||||
"* <Class 'TestClass'>",
|
"* <Class 'TestClass'>",
|
||||||
"* <Instance '()'>",
|
#"* <Instance '()'>",
|
||||||
"* <Function 'test_method'*>",
|
"* <Function 'test_method'*>",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -241,11 +222,11 @@ class TestCollectonly:
|
||||||
result = testdir.runpytest("--collectonly", p)
|
result = testdir.runpytest("--collectonly", p)
|
||||||
stderr = result.stderr.str().strip()
|
stderr = result.stderr.str().strip()
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
result.stdout.fnmatch_lines(py.code.Source("""
|
||||||
*<Module '*.py'>
|
*ERROR*
|
||||||
*ImportError*
|
*import Errlk*
|
||||||
*!!!*failures*!!!
|
*ImportError*
|
||||||
*test_collectonly_error.py:1*
|
*1 error*
|
||||||
""").strip())
|
""").strip())
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue