From c552b58dc5e2f1cba7c1e8db9e2d666aeff78db9 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 6 Mar 2011 18:32:00 +0100 Subject: [PATCH] fix issue27 - --collectonly and -k keyword selection now work together. internally, collectonly and terminal reporting has been unified. --- CHANGELOG | 9 ++- _pytest/terminal.py | 110 ++++++++++++++++--------------------- pytest.py | 2 +- setup.py | 2 +- testing/acceptance_test.py | 4 +- testing/test_collection.py | 2 + testing/test_python.py | 4 +- testing/test_terminal.py | 75 ++++++++++--------------- 8 files changed, 91 insertions(+), 117 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e7750ff8e..7b3f42c26 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,12 @@ 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 expressions you now get nice error reports. 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 @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 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 Starting with Python3.2 os.symlink may be supported. By requiring a newer py lib version the py.path.local() implementation acknowledges diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 2172ce6a5..afd5ffefa 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -32,22 +32,19 @@ def pytest_addoption(parser): def pytest_configure(config): config.option.verbose -= config.option.quiet - if config.option.collectonly: - reporter = CollectonlyReporter(config) - else: - # we try hard to make printing resilient against - # later changes on FD level. - stdout = py.std.sys.stdout - if hasattr(os, 'dup') and hasattr(stdout, 'fileno'): - try: - newfd = os.dup(stdout.fileno()) - #print "got newfd", newfd - except ValueError: - pass - else: - stdout = os.fdopen(newfd, stdout.mode, 1) - config._toclose = stdout - reporter = TerminalReporter(config, stdout) + # we try hard to make printing resilient against + # later changes on FD level. + stdout = py.std.sys.stdout + if hasattr(os, 'dup') and hasattr(stdout, 'fileno'): + try: + newfd = os.dup(stdout.fileno()) + #print "got newfd", newfd + except ValueError: + pass + else: + stdout = os.fdopen(newfd, stdout.mode, 1) + config._toclose = stdout + reporter = TerminalReporter(config, stdout) config.pluginmanager.register(reporter, 'terminalreporter') if config.option.debug or config.option.traceconfig: def mywriter(tags, args): @@ -273,11 +270,44 @@ class TerminalReporter: for line in flatten(lines): 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: return #for i, testarg in enumerate(self.config.args): # 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__): __multicall__.execute() @@ -403,52 +433,6 @@ class TerminalReporter: self.write_sep("=", "%d tests deselected by %r" %( 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): if v is None: v = sys.version_info diff --git a/pytest.py b/pytest.py index eb8e4b37d..2aee852a2 100644 --- a/pytest.py +++ b/pytest.py @@ -1,7 +1,7 @@ """ unit and functional testing with Python. """ -__version__ = '2.0.2.dev4' +__version__ = '2.0.2.dev5' __all__ = ['main'] from _pytest.core import main, UsageError, _preloadplugins diff --git a/setup.py b/setup.py index 464a40946..2b29d833d 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def main(): name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.0.2.dev4', + version='2.0.2.dev5', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index edf52f073..7d00d056a 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -89,9 +89,11 @@ class TestGeneralUsage: import pytest class MyFile(pytest.File): def collect(self): - return + return [MyItem("hello", parent=self)] def pytest_collect_file(path, parent): return MyFile(path, parent) + class MyItem(pytest.Item): + pass """) p = testdir.makepyfile("def test_hello(): pass") result = testdir.runpytest(p, "--collectonly") diff --git a/testing/test_collection.py b/testing/test_collection.py index 3ee040dd3..564b5bc63 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -94,6 +94,8 @@ class TestCollectFS: tmpdir.ensure(".whatever", 'test_notfound.py') tmpdir.ensure(".bzr", 'test_notfound.py') tmpdir.ensure("normal", 'test_found.py') + for x in tmpdir.visit("test_*.py"): + x.write("def test_hello(): pass") result = testdir.runpytest("--collectonly") s = result.stdout.str() diff --git a/testing/test_python.py b/testing/test_python.py index 76d1041f3..6c6273b22 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -359,8 +359,8 @@ class TestConftestCustomization: if path.basename == "test_xyz.py": return MyModule(path, parent) """) - testdir.makepyfile("def some(): pass") - testdir.makepyfile(test_xyz="") + testdir.makepyfile("def test_some(): pass") + testdir.makepyfile(test_xyz="def test_func(): pass") result = testdir.runpytest("--collectonly") result.stdout.fnmatch_lines([ "*" - ]) - item = modcol.collect()[0] - rep.config.hook.pytest_itemcollected(item=item) - linecomp.assert_contains_lines([ + result = testdir.runpytest("--collectonly",) + result.stdout.fnmatch_lines([ + "", " ", ]) - 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): - modcol = testdir.getmodulecol(configargs=['--collectonly'], source=""" + def test_collectonly_skipped_module(self, testdir): + testdir.makepyfile(""" import pytest - pytest.skip("nomod") - """) - 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(""" - - !!! Skipped: nomod !!! + pytest.skip("hello") """) + result = testdir.runpytest("--collectonly", "-rs") + result.stdout.fnmatch_lines([ + "SKIP*hello*", + "*1 skip*", + ]) - def test_collectonly_failed_module(self, testdir, linecomp): - modcol = testdir.getmodulecol(configargs=['--collectonly'], source=""" - raise ValueError(0) - """) - 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(""" - - !!! ValueError: 0 !!! - """) + def test_collectonly_failed_module(self, testdir): + testdir.makepyfile("""raise ValueError(0)""") + result = testdir.runpytest("--collectonly") + result.stdout.fnmatch_lines([ + "*raise ValueError*", + "*1 error*", + ]) def test_collectonly_fatal(self, testdir): p1 = testdir.makeconftest(""" @@ -228,11 +209,11 @@ class TestCollectonly: stderr = result.stderr.str().strip() #assert stderr.startswith("inserting into sys.path") assert result.ret == 0 - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*", "* ", "* ", - "* ", + #"* ", "* ", ]) @@ -241,11 +222,11 @@ class TestCollectonly: result = testdir.runpytest("--collectonly", p) stderr = result.stderr.str().strip() assert result.ret == 1 - extra = result.stdout.fnmatch_lines(py.code.Source(""" - * - *ImportError* - *!!!*failures*!!! - *test_collectonly_error.py:1* + result.stdout.fnmatch_lines(py.code.Source(""" + *ERROR* + *import Errlk* + *ImportError* + *1 error* """).strip())