fix issue27 - --collectonly and -k keyword selection now work together.

internally, collectonly and terminal reporting has been unified.
This commit is contained in:
holger krekel 2011-03-06 18:32:00 +01:00
parent 18e784c9c9
commit c552b58dc5
8 changed files with 91 additions and 117 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'],

View File

@ -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")

View File

@ -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()

View File

@ -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*",

View File

@ -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())