From commits-noreply at bitbucket.org Sun May 2 15:06:30 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 13:06:30 +0000 (UTC) Subject: [py-svn] py-trunk commit aadc25966770: update changelog, install info and bum version to 1.3.0 Message-ID: <20100502130630.944AE7EF78@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272805580 -7200 # Node ID aadc25966770117d4edc2bf0ee5aba43cbee4340 # Parent 593d06e2b51fe14cbce018324b8253467a4a6752 update changelog, install info and bum version to 1.3.0 rather than 1.2.2 because of the added features --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -__version__ = version = "1.2.2" +__version__ = version = "1.3.0" import py.apipkg --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= '1.2.2', + version= '1.3.0', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], --- a/doc/install.txt +++ b/doc/install.txt @@ -84,8 +84,15 @@ disguise. You can tell people to downlo and ask them to send you the resulting URL. The resulting script has all core features and runs unchanged under Python2 and Python3 interpreters. -Troubleshooting -======================== +Troubleshooting / known issues +=============================== + +.. _`Jython does not create command line launchers`: http://bugs.jython.org/issue1491 + +**Jython2.5.1 on XP**: `Jython does not create command line launchers`_ +so ``py.test`` will not work correctly. You may install py.test on +CPython and type ``py.test --genscript=mytest`` and then use +``jython mytest`` to run py.test for your tests to run in Jython. **On Linux**: If ``easy_install`` fails because it needs to run as the superuser you are trying to install things globally --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,15 @@ -Changes between 1.2.1 and 1.2.2 (release pending) +Changes between 1.2.1 and 1.3.0 (release pending) ================================================== -- new mechanism to allow plugins to register new hooks +- new mechanism to allow external plugins to register new hooks + (a recent pytest-xdist plugin for distributed and looponfailing + testing requires this feature) - (issue85) fix junitxml plugin to handle tests with non-ascii output - fix/refine python3 compatibility (thanks Benjamin Peterson) -- fixes for making the jython/win32 combination work +- fixes for making the jython/win32 combination work, note however: + jython2.5.1/win32 does not provide a command line launcher, see + http://bugs.jython.org/issue1491 . See pylib install documentation + for how to work around. - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing @@ -17,7 +22,7 @@ Changes between 1.2.1 and 1.2.2 (release - new pytest_pycollect_makemodule(path, parent) hook for allowing customization of the Module collection object for a matching test module. -- expose previously internal commonly useful methods: +- expose (previously internal) commonly useful methods: py.io.get_terminal_with() -> return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 py.io.saferepr(obj) -> return limited representation string From commits-noreply at bitbucket.org Sun May 2 15:07:06 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 13:07:06 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 44ac866f66ac: require py-1.3.0 for xdist plugin Message-ID: <20100502130706.43C5F7EF78@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272805620 -7200 # Node ID 44ac866f66ac0606a9bcf198e385e42842a0fe21 # Parent c74b6d807c87240f9bd9889d8c93a1ae328c3325 require py-1.3.0 for xdist plugin --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup( packages = ['xdist'], entry_points = {'pytest11': ['xdist = xdist.plugin'],}, zip_safe=False, - install_requires = ['execnet>=1.0.5', 'py>=1.2.2'], + install_requires = ['execnet>=1.0.5', 'py>=1.3.0'], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', From commits-noreply at bitbucket.org Sun May 2 16:36:29 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 14:36:29 +0000 (UTC) Subject: [py-svn] py-trunk commit 70c81c004272: refine and test new hook registration, now it is called "pytest_addhooks" Message-ID: <20100502143629.8E4B97EF7F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272811013 -7200 # Node ID 70c81c0042722c892a653199e024ccf948afda5c # Parent 2e8ad1a4fd49b2f6c8b8140ee4581e4ca89e3684 refine and test new hook registration, now it is called "pytest_addhooks" similar to pytest_addoption and raises on bogus input. --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -39,8 +39,7 @@ class PluginManager(object): if name in self._name2plugin: return False self._name2plugin[name] = plugin - self.call_plugin(plugin, "pytest_registerhooks", - {'pluginmanager': self}) + self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self}) self.hook.pytest_plugin_registered(manager=self, plugin=plugin) self.registry.register(plugin) return True @@ -59,8 +58,8 @@ class PluginManager(object): if plugin == val: return True - def registerhooks(self, spec): - self.hook._registerhooks(spec) + def addhooks(self, spec): + self.hook._addhooks(spec, prefix="pytest_") def getplugins(self): return list(self.registry) @@ -304,22 +303,31 @@ class Registry: return l class HookRelay: - def __init__(self, hookspecs, registry): + def __init__(self, hookspecs, registry, prefix="pytest_"): if not isinstance(hookspecs, list): hookspecs = [hookspecs] self._hookspecs = [] self._registry = registry for hookspec in hookspecs: - self._registerhooks(hookspec) + self._addhooks(hookspec, prefix) - def _registerhooks(self, hookspecs): + def _addhooks(self, hookspecs, prefix): self._hookspecs.append(hookspecs) + added = False for name, method in vars(hookspecs).items(): - if name[:1] != "_": + if name.startswith(prefix): + if not method.__doc__: + raise ValueError("docstring required for hook %r, in %r" + % (method, hookspecs)) firstresult = getattr(method, 'firstresult', False) hc = HookCaller(self, name, firstresult=firstresult) setattr(self, name, hc) + added = True #print ("setting new hook", name) + if not added: + raise ValueError("did not find new %r hooks in %r" %( + prefix, hookspecs,)) + def _performcall(self, name, multicall): return multicall.execute() --- a/py/_test/cmdline.py +++ b/py/_test/cmdline.py @@ -21,4 +21,3 @@ def main(args=None): e = sys.exc_info()[1] sys.stderr.write("ERROR: %s\n" %(e.args[0],)) raise SystemExit(3) - --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -202,6 +202,58 @@ class TestBootstrapping: impname = canonical_importname(name) class TestPytestPluginInteractions: + + def test_addhooks_conftestplugin(self, testdir): + from py._test.config import Config + newhooks = testdir.makepyfile(newhooks=""" + def pytest_myhook(xyz): + "new hook" + """) + conf = testdir.makeconftest(""" + import sys ; sys.path.insert(0, '.') + import newhooks + def pytest_addhooks(pluginmanager): + pluginmanager.addhooks(newhooks) + def pytest_myhook(xyz): + return xyz + 1 + """) + config = Config() + config._conftest.importconftest(conf) + print(config.pluginmanager.getplugins()) + res = config.hook.pytest_myhook(xyz=10) + assert res == [11] + + def test_addhooks_docstring_error(self, testdir): + newhooks = testdir.makepyfile(newhooks=""" + class A: # no pytest_ prefix + pass + def pytest_myhook(xyz): + pass + """) + conf = testdir.makeconftest(""" + import sys ; sys.path.insert(0, '.') + import newhooks + def pytest_addhooks(pluginmanager): + pluginmanager.addhooks(newhooks) + """) + res = testdir.runpytest() + assert res.ret != 0 + res.stderr.fnmatch_lines([ + "*docstring*pytest_myhook*newhooks*" + ]) + + def test_addhooks_nohooks(self, testdir): + conf = testdir.makeconftest(""" + import sys + def pytest_addhooks(pluginmanager): + pluginmanager.addhooks(sys) + """) + res = testdir.runpytest() + assert res.ret != 0 + res.stderr.fnmatch_lines([ + "*did not find*sys*" + ]) + def test_do_option_conftestplugin(self, testdir): from py._test.config import Config p = testdir.makepyfile(""" @@ -401,9 +453,9 @@ class TestHookRelay: registry = Registry() class Api: def hello(self, arg): - pass + "api hook 1" - mcm = HookRelay(hookspecs=Api, registry=registry) + mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he") assert hasattr(mcm, 'hello') assert repr(mcm.hello).find("hello") != -1 class Plugin: @@ -418,17 +470,18 @@ class TestHookRelay: registry = Registry() class Api: def hello(self, arg): - pass - mcm = HookRelay(hookspecs=Api, registry=registry) + "api hook 1" + mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he") py.test.raises(TypeError, "mcm.hello(3)") def test_firstresult_definition(self): registry = Registry() class Api: - def hello(self, arg): pass + def hello(self, arg): + "api hook 1" hello.firstresult = True - mcm = HookRelay(hookspecs=Api, registry=registry) + mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he") class Plugin: def hello(self, arg): return arg + 1 --- a/py/_plugin/hookspec.py +++ b/py/_plugin/hookspec.py @@ -6,14 +6,14 @@ hook specifications for py.test plugins # Command line and configuration # ------------------------------------------------------------------------- +def pytest_namespace(): + "return dict of name->object which will get stored at py.test. namespace" + def pytest_addoption(parser): - """ called before commandline parsing. """ + "add optparse-style options via parser.addoption." -def pytest_registerhooks(pluginmanager): - """ called after commandline parsing before pytest_configure. """ - -def pytest_namespace(): - """ return dict of name->object which will get stored at py.test. namespace""" +def pytest_addhooks(pluginmanager): + "add hooks via pluginmanager.registerhooks(module)" def pytest_configure(config): """ called after command line options have been parsed. --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,26 @@ Changes between 1.2.1 and 1.3.0 (release pending) ================================================== -- new mechanism to allow external plugins to register new hooks - (a recent pytest-xdist plugin for distributed and looponfailing - testing requires this feature) +- allow external plugins to register new hooks via the new + pytest_addhooks(pluginmanager) hook. The new release of + the pytest-xdist plugin for distributed and looponfailing + testing requires this feature. +- add a new pytest_ignore_collect(path, config) hook to allow projects and + plugins to define exclusion behaviour for their directory structure - + for example you may define in a conftest.py this method: + def pytest_ignore_collect(path): + return path.check(link=1) + to prevent even a collection try of any tests in symlinked dirs. +- new pytest_pycollect_makemodule(path, parent) hook for + allowing customization of the Module collection object for a + matching test module. +- expose (previously internal) commonly useful methods: + py.io.get_terminal_with() -> return terminal width + py.io.ansi_print(...) -> print colored/bold text on linux/win32 + py.io.saferepr(obj) -> return limited representation string +- expose test outcome related exceptions as py.test.skip.Exception, + py.test.raises.Exception etc., useful mostly for plugins + doing special outcome interpretation/tweaking - (issue85) fix junitxml plugin to handle tests with non-ascii output - fix/refine python3 compatibility (thanks Benjamin Peterson) - fixes for making the jython/win32 combination work, note however: @@ -13,22 +30,6 @@ Changes between 1.2.1 and 1.3.0 (release - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing -- add a new pytest_ignore_collect(path, config) hook to allow projects and - plugins to define exclusion behaviour for their directory structure - - for example you may define in a conftest.py this method: - def pytest_ignore_collect(path): - return path.check(link=1) - to prevent even a collection try of any tests in symlinked dirs. -- new pytest_pycollect_makemodule(path, parent) hook for - allowing customization of the Module collection object for a - matching test module. -- expose (previously internal) commonly useful methods: - py.io.get_terminal_with() -> return terminal width - py.io.ansi_print(...) -> print colored/bold text on linux/win32 - py.io.saferepr(obj) -> return limited representation string -- expose test outcome related exceptions as py.test.skip.Exception, - py.test.raises.Exception etc., useful mostly for plugins - doing special outcome interpretation/tweaking - refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method - ship distribute_setup.py version 0.6.10 - added links to the new capturelog and coverage plugins From commits-noreply at bitbucket.org Sun May 2 16:36:27 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 14:36:27 +0000 (UTC) Subject: [py-svn] py-trunk commit 2e8ad1a4fd49: rename pytest_ignore_collect_path to pytest_ignore_collect before release Message-ID: <20100502143627.E663F7EF06@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272806642 -7200 # Node ID 2e8ad1a4fd49b2f6c8b8140ee4581e4ca89e3684 # Parent aadc25966770117d4edc2bf0ee5aba43cbee4340 rename pytest_ignore_collect_path to pytest_ignore_collect before release --- a/testing/test_collect.py +++ b/testing/test_collect.py @@ -159,7 +159,7 @@ class TestPrunetraceback: class TestCustomConftests: def test_ignore_collect_path(self, testdir): testdir.makeconftest(""" - def pytest_ignore_collect_path(path, config): + def pytest_ignore_collect(path, config): return path.basename.startswith("x") or \ path.basename == "test_one.py" """) --- a/CHANGELOG +++ b/CHANGELOG @@ -13,10 +13,10 @@ Changes between 1.2.1 and 1.3.0 (release - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing -- add a new pytest_ignore_collect_path(path, config) hook to allow projects and +- add a new pytest_ignore_collect(path, config) hook to allow projects and plugins to define exclusion behaviour for their directory structure - for example you may define in a conftest.py this method: - def pytest_ignore_collect_path(path): + def pytest_ignore_collect(path): return path.check(link=1) to prevent even a collection try of any tests in symlinked dirs. - new pytest_pycollect_makemodule(path, parent) hook for --- a/doc/test/customize.txt +++ b/doc/test/customize.txt @@ -95,8 +95,8 @@ per-test run basetemp directory. .. _`function arguments`: funcargs.html .. _`extensions`: -Plugin basics -========================= +Plugin basics and project configuration +============================================= .. _`local plugin`: --- a/py/_plugin/hookspec.py +++ b/py/_plugin/hookspec.py @@ -27,12 +27,12 @@ def pytest_unconfigure(config): # collection hooks # ------------------------------------------------------------------------- -def pytest_ignore_collect_path(path, config): +def pytest_ignore_collect(path, config): """ return true value to prevent considering this path for collection. This hook is consulted for all files and directories prior to considering collection hooks. """ -pytest_ignore_collect_path.firstresult = True +pytest_ignore_collect.firstresult = True def pytest_collect_directory(path, parent): """ return Collection node or None for the given path. """ --- a/py/_plugin/pytest_default.py +++ b/py/_plugin/pytest_default.py @@ -28,7 +28,7 @@ def pytest_funcarg__pytestconfig(request """ the pytest config object with access to command line opts.""" return request.config -def pytest_ignore_collect_path(path, config): +def pytest_ignore_collect(path, config): ignore_paths = config.getconftest_pathlist("collect_ignore", path=path) ignore_paths = ignore_paths or [] excludeopt = config.getvalue("ignore") --- a/py/_test/collect.py +++ b/py/_test/collect.py @@ -297,7 +297,7 @@ class Directory(FSCollector): return l def consider(self, path): - if self.ihook.pytest_ignore_collect_path(path=path, config=self.config): + if self.ihook.pytest_ignore_collect(path=path, config=self.config): return if path.check(file=1): res = self.consider_file(path) From commits-noreply at bitbucket.org Sun May 2 16:44:15 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 14:44:15 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 3b690cd1706d: adapt to new 1.3.0 hook registration Message-ID: <20100502144415.0A96F7EF86@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272811499 -7200 # Node ID 3b690cd1706d6731c1e85af70f908cc65f20d1e7 # Parent 44ac866f66ac0606a9bcf198e385e42842a0fe21 adapt to new 1.3.0 hook registration --- a/xdist/plugin.py +++ b/xdist/plugin.py @@ -176,9 +176,9 @@ def pytest_addoption(parser): # ------------------------------------------------------------------------- # distributed testing hooks # ------------------------------------------------------------------------- -def pytest_registerhooks(pluginmanager): +def pytest_addhooks(pluginmanager): from xdist import newhooks - pluginmanager.registerhooks(newhooks) + pluginmanager.addhooks(newhooks) # ------------------------------------------------------------------------- # distributed testing initialization From commits-noreply at bitbucket.org Sun May 2 17:10:03 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 15:10:03 +0000 (UTC) Subject: [py-svn] py-trunk commit 10922586aed4: some internal fixes regarding the new required hook-finding prefix Message-ID: <20100502151003.A242A7EF86@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272813038 -7200 # Node ID 10922586aed48550d88a8ba8588a737d2aa793b0 # Parent 70c81c0042722c892a653199e024ccf948afda5c some internal fixes regarding the new required hook-finding prefix --- a/testing/plugin/test_pytest__pytest.py +++ b/testing/plugin/test_pytest__pytest.py @@ -6,25 +6,25 @@ from py._test.pluginmanager import Regis def test_hookrecorder_basic(): rec = HookRecorder(Registry()) class ApiClass: - def xyz(self, arg): - pass + def pytest_xyz(self, arg): + "x" rec.start_recording(ApiClass) - rec.hook.xyz(arg=123) - call = rec.popcall("xyz") + rec.hook.pytest_xyz(arg=123) + call = rec.popcall("pytest_xyz") assert call.arg == 123 - assert call._name == "xyz" + assert call._name == "pytest_xyz" py.test.raises(ValueError, "rec.popcall('abc')") def test_hookrecorder_basic_no_args_hook(): rec = HookRecorder(Registry()) apimod = type(os)('api') - def xyz(): - pass - apimod.xyz = xyz + def pytest_xyz(): + "x" + apimod.pytest_xyz = pytest_xyz rec.start_recording(apimod) - rec.hook.xyz() - call = rec.popcall("xyz") - assert call._name == "xyz" + rec.hook.pytest_xyz() + call = rec.popcall("pytest_xyz") + assert call._name == "pytest_xyz" def test_functional(testdir, linecomp): reprec = testdir.inline_runsource(""" @@ -33,14 +33,14 @@ def test_functional(testdir, linecomp): pytest_plugins="_pytest" def test_func(_pytest): class ApiClass: - def xyz(self, arg): pass + def pytest_xyz(self, arg): "x" hook = HookRelay([ApiClass], Registry()) rec = _pytest.gethookrecorder(hook) class Plugin: - def xyz(self, arg): + def pytest_xyz(self, arg): return arg + 1 rec._registry.register(Plugin()) - res = rec.hook.xyz(arg=41) + res = rec.hook.pytest_xyz(arg=41) assert res == [42] """) reprec.assertoutcome(passed=1) --- a/py/_plugin/pytest__pytest.py +++ b/py/_plugin/pytest__pytest.py @@ -46,7 +46,8 @@ class HookRecorder: recorder = RecordCalls() self._recorders[hookspec] = recorder self._registry.register(recorder) - self.hook = HookRelay(hookspecs, registry=self._registry) + self.hook = HookRelay(hookspecs, registry=self._registry, + prefix="pytest_") def finish_recording(self): for recorder in self._recorders.values(): From commits-noreply at bitbucket.org Sun May 2 22:12:53 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 2 May 2010 20:12:53 +0000 (UTC) Subject: [py-svn] py-trunk commit a12ca0a0974c: add new parameters: Message-ID: <20100502201253.61B857EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272831196 -7200 # Node ID a12ca0a0974c6b4207788c40f6e6c252aa3ecd49 # Parent 10922586aed48550d88a8ba8588a737d2aa793b0 add new parameters: xfail(run=False) will not run expected-to-fail tests xfail(reason=True) will report the specified reason --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -12,6 +12,30 @@ def test_xfail_not_report_default(testdi "*1 expected failures*--report=xfailed*", ]) +def test_xfail_not_run(testdir): + p = testdir.makepyfile(test_one=""" + import py + @py.test.mark.xfail(run=False, reason="noway") + def test_this(): + assert 0 + @py.test.mark.xfail("True", run=False, reason="noway") + def test_this_true(): + assert 0 + @py.test.mark.xfail("False", run=True, reason="huh") + def test_this_false(): + assert 1 + """) + result = testdir.runpytest(p, '-v') + result.stdout.fnmatch_lines([ + "*2 expected failures*--report=xfailed*", + "*1 passed*", + ]) + result = testdir.runpytest(p, '--report=xfailed', ) + result.stdout.fnmatch_lines([ + "*test_one*test_this*not run*noway", + "*test_one*test_this_true*not run*noway", + ]) + def test_skip_not_report_default(testdir): p = testdir.makepyfile(test_one=""" import py --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -83,10 +83,17 @@ Same as with skipif_ you can also select depending on platform:: @py.test.mark.xfail("sys.version_info >= (3,0)") - def test_function(): ... +To not run a test and still regard it as "xfailed":: + + @py.test.mark.xfail(..., run=False) + +To specify an explicit reason to be shown with xfailure detail:: + + @py.test.mark.xfail(..., reason="my reason") + skipping on a missing import dependency -------------------------------------------------- @@ -123,27 +130,41 @@ import py def pytest_runtest_setup(item): + if not isinstance(item, py.test.collect.Function): + return expr, result = evalexpression(item, 'skipif') if result: py.test.skip(expr) + holder = getattr(item.obj, 'xfail', None) + if holder and not holder.kwargs.get('run', True): + py.test.skip("") def pytest_runtest_makereport(__multicall__, item, call): - if call.when != "call": + if not isinstance(item, py.test.collect.Function): return - expr, result = evalexpression(item, 'xfail') - rep = __multicall__.execute() - if result: - if call.excinfo: - rep.skipped = True - rep.failed = rep.passed = False + if call.when == "setup": + holder = getattr(item.obj, 'xfail', None) + if holder: + rep = __multicall__.execute() + reason = holder.kwargs.get("reason", "") + rep.keywords['xfail'] = "[not run] " + reason + return rep + return + elif call.when == "call": + expr, result = evalexpression(item, 'xfail') + rep = __multicall__.execute() + if result: + if call.excinfo: + rep.skipped = True + rep.failed = rep.passed = False + else: + rep.skipped = rep.passed = False + rep.failed = True + rep.keywords['xfail'] = expr else: - rep.skipped = rep.passed = False - rep.failed = True - rep.keywords['xfail'] = expr - else: - if 'xfail' in rep.keywords: - del rep.keywords['xfail'] - return rep + if 'xfail' in rep.keywords: + del rep.keywords['xfail'] + return rep # called by terminalreporter progress reporting def pytest_report_teststatus(report): @@ -151,7 +172,7 @@ def pytest_report_teststatus(report): if report.skipped: return "xfailed", "x", "xfail" elif report.failed: - return "xpassed", "P", "xpass" + return "xpassed", "P", "XPASS" # called by the terminalreporter instance/plugin def pytest_terminal_summary(terminalreporter): @@ -172,10 +193,13 @@ def show_xfailed(terminalreporter): entry = rep.longrepr.reprcrash modpath = rep.item.getmodpath(includemodule=True) pos = "%s %s:%d: " %(modpath, entry.path, entry.lineno) - reason = rep.longrepr.reprcrash.message - i = reason.find("\n") - if i != -1: - reason = reason[:i] + if rep.keywords['xfail']: + reason = rep.keywords['xfail'].strip() + else: + reason = rep.longrepr.reprcrash.message + i = reason.find("\n") + if i != -1: + reason = reason[:i] tr._tw.line("%s %s" %(pos, reason)) xpassed = terminalreporter.stats.get("xpassed") --- a/testing/plugin/test_pytest_resultlog.py +++ b/testing/plugin/test_pytest_resultlog.py @@ -159,6 +159,9 @@ def test_generic(testdir, LineMatcher): @py.test.mark.xfail def test_xfail(): assert 0 + @py.test.mark.xfail(run=False) + def test_xfail_norun(): + assert 0 """) testdir.runpytest("--resultlog=result.log") lines = testdir.tmpdir.join("result.log").readlines(cr=0) @@ -167,5 +170,6 @@ def test_generic(testdir, LineMatcher): "F *:test_fail", "s *:test_skip", "x *:test_xfail", + "x *:test_xfail_norun", ]) --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,9 @@ Changes between 1.2.1 and 1.3.0 (release - new pytest_pycollect_makemodule(path, parent) hook for allowing customization of the Module collection object for a matching test module. +- extend py.test.mark.xfail to accept two more keyword arg parameters: + ``xfail(run=False)`` will not run the decorated test + ``xfail(reason="...")`` will print the reason string when reporting - expose (previously internal) commonly useful methods: py.io.get_terminal_with() -> return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 From commits-noreply at bitbucket.org Tue May 4 13:02:29 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 4 May 2010 11:02:29 +0000 (UTC) Subject: [py-svn] py-trunk commit bc6e7335cff1: add a terminalreporter.testid method Message-ID: <20100504110229.994E07EEFC@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272969472 -7200 # Node ID bc6e7335cff11abb1d2a90cfe3cffcab89c47af7 # Parent a12ca0a0974c6b4207788c40f6e6c252aa3ecd49 add a terminalreporter.testid method --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -111,6 +111,22 @@ class TestTerminal: assert lines[1].endswith("xy.py .") assert lines[2] == "hello world" + def test_testid(self, testdir, linecomp): + func,method = testdir.getitems(""" + def test_func(): + pass + class TestClass: + def test_method(self): + pass + """) + tr = TerminalReporter(func.config, file=linecomp.stringio) + id = tr.gettestid(func) + assert id.endswith("test_testid.py::test_func") + fspath = py.path.local(id.split("::")[0]) + assert fspath.check() + id = tr.gettestid(method) + assert id.endswith("test_testid.py::TestClass::test_method") + def test_looponfailreport(self, testdir, linecomp): modcol = testdir.getmodulecol(""" def test_fail(): --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -128,6 +128,20 @@ class TerminalReporter: else: return "???", dict(red=True) + def gettestid(self, item, relative=True): + fspath = item.fspath + chain = [x for x in item.listchain() if x.fspath == fspath] + chain = chain[1:] + names = [x.name for x in chain if x.name != "()"] + path = item.fspath + if relative: + relpath = path.relto(self.curdir) + if relpath: + path = relpath + names.insert(0, str(path)) + return "::".join(names) + + def pytest_internalerror(self, excrepr): for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) From commits-noreply at bitbucket.org Tue May 4 13:02:31 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 4 May 2010 11:02:31 +0000 (UTC) Subject: [py-svn] py-trunk commit 7c0f93775d2f: add unit-tests for xfail and refine xfail handling and reporting Message-ID: <20100504110231.861E47EF04@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272969476 -7200 # Node ID 7c0f93775d2fc602636ba10c081ab357679c9a47 # Parent bc6e7335cff11abb1d2a90cfe3cffcab89c47af7 add unit-tests for xfail and refine xfail handling and reporting --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -1,40 +1,185 @@ import py -def test_xfail_not_report_default(testdir): - p = testdir.makepyfile(test_one=""" - import py - @py.test.mark.xfail - def test_this(): - assert 0 - """) - result = testdir.runpytest(p, '-v') - result.stdout.fnmatch_lines([ - "*1 expected failures*--report=xfailed*", - ]) +from py._plugin.pytest_skipping import MarkEvaluator +from py._plugin.pytest_skipping import pytest_runtest_setup +from py._plugin.pytest_runner import runtestprotocol -def test_xfail_not_run(testdir): - p = testdir.makepyfile(test_one=""" - import py - @py.test.mark.xfail(run=False, reason="noway") - def test_this(): - assert 0 - @py.test.mark.xfail("True", run=False, reason="noway") - def test_this_true(): - assert 0 - @py.test.mark.xfail("False", run=True, reason="huh") - def test_this_false(): - assert 1 - """) - result = testdir.runpytest(p, '-v') - result.stdout.fnmatch_lines([ - "*2 expected failures*--report=xfailed*", - "*1 passed*", - ]) - result = testdir.runpytest(p, '--report=xfailed', ) - result.stdout.fnmatch_lines([ - "*test_one*test_this*not run*noway", - "*test_one*test_this_true*not run*noway", - ]) +class TestEvaluator: + def test_no_marker(self, testdir): + item = testdir.getitem("def test_func(): pass") + evalskipif = MarkEvaluator(item, 'skipif') + assert not evalskipif + assert not evalskipif.istrue() + + def test_marked_no_args(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.xyz + def test_func(): + pass + """) + ev = MarkEvaluator(item, 'xyz') + assert ev + assert ev.istrue() + expl = ev.getexplanation() + assert expl == "condition: True" + assert not ev.get("run", False) + + def test_marked_one_arg(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.xyz("hasattr(os, 'sep')") + def test_func(): + pass + """) + ev = MarkEvaluator(item, 'xyz') + assert ev + assert ev.istrue() + expl = ev.getexplanation() + assert expl == "condition: hasattr(os, 'sep')" + + def test_marked_one_arg_with_reason(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.xyz("hasattr(os, 'sep')", attr=2, reason="hello world") + def test_func(): + pass + """) + ev = MarkEvaluator(item, 'xyz') + assert ev + assert ev.istrue() + expl = ev.getexplanation() + assert expl == "hello world" + assert ev.get("attr") == 2 + + def test_skipif_class(self, testdir): + item, = testdir.getitems(""" + import py + class TestClass: + pytestmark = py.test.mark.skipif("config._hackxyz") + def test_func(self): + pass + """) + item.config._hackxyz = 3 + ev = MarkEvaluator(item, 'skipif') + assert ev.istrue() + expl = ev.getexplanation() + assert expl == "condition: config._hackxyz" + + +class TestXFail: + def test_xfail_simple(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.xfail + def test_func(): + assert 0 + """) + reports = runtestprotocol(item, log=False) + assert len(reports) == 3 + callreport = reports[1] + assert callreport.skipped + expl = callreport.keywords['xfail'] + assert expl == "condition: True" + + def test_xfail_xpassed(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.xfail + def test_func(): + assert 1 + """) + reports = runtestprotocol(item, log=False) + assert len(reports) == 3 + callreport = reports[1] + assert callreport.failed + expl = callreport.keywords['xfail'] + assert expl == "condition: True" + + def test_xfail_evalfalse_but_fails(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.xfail('False') + def test_func(): + assert 0 + """) + reports = runtestprotocol(item, log=False) + callreport = reports[1] + assert callreport.failed + assert 'xfail' not in callreport.keywords + + def test_xfail_not_report_default(self, testdir): + p = testdir.makepyfile(test_one=""" + import py + @py.test.mark.xfail + def test_this(): + assert 0 + """) + result = testdir.runpytest(p, '-v') + result.stdout.fnmatch_lines([ + "*1 expected failures*--report=xfailed*", + ]) + + def test_xfail_not_run_xfail_reporting(self, testdir): + p = testdir.makepyfile(test_one=""" + import py + @py.test.mark.xfail(run=False, reason="noway") + def test_this(): + assert 0 + @py.test.mark.xfail("True", run=False) + def test_this_true(): + assert 0 + @py.test.mark.xfail("False", run=False, reason="huh") + def test_this_false(): + assert 1 + """) + result = testdir.runpytest(p, '--report=xfailed', ) + result.stdout.fnmatch_lines([ + "*test_one*test_this*NOTRUN*noway", + "*test_one*test_this_true*NOTRUN*condition:*True*", + "*1 passed*", + ]) + + def test_xfail_xpass(self, testdir): + p = testdir.makepyfile(test_one=""" + import py + @py.test.mark.xfail + def test_that(): + assert 1 + """) + result = testdir.runpytest(p, '--report=xfailed') + result.stdout.fnmatch_lines([ + "*UNEXPECTEDLY PASSING*", + "*test_that*", + "*1 xpassed*" + ]) + assert result.ret == 1 + +class TestSkipif: + def test_skipif_conditional(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.skipif("hasattr(os, 'sep')") + def test_func(): + pass + """) + x = py.test.raises(py.test.skip.Exception, "pytest_runtest_setup(item)") + assert x.value.msg == "condition: hasattr(os, 'sep')" + + + def test_skipif_reporting(self, testdir): + p = testdir.makepyfile(""" + import py + @py.test.mark.skipif("hasattr(sys, 'platform')") + def test_that(): + assert 0 + """) + result = testdir.runpytest(p, '-s', '--report=skipped') + result.stdout.fnmatch_lines([ + "*Skipped*platform*", + "*1 skipped*" + ]) + assert result.ret == 0 def test_skip_not_report_default(testdir): p = testdir.makepyfile(test_one=""" @@ -47,69 +192,6 @@ def test_skip_not_report_default(testdir "*1 skipped*--report=skipped*", ]) -def test_xfail_decorator(testdir): - p = testdir.makepyfile(test_one=""" - import py - @py.test.mark.xfail - def test_this(): - assert 0 - - @py.test.mark.xfail - def test_that(): - assert 1 - """) - result = testdir.runpytest(p, '--report=xfailed') - result.stdout.fnmatch_lines([ - "*expected failures*", - "*test_one.test_this*test_one.py:4*", - "*UNEXPECTEDLY PASSING*", - "*test_that*", - "*1 xfailed*" - ]) - assert result.ret == 1 - -def test_xfail_at_module(testdir): - p = testdir.makepyfile(""" - import py - pytestmark = py.test.mark.xfail('True') - def test_intentional_xfail(): - assert 0 - """) - result = testdir.runpytest(p, '--report=xfailed') - result.stdout.fnmatch_lines([ - "*expected failures*", - "*test_intentional_xfail*:4*", - "*1 xfailed*" - ]) - assert result.ret == 0 - -def test_xfail_evalfalse_but_fails(testdir): - p = testdir.makepyfile(""" - import py - @py.test.mark.xfail('False') - def test_fail(): - assert 0 - """) - result = testdir.runpytest(p, '--report=xfailed') - result.stdout.fnmatch_lines([ - "*test_xfail_evalfalse_but_fails*:4*", - "*1 failed*" - ]) - assert result.ret == 1 - -def test_skipif_decorator(testdir): - p = testdir.makepyfile(""" - import py - @py.test.mark.skipif("hasattr(sys, 'platform')") - def test_that(): - assert 0 - """) - result = testdir.runpytest(p, '--report=skipped') - result.stdout.fnmatch_lines([ - "*Skipped*platform*", - "*1 skipped*" - ]) - assert result.ret == 0 def test_skipif_class(testdir): p = testdir.makepyfile(""" @@ -127,19 +209,6 @@ def test_skipif_class(testdir): "*2 skipped*" ]) -def test_evalexpression_cls_config_example(testdir): - from py._plugin.pytest_skipping import evalexpression - item, = testdir.getitems(""" - import py - class TestClass: - pytestmark = py.test.mark.skipif("config._hackxyz") - def test_func(self): - pass - """) - item.config._hackxyz = 3 - x, y = evalexpression(item, 'skipif') - assert x == 'config._hackxyz' - assert y == 3 def test_skip_reasons_folding(): from py._plugin import pytest_runner as runner --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -123,44 +123,83 @@ within test or setup code. Example:: py.test.skip("unsuppored configuration") """ -# XXX py.test.skip, .importorskip and the Skipped class -# should also be defined in this plugin, requires thought/changes import py +class MarkEvaluator: + def __init__(self, item, name): + self.item = item + self.name = name + self.holder = getattr(item.obj, name, None) + + def __bool__(self): + return bool(self.holder) + __nonzero__ = __bool__ + + def istrue(self): + if self.holder: + d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config} + self.result = True + for expr in self.holder.args: + self.expr = expr + if isinstance(expr, str): + result = cached_eval(self.item.config, expr, d) + else: + result = expr + if not result: + self.result = False + self.expr = expr + break + return getattr(self, 'result', False) + + def get(self, attr, default=None): + return self.holder.kwargs.get(attr, default) + + def getexplanation(self): + expl = self.get('reason', None) + if not expl: + if not hasattr(self, 'expr'): + return "condition: True" + else: + return "condition: " + self.expr + return expl + def pytest_runtest_setup(item): if not isinstance(item, py.test.collect.Function): return - expr, result = evalexpression(item, 'skipif') - if result: - py.test.skip(expr) - holder = getattr(item.obj, 'xfail', None) - if holder and not holder.kwargs.get('run', True): - py.test.skip("") + evalskip = MarkEvaluator(item, 'skipif') + if evalskip.istrue(): + py.test.skip(evalskip.getexplanation()) + item._evalxfail = MarkEvaluator(item, 'xfail') + if item._evalxfail.istrue(): + if not item._evalxfail.get('run', True): + py.test.skip("xfail") def pytest_runtest_makereport(__multicall__, item, call): if not isinstance(item, py.test.collect.Function): return + evalxfail = getattr(item, '_evalxfail', None) + if not evalxfail: + return if call.when == "setup": - holder = getattr(item.obj, 'xfail', None) - if holder: - rep = __multicall__.execute() - reason = holder.kwargs.get("reason", "") - rep.keywords['xfail'] = "[not run] " + reason - return rep - return + rep = __multicall__.execute() + if rep.skipped and evalxfail.istrue(): + expl = evalxfail.getexplanation() + if not evalxfail.get("run", True): + expl = "[NOTRUN] " + expl + rep.keywords['xfail'] = expl + return rep elif call.when == "call": - expr, result = evalexpression(item, 'xfail') rep = __multicall__.execute() - if result: + if evalxfail.istrue(): if call.excinfo: rep.skipped = True rep.failed = rep.passed = False else: rep.skipped = rep.passed = False rep.failed = True - rep.keywords['xfail'] = expr + rep.keywords['xfail'] = evalxfail.getexplanation() else: if 'xfail' in rep.keywords: del rep.keywords['xfail'] @@ -190,43 +229,17 @@ def show_xfailed(terminalreporter): return tr.write_sep("_", "expected failures") for rep in xfailed: - entry = rep.longrepr.reprcrash - modpath = rep.item.getmodpath(includemodule=True) - pos = "%s %s:%d: " %(modpath, entry.path, entry.lineno) - if rep.keywords['xfail']: - reason = rep.keywords['xfail'].strip() - else: - reason = rep.longrepr.reprcrash.message - i = reason.find("\n") - if i != -1: - reason = reason[:i] + pos = terminalreporter.gettestid(rep.item) + reason = rep.keywords['xfail'] tr._tw.line("%s %s" %(pos, reason)) xpassed = terminalreporter.stats.get("xpassed") if xpassed: tr.write_sep("_", "UNEXPECTEDLY PASSING TESTS") for rep in xpassed: - fspath, lineno, modpath = rep.item.reportinfo() - pos = "%s %s:%d: unexpectedly passing" %(modpath, fspath, lineno) - tr._tw.line(pos) - - -def evalexpression(item, keyword): - if isinstance(item, py.test.collect.Function): - markholder = getattr(item.obj, keyword, None) - result = False - if markholder: - d = {'os': py.std.os, 'sys': py.std.sys, 'config': item.config} - expr, result = None, True - for expr in markholder.args: - if isinstance(expr, str): - result = cached_eval(item.config, expr, d) - else: - result = expr - if not result: - break - return expr, result - return None, False + pos = terminalreporter.gettestid(rep.item) + reason = rep.keywords['xfail'] + tr._tw.line("%s %s" %(pos, reason)) def cached_eval(config, expr, d): if not hasattr(config, '_evalcache'): From commits-noreply at bitbucket.org Tue May 4 13:02:31 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 4 May 2010 11:02:31 +0000 (UTC) Subject: [py-svn] py-trunk commit fb4d3ade6719: new --runxfail option to ignore xfail markers on functions Message-ID: <20100504110231.9BE507EF0C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272970947 -7200 # Node ID fb4d3ade6719dffbd062f5dde48781f663f2a4b4 # Parent 7c0f93775d2fc602636ba10c081ab357679c9a47 new --runxfail option to ignore xfail markers on functions --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -96,6 +96,21 @@ class TestXFail: expl = callreport.keywords['xfail'] assert expl == "condition: True" + def test_xfail_run_anyway(self, testdir): + testdir.makepyfile(""" + import py + @py.test.mark.xfail + def test_func(): + assert 0 + """) + result = testdir.runpytest("--runxfail") + assert result.ret == 1 + result.stdout.fnmatch_lines([ + "*def test_func():*", + "*assert 0*", + "*1 failed*", + ]) + def test_xfail_evalfalse_but_fails(self, testdir): item = testdir.getitem(""" import py --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -126,6 +126,12 @@ within test or setup code. Example:: import py +def pytest_addoption(parser): + group = parser.getgroup("general") + group.addoption('--runxfail', + action="store_true", dest="runxfail", default=False, + help="run tests even if they are marked xfail") + class MarkEvaluator: def __init__(self, item, name): self.item = item @@ -172,9 +178,10 @@ def pytest_runtest_setup(item): if evalskip.istrue(): py.test.skip(evalskip.getexplanation()) item._evalxfail = MarkEvaluator(item, 'xfail') - if item._evalxfail.istrue(): - if not item._evalxfail.get('run', True): - py.test.skip("xfail") + if not item.config.getvalue("runxfail"): + if item._evalxfail.istrue(): + if not item._evalxfail.get('run', True): + py.test.skip("xfail") def pytest_runtest_makereport(__multicall__, item, call): if not isinstance(item, py.test.collect.Function): @@ -192,7 +199,7 @@ def pytest_runtest_makereport(__multical return rep elif call.when == "call": rep = __multicall__.execute() - if evalxfail.istrue(): + if not item.config.getvalue("runxfail") and evalxfail.istrue(): if call.excinfo: rep.skipped = True rep.failed = rep.passed = False --- a/CHANGELOG +++ b/CHANGELOG @@ -14,9 +14,10 @@ Changes between 1.2.1 and 1.3.0 (release - new pytest_pycollect_makemodule(path, parent) hook for allowing customization of the Module collection object for a matching test module. -- extend py.test.mark.xfail to accept two more keyword arg parameters: - ``xfail(run=False)`` will not run the decorated test - ``xfail(reason="...")`` will print the reason string when reporting +- extend and refine xfail mechanism: + ``@py.test.mark.xfail(run=False)`` do not run the decorated test + ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries + specifiying ``--runxfail`` on command line virtually ignores xfail markers - expose (previously internal) commonly useful methods: py.io.get_terminal_with() -> return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 From commits-noreply at bitbucket.org Wed May 5 14:23:35 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 12:23:35 +0000 (UTC) Subject: [py-svn] py-trunk commit bc00163d0dff: add python3 classifier Message-ID: <20100505122335.66A317EEF7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273062242 -7200 # Node ID bc00163d0dff59ed311ad8a23dbcaad298a20ea0 # Parent fb4d3ade6719dffbd062f5dde48781f663f2a4b4 add python3 classifier --- a/setup.py +++ b/setup.py @@ -42,7 +42,8 @@ def main(): 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities', - 'Programming Language :: Python'], + 'Programming Language :: Python', + 'Programming Language :: Python :: 3'], packages=['py', 'py._plugin', 'py._cmdline', From commits-noreply at bitbucket.org Wed May 5 14:25:04 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 12:25:04 +0000 (UTC) Subject: [py-svn] pytest-xdist commit a72c7db7b014: add python3 classifier Message-ID: <20100505122504.4B7607EEF7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1273062359 -7200 # Node ID a72c7db7b01403c5dff73284577764b19f0c6476 # Parent 3b690cd1706d6731c1e85af70f908cc65f20d1e7 add python3 classifier --- a/setup.py +++ b/setup.py @@ -34,5 +34,6 @@ setup( 'Topic :: Software Development :: Quality Assurance', 'Topic :: Utilities', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', ], ) From commits-noreply at bitbucket.org Wed May 5 20:17:55 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 18:17:55 +0000 (UTC) Subject: [py-svn] py-trunk commit fea7cfc4866d: deprecate --report option in favour of a new shorter and easier to remember -r option: this takes a string argument consisting of any combination of 'xsfX' Message-ID: <20100505181755.B106D7EEE7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273081859 -7200 # Node ID fea7cfc4866d15a3c5da8e936bc224a219b44808 # Parent bc00163d0dff59ed311ad8a23dbcaad298a20ea0 deprecate --report option in favour of a new shorter and easier to remember -r option: this takes a string argument consisting of any combination of 'xsfX' Those letters basically correspond to the letters you see during terminal reporting. --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -12,12 +12,16 @@ def pytest_addoption(parser): group = parser.getgroup("terminal reporting", "reporting", after="general") group._addoption('-v', '--verbose', action="count", dest="verbose", default=0, help="increase verbosity."), + group._addoption('-r', + action="store", dest="reportchars", default=None, metavar="chars", + help="show extra test summary info as specified by chars (f)ailed, " + "(s)skipped, (x)failed, (X)passed.") group._addoption('-l', '--showlocals', - action="store_true", dest="showlocals", default=False, - help="show locals in tracebacks (disabled by default).") - group.addoption('--report', - action="store", dest="report", default=None, metavar="opts", - help="show more info, valid: skipped,xfailed") + action="store_true", dest="showlocals", default=False, + help="show locals in tracebacks (disabled by default).") + group._addoption('--report', + action="store", dest="report", default=None, metavar="opts", + help="(deprecated, use -r)") group._addoption('--tb', metavar="style", action="store", dest="tbstyle", default='long', type="choice", choices=['long', 'short', 'no', 'line'], @@ -47,17 +51,25 @@ def pytest_configure(config): setattr(reporter._tw, name, getattr(config, attr)) config.pluginmanager.register(reporter, 'terminalreporter') -def getreportopt(optvalue): - d = {} +def getreportopt(config): + reportopts = "" + optvalue = config.getvalue("report") if optvalue: - for setting in optvalue.split(","): - setting = setting.strip() - val = True - if setting.startswith("no"): - val = False - setting = setting[2:] - d[setting] = val - return d + py.builtin.print_("DEPRECATED: use -r instead of --report option.", + file=py.std.sys.stderr) + if optvalue: + for setting in optvalue.split(","): + setting = setting.strip() + if setting == "skipped": + reportopts += "s" + elif setting == "xfailed": + reportopts += "x" + reportchars = config.getvalue("reportchars") + if reportchars: + for char in reportchars: + if char not in reportopts: + reportopts += char + return reportopts class TerminalReporter: def __init__(self, config, file=None): @@ -69,10 +81,11 @@ class TerminalReporter: self._tw = py.io.TerminalWriter(file) self.currentfspath = None self.gateway2info = {} - self._reportopt = getreportopt(config.getvalue('report')) + self.reportchars = getreportopt(config) - def hasopt(self, name): - return self._reportopt.get(name, False) + def hasopt(self, char): + char = {'xfailed': 'x', 'skipped': 's'}.get(char,char) + return char in self.reportchars def write_fspath_result(self, fspath, res): fspath = self.curdir.bestrelpath(fspath) --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -575,12 +575,41 @@ class TestTerminalFunctional: ]) assert result.ret == 1 +def test_fail_extra_reporting(testdir): + p = testdir.makepyfile("def test_this(): assert 0") + result = testdir.runpytest(p) + assert 'short test summary' not in result.stdout.str() + result = testdir.runpytest(p, '-rf') + result.stdout.fnmatch_lines([ + "*test summary*", + "FAIL*test_fail_extra_reporting*", + ]) + +def test_fail_reporting_on_pass(testdir): + p = testdir.makepyfile("def test_this(): assert 1") + result = testdir.runpytest(p, '-rf') + assert 'short test summary' not in result.stdout.str() def test_getreportopt(): - assert getreportopt(None) == {} - assert getreportopt("hello") == {'hello': True} - assert getreportopt("hello, world") == dict(hello=True, world=True) - assert getreportopt("nohello") == dict(hello=False) + testdict = {} + class Config: + def getvalue(self, name): + return testdict.get(name, None) + config = Config() + testdict.update(dict(report="xfailed")) + assert getreportopt(config) == "x" + + testdict.update(dict(report="xfailed,skipped")) + assert getreportopt(config) == "xs" + + testdict.update(dict(report="skipped,xfailed")) + assert getreportopt(config) == "sx" + + testdict.update(dict(report="skipped", reportchars="sf")) + assert getreportopt(config) == "sf" + + testdict.update(dict(reportchars="sfx")) + assert getreportopt(config) == "sfx" def test_terminalreporter_reportopt_conftestsetting(testdir): testdir.makeconftest("option_report = 'skipped'") --- a/CHANGELOG +++ b/CHANGELOG @@ -1,41 +1,65 @@ -Changes between 1.2.1 and 1.3.0 (release pending) +Changes between 1.2.1 and 1.3.0 ================================================== +- deprecate --report option in favour of a new shorter and easier to + remember -r option: it takes a string argument consisting of any + combination of 'xfsX' characters. They relate to the single chars + you see during the dotted progress printing and will print an extra line + per test at the end of the test run. This extra line indicates the exact + position or test ID that you directly paste to the py.test cmdline in order + to re-run a particular test. + - allow external plugins to register new hooks via the new pytest_addhooks(pluginmanager) hook. The new release of the pytest-xdist plugin for distributed and looponfailing testing requires this feature. + - add a new pytest_ignore_collect(path, config) hook to allow projects and plugins to define exclusion behaviour for their directory structure - - for example you may define in a conftest.py this method: + for example you may define in a conftest.py this method:: + def pytest_ignore_collect(path): return path.check(link=1) + to prevent even a collection try of any tests in symlinked dirs. + - new pytest_pycollect_makemodule(path, parent) hook for allowing customization of the Module collection object for a matching test module. + - extend and refine xfail mechanism: ``@py.test.mark.xfail(run=False)`` do not run the decorated test ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries specifiying ``--runxfail`` on command line virtually ignores xfail markers + - expose (previously internal) commonly useful methods: py.io.get_terminal_with() -> return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 py.io.saferepr(obj) -> return limited representation string + - expose test outcome related exceptions as py.test.skip.Exception, py.test.raises.Exception etc., useful mostly for plugins doing special outcome interpretation/tweaking + - (issue85) fix junitxml plugin to handle tests with non-ascii output + - fix/refine python3 compatibility (thanks Benjamin Peterson) + - fixes for making the jython/win32 combination work, note however: jython2.5.1/win32 does not provide a command line launcher, see http://bugs.jython.org/issue1491 . See pylib install documentation for how to work around. + - fixes for handling of unicode exception values and unprintable objects + - (issue87) fix unboundlocal error in assertionold code + - (issue86) improve documentation for looponfailing + - refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method + - ship distribute_setup.py version 0.6.10 + - added links to the new capturelog and coverage plugins --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -10,9 +10,8 @@ The need for skipping a test is usually If a test fails under all conditions then it's probably better to mark your test as 'xfail'. -By passing ``--report=xfailed,skipped`` to the terminal reporter -you will see summary information on skips and xfail-run tests -at the end of a test run. +By passing ``-rxs`` to the terminal reporter you will see extra +summary information on skips and xfail-run tests at the end of a test run. .. _skipif: @@ -165,7 +164,7 @@ class MarkEvaluator: expl = self.get('reason', None) if not expl: if not hasattr(self, 'expr'): - return "condition: True" + return "" else: return "condition: " + self.expr return expl @@ -222,31 +221,53 @@ def pytest_report_teststatus(report): # called by the terminalreporter instance/plugin def pytest_terminal_summary(terminalreporter): - show_xfailed(terminalreporter) - show_skipped(terminalreporter) + tr = terminalreporter + if not tr.reportchars: + #for name in "xfailed skipped failed xpassed": + # if not tr.stats.get(name, 0): + # tr.write_line("HINT: use '-r' option to see extra " + # "summary info about tests") + # break + return -def show_xfailed(terminalreporter): - tr = terminalreporter - xfailed = tr.stats.get("xfailed") + lines = [] + for char in tr.reportchars: + if char == "x": + show_xfailed(terminalreporter, lines) + elif char == "X": + show_xpassed(terminalreporter, lines) + elif char == "f": + show_failed(terminalreporter, lines) + elif char == "s": + show_skipped(terminalreporter, lines) + if lines: + tr._tw.sep("=", "short test summary info") + for line in lines: + tr._tw.line(line) + +def show_failed(terminalreporter, lines): + tw = terminalreporter._tw + failed = terminalreporter.stats.get("failed") + if failed: + for rep in failed: + pos = terminalreporter.gettestid(rep.item) + lines.append("FAIL %s" %(pos, )) + +def show_xfailed(terminalreporter, lines): + xfailed = terminalreporter.stats.get("xfailed") if xfailed: - if not tr.hasopt('xfailed'): - tr.write_line( - "%d expected failures, use --report=xfailed for more info" % - len(xfailed)) - return - tr.write_sep("_", "expected failures") for rep in xfailed: pos = terminalreporter.gettestid(rep.item) reason = rep.keywords['xfail'] - tr._tw.line("%s %s" %(pos, reason)) + lines.append("XFAIL %s %s" %(pos, reason)) +def show_xpassed(terminalreporter, lines): xpassed = terminalreporter.stats.get("xpassed") if xpassed: - tr.write_sep("_", "UNEXPECTEDLY PASSING TESTS") for rep in xpassed: pos = terminalreporter.gettestid(rep.item) reason = rep.keywords['xfail'] - tr._tw.line("%s %s" %(pos, reason)) + lines.append("XPASS %s %s" %(pos, reason)) def cached_eval(config, expr, d): if not hasattr(config, '_evalcache'): @@ -271,17 +292,20 @@ def folded_skips(skipped): l.append((len(events),) + key) return l -def show_skipped(terminalreporter): +def show_skipped(terminalreporter, lines): tr = terminalreporter skipped = tr.stats.get('skipped', []) if skipped: - if not tr.hasopt('skipped'): - tr.write_line( - "%d skipped tests, use --report=skipped for more info" % - len(skipped)) - return + #if not tr.hasopt('skipped'): + # tr.write_line( + # "%d skipped tests, specify -rs for more info" % + # len(skipped)) + # return fskips = folded_skips(skipped) if fskips: - tr.write_sep("_", "skipped test summary") + #tr.write_sep("_", "skipped test summary") for num, fspath, lineno, reason in fskips: - tr._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason)) + if reason.startswith("Skipped: "): + reason = reason[9:] + lines.append("SKIP [%d] %s:%d: %s" % + (num, fspath, lineno, reason)) --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -22,7 +22,7 @@ class TestEvaluator: assert ev assert ev.istrue() expl = ev.getexplanation() - assert expl == "condition: True" + assert expl == "" assert not ev.get("run", False) def test_marked_one_arg(self, testdir): @@ -80,7 +80,7 @@ class TestXFail: callreport = reports[1] assert callreport.skipped expl = callreport.keywords['xfail'] - assert expl == "condition: True" + assert expl == "" def test_xfail_xpassed(self, testdir): item = testdir.getitem(""" @@ -94,7 +94,7 @@ class TestXFail: callreport = reports[1] assert callreport.failed expl = callreport.keywords['xfail'] - assert expl == "condition: True" + assert expl == "" def test_xfail_run_anyway(self, testdir): testdir.makepyfile(""" @@ -131,9 +131,9 @@ class TestXFail: assert 0 """) result = testdir.runpytest(p, '-v') - result.stdout.fnmatch_lines([ - "*1 expected failures*--report=xfailed*", - ]) + #result.stdout.fnmatch_lines([ + # "*HINT*use*-r*" + #]) def test_xfail_not_run_xfail_reporting(self, testdir): p = testdir.makepyfile(test_one=""" @@ -162,10 +162,9 @@ class TestXFail: def test_that(): assert 1 """) - result = testdir.runpytest(p, '--report=xfailed') + result = testdir.runpytest(p, '-rX') result.stdout.fnmatch_lines([ - "*UNEXPECTEDLY PASSING*", - "*test_that*", + "*XPASS*test_that*", "*1 xpassed*" ]) assert result.ret == 1 @@ -189,9 +188,9 @@ class TestSkipif: def test_that(): assert 0 """) - result = testdir.runpytest(p, '-s', '--report=skipped') + result = testdir.runpytest(p, '-s', '-rs') result.stdout.fnmatch_lines([ - "*Skipped*platform*", + "*SKIP*1*platform*", "*1 skipped*" ]) assert result.ret == 0 @@ -204,7 +203,8 @@ def test_skip_not_report_default(testdir """) result = testdir.runpytest(p, '-v') result.stdout.fnmatch_lines([ - "*1 skipped*--report=skipped*", + #"*HINT*use*-r*", + "*1 skipped*", ]) @@ -276,8 +276,7 @@ def test_skipped_reasons_functional(test result.stdout.fnmatch_lines([ "*test_one.py ss", "*test_two.py S", - "___* skipped test summary *_", - "*conftest.py:3: *3* Skipped: 'test'", + "*SKIP*3*conftest.py:3: 'test'", ]) assert result.ret == 0 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ py.test and pylib: rapid testing and dev - `py.code`_: dynamic code compile and traceback printing support Platforms: Linux, Win32, OSX -Interpreters: Python versions 2.4 through to 3.1, Jython 2.5.1. +Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy For questions please check out http://pylib.org/contact.html .. _`py.test`: http://pytest.org From commits-noreply at bitbucket.org Wed May 5 20:17:57 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 18:17:57 +0000 (UTC) Subject: [py-svn] py-trunk commit 0a2e11de6290: update release announcement Message-ID: <20100505181757.DAA897EF11@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273083542 -7200 # Node ID 0a2e11de6290e13ec6a4188634f3d1c2711ac281 # Parent fea7cfc4866d15a3c5da8e936bc224a219b44808 update release announcement --- a/doc/announce/release-1.2.2.txt +++ /dev/null @@ -1,34 +0,0 @@ -py.test/pylib 1.2.2: bugfixes and per-plugin hooks --------------------------------------------------------------------------------- - -py.test-1.2.2 is a minor release with brings a number of -bug fixes and improved compatibility with Python3 and Jython-2.5.1 on Windows. -It also is the first release that allows plugins to dynamically register -new hooks (which might be implemented by yet other plugins) allowing plugins to -interoperate without requiring a new core release. - -py.test is an advanced automated testing tool working with -Python2, Python3 and Jython versions on all major operating -systems. It has a simple plugin architecture and can run many -existing common Python test suites without modification. It offers -some unique features not found in other testing tools. -See http://pytest.org for more info. - -For more detailed information see the changelog below. - -cheers and have fun, - -holger - -Changes between 1.2.1 and 1.2.2 (release pending) -================================================== - -- new mechanism to allow plugins to register new hooks -- (issue85) fix junitxml plugin to handle tests with non-ascii output -- fix some python3 compatibility issues (thanks Benjamin Peterson) -- fixes for making the jython/win32 combination work -- fixes for handling of unicode exception values -- added links to the new capturelog and coverage plugins -- (issue87) fix unboundlocal error in assertionold code -- (issue86) improve documentation for looponfailing -- ship distribute_setup.py version 0.6.10 --- /dev/null +++ b/doc/announce/release-1.3.0.txt @@ -0,0 +1,577 @@ +py.test/pylib 1.3.0: new options, per-plugin hooks, fixes ... +=========================================================================== + +The 1.3.0 release introces new options, bug fixes and improved compatibility +with Python3 and Jython-2.5.1 on Windows. If you already use py-1.2 chances +are you can use py-1.3.0. See the below CHANGELOG for more details and +http://pylib.org/install.html for installation instructions. + +py.test is an advanced automated testing tool working with Python2, +Python3, Jython and PyPy versions on all major operating systems. It +offers a no-boilerplate testing approach and has inspired other testing +tools and enhancements in the standard Python library for more than five +years. It has a simple and extensive plugin architecture, configurable +reporting and provides unique ways to make it fit to your testing +process and needs. + +See http://pytest.org for more info. + +cheers and have fun, + +holger krekel + +Changes between 1.2.1 and 1.3.0 +================================================== + +- deprecate --report option in favour of a new shorter and easier to + remember -r option: it takes a string argument consisting of any + combination of 'xfsX' characters. They relate to the single chars + you see during the dotted progress printing and will print an extra line + per test at the end of the test run. This extra line indicates the exact + position or test ID that you directly paste to the py.test cmdline in order + to re-run a particular test. + +- allow external plugins to register new hooks via the new + pytest_addhooks(pluginmanager) hook. The new release of + the pytest-xdist plugin for distributed and looponfailing + testing requires this feature. + +- add a new pytest_ignore_collect(path, config) hook to allow projects and + plugins to define exclusion behaviour for their directory structure - + for example you may define in a conftest.py this method:: + + def pytest_ignore_collect(path): + return path.check(link=1) + + to prevent even collection of any tests in symlinked dirs. + +- new pytest_pycollect_makemodule(path, parent) hook for + allowing customization of the Module collection object for a + matching test module. + +- extend and refine xfail mechanism: + ``@py.test.mark.xfail(run=False)`` do not run the decorated test + ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries + specifiying ``--runxfail`` on command line virtually ignores xfail markers + +- expose (previously internal) commonly useful methods: + py.io.get_terminal_with() -> return terminal width + py.io.ansi_print(...) -> print colored/bold text on linux/win32 + py.io.saferepr(obj) -> return limited representation string + +- expose test outcome related exceptions as py.test.skip.Exception, + py.test.raises.Exception etc., useful mostly for plugins + doing special outcome interpretation/tweaking + +- (issue85) fix junitxml plugin to handle tests with non-ascii output + +- fix/refine python3 compatibility (thanks Benjamin Peterson) + +- fixes for making the jython/win32 combination work, note however: + jython2.5.1/win32 does not provide a command line launcher, see + http://bugs.jython.org/issue1491 . See pylib install documentation + for how to work around. + +- fixes for handling of unicode exception values and unprintable objects + +- (issue87) fix unboundlocal error in assertionold code + +- (issue86) improve documentation for looponfailing + +- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method + +- ship distribute_setup.py version 0.6.10 + +- added links to the new capturelog and coverage plugins + + +Changes between 1.2.1 and 1.2.0 +===================================== + +- refined usage and options for "py.cleanup":: + + py.cleanup # remove "*.pyc" and "*$py.class" (jython) files + py.cleanup -e .swp -e .cache # also remove files with these extensions + py.cleanup -s # remove "build" and "dist" directory next to setup.py files + py.cleanup -d # also remove empty directories + py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'" + py.cleanup -n # dry run, only show what would be removed + +- add a new option "py.test --funcargs" which shows available funcargs + and their help strings (docstrings on their respective factory function) + for a given test path + +- display a short and concise traceback if a funcarg lookup fails + +- early-load "conftest.py" files in non-dot first-level sub directories. + allows to conveniently keep and access test-related options in a ``test`` + subdir and still add command line options. + +- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value + +- fix issue78: always call python-level teardown functions even if the + according setup failed. This includes refinements for calling setup_module/class functions + which will now only be called once instead of the previous behaviour where they'd be called + multiple times if they raise an exception (including a Skipped exception). Any exception + will be re-corded and associated with all tests in the according module/class scope. + +- fix issue63: assume <40 columns to be a bogus terminal width, default to 80 + +- fix pdb debugging to be in the correct frame on raises-related errors + +- update apipkg.py to fix an issue where recursive imports might + unnecessarily break importing + +- fix plugin links + +Changes between 1.2 and 1.1.1 +===================================== + +- moved dist/looponfailing from py.test core into a new + separately released pytest-xdist plugin. + +- new junitxml plugin: --junitxml=path will generate a junit style xml file + which is processable e.g. by the Hudson CI system. + +- new option: --genscript=path will generate a standalone py.test script + which will not need any libraries installed. thanks to Ralf Schmitt. + +- new option: --ignore will prevent specified path from collection. + Can be specified multiple times. + +- new option: --confcutdir=dir will make py.test only consider conftest + files that are relative to the specified dir. + +- new funcarg: "pytestconfig" is the pytest config object for access + to command line args and can now be easily used in a test. + +- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to + disambiguate between Python3, python2.X, Jython and PyPy installed versions. + +- new "pytestconfig" funcarg allows access to test config object + +- new "pytest_report_header" hook can return additional lines + to be displayed at the header of a test run. + +- (experimental) allow "py.test path::name1::name2::..." for pointing + to a test within a test collection directly. This might eventually + evolve as a full substitute to "-k" specifications. + +- streamlined plugin loading: order is now as documented in + customize.html: setuptools, ENV, commandline, conftest. + also setuptools entry point names are turned to canonical namees ("pytest_*") + +- automatically skip tests that need 'capfd' but have no os.dup + +- allow pytest_generate_tests to be defined in classes as well + +- deprecate usage of 'disabled' attribute in favour of pytestmark +- deprecate definition of Directory, Module, Class and Function nodes + in conftest.py files. Use pytest collect hooks instead. + +- collection/item node specific runtest/collect hooks are only called exactly + on matching conftest.py files, i.e. ones which are exactly below + the filesystem path of an item + +- change: the first pytest_collect_directory hook to return something + will now prevent further hooks to be called. + +- change: figleaf plugin now requires --figleaf to run. Also + change its long command line options to be a bit shorter (see py.test -h). + +- change: pytest doctest plugin is now enabled by default and has a + new option --doctest-glob to set a pattern for file matches. + +- change: remove internal py._* helper vars, only keep py._pydir + +- robustify capturing to survive if custom pytest_runtest_setup + code failed and prevented the capturing setup code from running. + +- make py.test.* helpers provided by default plugins visible early - + works transparently both for pydoc and for interactive sessions + which will regularly see e.g. py.test.mark and py.test.importorskip. + +- simplify internal plugin manager machinery +- simplify internal collection tree by introducing a RootCollector node + +- fix assert reinterpreation that sees a call containing "keyword=..." + +- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish + hooks on slaves during dist-testing, report module/session teardown + hooks correctly. + +- fix issue65: properly handle dist-testing if no + execnet/py lib installed remotely. + +- skip some install-tests if no execnet is available + +- fix docs, fix internal bin/ script generation + + +Changes between 1.1.1 and 1.1.0 +===================================== + +- introduce automatic plugin registration via 'pytest11' + entrypoints via setuptools' pkg_resources.iter_entry_points + +- fix py.test dist-testing to work with execnet >= 1.0.0b4 + +- re-introduce py.test.cmdline.main() for better backward compatibility + +- svn paths: fix a bug with path.check(versioned=True) for svn paths, + allow '%' in svn paths, make svnwc.update() default to interactive mode + like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction. + +- refine distributed tarball to contain test and no pyc files + +- try harder to have deprecation warnings for py.compat.* accesses + report a correct location + +Changes between 1.1.0 and 1.0.2 +===================================== + +* adjust and improve docs + +* 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 + +Changes between 1.0.1 and 1.0.2 +===================================== + +* fixing packaging issues, triggered by fedora redhat packaging, + also added doc, examples and contrib dirs to the tarball. + +* added a documentation link to the new django plugin. + +Changes between 1.0.0 and 1.0.1 +===================================== + +* added a 'pytest_nose' plugin which handles nose.SkipTest, + nose-style function/method/generator setup/teardown and + tries to report functions correctly. + +* capturing of unicode writes or encoded strings to sys.stdout/err + work better, also terminalwriting was adapted and somewhat + unified between windows and linux. + +* improved documentation layout and content a lot + +* added a "--help-config" option to show conftest.py / ENV-var names for + all longopt cmdline options, and some special conftest.py variables. + renamed 'conf_capture' conftest setting to 'option_capture' accordingly. + +* fix issue #27: better reporting on non-collectable items given on commandline + (e.g. pyc files) + +* fix issue #33: added --version flag (thanks Benjamin Peterson) + +* fix issue #32: adding support for "incomplete" paths to wcpath.status() + +* "Test" prefixed classes are *not* collected by default anymore if they + have an __init__ method + +* monkeypatch setenv() now accepts a "prepend" parameter + +* improved reporting of collection error tracebacks + +* simplified multicall mechanism and plugin architecture, + renamed some internal methods and argnames + +Changes between 1.0.0b9 and 1.0.0 +===================================== + +* more terse reporting try to show filesystem path relatively to current dir +* improve xfail output a bit + +Changes between 1.0.0b8 and 1.0.0b9 +===================================== + +* cleanly handle and report final teardown of test setup + +* fix svn-1.6 compat issue with py.path.svnwc().versioned() + (thanks Wouter Vanden Hove) + +* setup/teardown or collection problems now show as ERRORs + or with big "E"'s in the progress lines. they are reported + and counted separately. + +* dist-testing: properly handle test items that get locally + collected but cannot be collected on the remote side - often + due to platform/dependency reasons + +* simplified py.test.mark API - see keyword plugin documentation + +* integrate better with logging: capturing now by default captures + test functions and their immediate setup/teardown in a single stream + +* capsys and capfd funcargs now have a readouterr() and a close() method + (underlyingly py.io.StdCapture/FD objects are used which grew a + readouterr() method as well to return snapshots of captured out/err) + +* make assert-reinterpretation work better with comparisons not + returning bools (reported with numpy from thanks maciej fijalkowski) + +* reworked per-test output capturing into the pytest_iocapture.py plugin + and thus removed capturing code from config object + +* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr) + + +Changes between 1.0.0b7 and 1.0.0b8 +===================================== + +* pytest_unittest-plugin is now enabled by default + +* introduced pytest_keyboardinterrupt hook and + refined pytest_sessionfinish hooked, added tests. + +* workaround a buggy logging module interaction ("closing already closed + files"). Thanks to Sridhar Ratnakumar for triggering. + +* if plugins use "py.test.importorskip" for importing + a dependency only a warning will be issued instead + of exiting the testing process. + +* many improvements to docs: + - refined funcargs doc , use the term "factory" instead of "provider" + - added a new talk/tutorial doc page + - better download page + - better plugin docstrings + - added new plugins page and automatic doc generation script + +* fixed teardown problem related to partially failing funcarg setups + (thanks MrTopf for reporting), "pytest_runtest_teardown" is now + always invoked even if the "pytest_runtest_setup" failed. + +* tweaked doctest output for docstrings in py modules, + thanks Radomir. + +Changes between 1.0.0b3 and 1.0.0b7 +============================================= + +* renamed py.test.xfail back to py.test.mark.xfail to avoid + two ways to decorate for xfail + +* re-added py.test.mark decorator for setting keywords on functions + (it was actually documented so removing it was not nice) + +* remove scope-argument from request.addfinalizer() because + request.cached_setup has the scope arg. TOOWTDI. + +* perform setup finalization before reporting failures + +* apply modified patches from Andreas Kloeckner to allow + test functions to have no func_code (#22) and to make + "-k" and function keywords work (#20) + +* apply patch from Daniel Peolzleithner (issue #23) + +* resolve issue #18, multiprocessing.Manager() and + redirection clash + +* make __name__ == "__channelexec__" for remote_exec code + +Changes between 1.0.0b1 and 1.0.0b3 +============================================= + +* plugin classes are removed: one now defines + hooks directly in conftest.py or global pytest_*.py + files. + +* added new pytest_namespace(config) hook that allows + to inject helpers directly to the py.test.* namespace. + +* documented and refined many hooks + +* added new style of generative tests via + pytest_generate_tests hook that integrates + well with function arguments. + + +Changes between 0.9.2 and 1.0.0b1 +============================================= + +* introduced new "funcarg" setup method, + see doc/test/funcarg.txt + +* introduced plugin architecuture and many + new py.test plugins, see + doc/test/plugins.txt + +* teardown_method is now guaranteed to get + called after a test method has run. + +* new method: py.test.importorskip(mod,minversion) + will either import or call py.test.skip() + +* completely revised internal py.test architecture + +* new py.process.ForkedFunc object allowing to + fork execution of a function to a sub process + and getting a result back. + +XXX lots of things missing here XXX + +Changes between 0.9.1 and 0.9.2 +=============================== + +* refined installation and metadata, created new setup.py, + now based on setuptools/ez_setup (thanks to Ralf Schmitt + for his support). + +* improved the way of making py.* scripts available in + windows environments, they are now added to the + Scripts directory as ".cmd" files. + +* py.path.svnwc.status() now is more complete and + uses xml output from the 'svn' command if available + (Guido Wesdorp) + +* fix for py.path.svn* to work with svn 1.5 + (Chris Lamb) + +* fix path.relto(otherpath) method on windows to + use normcase for checking if a path is relative. + +* py.test's traceback is better parseable from editors + (follows the filenames:LINENO: MSG convention) + (thanks to Osmo Salomaa) + +* fix to javascript-generation, "py.test --runbrowser" + should work more reliably now + +* removed previously accidentally added + py.test.broken and py.test.notimplemented helpers. + +* there now is a py.__version__ attribute + +Changes between 0.9.0 and 0.9.1 +=============================== + +This is a fairly complete list of changes between 0.9 and 0.9.1, which can +serve as a reference for developers. + +* allowing + signs in py.path.svn urls [39106] +* fixed support for Failed exceptions without excinfo in py.test [39340] +* added support for killing processes for Windows (as well as platforms that + support os.kill) in py.misc.killproc [39655] +* added setup/teardown for generative tests to py.test [40702] +* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739] +* fixed problem with calling .remove() on wcpaths of non-versioned files in + py.path [44248] +* fixed some import and inheritance issues in py.test [41480, 44648, 44655] +* fail to run greenlet tests when pypy is available, but without stackless + [45294] +* small fixes in rsession tests [45295] +* fixed issue with 2.5 type representations in py.test [45483, 45484] +* made that internal reporting issues displaying is done atomically in py.test + [45518] +* made that non-existing files are igored by the py.lookup script [45519] +* improved exception name creation in py.test [45535] +* made that less threads are used in execnet [merge in 45539] +* removed lock required for atomical reporting issue displaying in py.test + [45545] +* removed globals from execnet [45541, 45547] +* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit + get called in 2.5 (py.execnet) [45548] +* fixed bug in joining threads in py.execnet's servemain [45549] +* refactored py.test.rsession tests to not rely on exact output format anymore + [45646] +* using repr() on test outcome [45647] +* added 'Reason' classes for py.test.skip() [45648, 45649] +* killed some unnecessary sanity check in py.test.collect [45655] +* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only + usable by Administrators [45901] +* added support for locking and non-recursive commits to py.path.svnwc [45994] +* locking files in py.execnet to prevent CPython from segfaulting [46010] +* added export() method to py.path.svnurl +* fixed -d -x in py.test [47277] +* fixed argument concatenation problem in py.path.svnwc [49423] +* restore py.test behaviour that it exits with code 1 when there are failures + [49974] +* don't fail on html files that don't have an accompanying .txt file [50606] +* fixed 'utestconvert.py < input' [50645] +* small fix for code indentation in py.code.source [50755] +* fix _docgen.py documentation building [51285] +* improved checks for source representation of code blocks in py.test [51292] +* added support for passing authentication to py.path.svn* objects [52000, + 52001] +* removed sorted() call for py.apigen tests in favour of [].sort() to support + Python 2.3 [52481] --- a/doc/faq.txt +++ b/doc/faq.txt @@ -10,15 +10,17 @@ Frequently Asked Questions On naming, nosetests, licensing and magic =========================================== -Why the ``py`` naming? what is it? ------------------------------------- +Why the ``py`` naming? Why not ``pytest``? +---------------------------------------------------- -Because the name was available and there was the -idea to have the package evolve into a "standard" library -kind of thing that works cross-python versions and is -not tied to a particular CPython revision or its release -cycle. Clearly, this was ambitious and the naming -has maybe haunted the project rather than helping it. +This mostly has historic reasons - the aim is +to get away from the somewhat questionable 'py' name +at some point. These days (2010) the 'py' library +almost completely comprises APIs that are used +by the ``py.test`` tool. There also are some +other uses, e.g. of the ``py.path.local()`` and +other path implementations. So it requires some +work to factor them out and do the shift. Why the ``py.test`` naming? ------------------------------------ @@ -115,7 +117,7 @@ argument usage and creation. Can I yield multiple values from a factory function? ----------------------------------------------------- -There are two reasons why yielding from a factory function +There are two conceptual reasons why yielding from a factory function is not possible: * Calling factories for obtaining test function arguments From commits-noreply at bitbucket.org Wed May 5 20:18:20 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 18:18:20 +0000 (UTC) Subject: [py-svn] pytest-xdist commit abcf0e02e0f9: require execnet-1.0.6 - it's release already Message-ID: <20100505181820.3FE967EEE7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1273083570 -7200 # Node ID abcf0e02e0f914ce0dc8e600667dcde83f8f2bb2 # Parent a72c7db7b01403c5dff73284577764b19f0c6476 require execnet-1.0.6 - it's release already --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup( packages = ['xdist'], entry_points = {'pytest11': ['xdist = xdist.plugin'],}, zip_safe=False, - install_requires = ['execnet>=1.0.5', 'py>=1.3.0'], + install_requires = ['execnet>=1.0.6', 'py>=1.3.0'], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', From commits-noreply at bitbucket.org Wed May 5 20:23:24 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 18:23:24 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 56d8e5280be2: modify CHANGELOG Message-ID: <20100505182324.7552C7EF04@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1273083874 -7200 # Node ID 56d8e5280be224a0ad3220a9deed55334710bd23 # Parent abcf0e02e0f914ce0dc8e600667dcde83f8f2bb2 modify CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,14 @@ 1.2 ------------------------- -- fix issue79: sessionfinish/teardown hooks more systematically +- fix issue79: sessionfinish/teardown hooks are now called systematically on the slave side - introduce a new data input/output mechanism to allow the master side to send and receive data from a slave. - fix race condition in underlying pickling/unpickling handling -- use and require new register hooks facility of py.test>=1.2.2 +- use and require new register hooks facility of py.test>=1.3.0 +- require improved execnet>=1.0.6 because of various race conditions + that can arise in xdist testing modes. - fix some python3 related pickling related race conditions - fix PyPI description From commits-noreply at bitbucket.org Wed May 5 21:02:40 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 19:02:40 +0000 (UTC) Subject: [py-svn] py-trunk commit 92e1d860180e: updating docs Message-ID: <20100505190240.342367EF04@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273086114 -7200 # Node ID 92e1d860180ef10af80472cb9c46fb32a58dde11 # Parent 0a2e11de6290e13ec6a4188634f3d1c2711ac281 updating docs --- a/doc/install.txt +++ b/doc/install.txt @@ -4,7 +4,7 @@ py.test/pylib installation info in a nutshell =================================================== -**Pythons**: 2.4, 2.5, 2.6, 3.0, 3.1, Jython-2.5.1, PyPy-1.1 +**Pythons**: 2.4, 2.5, 2.6, 3.0, 3.1, 3.2, Jython-2.5.1, PyPy-1.2 **Operating systems**: Linux, Windows, OSX, Unix --- a/doc/test/plugin/skipping.txt +++ b/doc/test/plugin/skipping.txt @@ -15,9 +15,8 @@ The need for skipping a test is usually If a test fails under all conditions then it's probably better to mark your test as 'xfail'. -By passing ``--report=xfailed,skipped`` to the terminal reporter -you will see summary information on skips and xfail-run tests -at the end of a test run. +By passing ``-rxs`` to the terminal reporter you will see extra +summary information on skips and xfail-run tests at the end of a test run. .. _skipif: @@ -88,10 +87,17 @@ Same as with skipif_ you can also select depending on platform:: @py.test.mark.xfail("sys.version_info >= (3,0)") - def test_function(): ... +To not run a test and still regard it as "xfailed":: + + @py.test.mark.xfail(..., run=False) + +To specify an explicit reason to be shown with xfailure detail:: + + @py.test.mark.xfail(..., reason="my reason") + skipping on a missing import dependency -------------------------------------------------- @@ -120,6 +126,13 @@ within test or setup code. Example:: if not valid_config(): py.test.skip("unsuppored configuration") +command line options +-------------------- + + +``--runxfail`` + run tests even if they are marked xfail + Start improving this plugin in 30 seconds ========================================= --- a/doc/test/plugin/terminal.txt +++ b/doc/test/plugin/terminal.txt @@ -14,10 +14,12 @@ command line options ``-v, --verbose`` increase verbosity. +``-r chars`` + show extra test summary info as specified by chars (f)ailed, (s)skipped, (x)failed, (X)passed. ``-l, --showlocals`` show locals in tracebacks (disabled by default). ``--report=opts`` - show more info, valid: skipped,xfailed + (deprecated, use -r) ``--tb=style`` traceback print mode (long/short/line/no). ``--fulltrace`` --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -1,46 +1,46 @@ .. _`helpconfig`: helpconfig.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_recwarn.py .. _`unittest`: unittest.html -.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_monkeypatch.py -.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_genscript.py +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_monkeypatch.py +.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_genscript.py .. _`pastebin`: pastebin.html .. _`skipping`: skipping.html .. _`genscript`: genscript.html .. _`plugins`: index.html .. _`mark`: mark.html .. _`tmpdir`: tmpdir.html -.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_doctest.py +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_doctest.py .. _`capture`: capture.html -.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_nose.py -.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_restdoc.py +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_restdoc.py .. _`restdoc`: restdoc.html .. _`xdist`: xdist.html -.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_pastebin.py -.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_tmpdir.py +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_pastebin.py +.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_tmpdir.py .. _`terminal`: terminal.html -.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_hooklog.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_hooklog.py .. _`capturelog`: capturelog.html .. _`junitxml`: junitxml.html -.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_skipping.py +.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_skipping.py .. _`checkout the py.test development version`: ../../install.html#checkout -.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_helpconfig.py +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html .. _`doctest`: doctest.html -.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_mark.py +.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_mark.py .. _`get in contact`: ../../contact.html -.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_capture.py +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_capture.py .. _`figleaf`: figleaf.html .. _`customize`: ../customize.html .. _`hooklog`: hooklog.html -.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_terminal.py +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_terminal.py .. _`recwarn`: recwarn.html -.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_pdb.py +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_pdb.py .. _`monkeypatch`: monkeypatch.html .. _`coverage`: coverage.html .. _`resultlog`: resultlog.html -.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_junitxml.py +.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_junitxml.py .. _`django`: django.html -.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_unittest.py +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_unittest.py .. _`nose`: nose.html -.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_resultlog.py +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_resultlog.py .. _`pdb`: pdb.html --- a/doc/test/plugin/hookspec.txt +++ b/doc/test/plugin/hookspec.txt @@ -12,11 +12,14 @@ hook specification sourcecode # Command line and configuration # ------------------------------------------------------------------------- + def pytest_namespace(): + "return dict of name->object which will get stored at py.test. namespace" + def pytest_addoption(parser): - """ called before commandline parsing. """ + "add optparse-style options via parser.addoption." - def pytest_namespace(): - """ return dict of name->object which will get stored at py.test. namespace""" + def pytest_addhooks(pluginmanager): + "add hooks via pluginmanager.registerhooks(module)" def pytest_configure(config): """ called after command line options have been parsed. @@ -30,6 +33,13 @@ hook specification sourcecode # collection hooks # ------------------------------------------------------------------------- + def pytest_ignore_collect(path, config): + """ return true value to prevent considering this path for collection. + This hook is consulted for all files and directories prior to considering + collection hooks. + """ + pytest_ignore_collect.firstresult = True + def pytest_collect_directory(path, parent): """ return Collection node or None for the given path. """ pytest_collect_directory.firstresult = True @@ -58,6 +68,14 @@ hook specification sourcecode # Python test function related hooks # ------------------------------------------------------------------------- + def pytest_pycollect_makemodule(path, parent): + """ return a Module collector or None for the given path. + This hook will be called for each matching test module path. + The pytest_collect_file hook needs to be used if you want to + create test modules for files that do not match as a test module. + """ + pytest_pycollect_makemodule.firstresult = True + def pytest_pycollect_makeitem(collector, name, obj): """ return custom item/collector for a python object in a module, or None. """ pytest_pycollect_makeitem.firstresult = True @@ -139,31 +157,6 @@ hook specification sourcecode """ return processed content for a given doctest""" pytest_doctest_prepare_content.firstresult = True - # ------------------------------------------------------------------------- - # distributed testing - # ------------------------------------------------------------------------- - - def pytest_gwmanage_newgateway(gateway, platinfo): - """ called on new raw gateway creation. """ - - def pytest_gwmanage_rsyncstart(source, gateways): - """ called before rsyncing a directory to remote gateways takes place. """ - - def pytest_gwmanage_rsyncfinish(source, gateways): - """ called after rsyncing a directory to remote gateways takes place. """ - - def pytest_testnodeready(node): - """ Test Node is ready to operate. """ - - def pytest_testnodedown(node, error): - """ Test Node is down. """ - - def pytest_rescheduleitems(items): - """ reschedule Items from a node that went down. """ - - def pytest_looponfailinfo(failreports, rootdirs): - """ info for repeating failing tests. """ - # ------------------------------------------------------------------------- # error handling and internal debugging hooks @@ -184,4 +177,34 @@ hook specification sourcecode def pytest_trace(category, msg): """ called for debug info. """ +hook specification sourcecode +============================= + +.. sourcecode:: python + + + def pytest_gwmanage_newgateway(gateway, platinfo): + """ called on new raw gateway creation. """ + + def pytest_gwmanage_rsyncstart(source, gateways): + """ called before rsyncing a directory to remote gateways takes place. """ + + def pytest_gwmanage_rsyncfinish(source, gateways): + """ called after rsyncing a directory to remote gateways takes place. """ + + def pytest_configure_node(node): + """ configure node information before it gets instantiated. """ + + def pytest_testnodeready(node): + """ Test Node is ready to operate. """ + + def pytest_testnodedown(node, error): + """ Test Node is down. """ + + def pytest_rescheduleitems(items): + """ reschedule Items from a node that went down. """ + + def pytest_looponfailinfo(failreports, rootdirs): + """ info for repeating failing tests. """ + .. include:: links.txt --- a/doc/test/plugin/xdist.txt +++ b/doc/test/plugin/xdist.txt @@ -28,18 +28,6 @@ You may specify different Python version .. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist -Install / Uninstall ---------------------- - -To install the xdist plugin simply type:: - - easy_install pytest-xdist # or - pip install pytest-xdist - -and to uninstall:: - - pip uninstall pytest-xdist - Usage examples --------------------- --- a/bin-for-dist/makepluginlist.py +++ b/bin-for-dist/makepluginlist.py @@ -151,10 +151,10 @@ class PluginOverview(RestWriter): class HookSpec(RestWriter): def makerest(self, config): - module = config.pluginmanager.hook._hookspecs - source = py.code.Source(module) - self.h1("hook specification sourcecode") - self.sourcecode(source) + for module in config.pluginmanager.hook._hookspecs: + source = py.code.Source(module) + self.h1("hook specification sourcecode") + self.sourcecode(source) class PluginDoc(RestWriter): def makerest(self, config, name): From commits-noreply at bitbucket.org Wed May 5 21:04:19 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 19:04:19 +0000 (UTC) Subject: [py-svn] py-trunk commit eafd3c256e87: fix interpreter compat Message-ID: <20100505190419.62D297EF04@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273086223 -7200 # Node ID eafd3c256e8732dfb0a4d49d051b5b4339858926 # Parent 92e1d860180ef10af80472cb9c46fb32a58dde11 fix interpreter compat --- a/doc/install.txt +++ b/doc/install.txt @@ -4,7 +4,7 @@ py.test/pylib installation info in a nutshell =================================================== -**Pythons**: 2.4, 2.5, 2.6, 3.0, 3.1, 3.2, Jython-2.5.1, PyPy-1.2 +**Pythons**: 2.4, 2.5, 2.6, 2.7, 3.0, 3.1.x, Jython-2.5.1, PyPy-1.2 **Operating systems**: Linux, Windows, OSX, Unix From commits-noreply at bitbucket.org Wed May 5 22:05:44 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 20:05:44 +0000 (UTC) Subject: [py-svn] py-trunk commit c0e89efb6930: Added tag 1.3.0 for changeset eafd3c256e87 Message-ID: <20100505200544.18B557EEE5@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273088124 -7200 # Node ID c0e89efb6930d99a62cb284bd3b15f4f477ec607 # Parent eafd3c256e8732dfb0a4d49d051b5b4339858926 Added tag 1.3.0 for changeset eafd3c256e87 --- a/.hgtags +++ b/.hgtags @@ -22,3 +22,4 @@ 60c44bdbf093285dc69d5462d4dbb4acad325ca6 319187fcda66714c5eb1353492babeec3d3c826f 1.1.1 4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0 c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1 +eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0 From commits-noreply at bitbucket.org Wed May 5 22:05:46 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 20:05:46 +0000 (UTC) Subject: [py-svn] py-trunk commit 91f6fe660c83: fix some ReST issues Message-ID: <20100505200546.264667EF11@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273089893 -7200 # Node ID 91f6fe660c833f803c239c35ccfcd7c63963b19a # Parent 772043dcbeac4f63d512460395852c02a0919866 fix some ReST issues --- a/doc/announce/release-1.3.0.txt +++ b/doc/announce/release-1.3.0.txt @@ -49,10 +49,13 @@ Changes between 1.2.1 and 1.3.0 allowing customization of the Module collection object for a matching test module. -- extend and refine xfail mechanism: - ``@py.test.mark.xfail(run=False)`` do not run the decorated test - ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries - specifiying ``--runxfail`` on command line virtually ignores xfail markers +- extend and refine xfail mechanism:: + + @py.test.mark.xfail(run=False) do not run the decorated test + @py.test.mark.xfail(reason="...") prints the reason string in xfail summaries + + specifiying ``--runxfail`` on command line ignores xfail markers to show + you the underlying traceback. - expose (previously internal) commonly useful methods: py.io.get_terminal_with() -> return terminal width From commits-noreply at bitbucket.org Wed May 5 22:05:46 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 5 May 2010 20:05:46 +0000 (UTC) Subject: [py-svn] py-trunk commit 772043dcbeac: fix typo Message-ID: <20100505200546.138007EF04@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273089216 -7200 # Node ID 772043dcbeac4f63d512460395852c02a0919866 # Parent c0e89efb6930d99a62cb284bd3b15f4f477ec607 fix typo --- a/doc/announce/release-1.3.0.txt +++ b/doc/announce/release-1.3.0.txt @@ -1,7 +1,7 @@ py.test/pylib 1.3.0: new options, per-plugin hooks, fixes ... =========================================================================== -The 1.3.0 release introces new options, bug fixes and improved compatibility +The 1.3.0 release introduces new options, bug fixes and improved compatibility with Python3 and Jython-2.5.1 on Windows. If you already use py-1.2 chances are you can use py-1.3.0. See the below CHANGELOG for more details and http://pylib.org/install.html for installation instructions. From commits-noreply at bitbucket.org Sun May 9 12:49:52 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 9 May 2010 10:49:52 +0000 (UTC) Subject: [py-svn] py-trunk commit df247537c752: fix init-check to work also on pypy-c (armin around) Message-ID: <20100509104952.903AA7EF0A@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273400824 -7200 # Node ID df247537c752e41ec5de0a2af496da0c25dd2cfb # Parent 91f6fe660c833f803c239c35ccfcd7c63963b19a fix init-check to work also on pypy-c (armin around) --- a/py/_test/pycollect.py +++ b/py/_test/pycollect.py @@ -393,5 +393,5 @@ class Function(FunctionMixin, py.test.co def hasinit(obj): init = getattr(obj, '__init__', None) if init: - if not isinstance(init, type(object.__init__)): + if init != object.__init__: return True From commits-noreply at bitbucket.org Tue May 11 19:55:34 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 11 May 2010 17:55:34 +0000 (UTC) Subject: [py-svn] py-trunk commit b6b06354b555: avoid helper functions showing up in py.test tracebacks Message-ID: <20100511175534.EB6D27EE79@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273592636 -7200 # Node ID b6b06354b555e6e0194220d3fa9559859c67c5f5 # Parent df247537c752e41ec5de0a2af496da0c25dd2cfb avoid helper functions showing up in py.test tracebacks --- a/py/_builtin.py +++ b/py/_builtin.py @@ -175,6 +175,7 @@ else: def exec_(obj, globals=None, locals=None): """ minimal backport of py3k exec statement. """ + __tracebackhide__ = True if globals is None: frame = sys._getframe(1) globals = frame.f_globals @@ -187,14 +188,17 @@ else: if sys.version_info >= (3,0): exec (""" def _reraise(cls, val, tb): + __tracebackhide__ = True assert hasattr(val, '__traceback__') raise val """) else: exec (""" def _reraise(cls, val, tb): + __tracebackhide__ = True raise cls, val, tb def exec2(obj, globals, locals): + __tracebackhide__ = True exec obj in globals, locals """) --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +Changes between 1.3.0 and 1.3.1 +================================================== + +- improve tracebacks showing: + - raises shows shorter more relevant tracebacks + Changes between 1.2.1 and 1.3.0 ================================================== From commits-noreply at bitbucket.org Tue May 11 19:55:37 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 11 May 2010 17:55:37 +0000 (UTC) Subject: [py-svn] py-trunk commit 9992aac91625: allow to run py.test.cmdline.main() multiple times. Message-ID: <20100511175537.57BD57EEED@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273600582 -7200 # Node ID 9992aac916258d2450a62315e081e5e47b54f23d # Parent b6b06354b555e6e0194220d3fa9559859c67c5f5 allow to run py.test.cmdline.main() multiple times. --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -89,3 +89,19 @@ class TestGeneralUsage: assert result.ret == 0 s = result.stdout.str() assert 'MarkGenerator' in s + + def test_double_pytestcmdline(self, testdir): + p = testdir.makepyfile(run=""" + import py + py.test.cmdline.main() + py.test.cmdline.main() + """) + testdir.makepyfile(""" + def test_hello(): + pass + """) + result = testdir.runpython(p) + result.stdout.fnmatch_lines([ + "*1 passed*", + "*1 passed*", + ]) --- a/py/__init__.py +++ b/py/__init__.py @@ -25,7 +25,6 @@ py.apipkg.initpkg(__name__, dict( 'pytest': '._cmdline.pytest:main', 'pylookup': '._cmdline.pylookup:main', 'pycountloc': '._cmdline.pycountlog:main', - 'pytest': '._test.cmdline:main', 'pylookup': '._cmdline.pylookup:main', 'pycountloc': '._cmdline.pycountloc:main', 'pycleanup': '._cmdline.pycleanup:main', --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,14 @@ Changes between 1.3.0 and 1.3.1 ================================================== -- improve tracebacks showing: +- make py.test.cmdline.main() return the exitstatus + instead of raising (which is still done by py.cmdline.pytest()) + and make it so that py.test.cmdline.main() can be called + multiple times, at least as far as py.test's internal + state is concerned - previously it would raise an exception + on the second time. + +- improve tracebacks presentation: - raises shows shorter more relevant tracebacks Changes between 1.2.1 and 1.3.0 --- a/py/_test/cmdline.py +++ b/py/_test/cmdline.py @@ -16,8 +16,9 @@ def main(args=None): colitems = config.getinitialnodes() exitstatus = session.main(colitems) config.pluginmanager.do_unconfigure(config) - raise SystemExit(exitstatus) except config.Error: e = sys.exc_info()[1] sys.stderr.write("ERROR: %s\n" %(e.args[0],)) - raise SystemExit(3) + exitstatus = 3 + py.test.config = py.test.config.__class__() + return exitstatus --- a/contrib/runtesthelper.py +++ b/contrib/runtesthelper.py @@ -1,7 +1,10 @@ """ this little helper allows to run tests multiple times in the same process. useful for running tests from -a console. +a console. + +NOTE: since 1.3.1 you can just call py.test.cmdline.main() +multiple times - no special logic needed. """ import py, sys --- a/py/_cmdline/pytest.py +++ b/py/_cmdline/pytest.py @@ -1,5 +1,5 @@ #!/usr/bin/env python import py -def main(args): - py.test.cmdline.main(args) +def main(args=None): + raise SystemExit(py.test.cmdline.main(args)) From commits-noreply at bitbucket.org Tue May 11 22:55:24 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 11 May 2010 20:55:24 +0000 (UTC) Subject: [py-svn] py-trunk commit 60aa42a32814: remove code.new() function and store lines directly into linecache.cache instead. Message-ID: <20100511205524.37EA37EEFC@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273611244 -7200 # Node ID 60aa42a328142d6c80df87d29377833daf3b6bea # Parent 9992aac916258d2450a62315e081e5e47b54f23d remove code.new() function and store lines directly into linecache.cache instead. This avoids the need for custom code objects, improving compatibility for jython and pypy-c. --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,11 @@ Changes between 1.3.0 and 1.3.1 - improve tracebacks presentation: - raises shows shorter more relevant tracebacks +- improve support for raises and other dynamically compiled code by + manipulating python's linecache.cache instead of the previous + rather hacky way of creating custom code objects. This makes + it seemlessly work on Jython and PyPy at least. + Changes between 1.2.1 and 1.3.0 ================================================== --- a/py/_code/code.py +++ b/py/_code/code.py @@ -23,64 +23,14 @@ class Code(object): def __ne__(self, other): return not self == other - def new(self, rec=False, **kwargs): - """ return new code object with modified attributes. - if rec-cursive is true then dive into code - objects contained in co_consts. - """ - if sys.platform.startswith("java"): - # XXX jython does not support the below co_filename hack - return self.raw - names = [x for x in dir(self.raw) if x[:3] == 'co_'] - for name in kwargs: - if name not in names: - raise TypeError("unknown code attribute: %r" %(name, )) - if rec and hasattr(self.raw, 'co_consts'): # jython - newconstlist = [] - co = self.raw - cotype = type(co) - for c in co.co_consts: - if isinstance(c, cotype): - c = self.__class__(c).new(rec=True, **kwargs) - newconstlist.append(c) - return self.new(rec=False, co_consts=tuple(newconstlist), **kwargs) - for name in names: - if name not in kwargs: - kwargs[name] = getattr(self.raw, name) - arglist = [ - kwargs['co_argcount'], - kwargs['co_nlocals'], - kwargs.get('co_stacksize', 0), # jython - kwargs.get('co_flags', 0), # jython - kwargs.get('co_code', ''), # jython - kwargs.get('co_consts', ()), # jython - kwargs.get('co_names', []), # - kwargs['co_varnames'], - kwargs['co_filename'], - kwargs['co_name'], - kwargs['co_firstlineno'], - kwargs.get('co_lnotab', ''), #jython - kwargs.get('co_freevars', None), #jython - kwargs.get('co_cellvars', None), # jython - ] - if sys.version_info >= (3,0): - arglist.insert(1, kwargs['co_kwonlyargcount']) - return self.raw.__class__(*arglist) - else: - return py.std.new.code(*arglist) - def path(self): """ return a path object pointing to source code""" - fn = self.raw.co_filename - try: - return fn.__path__ - except AttributeError: - p = py.path.local(self.raw.co_filename) - if not p.check(): - # XXX maybe try harder like the weird logic - # in the standard lib [linecache.updatecache] does? - p = self.raw.co_filename - return p + p = py.path.local(self.raw.co_filename) + if not p.check(): + # XXX maybe try harder like the weird logic + # in the standard lib [linecache.updatecache] does? + p = self.raw.co_filename + return p path = property(path, None, None, "path of this code object") --- a/py/_builtin.py +++ b/py/_builtin.py @@ -151,7 +151,10 @@ else: return getattr(function, "__dict__", None) def _getcode(function): - return getattr(function, "func_code", None) + try: + return getattr(function, "__code__") + except AttributeError: + return getattr(function, "func_code", None) def print_(*args, **kwargs): """ minimal backport of py3k print statement. """ --- a/py/_code/source.py +++ b/py/_code/source.py @@ -212,10 +212,10 @@ class Source(object): else: if flag & _AST_FLAG: return co - co_filename = MyStr(filename) - co_filename.__source__ = self - return py.code.Code(co).new(rec=1, co_filename=co_filename) - #return newcode_withfilename(co, co_filename) + from types import ModuleType + lines = [(x + "\n") for x in self.lines] + py.std.linecache.cache[filename] = (1, None, lines, filename) + return co # # public API shortcut functions @@ -224,11 +224,9 @@ class Source(object): def compile_(source, filename=None, mode='exec', flags= generators.compiler_flag, dont_inherit=0): """ compile the given source to a raw code object, - which points back to the source code through - "co_filename.__source__". All code objects - contained in the code object will recursively - also have this special subclass-of-string - filename. + and maintain an internal cache which allows later + retrieval of the source code for the code object + and any recursively created code objects. """ if _ast is not None and isinstance(source, _ast.AST): # XXX should Source support having AST? @@ -262,44 +260,26 @@ def getfslineno(obj): # # helper functions # -class MyStr(str): - """ custom string which allows to add attributes. """ def findsource(obj): - obj = py.code.getrawcode(obj) try: - fullsource = obj.co_filename.__source__ - except AttributeError: - try: - sourcelines, lineno = py.std.inspect.findsource(obj) - except (KeyboardInterrupt, SystemExit): - raise - except: - return None, None - source = Source() - source.lines = [line.rstrip() for line in sourcelines] - return source, lineno - else: - lineno = obj.co_firstlineno - 1 - return fullsource, lineno - + sourcelines, lineno = py.std.inspect.findsource(obj) + except (KeyboardInterrupt, SystemExit): + raise + except: + return None, None + source = Source() + source.lines = [line.rstrip() for line in sourcelines] + return source, lineno def getsource(obj, **kwargs): obj = py.code.getrawcode(obj) try: - fullsource = obj.co_filename.__source__ - except AttributeError: - try: - strsrc = inspect.getsource(obj) - except IndentationError: - strsrc = "\"Buggy python version consider upgrading, cannot get source\"" - assert isinstance(strsrc, str) - return Source(strsrc, **kwargs) - else: - lineno = obj.co_firstlineno - 1 - end = fullsource.getblockend(lineno) - return Source(fullsource[lineno:end+1], deident=True) - + strsrc = inspect.getsource(obj) + except IndentationError: + strsrc = "\"Buggy python version consider upgrading, cannot get source\"" + assert isinstance(strsrc, str) + return Source(strsrc, **kwargs) def deindent(lines, offset=None): if offset is None: --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -80,11 +80,10 @@ def test_source_strip_multiline(): source2 = source.strip() assert source2.lines == [" hello"] - at failsonjython def test_syntaxerror_rerepresentation(): - ex = py.test.raises(SyntaxError, py.code.compile, 'x x') + ex = py.test.raises(SyntaxError, py.code.compile, 'xyz xyz') assert ex.value.lineno == 1 - assert ex.value.offset == 3 + assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython? assert ex.value.text.strip(), 'x x' def test_isparseable(): @@ -132,7 +131,6 @@ class TestSourceParsingAndCompiling: exec (co, d) assert d['x'] == 3 - @failsonjython def test_compile_and_getsource_simple(self): co = py.code.compile("x=3") exec (co) @@ -203,7 +201,6 @@ class TestSourceParsingAndCompiling: assert isinstance(mod, ast.Module) compile(mod, "", "exec") - @failsonjython def test_compile_and_getsource(self): co = self.source.compile() py.builtin.exec_(co, globals()) @@ -260,7 +257,6 @@ def test_getstartingblock_multiline(): l = [i for i in x.source.lines if i.strip()] assert len(l) == 4 - at failsonjython def test_getline_finally(): def c(): pass excinfo = py.test.raises(TypeError, """ @@ -274,7 +270,6 @@ def test_getline_finally(): source = excinfo.traceback[-1].statement assert str(source).strip() == 'c(1)' - at failsonjython def test_getfuncsource_dynamic(): source = """ def f(): @@ -341,7 +336,6 @@ def test_getsource_fallback(): src = getsource(x) assert src == expected - at failsonjython def test_idem_compile_and_getsource(): from py._code.source import getsource expected = "def x(): pass" @@ -355,8 +349,7 @@ def test_findsource_fallback(): assert 'test_findsource_simple' in str(src) assert src[lineno] == ' def x():' - at failsonjython -def test_findsource___source__(): +def test_findsource(): from py._code.source import findsource co = py.code.compile("""if 1: def x(): @@ -373,7 +366,6 @@ def test_findsource___source__(): assert src[lineno] == " def x():" - at failsonjython def test_getfslineno(): from py.code import getfslineno --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,87 +1,12 @@ -from __future__ import generators import py import sys -failsonjython = py.test.mark.xfail("sys.platform.startswith('java')") - -def test_newcode(): - source = "i = 3" - co = compile(source, '', 'exec') - code = py.code.Code(co) - newco = code.new() - assert co == newco - def test_ne(): code1 = py.code.Code(compile('foo = "bar"', '', 'exec')) assert code1 == code1 code2 = py.code.Code(compile('foo = "baz"', '', 'exec')) assert code2 != code1 - at failsonjython -def test_newcode_unknown_args(): - code = py.code.Code(compile("", '', 'exec')) - py.test.raises(TypeError, 'code.new(filename="hello")') - - at failsonjython -def test_newcode_withfilename(): - source = py.code.Source(""" - def f(): - def g(): - pass - """) - co = compile(str(source)+'\n', 'nada', 'exec') - obj = 'hello' - newco = py.code.Code(co).new(rec=True, co_filename=obj) - def walkcode(co): - for x in co.co_consts: - if isinstance(x, type(co)): - for y in walkcode(x): - yield y - yield co - - names = [] - for code in walkcode(newco): - assert newco.co_filename == obj - assert newco.co_filename is obj - names.append(code.co_name) - assert 'f' in names - assert 'g' in names - - at failsonjython -def test_newcode_with_filename(): - source = "i = 3" - co = compile(source, '', 'exec') - code = py.code.Code(co) - class MyStr(str): - pass - filename = MyStr("hello") - filename.__source__ = py.code.Source(source) - newco = code.new(rec=True, co_filename=filename) - assert newco.co_filename.__source__ == filename.__source__ - s = py.code.Source(newco) - assert str(s) == source - - - at failsonjython -def test_new_code_object_carries_filename_through(): - class mystr(str): - pass - filename = mystr("dummy") - co = compile("hello\n", filename, 'exec') - assert not isinstance(co.co_filename, mystr) - args = [ - co.co_argcount, co.co_nlocals, co.co_stacksize, - co.co_flags, co.co_code, co.co_consts, - co.co_names, co.co_varnames, - filename, - co.co_name, co.co_firstlineno, co.co_lnotab, - co.co_freevars, co.co_cellvars - ] - if sys.version_info > (3,0): - args.insert(1, co.co_kwonlyargcount) - c2 = py.std.types.CodeType(*args) - assert c2.co_filename is filename - def test_code_gives_back_name_for_not_existing_file(): name = 'abc-123' co_code = compile("pass\n", name, 'exec') --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -316,7 +316,11 @@ def test_runtest_in_module_ordering(test class TestRaises: def test_raises(self): - py.test.raises(ValueError, "int('qwe')") + source = "int('qwe')" + excinfo = py.test.raises(ValueError, source) + code = excinfo.traceback[-1].frame.code + s = str(code.fullsource) + assert s == source def test_raises_exec(self): py.test.raises(ValueError, "a,x = []") --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -293,7 +293,6 @@ class TestFormattedExcinfo: assert lines[0] == "| def f(x):" assert lines[1] == " pass" - @failsonjython def test_repr_source_excinfo(self): """ check if indentation is right """ pr = FormattedExcinfo() From commits-noreply at bitbucket.org Wed May 12 10:28:37 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 08:28:37 +0000 (UTC) Subject: [py-svn] py-trunk commit bf8e2f220d71: bump version to 1.3.1a1 for now Message-ID: <20100512082837.3EAD77EE7D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273653034 -7200 # Node ID bf8e2f220d7153e1571d45c42c8ad345a198801b # Parent 60aa42a328142d6c80df87d29377833daf3b6bea bump version to 1.3.1a1 for now --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= '1.3.0', + version= '1.3.1a1', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -__version__ = version = "1.3.0" +__version__ = version = "1.3.1a1" import py.apipkg From commits-noreply at bitbucket.org Wed May 12 10:57:51 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 08:57:51 +0000 (UTC) Subject: [py-svn] py-trunk commit 932404f79c52: merge with issue-commits Message-ID: <20100512085751.AC2B57EEFB@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273654597 -7200 # Node ID 932404f79c5236d4598f95e42b2c7a82013ca8d5 # Parent e321136536fad7786245462ff6f65c4b24fc7394 # Parent bf8e2f220d7153e1571d45c42c8ad345a198801b merge with issue-commits From commits-noreply at bitbucket.org Wed May 12 10:57:47 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 08:57:47 +0000 (UTC) Subject: [py-svn] py-trunk commit 9d51ff552378: update ISSUES Message-ID: <20100512085747.AB3067EE88@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273387408 -7200 # Node ID 9d51ff55237892ecaf9f268b638136bd7da46f31 # Parent 91f6fe660c833f803c239c35ccfcd7c63963b19a update ISSUES --- a/ISSUES.txt +++ b/ISSUES.txt @@ -1,6 +1,6 @@ refine session initialization / fix custom collect crash --------------------------------------------------------------- -tags: bug 1.2 core xdist +tags: bug 1.4 core xdist When calling "py.test path/X" py.test can crash if the collection of that directory is skipped. Calling "py.test path" will give @@ -14,7 +14,7 @@ and the core session probably converge s introduce py.test.mark.nocollect ------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 for not considering a function for test collection at all. maybe also introduce a py.test.mark.test to explicitely @@ -72,16 +72,9 @@ tags: feature 1.3 introduce py.test.mark.multi to specify a number of values for a given function argument. -introduce py.test.mark.multi ------------------------------------------ -tags: feature 1.3 - -introduce py.test.mark.multi to specify a number -of values for a given function argument. - have imported module mismatch honour relative paths -------------------------------------------------------- -tags: bug 1.2 +tags: bug 1.4 With 1.1.1 py.test fails at least on windows if an import is relative and compared against an absolute conftest.py @@ -89,7 +82,7 @@ path. Normalize. make node._checkcollectable more robust ------------------------------------------------- -tags: bug 1.2 +tags: bug 1.4 currently node._checkcollectable() can raise exceptions for all kinds of reasons ('conftest.py' loading @@ -99,22 +92,15 @@ a good error message. call termination with small timeout ------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node Call gateway group termination with a small timeout if available. Should make dist-testing less likely to leave lost processes. -have --report=xfailed[-detail] report the actual tracebacks ------------------------------------------------------------------- -tags: feature - -there is no way to induce py.test to display the full tracebacks -of the expected failure. Introduce one. - consider globals: py.test.ensuretemp and config -------------------------------------------------------------- -tags: experimental-wish 1.2 +tags: experimental-wish 1.4 consider deprecating py.test.ensuretemp and py.test.config to further reduce py.test globality. Also consider @@ -123,7 +109,7 @@ a plugin rather than being there from th consider allowing funcargs to setup methods -------------------------------------------------------------- -tags: experimental-wish 1.2 +tags: experimental-wish 1.4 Users have expressed the wish to have funcargs available to setup functions. Experiment with allowing funcargs there - it might @@ -135,7 +121,7 @@ and funcargs? consider pytest_addsyspath hook ----------------------------------------- -tags: 1.2 +tags: 1.4 py.test could call a new pytest_addsyspath() in order to systematically allow manipulation of sys.path and to inhibit it via --no-addsyspath @@ -146,7 +132,7 @@ and pytest_configure. relax requirement to have tests/testing contain an __init__ ---------------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 bb: http://bitbucket.org/hpk42/py-trunk/issue/64 A local test run of a "tests" directory may work @@ -157,7 +143,7 @@ an error or make it work without the __i show plugin information in test header ---------------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 Now that external plugins are becoming more numerous it would be useful to have external plugins along with @@ -165,9 +151,9 @@ their versions displayed as a header lin generate/refine plugin doc generation ---------------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 -review and prepare docs for 1.2.0 release. Probably +review and prepare docs for 1.4.0 release. Probably have docs living with the plugin and require them to be available on doc generation time, at least when the target is the website? Or rather go for interactive help? From commits-noreply at bitbucket.org Wed May 12 10:57:49 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 08:57:49 +0000 (UTC) Subject: [py-svn] py-trunk commit e321136536fa: add an issue about py.test.config deprecation Message-ID: <20100512085749.E5D2F7EEE5@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273510457 -7200 # Node ID e321136536fad7786245462ff6f65c4b24fc7394 # Parent 9d51ff55237892ecaf9f268b638136bd7da46f31 add an issue about py.test.config deprecation --- a/ISSUES.txt +++ b/ISSUES.txt @@ -158,3 +158,12 @@ have docs living with the plugin and req be available on doc generation time, at least when the target is the website? Or rather go for interactive help? +deprecate global py.test.config usage +---------------------------------------------------------------- +tags: feature 1.4 + +py.test.ensuretemp and py.test.config are probably the last +objects containing global state. Often using them is not +neccessary. This is about trying to get rid of them, i.e. +deprecating them and checking with PyPy's usages as well +as others. From commits-noreply at bitbucket.org Wed May 12 14:12:38 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 12:12:38 +0000 (UTC) Subject: [py-svn] py-trunk commit b2337036824d: remove duplicate and done issues Message-ID: <20100512121238.B14617EE7F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273656211 -7200 # Node ID b2337036824d3104dad7845e9ee766e44d8721a5 # Parent bf8e2f220d7153e1571d45c42c8ad345a198801b remove duplicate and done issues --- a/ISSUES.txt +++ b/ISSUES.txt @@ -72,13 +72,6 @@ tags: feature 1.3 introduce py.test.mark.multi to specify a number of values for a given function argument. -introduce py.test.mark.multi ------------------------------------------ -tags: feature 1.3 - -introduce py.test.mark.multi to specify a number -of values for a given function argument. - have imported module mismatch honour relative paths -------------------------------------------------------- tags: bug 1.2 @@ -105,13 +98,6 @@ test: testing/pytest/dist/test_dsession. Call gateway group termination with a small timeout if available. Should make dist-testing less likely to leave lost processes. -have --report=xfailed[-detail] report the actual tracebacks ------------------------------------------------------------------- -tags: feature - -there is no way to induce py.test to display the full tracebacks -of the expected failure. Introduce one. - consider globals: py.test.ensuretemp and config -------------------------------------------------------------- tags: experimental-wish 1.2 From commits-noreply at bitbucket.org Wed May 12 14:12:39 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 12:12:39 +0000 (UTC) Subject: [py-svn] py-trunk commit 25d1d792d80e: merging again - seems i am using mercurial wrong or in some confused way ... Message-ID: <20100512121239.1A8B77EEE5@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273666445 -7200 # Node ID 25d1d792d80e89a66665ed403b250430491f1477 # Parent b2337036824d3104dad7845e9ee766e44d8721a5 # Parent 932404f79c5236d4598f95e42b2c7a82013ca8d5 merging again - seems i am using mercurial wrong or in some confused way ... --- a/ISSUES.txt +++ b/ISSUES.txt @@ -1,6 +1,6 @@ refine session initialization / fix custom collect crash --------------------------------------------------------------- -tags: bug 1.2 core xdist +tags: bug 1.4 core xdist When calling "py.test path/X" py.test can crash if the collection of that directory is skipped. Calling "py.test path" will give @@ -14,7 +14,7 @@ and the core session probably converge s introduce py.test.mark.nocollect ------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 for not considering a function for test collection at all. maybe also introduce a py.test.mark.test to explicitely @@ -74,7 +74,7 @@ of values for a given function argument. have imported module mismatch honour relative paths -------------------------------------------------------- -tags: bug 1.2 +tags: bug 1.4 With 1.1.1 py.test fails at least on windows if an import is relative and compared against an absolute conftest.py @@ -82,7 +82,7 @@ path. Normalize. make node._checkcollectable more robust ------------------------------------------------- -tags: bug 1.2 +tags: bug 1.4 currently node._checkcollectable() can raise exceptions for all kinds of reasons ('conftest.py' loading @@ -92,7 +92,7 @@ a good error message. call termination with small timeout ------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node Call gateway group termination with a small timeout if available. @@ -100,7 +100,7 @@ Should make dist-testing less likely to consider globals: py.test.ensuretemp and config -------------------------------------------------------------- -tags: experimental-wish 1.2 +tags: experimental-wish 1.4 consider deprecating py.test.ensuretemp and py.test.config to further reduce py.test globality. Also consider @@ -109,7 +109,7 @@ a plugin rather than being there from th consider allowing funcargs to setup methods -------------------------------------------------------------- -tags: experimental-wish 1.2 +tags: experimental-wish 1.4 Users have expressed the wish to have funcargs available to setup functions. Experiment with allowing funcargs there - it might @@ -121,7 +121,7 @@ and funcargs? consider pytest_addsyspath hook ----------------------------------------- -tags: 1.2 +tags: 1.4 py.test could call a new pytest_addsyspath() in order to systematically allow manipulation of sys.path and to inhibit it via --no-addsyspath @@ -132,7 +132,7 @@ and pytest_configure. relax requirement to have tests/testing contain an __init__ ---------------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 bb: http://bitbucket.org/hpk42/py-trunk/issue/64 A local test run of a "tests" directory may work @@ -143,7 +143,7 @@ an error or make it work without the __i show plugin information in test header ---------------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 Now that external plugins are becoming more numerous it would be useful to have external plugins along with @@ -151,10 +151,19 @@ their versions displayed as a header lin generate/refine plugin doc generation ---------------------------------------------------------------- -tags: feature 1.2 +tags: feature 1.4 -review and prepare docs for 1.2.0 release. Probably +review and prepare docs for 1.4.0 release. Probably have docs living with the plugin and require them to be available on doc generation time, at least when the target is the website? Or rather go for interactive help? +deprecate global py.test.config usage +---------------------------------------------------------------- +tags: feature 1.4 + +py.test.ensuretemp and py.test.config are probably the last +objects containing global state. Often using them is not +neccessary. This is about trying to get rid of them, i.e. +deprecating them and checking with PyPy's usages as well +as others. From commits-noreply at bitbucket.org Wed May 12 14:12:41 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 12 May 2010 12:12:41 +0000 (UTC) Subject: [py-svn] py-trunk commit 11fce19ddcd9: fix test to account for jython python file ending Message-ID: <20100512121241.28D4C7EEFB@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273666327 -7200 # Node ID 11fce19ddcd940f921845b7624b30cf45f452da7 # Parent 25d1d792d80e89a66665ed403b250430491f1477 fix test to account for jython python file ending --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -375,8 +375,7 @@ def test_getfslineno(): fspath, lineno = getfslineno(f) fname = __file__ - if fname.lower().endswith('.pyc'): - fname = fname[:-1] + fname = fname[:fname.find('.py')] + '.py' assert fspath == py.path.local(fname) assert lineno == py.code.getrawcode(f).co_firstlineno-1 # see findsource From commits-noreply at bitbucket.org Fri May 14 12:00:35 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 14 May 2010 10:00:35 +0000 (UTC) Subject: [py-svn] py-trunk commit 89aab690e618: adding three x-failing tests for issue88, issue93 and related issues Message-ID: <20100514100035.0A76F7EF1B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273831363 -7200 # Node ID 89aab690e618d06c8bd2efe399fdacbb65d59317 # Parent 11fce19ddcd940f921845b7624b30cf45f452da7 adding three x-failing tests for issue88, issue93 and related issues --- a/ISSUES.txt +++ b/ISSUES.txt @@ -167,3 +167,12 @@ objects containing global state. Often neccessary. This is about trying to get rid of them, i.e. deprecating them and checking with PyPy's usages as well as others. + +remove deprecated bits in collect.py +------------------------------------------------------------------- +tags: feature 1.4 + +In an effort to further simplify code, review and remove deprecated bits +in collect.py. Probably good: +- inline consider_file/dir methods, no need to have them + subclass-overridable because of hooks --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -105,3 +105,45 @@ class TestGeneralUsage: "*1 passed*", "*1 passed*", ]) + + + @py.test.mark.xfail + def test_early_skip(self, testdir): + testdir.mkdir("xyz") + testdir.makeconftest(""" + import py + def pytest_collect_directory(): + py.test.skip("early") + """) + result = testdir.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 skip*" + ]) + + + @py.test.mark.xfail + def test_issue88_initial_file_multinodes(self, testdir): + testdir.makeconftest(""" + import py + class MyFile(py.test.collect.File): + def collect(self): + return + def pytest_collect_file(path, parent): + return MyFile(path, parent) + """) + p = testdir.makepyfile("def test_hello(): pass") + result = testdir.runpytest(p, "--collectonly") + result.stdout.fnmatch_lines([ + "*MyFile*test_issue88*", + "*Module*test_issue88*", + ]) + + @py.test.mark.xfail + def test_issue93_initialnode_importing_capturing(self, testdir): + testdir.makeconftest(""" + print "should not be seen" + """) + result = testdir.runpytest() + assert result.ret == 0 + assert "should not be seen" not in result.stdout.str() From commits-noreply at bitbucket.org Fri May 14 15:23:10 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 14 May 2010 13:23:10 +0000 (UTC) Subject: [py-svn] py-trunk commit 0ce74aa42bd8: fix test to work on jython and cpy Message-ID: <20100514132310.38DF37EEE7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273843524 -7200 # Node ID 0ce74aa42bd80db92d719b148df719497e9ab247 # Parent 89aab690e618d06c8bd2efe399fdacbb65d59317 fix test to work on jython and cpy --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -374,10 +374,7 @@ def test_getfslineno(): fspath, lineno = getfslineno(f) - fname = __file__ - fname = fname[:fname.find('.py')] + '.py' - - assert fspath == py.path.local(fname) + assert fspath.basename == "test_source.py" assert lineno == py.code.getrawcode(f).co_firstlineno-1 # see findsource class A(object): @@ -386,5 +383,5 @@ def test_getfslineno(): fspath, lineno = getfslineno(A) _, A_lineno = py.std.inspect.findsource(A) - assert fspath == py.path.local(fname) + assert fspath.basename == "test_source.py" assert lineno == A_lineno From commits-noreply at bitbucket.org Fri May 14 23:24:24 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 14 May 2010 21:24:24 +0000 (UTC) Subject: [py-svn] py-trunk commit d699a5f29cdd: tentative fix to py3's dependency on a filename->module->__loader__ chain Message-ID: <20100514212424.5027F7EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1273872387 -7200 # Node ID d699a5f29cdd04524f985568303a6918aaa5df8c # Parent 0ce74aa42bd80db92d719b148df719497e9ab247 tentative fix to py3's dependency on a filename->module->__loader__ chain for executing inspect.findsource ... --- a/py/_code/source.py +++ b/py/_code/source.py @@ -2,6 +2,7 @@ from __future__ import generators import sys import inspect, tokenize import py +from types import ModuleType cpy_compile = compile try: @@ -212,8 +213,15 @@ class Source(object): else: if flag & _AST_FLAG: return co - from types import ModuleType lines = [(x + "\n") for x in self.lines] + if sys.version_info[0] >= 3: + # XXX py3's inspect.getsourcefile() checks for a module + # and a pep302 __loader__ ... we don't have a module + # at code compile-time so we need to fake it here + m = ModuleType("_pycodecompile_pseudo_module") + py.std.inspect.modulesbyfile[filename] = None + py.std.sys.modules[None] = m + m.__loader__ = 1 py.std.linecache.cache[filename] = (1, None, lines, filename) return co From commits-noreply at bitbucket.org Mon May 17 17:29:44 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 17 May 2010 15:29:44 +0000 (UTC) Subject: [py-svn] pytest-xdist commit f9015ac57647: fix looponfailing issue to always run against the newest version of the source Message-ID: <20100517152944.33B077EFDA@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274110354 -7200 # Node ID f9015ac5764770f42c6d6b69db9d8e91fc93ea76 # Parent 56d8e5280be224a0ad3220a9deed55334710bd23 fix looponfailing issue to always run against the newest version of the source --- a/testing/test_remote.py +++ b/testing/test_remote.py @@ -61,7 +61,6 @@ class TestLooponFailing: """) session = LooponfailingSession(modcol.config) loopstate = LoopState() - session.remotecontrol.setup() session.loop_once(loopstate) assert len(loopstate.colitems) == 1 @@ -83,7 +82,6 @@ class TestLooponFailing: """) session = LooponfailingSession(modcol.config) loopstate = LoopState() - session.remotecontrol.setup() loopstate.colitems = [] session.loop_once(loopstate) assert len(loopstate.colitems) == 1 @@ -110,7 +108,6 @@ class TestLooponFailing: """) session = LooponfailingSession(modcol.config) loopstate = LoopState() - session.remotecontrol.setup() loopstate.colitems = [] session.loop_once(loopstate) assert len(loopstate.colitems) == 2 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -24,7 +24,6 @@ class LooponfailingSession(Session): def main(self, initialitems): try: self.loopstate = loopstate = LoopState([]) - self.remotecontrol.setup() while 1: self.loop_once(loopstate) if not loopstate.colitems and loopstate.wasfailing: @@ -34,10 +33,10 @@ class LooponfailingSession(Session): print def loop_once(self, loopstate): + self.remotecontrol.setup() colitems = loopstate.colitems loopstate.wasfailing = colitems and len(colitems) loopstate.colitems = self.remotecontrol.runsession(colitems or ()) - self.remotecontrol.setup() class LoopState: def __init__(self, colitems=None): --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +1.3 +------------------------- + +- fix --looponfailing - it would not actually run against the fully changed + source tree when initial conftest files load application state. + 1.2 ------------------------- From commits-noreply at bitbucket.org Mon May 17 18:58:21 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 17 May 2010 16:58:21 +0000 (UTC) Subject: [py-svn] py-trunk commit 3afeedc7eba5: fix issue96 - make capturing more resilient against KeyboardInterrupt Message-ID: <20100517165821.6D2B17EFE8@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274115639 -7200 # Node ID 3afeedc7eba5e2da4b2940fd8e99ec99d1a88a14 # Parent d699a5f29cdd04524f985568303a6918aaa5df8c fix issue96 - make capturing more resilient against KeyboardInterrupt --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -39,6 +39,10 @@ class TestCaptureManager: old = sys.stdout, sys.stderr, sys.stdin try: capman = CaptureManager() + # call suspend without resume or start + outerr = capman.suspendcapture() + outerr = capman.suspendcapture() + assert outerr == ("", "") capman.resumecapture(method) print ("hello") out, err = capman.suspendcapture() --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -29,21 +29,25 @@ except ImportError: class FDCapture: """ Capture IO to/from a given os-level filedescriptor. """ - def __init__(self, targetfd, tmpfile=None): + def __init__(self, targetfd, tmpfile=None, now=True): """ save targetfd descriptor, and open a new temporary file there. If no tmpfile is specified a tempfile.Tempfile() will be opened in text mode. """ self.targetfd = targetfd + self._patched = [] if tmpfile is None: f = tempfile.TemporaryFile('wb+') tmpfile = dupfile(f, encoding="UTF-8") f.close() self.tmpfile = tmpfile - self._savefd = os.dup(targetfd) - os.dup2(self.tmpfile.fileno(), targetfd) - self._patched = [] + if now: + self.start() + + def start(self): + self._savefd = os.dup(self.targetfd) + os.dup2(self.tmpfile.fileno(), self.targetfd) def setasfile(self, name, module=sys): """ patch . to self.tmpfile @@ -62,10 +66,13 @@ class FDCapture: def done(self): """ unpatch and clean up, returns the self.tmpfile (file object) """ - os.dup2(self._savefd, self.targetfd) + try: + os.dup2(self._savefd, self.targetfd) + os.close(self._savefd) + self.tmpfile.seek(0) + except (AttributeError, ValueError, OSError): + pass self.unsetfiles() - os.close(self._savefd) - self.tmpfile.seek(0) return self.tmpfile def writeorg(self, data): @@ -146,89 +153,92 @@ class Capture(object): def reset(self): """ reset sys.stdout/stderr and return captured output as strings. """ - if hasattr(self, '_suspended'): - outfile = self._kwargs['out'] - errfile = self._kwargs['err'] - del self._kwargs - else: - outfile, errfile = self.done() + outfile, errfile = self.done() out, err = "", "" - if outfile: + if outfile and not outfile.closed: out = outfile.read() outfile.close() - if errfile and errfile != outfile: + if errfile and errfile != outfile and not errfile.closed: err = errfile.read() errfile.close() return out, err def suspend(self): """ return current snapshot captures, memorize tempfiles. """ - assert not hasattr(self, '_suspended') - self._suspended = True outerr = self.readouterr() outfile, errfile = self.done() - self._kwargs['out'] = outfile - self._kwargs['err'] = errfile return outerr - def resume(self): - """ resume capturing with original temp files. """ - assert self._suspended - self._initialize(**self._kwargs) - del self._suspended - class StdCaptureFD(Capture): """ This class allows to capture writes to FD1 and FD2 and may connect a NULL file to FD0 (and prevent reads from sys.stdin) """ - def __init__(self, out=True, err=True, - mixed=False, in_=True, patchsys=True): - self._kwargs = locals().copy() - del self._kwargs['self'] - self._initialize(**self._kwargs) - - def _initialize(self, out=True, err=True, - mixed=False, in_=True, patchsys=True): + def __init__(self, out=True, err=True, mixed=False, + in_=True, patchsys=True, now=True): + self.in_ = in_ if in_: self._oldin = (sys.stdin, os.dup(0)) - sys.stdin = DontReadFromInput() - fd = os.open(devnullpath, os.O_RDONLY) - os.dup2(fd, 0) - os.close(fd) - if out: + if out: tmpfile = None if hasattr(out, 'write'): - tmpfile = out - self.out = py.io.FDCapture(1, tmpfile=tmpfile) - if patchsys: - self.out.setasfile('stdout') - if err: - if mixed and out: + tmpfile = None + self.out = py.io.FDCapture(1, tmpfile=tmpfile, now=False) + self.out_tmpfile = tmpfile + if err: + if out and mixed: tmpfile = self.out.tmpfile elif hasattr(err, 'write'): tmpfile = err else: tmpfile = None - self.err = py.io.FDCapture(2, tmpfile=tmpfile) - if patchsys: - self.err.setasfile('stderr') + self.err = py.io.FDCapture(2, tmpfile=tmpfile, now=False) + self.err_tmpfile = tmpfile + self.patchsys = patchsys + if now: + self.startall() + + def startall(self): + if self.in_: + sys.stdin = DontReadFromInput() + fd = os.open(devnullpath, os.O_RDONLY) + os.dup2(fd, 0) + os.close(fd) + + out = getattr(self, 'out', None) + if out: + out.start() + if self.patchsys: + out.setasfile('stdout') + err = getattr(self, 'err', None) + if err: + err.start() + if self.patchsys: + err.setasfile('stderr') + + def resume(self): + """ resume capturing with original temp files. """ + #if hasattr(self, 'out'): + # self.out.restart() + #if hasattr(self, 'err'): + # self.err.restart() + self.startall() def done(self): """ return (outfile, errfile) and stop capturing. """ + outfile = errfile = None if hasattr(self, 'out'): outfile = self.out.done() - else: - outfile = None if hasattr(self, 'err'): errfile = self.err.done() - else: - errfile = None if hasattr(self, '_oldin'): oldsys, oldfd = self._oldin - os.dup2(oldfd, 0) - os.close(oldfd) + try: + os.dup2(oldfd, 0) + os.close(oldfd) + except OSError: + pass sys.stdin = oldsys return outfile, errfile @@ -252,69 +262,61 @@ class StdCapture(Capture): modifies sys.stdout|stderr|stdin attributes and does not touch underlying File Descriptors (use StdCaptureFD for that). """ - def __init__(self, out=True, err=True, in_=True, mixed=False): - self._kwargs = locals().copy() - del self._kwargs['self'] - self._initialize(**self._kwargs) - - def _initialize(self, out, err, in_, mixed): - self._out = out - self._err = err - self._in = in_ - if out: - self._oldout = sys.stdout - if not hasattr(out, 'write'): - out = TextIO() - sys.stdout = self.out = out - if err: - self._olderr = sys.stderr - if out and mixed: - err = self.out + def __init__(self, out=True, err=True, in_=True, mixed=False, now=True): + self._oldout = sys.stdout + self._olderr = sys.stderr + self._oldin = sys.stdin + if out and not hasattr(out, 'file'): + out = TextIO() + self.out = out + if err: + if mixed: + err = out elif not hasattr(err, 'write'): err = TextIO() - sys.stderr = self.err = err - if in_: - self._oldin = sys.stdin - sys.stdin = self.newin = DontReadFromInput() + self.err = err + self.in_ = in_ + if now: + self.startall() + + def startall(self): + if self.out: + sys.stdout = self.out + if self.err: + sys.stderr = self.err + if self.in_: + sys.stdin = self.in_ = DontReadFromInput() def done(self): """ return (outfile, errfile) and stop capturing. """ - o,e = sys.stdout, sys.stderr - if self._out: - try: - sys.stdout = self._oldout - except AttributeError: - raise IOError("stdout capturing already reset") - del self._oldout + outfile = errfile = None + if self.out and not self.out.closed: + sys.stdout = self._oldout outfile = self.out outfile.seek(0) - else: - outfile = None - if self._err: - try: - sys.stderr = self._olderr - except AttributeError: - raise IOError("stderr capturing already reset") - del self._olderr + if self.err and not self.err.closed: + sys.stderr = self._olderr errfile = self.err errfile.seek(0) - else: - errfile = None - if self._in: + if self.in_: sys.stdin = self._oldin return outfile, errfile + def resume(self): + """ resume capturing with original temp files. """ + self.startall() + def readouterr(self): """ return snapshot value of stdout/stderr capturings. """ out = err = "" - if self._out: - out = sys.stdout.getvalue() - sys.stdout.truncate(0) - sys.stdout.seek(0) - if self._err: - err = sys.stderr.getvalue() - sys.stderr.truncate(0) - sys.stderr.seek(0) + if self.out: + out = self.out.getvalue() + self.out.truncate(0) + self.out.seek(0) + if self.err: + err = self.err.getvalue() + self.err.truncate(0) + self.err.seek(0) return out, err class DontReadFromInput: @@ -344,5 +346,3 @@ except AttributeError: devnullpath = 'NUL' else: devnullpath = '/dev/null' - - --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -96,6 +96,21 @@ def test_dupfile(tmpfile): class TestFDCapture: pytestmark = needsdup + def test_not_now(self, tmpfile): + fd = tmpfile.fileno() + cap = py.io.FDCapture(fd, now=False) + data = tobytes("hello") + os.write(fd, data) + f = cap.done() + s = f.read() + assert not s + cap = py.io.FDCapture(fd, now=False) + cap.start() + os.write(fd, data) + f = cap.done() + s = f.read() + assert s == "hello" + def test_stdout(self, tmpfile): fd = tmpfile.fileno() cap = py.io.FDCapture(fd) @@ -185,8 +200,11 @@ class TestStdCapture: def test_capturing_twice_error(self): cap = self.getcapture() print ("hello") - cap.reset() - py.test.raises(Exception, "cap.reset()") + out, err = cap.reset() + print ("world") + out2, err = cap.reset() + assert out == "hello\n" + assert not err def test_capturing_modify_sysouterr_in_between(self): oldout = sys.stdout @@ -210,7 +228,6 @@ class TestStdCapture: cap2 = self.getcapture() print ("cap2") out2, err2 = cap2.reset() - py.test.raises(Exception, "cap2.reset()") out1, err1 = cap1.reset() assert out1 == "cap1\n" assert out2 == "cap2\n" @@ -265,6 +282,13 @@ class TestStdCapture: assert out == "after\n" assert not err +class TestStdCaptureNotNow(TestStdCapture): + def getcapture(self, **kw): + kw['now'] = False + cap = py.io.StdCapture(**kw) + cap.startall() + return cap + class TestStdCaptureFD(TestStdCapture): pytestmark = needsdup @@ -296,6 +320,22 @@ class TestStdCaptureFD(TestStdCapture): assert out.startswith("3") assert err.startswith("4") +class TestStdCaptureFDNotNow(TestStdCaptureFD): + pytestmark = needsdup + + def getcapture(self, **kw): + kw['now'] = False + cap = py.io.StdCaptureFD(**kw) + cap.startall() + return cap + +def test_capture_not_started_but_reset(): + capsys = py.io.StdCapture(now=False) + capsys.done() + capsys.done() + capsys.reset() + capsys.reset() + @needsdup def test_capture_no_sys(): capsys = py.io.StdCapture() --- a/py/_plugin/pytest_capture.py +++ b/py/_plugin/pytest_capture.py @@ -106,6 +106,14 @@ def addouterr(rep, outerr): def pytest_configure(config): config.pluginmanager.register(CaptureManager(), 'capturemanager') +class NoCapture: + def startall(self): + pass + def resume(self): + pass + def suspend(self): + return "", "" + class CaptureManager: def __init__(self): self._method2capture = {} @@ -118,15 +126,17 @@ class CaptureManager: def _makestringio(self): return py.io.TextIO() - def _startcapture(self, method): + def _getcapture(self, method): if method == "fd": - return py.io.StdCaptureFD( + return py.io.StdCaptureFD(now=False, out=self._maketempfile(), err=self._maketempfile() ) elif method == "sys": - return py.io.StdCapture( + return py.io.StdCapture(now=False, out=self._makestringio(), err=self._makestringio() ) + elif method == "no": + return NoCapture() else: raise ValueError("unknown capturing method: %r" % method) @@ -152,27 +162,25 @@ class CaptureManager: if hasattr(self, '_capturing'): raise ValueError("cannot resume, already capturing with %r" % (self._capturing,)) - if method != "no": - cap = self._method2capture.get(method) - if cap is None: - cap = self._startcapture(method) - self._method2capture[method] = cap - else: - cap.resume() + cap = self._method2capture.get(method) self._capturing = method + if cap is None: + self._method2capture[method] = cap = self._getcapture(method) + cap.startall() + else: + cap.resume() def suspendcapture(self, item=None): self.deactivate_funcargs() if hasattr(self, '_capturing'): method = self._capturing - if method != "no": - cap = self._method2capture[method] + cap = self._method2capture.get(method) + if cap is not None: outerr = cap.suspend() - else: - outerr = "", "" del self._capturing if item: - outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1]) + outerr = (item.outerr[0] + outerr[0], + item.outerr[1] + outerr[1]) return outerr return "", "" @@ -180,19 +188,17 @@ class CaptureManager: if not hasattr(pyfuncitem, 'funcargs'): return assert not hasattr(self, '_capturing_funcargs') - l = [] - for name, obj in pyfuncitem.funcargs.items(): - if name == 'capfd' and not hasattr(os, 'dup'): - py.test.skip("capfd funcarg needs os.dup") + self._capturing_funcargs = capturing_funcargs = [] + for name, capfuncarg in pyfuncitem.funcargs.items(): if name in ('capsys', 'capfd'): - obj._start() - l.append(obj) - if l: - self._capturing_funcargs = l + capturing_funcargs.append(capfuncarg) + capfuncarg._start() def deactivate_funcargs(self): - if hasattr(self, '_capturing_funcargs'): - for capfuncarg in self._capturing_funcargs: + capturing_funcargs = getattr(self, '_capturing_funcargs', None) + if capturing_funcargs is not None: + while capturing_funcargs: + capfuncarg = capturing_funcargs.pop() capfuncarg._finalize() del self._capturing_funcargs @@ -256,16 +262,19 @@ def pytest_funcarg__capfd(request): platform does not have ``os.dup`` (e.g. Jython) tests using this funcarg will automatically skip. """ + if not hasattr(os, 'dup'): + py.test.skip("capfd funcarg needs os.dup") return CaptureFuncarg(request, py.io.StdCaptureFD) class CaptureFuncarg: def __init__(self, request, captureclass): self._cclass = captureclass + self.capture = self._cclass(now=False) #request.addfinalizer(self._finalize) def _start(self): - self.capture = self._cclass() + self.capture.startall() def _finalize(self): if hasattr(self, 'capture'): @@ -276,6 +285,4 @@ class CaptureFuncarg: return self.capture.readouterr() def close(self): - self.capture.reset() - del self.capture - + self._finalize() --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ Changes between 1.3.0 and 1.3.1 ================================================== +- fix issue96: make capturing more resilient against Control-C + interruptions (involved somewhat substantial refactoring + to the underlying capturing functionality to avoid race + conditions). + - make py.test.cmdline.main() return the exitstatus instead of raising (which is still done by py.cmdline.pytest()) and make it so that py.test.cmdline.main() can be called From commits-noreply at bitbucket.org Tue May 18 09:51:17 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 07:51:17 +0000 (UTC) Subject: [py-svn] py-trunk commit 408c79d080cb: fix test to account for earlier capfd skipping (on jython) Message-ID: <20100518075117.7AE3A7EF74@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274169244 -7200 # Node ID 408c79d080cb5bf0b5f2c2790bbdfa8fe21a43ec # Parent 3afeedc7eba5e2da4b2940fd8e99ec99d1a88a14 fix test to account for earlier capfd skipping (on jython) --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -358,7 +358,7 @@ class TestCaptureFuncarg: def test_partial_setup_failure(self, testdir): p = testdir.makepyfile(""" - def test_hello(capfd, missingarg): + def test_hello(capsys, missingarg): pass """) result = testdir.runpytest(p) From commits-noreply at bitbucket.org Tue May 18 15:59:35 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 13:59:35 +0000 (UTC) Subject: [py-svn] py-trunk commit c3995676e660: - try to fix the nightly failures by refining internal capturing mechanism Message-ID: <20100518135935.42FC27EF73@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274191318 -7200 # Node ID c3995676e6608c6b6f686d6b9c3ecb9c99aa91df # Parent 408c79d080cb5bf0b5f2c2790bbdfa8fe21a43ec - try to fix the nightly failures by refining internal capturing mechanism and adding tests, including a "lsof" test for making sure the number of open file descriptors does not increase. - also move a py.io related logging test to testing/io --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -251,36 +251,6 @@ class TestLoggingInteraction: """) result = testdir.runpytest(p) result.stderr.str().find("atexit") == -1 - - def test_capturing_and_logging_fundamentals(self, testdir): - # here we check a fundamental feature - p = testdir.makepyfile(""" - import sys, os - import py, logging - if hasattr(os, 'dup'): - cap = py.io.StdCaptureFD(out=False, in_=False) - else: - cap = py.io.StdCapture(out=False, in_=False) - logging.warn("hello1") - outerr = cap.suspend() - - print ("suspeneded and captured %s" % (outerr,)) - - logging.warn("hello2") - - cap.resume() - logging.warn("hello3") - - outerr = cap.suspend() - print ("suspend2 and captured %s" % (outerr,)) - """) - result = testdir.runpython(p) - result.stdout.fnmatch_lines([ - "suspeneded and captured*hello1*", - "suspend2 and captured*hello2*WARNING:root:hello3*", - ]) - assert "atexit" not in result.stderr.str() - def test_logging_and_immediate_setupteardown(self, testdir): p = testdir.makepyfile(""" @@ -402,7 +372,7 @@ def test_fdfuncarg_skips_on_no_osdup(tes def test_hello(capfd): pass """) - result = testdir.runpytest() + result = testdir.runpytest("--capture=no") result.stdout.fnmatch_lines([ "*1 skipped*" ]) --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -42,11 +42,16 @@ class FDCapture: tmpfile = dupfile(f, encoding="UTF-8") f.close() self.tmpfile = tmpfile + self._savefd = os.dup(self.targetfd) if now: self.start() def start(self): - self._savefd = os.dup(self.targetfd) + try: + os.fstat(self._savefd) + except OSError: + raise ValueError("saved filedescriptor not valid, " + "did you call start() twice?") os.dup2(self.tmpfile.fileno(), self.targetfd) def setasfile(self, name, module=sys): @@ -66,12 +71,9 @@ class FDCapture: def done(self): """ unpatch and clean up, returns the self.tmpfile (file object) """ - try: - os.dup2(self._savefd, self.targetfd) - os.close(self._savefd) - self.tmpfile.seek(0) - except (AttributeError, ValueError, OSError): - pass + os.dup2(self._savefd, self.targetfd) + os.close(self._savefd) + self.tmpfile.seek(0) self.unsetfiles() return self.tmpfile @@ -107,7 +109,7 @@ def dupfile(f, mode=None, buffering=0, r if encoding is not None: mode = mode.replace("b", "") buffering = True - return os.fdopen(newfd, mode, buffering, encoding, closefd=False) + return os.fdopen(newfd, mode, buffering, encoding, closefd=True) else: f = os.fdopen(newfd, mode, buffering) if encoding is not None: @@ -177,15 +179,26 @@ class StdCaptureFD(Capture): """ def __init__(self, out=True, err=True, mixed=False, in_=True, patchsys=True, now=True): + self._options = locals() + self._save() + self.patchsys = patchsys + if now: + self.startall() + + def _save(self): + in_ = self._options['in_'] + out = self._options['out'] + err = self._options['err'] + mixed = self._options['mixed'] self.in_ = in_ if in_: self._oldin = (sys.stdin, os.dup(0)) if out: tmpfile = None if hasattr(out, 'write'): - tmpfile = None + tmpfile = out self.out = py.io.FDCapture(1, tmpfile=tmpfile, now=False) - self.out_tmpfile = tmpfile + self._options['out'] = self.out.tmpfile if err: if out and mixed: tmpfile = self.out.tmpfile @@ -194,10 +207,7 @@ class StdCaptureFD(Capture): else: tmpfile = None self.err = py.io.FDCapture(2, tmpfile=tmpfile, now=False) - self.err_tmpfile = tmpfile - self.patchsys = patchsys - if now: - self.startall() + self._options['err'] = self.err.tmpfile def startall(self): if self.in_: @@ -219,27 +229,21 @@ class StdCaptureFD(Capture): def resume(self): """ resume capturing with original temp files. """ - #if hasattr(self, 'out'): - # self.out.restart() - #if hasattr(self, 'err'): - # self.err.restart() self.startall() def done(self): """ return (outfile, errfile) and stop capturing. """ outfile = errfile = None - if hasattr(self, 'out'): + if hasattr(self, 'out') and not self.out.tmpfile.closed: outfile = self.out.done() - if hasattr(self, 'err'): + if hasattr(self, 'err') and not self.err.tmpfile.closed: errfile = self.err.done() if hasattr(self, '_oldin'): oldsys, oldfd = self._oldin - try: - os.dup2(oldfd, 0) - os.close(oldfd) - except OSError: - pass + os.dup2(oldfd, 0) + os.close(oldfd) sys.stdin = oldsys + self._save() return outfile, errfile def readouterr(self): --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -76,22 +76,21 @@ def pytest_funcarg__tmpfile(request): @needsdup def test_dupfile(tmpfile): - somefile = tmpfile flist = [] for i in range(5): - nf = py.io.dupfile(somefile, encoding="utf-8") - assert nf != somefile - assert nf.fileno() != somefile.fileno() + nf = py.io.dupfile(tmpfile, encoding="utf-8") + assert nf != tmpfile + assert nf.fileno() != tmpfile.fileno() assert nf not in flist print_(i, end="", file=nf) flist.append(nf) for i in range(5): f = flist[i] f.close() - somefile.seek(0) - s = somefile.read() + tmpfile.seek(0) + s = tmpfile.read() assert "01234" in repr(s) - somefile.close() + tmpfile.close() class TestFDCapture: pytestmark = needsdup @@ -111,7 +110,7 @@ class TestFDCapture: s = f.read() assert s == "hello" - def test_stdout(self, tmpfile): + def test_simple(self, tmpfile): fd = tmpfile.fileno() cap = py.io.FDCapture(fd) data = tobytes("hello") @@ -119,6 +118,30 @@ class TestFDCapture: f = cap.done() s = f.read() assert s == "hello" + f.close() + + def test_simple_many(self, tmpfile): + for i in range(10): + self.test_simple(tmpfile) + + def test_simple_many_check_open_files(self, tmpfile): + pid = os.getpid() + try: + out = py.process.cmdexec("lsof -p %d" % pid) + except py.process.cmdexec.Error: + py.test.skip("could not run 'lsof'") + self.test_simple_many(tmpfile) + out2 = py.process.cmdexec("lsof -p %d" % pid) + len1 = len([x for x in out.split("\n") if "REG" in x]) + len2 = len([x for x in out2.split("\n") if "REG" in x]) + assert len2 < len1 + 3, out2 + + def test_simple_fail_second_start(self, tmpfile): + fd = tmpfile.fileno() + cap = py.io.FDCapture(fd) + f = cap.done() + py.test.raises(ValueError, cap.start) + f.close() def test_stderr(self): cap = py.io.FDCapture(2) @@ -329,6 +352,14 @@ class TestStdCaptureFDNotNow(TestStdCapt cap.startall() return cap + at needsdup +def test_stdcapture_fd_tmpfile(tmpfile): + capfd = py.io.StdCaptureFD(out=tmpfile) + os.write(1, "hello".encode("ascii")) + os.write(2, "world".encode("ascii")) + outf, errf = capfd.done() + assert outf == tmpfile + def test_capture_not_started_but_reset(): capsys = py.io.StdCapture(now=False) capsys.done() @@ -368,3 +399,45 @@ def test_callcapture_nofd(): assert res == 42 assert out.startswith("3") assert err.startswith("4") + + at needsdup + at py.test.mark.multi(use=[True, False]) +def test_fdcapture_tmpfile_remains_the_same(tmpfile, use): + if not use: + tmpfile = True + cap = py.io.StdCaptureFD(out=False, err=tmpfile, now=False) + cap.startall() + capfile = cap.err.tmpfile + cap.suspend() + cap.resume() + capfile2 = cap.err.tmpfile + assert capfile2 == capfile + + at py.test.mark.multi(method=['StdCapture', 'StdCaptureFD']) +def test_capturing_and_logging_fundamentals(testdir, method): + if method == "StdCaptureFD" and not hasattr(os, 'dup'): + py.test.skip("need os.dup") + # here we check a fundamental feature + p = testdir.makepyfile(""" + import sys, os + import py, logging + cap = py.io.%s(out=False, in_=False) + + logging.warn("hello1") + outerr = cap.suspend() + print ("suspend, captured %%s" %%(outerr,)) + logging.warn("hello2") + + cap.resume() + logging.warn("hello3") + + outerr = cap.suspend() + print ("suspend2, captured %%s" %% (outerr,)) + """ % (method,)) + result = testdir.runpython(p) + result.stdout.fnmatch_lines([ + "suspend, captured*hello1*", + "suspend2, captured*hello2*WARNING:root:hello3*", + ]) + assert "atexit" not in result.stderr.str() + From commits-noreply at bitbucket.org Tue May 18 16:49:59 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 14:49:59 +0000 (UTC) Subject: [py-svn] py-trunk commit c44081b42ee3: deal gracefully with invalid file descriptors - don't capture the particular stream Message-ID: <20100518144959.3E18C7EF6D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274194376 -7200 # Node ID c44081b42ee32f976a9f82dd376e8ac8a9aaa8f5 # Parent c3995676e6608c6b6f686d6b9c3ecb9c99aa91df deal gracefully with invalid file descriptors - don't capture the particular stream --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -175,7 +175,8 @@ class Capture(object): class StdCaptureFD(Capture): """ This class allows to capture writes to FD1 and FD2 and may connect a NULL file to FD0 (and prevent - reads from sys.stdin) + reads from sys.stdin). If any of the 0,1,2 file descriptors + is invalid it will not be captured. """ def __init__(self, out=True, err=True, mixed=False, in_=True, patchsys=True, now=True): @@ -192,13 +193,19 @@ class StdCaptureFD(Capture): mixed = self._options['mixed'] self.in_ = in_ if in_: - self._oldin = (sys.stdin, os.dup(0)) + try: + self._oldin = (sys.stdin, os.dup(0)) + except OSError: + pass if out: tmpfile = None if hasattr(out, 'write'): tmpfile = out - self.out = py.io.FDCapture(1, tmpfile=tmpfile, now=False) - self._options['out'] = self.out.tmpfile + try: + self.out = FDCapture(1, tmpfile=tmpfile, now=False) + self._options['out'] = self.out.tmpfile + except OSError: + pass if err: if out and mixed: tmpfile = self.out.tmpfile @@ -206,8 +213,11 @@ class StdCaptureFD(Capture): tmpfile = err else: tmpfile = None - self.err = py.io.FDCapture(2, tmpfile=tmpfile, now=False) - self._options['err'] = self.err.tmpfile + try: + self.err = FDCapture(2, tmpfile=tmpfile, now=False) + self._options['err'] = self.err.tmpfile + except OSError: + pass def startall(self): if self.in_: --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -360,6 +360,28 @@ def test_stdcapture_fd_tmpfile(tmpfile): outf, errf = capfd.done() assert outf == tmpfile +class TestStdCaptureFDinvalidFD: + pytestmark = needsdup + def test_stdcapture_fd_invalid_fd(self, testdir): + testdir.makepyfile(""" + import py, os + def test_stdout(): + os.close(1) + cap = py.io.StdCaptureFD(out=True, err=False, in_=False) + cap.done() + def test_stderr(): + os.close(2) + cap = py.io.StdCaptureFD(out=False, err=True, in_=False) + cap.done() + def test_stdin(): + os.close(0) + cap = py.io.StdCaptureFD(out=False, err=False, in_=True) + cap.done() + """) + result = testdir.runpytest("--capture=fd") + assert result.ret == 0 + assert result.parseoutcomes()['passed'] == 3 + def test_capture_not_started_but_reset(): capsys = py.io.StdCapture(now=False) capsys.done() From commits-noreply at bitbucket.org Tue May 18 18:38:22 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 16:38:22 +0000 (UTC) Subject: [py-svn] pytest-xdist commit dd7765376b0e: fix conftest related documentation Message-ID: <20100518163822.645F57EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274200848 -7200 # Node ID dd7765376b0ee5e7db1663f95d08df543b01ce1e # Parent f9015ac5764770f42c6d6b69db9d8e91fc93ea76 fix conftest related documentation --- a/xdist/plugin.py +++ b/xdist/plugin.py @@ -120,11 +120,11 @@ Specifying test exec environments in a c Instead of specifying command line options, you can put options values in a ``conftest.py`` file like this:: - pytest_option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5'] - pytest_option_dist = True + option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5'] + option_dist = True -Any commandline ``--tx`` specifictions will add to the list of available execution -environments. +Any commandline ``--tx`` specifictions will add to the list of +available execution environments. Specifying "rsync" dirs in a conftest.py +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From commits-noreply at bitbucket.org Tue May 18 18:43:27 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 16:43:27 +0000 (UTC) Subject: [py-svn] py-trunk commit 4877f640d18c: fix issue98 - xdist documentation wrongly told to set pytest_option_X Message-ID: <20100518164327.5274D7EF74@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274201112 -7200 # Node ID 4877f640d18c401e4d9f285c1fa99db725d4ffb4 # Parent c44081b42ee32f976a9f82dd376e8ac8a9aaa8f5 fix issue98 - xdist documentation wrongly told to set pytest_option_X where it is only option_X that is correct. --- a/doc/test/plugin/xdist.txt +++ b/doc/test/plugin/xdist.txt @@ -126,11 +126,11 @@ Specifying test exec environments in a c Instead of specifying command line options, you can put options values in a ``conftest.py`` file like this:: - pytest_option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5'] - pytest_option_dist = True + option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5'] + option_dist = True -Any commandline ``--tx`` specifictions will add to the list of available execution -environments. +Any commandline ``--tx`` specifictions will add to the list of +available execution environments. Specifying "rsync" dirs in a conftest.py +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From commits-noreply at bitbucket.org Tue May 18 20:01:06 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 18:01:06 +0000 (UTC) Subject: [py-svn] py-trunk commit f740e5319ec4: simplify and unify FDCapture API and usage: Message-ID: <20100518180106.EEAA97EF6D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274205824 -7200 # Node ID f740e5319ec4ceabc76077885f94706894ad62cc # Parent 4877f640d18c401e4d9f285c1fa99db725d4ffb4 simplify and unify FDCapture API and usage: * FDCapture now takes care through the 'patchsys' option to also set sys.stdin/out/err - setfiles/unsetfiles methods removed - i doubt anybody uses this outside of py.test's own old usage. * stdin also goes through FDCapture now. --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -26,23 +26,26 @@ except ImportError: raise TypeError("not a byte value: %r" %(data,)) StringIO.write(self, data) +patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'} + class FDCapture: """ Capture IO to/from a given os-level filedescriptor. """ - def __init__(self, targetfd, tmpfile=None, now=True): + def __init__(self, targetfd, tmpfile=None, now=True, patchsys=False): """ save targetfd descriptor, and open a new temporary file there. If no tmpfile is specified a tempfile.Tempfile() will be opened in text mode. """ self.targetfd = targetfd - self._patched = [] if tmpfile is None: f = tempfile.TemporaryFile('wb+') tmpfile = dupfile(f, encoding="UTF-8") f.close() self.tmpfile = tmpfile self._savefd = os.dup(self.targetfd) + if patchsys: + self._oldsys = getattr(sys, patchsysdict[targetfd]) if now: self.start() @@ -53,20 +56,8 @@ class FDCapture: raise ValueError("saved filedescriptor not valid, " "did you call start() twice?") os.dup2(self.tmpfile.fileno(), self.targetfd) - - def setasfile(self, name, module=sys): - """ patch . to self.tmpfile - """ - key = (module, name) - self._patched.append((key, getattr(module, name))) - setattr(module, name, self.tmpfile) - - def unsetfiles(self): - """ unpatch all patched items - """ - while self._patched: - (module, name), value = self._patched.pop() - setattr(module, name, value) + if hasattr(self, '_oldsys'): + setattr(sys, patchsysdict[self.targetfd], self.tmpfile) def done(self): """ unpatch and clean up, returns the self.tmpfile (file object) @@ -74,7 +65,8 @@ class FDCapture: os.dup2(self._savefd, self.targetfd) os.close(self._savefd) self.tmpfile.seek(0) - self.unsetfiles() + if hasattr(self, '_oldsys'): + setattr(sys, patchsysdict[self.targetfd], self._oldsys) return self.tmpfile def writeorg(self, data): @@ -182,7 +174,6 @@ class StdCaptureFD(Capture): in_=True, patchsys=True, now=True): self._options = locals() self._save() - self.patchsys = patchsys if now: self.startall() @@ -191,10 +182,17 @@ class StdCaptureFD(Capture): out = self._options['out'] err = self._options['err'] mixed = self._options['mixed'] - self.in_ = in_ + patchsys = self._options['patchsys'] if in_: + if hasattr(in_, 'read'): + tmpfile = in_ + else: + fd = os.open(devnullpath, os.O_RDONLY) + tmpfile = os.fdopen(fd) try: - self._oldin = (sys.stdin, os.dup(0)) + self.in_ = FDCapture(0, tmpfile=tmpfile, now=False, + patchsys=patchsys) + self._options['in_'] = self.in_.tmpfile except OSError: pass if out: @@ -202,7 +200,8 @@ class StdCaptureFD(Capture): if hasattr(out, 'write'): tmpfile = out try: - self.out = FDCapture(1, tmpfile=tmpfile, now=False) + self.out = FDCapture(1, tmpfile=tmpfile, + now=False, patchsys=patchsys) self._options['out'] = self.out.tmpfile except OSError: pass @@ -214,28 +213,23 @@ class StdCaptureFD(Capture): else: tmpfile = None try: - self.err = FDCapture(2, tmpfile=tmpfile, now=False) + self.err = FDCapture(2, tmpfile=tmpfile, + now=False, patchsys=patchsys) self._options['err'] = self.err.tmpfile except OSError: pass def startall(self): - if self.in_: + if hasattr(self, 'in_'): + self.in_.start() sys.stdin = DontReadFromInput() - fd = os.open(devnullpath, os.O_RDONLY) - os.dup2(fd, 0) - os.close(fd) out = getattr(self, 'out', None) if out: out.start() - if self.patchsys: - out.setasfile('stdout') err = getattr(self, 'err', None) if err: err.start() - if self.patchsys: - err.setasfile('stderr') def resume(self): """ resume capturing with original temp files. """ @@ -248,11 +242,8 @@ class StdCaptureFD(Capture): outfile = self.out.done() if hasattr(self, 'err') and not self.err.tmpfile.closed: errfile = self.err.done() - if hasattr(self, '_oldin'): - oldsys, oldfd = self._oldin - os.dup2(oldfd, 0) - os.close(oldfd) - sys.stdin = oldsys + if hasattr(self, 'in_'): + tmpfile = self.in_.done() self._save() return outfile, errfile --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -144,8 +144,7 @@ class TestFDCapture: f.close() def test_stderr(self): - cap = py.io.FDCapture(2) - cap.setasfile('stderr') + cap = py.io.FDCapture(2, patchsys=True) print_("hello", file=sys.stderr) f = cap.done() s = f.read() From commits-noreply at bitbucket.org Tue May 18 20:43:46 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 18:43:46 +0000 (UTC) Subject: [py-svn] py-trunk commit eb7ffb9f3553: for now don't test close(0) on windows - it hangs there Message-ID: <20100518184346.724177EF6E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274208202 25200 # Node ID eb7ffb9f3553af6b41d3d27c9dafafba56885fe1 # Parent f740e5319ec4ceabc76077885f94706894ad62cc for now don't test close(0) on windows - it hangs there --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -372,6 +372,15 @@ class TestStdCaptureFDinvalidFD: os.close(2) cap = py.io.StdCaptureFD(out=False, err=True, in_=False) cap.done() + """) + result = testdir.runpytest("--capture=fd") + assert result.ret == 0 + assert result.parseoutcomes()['passed'] == 2 + + @py.test.mark.xfail("sys.platform == 'win32'", run=False) + def test_stdcapture_fd_invalid_fd_null(self, testdir): + testdir.makepyfile(""" + import py, os def test_stdin(): os.close(0) cap = py.io.StdCaptureFD(out=False, err=False, in_=True) @@ -379,7 +388,8 @@ class TestStdCaptureFDinvalidFD: """) result = testdir.runpytest("--capture=fd") assert result.ret == 0 - assert result.parseoutcomes()['passed'] == 3 + assert result.parseoutcomes()['passed'] == 1 + def test_capture_not_started_but_reset(): capsys = py.io.StdCapture(now=False) From commits-noreply at bitbucket.org Tue May 18 21:12:51 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 19:12:51 +0000 (UTC) Subject: [py-svn] py-trunk commit 10c5b10df1d4: some special handling of stdin capturing, unification, un-xfail the win32 test Message-ID: <20100518191251.BAA587EF6E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274209954 25200 # Node ID 10c5b10df1d4fce68554092457178273bb1b2d98 # Parent eb7ffb9f3553af6b41d3d27c9dafafba56885fe1 some special handling of stdin capturing, unification, un-xfail the win32 test --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -38,7 +38,7 @@ class FDCapture: in text mode. """ self.targetfd = targetfd - if tmpfile is None: + if tmpfile is None and targetfd != 0: f = tempfile.TemporaryFile('wb+') tmpfile = dupfile(f, encoding="UTF-8") f.close() @@ -55,16 +55,24 @@ class FDCapture: except OSError: raise ValueError("saved filedescriptor not valid, " "did you call start() twice?") - os.dup2(self.tmpfile.fileno(), self.targetfd) - if hasattr(self, '_oldsys'): - setattr(sys, patchsysdict[self.targetfd], self.tmpfile) + if self.targetfd == 0 and not self.tmpfile: + fd = os.open(devnullpath, os.O_RDONLY) + os.dup2(fd, 0) + if hasattr(self, '_oldsys'): + setattr(sys, patchsysdict[self.targetfd], DontReadFromInput()) + else: + fd = self.tmpfile.fileno() + os.dup2(self.tmpfile.fileno(), self.targetfd) + if hasattr(self, '_oldsys'): + setattr(sys, patchsysdict[self.targetfd], self.tmpfile) def done(self): """ unpatch and clean up, returns the self.tmpfile (file object) """ os.dup2(self._savefd, self.targetfd) os.close(self._savefd) - self.tmpfile.seek(0) + if self.targetfd != 0: + self.tmpfile.seek(0) if hasattr(self, '_oldsys'): setattr(sys, patchsysdict[self.targetfd], self._oldsys) return self.tmpfile @@ -184,15 +192,9 @@ class StdCaptureFD(Capture): mixed = self._options['mixed'] patchsys = self._options['patchsys'] if in_: - if hasattr(in_, 'read'): - tmpfile = in_ - else: - fd = os.open(devnullpath, os.O_RDONLY) - tmpfile = os.fdopen(fd) try: - self.in_ = FDCapture(0, tmpfile=tmpfile, now=False, + self.in_ = FDCapture(0, tmpfile=None, now=False, patchsys=patchsys) - self._options['in_'] = self.in_.tmpfile except OSError: pass if out: @@ -222,14 +224,10 @@ class StdCaptureFD(Capture): def startall(self): if hasattr(self, 'in_'): self.in_.start() - sys.stdin = DontReadFromInput() - - out = getattr(self, 'out', None) - if out: - out.start() - err = getattr(self, 'err', None) - if err: - err.start() + if hasattr(self, 'out'): + self.out.start() + if hasattr(self, 'err'): + self.err.start() def resume(self): """ resume capturing with original temp files. """ --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -372,15 +372,6 @@ class TestStdCaptureFDinvalidFD: os.close(2) cap = py.io.StdCaptureFD(out=False, err=True, in_=False) cap.done() - """) - result = testdir.runpytest("--capture=fd") - assert result.ret == 0 - assert result.parseoutcomes()['passed'] == 2 - - @py.test.mark.xfail("sys.platform == 'win32'", run=False) - def test_stdcapture_fd_invalid_fd_null(self, testdir): - testdir.makepyfile(""" - import py, os def test_stdin(): os.close(0) cap = py.io.StdCaptureFD(out=False, err=False, in_=True) @@ -388,8 +379,7 @@ class TestStdCaptureFDinvalidFD: """) result = testdir.runpytest("--capture=fd") assert result.ret == 0 - assert result.parseoutcomes()['passed'] == 1 - + assert result.parseoutcomes()['passed'] == 3 def test_capture_not_started_but_reset(): capsys = py.io.StdCapture(now=False) From commits-noreply at bitbucket.org Tue May 18 21:22:21 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 18 May 2010 19:22:21 +0000 (UTC) Subject: [py-svn] py-trunk commit 1940650af8d4: a crucial close() to prevent too-many-open-files Message-ID: <20100518192221.2B4377EF6E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274210720 -7200 # Node ID 1940650af8d4761e9b36bb49bc99b57064fc2941 # Parent 10c5b10df1d4fce68554092457178273bb1b2d98 a crucial close() to prevent too-many-open-files --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -58,6 +58,7 @@ class FDCapture: if self.targetfd == 0 and not self.tmpfile: fd = os.open(devnullpath, os.O_RDONLY) os.dup2(fd, 0) + os.close(fd) if hasattr(self, '_oldsys'): setattr(sys, patchsysdict[self.targetfd], DontReadFromInput()) else: From commits-noreply at bitbucket.org Wed May 19 16:19:56 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 19 May 2010 14:19:56 +0000 (UTC) Subject: [py-svn] py-trunk commit 40db28345096: fix issue95 - treat a failing pytest_genscript import Message-ID: <20100519141956.BBDDD7F057@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274278943 -7200 # Node ID 40db2834509658ebb5c7c5881e7cc115fc14a1e3 # Parent 1940650af8d4761e9b36bb49bc99b57064fc2941 fix issue95 - treat a failing pytest_genscript import as non-critical, give a hint. --- a/testing/plugin/test_pytest_genscript.py +++ b/testing/plugin/test_pytest_genscript.py @@ -37,3 +37,4 @@ def test_rundist(testdir, pytestconfig, result.stdout.fnmatch_lines([ "*1 passed*", ]) + --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -140,6 +140,13 @@ class PluginManager(object): except py.test.skip.Exception: e = py.std.sys.exc_info()[1] self._hints.append("skipped plugin %r: %s" %((modname, e.msg))) + except ImportError: + e = py.std.sys.exc_info()[1] + if "zlib" in str(e) and modname == "pytest_genscript": + self._hints.append("skipped plugin %r: failed to import %r" %( + (modname, str(e)))) + else: + raise else: check_old_use(mod, modname) self.register(mod) --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -489,3 +489,29 @@ class TestHookRelay: res = mcm.hello(arg=3) assert res == 4 +def test_pluginmanager_import_error_zlib(testdir): + p = testdir.makepyfile(""" + try: + import builtins + except ImportError: + import __builtin__ as builtins + oldimport = builtins.__import__ + + def import_(name, *args): + #print "import", name, "start" + if name == "zlib": + raise ImportError("zlib") + mod = oldimport(name, *args) + #print "import", name, "successful" + return mod + + if __name__ == "__main__": + builtins.__import__ = import_ + import py + py.test.cmdline.main(["--traceconfig"]) + """) + result = testdir.runpython(p) + result.stdout.fnmatch_lines([ + "*skipped plugin*genscript*import*zlib*", + ]) + From commits-noreply at bitbucket.org Wed May 19 16:39:20 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 19 May 2010 14:39:20 +0000 (UTC) Subject: [py-svn] py-trunk commit d7b613d30cc4: revert 1735 - fix issue95 differently: just shift the offending zlib Message-ID: <20100519143920.F10337F05E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274280142 -7200 # Node ID d7b613d30cc45b0702056f8cdfcb3413b3104ecf # Parent 40db2834509658ebb5c7c5881e7cc115fc14a1e3 revert 1735 - fix issue95 differently: just shift the offending zlib import (and others) to happen when they are actually needed --- a/testing/plugin/test_pytest_genscript.py +++ b/testing/plugin/test_pytest_genscript.py @@ -37,4 +37,3 @@ def test_rundist(testdir, pytestconfig, result.stdout.fnmatch_lines([ "*1 passed*", ]) - --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -140,13 +140,6 @@ class PluginManager(object): except py.test.skip.Exception: e = py.std.sys.exc_info()[1] self._hints.append("skipped plugin %r: %s" %((modname, e.msg))) - except ImportError: - e = py.std.sys.exc_info()[1] - if "zlib" in str(e) and modname == "pytest_genscript": - self._hints.append("skipped plugin %r: failed to import %r" %( - (modname, str(e)))) - else: - raise else: check_old_use(mod, modname) self.register(mod) --- a/py/_plugin/pytest_genscript.py +++ b/py/_plugin/pytest_genscript.py @@ -4,14 +4,7 @@ generate standalone test script to be di """ import os -import zlib -import base64 import sys -try: - import pickle -except Importerror: - import cPickle as pickle - def pytest_addoption(parser): group = parser.getgroup("debugconfig") group.addoption("--genscript", action="store", default=None, @@ -30,6 +23,13 @@ def pytest_configure(config): raise SystemExit(0) def main(pybasedir, outfile, infile): + import base64 + import zlib + try: + import pickle + except Importerror: + import cPickle as pickle + outfile = str(outfile) infile = str(infile) assert os.path.isabs(outfile) --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -489,29 +489,3 @@ class TestHookRelay: res = mcm.hello(arg=3) assert res == 4 -def test_pluginmanager_import_error_zlib(testdir): - p = testdir.makepyfile(""" - try: - import builtins - except ImportError: - import __builtin__ as builtins - oldimport = builtins.__import__ - - def import_(name, *args): - #print "import", name, "start" - if name == "zlib": - raise ImportError("zlib") - mod = oldimport(name, *args) - #print "import", name, "successful" - return mod - - if __name__ == "__main__": - builtins.__import__ = import_ - import py - py.test.cmdline.main(["--traceconfig"]) - """) - result = testdir.runpython(p) - result.stdout.fnmatch_lines([ - "*skipped plugin*genscript*import*zlib*", - ]) - --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,9 @@ Changes between 1.3.0 and 1.3.1 to the underlying capturing functionality to avoid race conditions). +- fix issue95: late-import zlib so that it's not required + for general py.test startup. + - make py.test.cmdline.main() return the exitstatus instead of raising (which is still done by py.cmdline.pytest()) and make it so that py.test.cmdline.main() can be called From commits-noreply at bitbucket.org Wed May 19 16:49:00 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 19 May 2010 14:49:00 +0000 (UTC) Subject: [py-svn] py-trunk commit ecf47488171a: fix and test "-rP" option to show xpass-test ids Message-ID: <20100519144900.6F7657F060@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274280723 -7200 # Node ID ecf47488171a52cc2eb3e7b4a8735c998ae307a5 # Parent d7b613d30cc45b0702056f8cdfcb3413b3104ecf fix and test "-rP" option to show xpass-test ids --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -280,3 +280,24 @@ def test_skipped_reasons_functional(test ]) assert result.ret == 0 +def test_reportchars(testdir): + testdir.makepyfile(""" + import py + def test_1(): + assert 0 + @py.test.mark.xfail + def test_2(): + assert 0 + @py.test.mark.xfail + def test_3(): + pass + def test_4(): + py.test.skip("four") + """) + result = testdir.runpytest("-rfxPs") + result.stdout.fnmatch_lines([ + "FAIL*test_1*", + "XFAIL*test_2*", + "XPASS*test_3*", + "SKIP*four*", + ]) --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -234,7 +234,7 @@ def pytest_terminal_summary(terminalrepo for char in tr.reportchars: if char == "x": show_xfailed(terminalreporter, lines) - elif char == "X": + elif char == "P": show_xpassed(terminalreporter, lines) elif char == "f": show_failed(terminalreporter, lines) From commits-noreply at bitbucket.org Wed May 19 17:23:27 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 19 May 2010 15:23:27 +0000 (UTC) Subject: [py-svn] py-trunk commit cafe6568b5ae: fix wrong test invocation Message-ID: <20100519152327.E84F57F025@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274281513 -7200 # Node ID cafe6568b5aeb2a151658752867ce7143ee3b7ba # Parent ecf47488171a52cc2eb3e7b4a8735c998ae307a5 fix wrong test invocation --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -162,7 +162,7 @@ class TestXFail: def test_that(): assert 1 """) - result = testdir.runpytest(p, '-rX') + result = testdir.runpytest(p, '-rP') result.stdout.fnmatch_lines([ "*XPASS*test_that*", "*1 xpassed*" From commits-noreply at bitbucket.org Thu May 20 14:32:13 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 20 May 2010 12:32:13 +0000 (UTC) Subject: [py-svn] py-trunk commit 06a7ad82f24c: fix issue91 introduce new py.test.xfail(reason) helper Message-ID: <20100520123213.304087EEDE@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274354991 -7200 # Node ID 06a7ad82f24cf06ecb01e7c66796a1b6e0ed5dcc # Parent cafe6568b5aeb2a151658752867ce7143ee3b7ba fix issue91 introduce new py.test.xfail(reason) helper to imperatively mark a test as expected to fail. Can be used from within setup and test functions. This is useful especially for parametrized tests when certain configurations are expected-to-fail. In this case the declarative approach with the @py.test.mark.xfail cannot be used as it would mark all configurations as xfail. --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -169,6 +169,43 @@ class TestXFail: ]) assert result.ret == 1 + def test_xfail_imperative(self, testdir): + p = testdir.makepyfile(""" + import py + def test_this(): + py.test.xfail("hello") + """) + result = testdir.runpytest(p) + result.stdout.fnmatch_lines([ + "*1 xfailed*", + ]) + result = testdir.runpytest(p, "-rx") + result.stdout.fnmatch_lines([ + "*XFAIL*test_this*reason:*hello*", + ]) + + def test_xfail_imperative_in_setup_function(self, testdir): + p = testdir.makepyfile(""" + import py + def setup_function(function): + py.test.xfail("hello") + + def test_this(): + assert 0 + """) + result = testdir.runpytest(p) + result.stdout.fnmatch_lines([ + "*1 xfailed*", + ]) + result = testdir.runpytest(p, "-rx") + result.stdout.fnmatch_lines([ + "*XFAIL*test_this*reason:*hello*", + ]) + + + + + class TestSkipif: def test_skipif_conditional(self, testdir): item = testdir.getitem(""" --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -10,6 +10,7 @@ def pytest_namespace(): 'skip' : skip, 'importorskip' : importorskip, 'fail' : fail, + 'xfail' : xfail, 'exit' : exit, } @@ -295,6 +296,10 @@ class Failed(OutcomeException): """ raised from an explicit call to py.test.fail() """ __module__ = 'builtins' +class XFailed(OutcomeException): + """ raised from an explicit call to py.test.xfail() """ + __module__ = 'builtins' + class ExceptionFailure(Failed): """ raised by py.test.raises on an exception-assertion mismatch. """ def __init__(self, expr, expected, msg=None, excinfo=None): @@ -335,6 +340,14 @@ def fail(msg=""): fail.Exception = Failed +def xfail(reason=""): + """ xfail an executing test or setup functions, taking an optional + reason string. + """ + __tracebackhide__ = True + raise XFailed(reason) +xfail.Exception = XFailed + def raises(ExpectedException, *args, **kwargs): """ if args[0] is callable: raise AssertionError if calling it with the remaining arguments does not raise the expected exception. --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -185,9 +185,17 @@ def pytest_runtest_setup(item): def pytest_runtest_makereport(__multicall__, item, call): if not isinstance(item, py.test.collect.Function): return - evalxfail = getattr(item, '_evalxfail', None) - if not evalxfail: - return + if not (call.excinfo and + call.excinfo.errisinstance(py.test.xfail.Exception)): + evalxfail = getattr(item, '_evalxfail', None) + if not evalxfail: + return + if call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception): + rep = __multicall__.execute() + rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg + rep.skipped = True + rep.failed = False + return rep if call.when == "setup": rep = __multicall__.execute() if rep.skipped and evalxfail.istrue(): --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,14 @@ Changes between 1.3.0 and 1.3.1 - fix issue95: late-import zlib so that it's not required for general py.test startup. +- fix issue91: introduce new py.test.xfail(reason) helper + to imperatively mark a test as expected to fail. Can + be used from within setup and test functions. This is + useful especially for parametrized tests when certain + configurations are expected-to-fail. In this case the + declarative approach with the @py.test.mark.xfail cannot + be used as it would mark all configurations as xfail. + - make py.test.cmdline.main() return the exitstatus instead of raising (which is still done by py.cmdline.pytest()) and make it so that py.test.cmdline.main() can be called From commits-noreply at bitbucket.org Thu May 20 14:32:15 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 20 May 2010 12:32:15 +0000 (UTC) Subject: [py-svn] py-trunk commit 9eb1cef24b56: unify handling of reportcharacters across resultlog/junitxml plugins Message-ID: <20100520123215.1E1737EF7F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274358913 -7200 # Node ID 9eb1cef24b56e4f61772da8776025475ca903ac9 # Parent 06a7ad82f24cf06ecb01e7c66796a1b6e0ed5dcc unify handling of reportcharacters across resultlog/junitxml plugins --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,17 @@ Changes between 1.3.0 and 1.3.1 declarative approach with the @py.test.mark.xfail cannot be used as it would mark all configurations as xfail. +- improve and refine letter reporting in the progress bar: + . pass + f failed test + s skipped tests (reminder: use for dependency/platform mismatch only) + x xfailed test (test that was expected to fail) + X xpassed test (test that was expected to fail but passed) + + You can use any combination of 'fsxX' with the '-r' extended + reporting option. The xfail/xpass results will show up as + skipped tests in the junitxml output. + - make py.test.cmdline.main() return the exitstatus instead of raising (which is still done by py.cmdline.pytest()) and make it so that py.test.cmdline.main() can be called --- a/testing/plugin/test_pytest_junitxml.py +++ b/testing/plugin/test_pytest_junitxml.py @@ -25,11 +25,17 @@ class TestPython: assert 0 def test_skip(): py.test.skip("") + @py.test.mark.xfail + def test_xfail(): + assert 0 + @py.test.mark.xfail + def test_xpass(): + assert 1 """) result, dom = runandparse(testdir) assert result.ret node = dom.getElementsByTagName("testsuite")[0] - assert_attr(node, errors=0, failures=1, skips=1, tests=2) + assert_attr(node, errors=0, failures=1, skips=3, tests=2) def test_setup_error(self, testdir): testdir.makepyfile(""" @@ -92,6 +98,43 @@ class TestPython: assert_attr(fnode, message="test failure") assert "ValueError" in fnode.toxml() + def test_xfailure_function(self, testdir): + testdir.makepyfile(""" + import py + def test_xfail(): + py.test.xfail("42") + """) + result, dom = runandparse(testdir) + assert not result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, skips=1, tests=0) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, + classname="test_xfailure_function.test_xfailure_function", + name="test_xfail") + fnode = tnode.getElementsByTagName("skipped")[0] + assert_attr(fnode, message="expected test failure") + #assert "ValueError" in fnode.toxml() + + def test_xfailure_xpass(self, testdir): + testdir.makepyfile(""" + import py + @py.test.mark.xfail + def test_xpass(): + pass + """) + result, dom = runandparse(testdir) + #assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, skips=1, tests=0) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, + classname="test_xfailure_xpass.test_xfailure_xpass", + name="test_xpass") + fnode = tnode.getElementsByTagName("skipped")[0] + assert_attr(fnode, message="xfail-marked test passes unexpectedly") + #assert "ValueError" in fnode.toxml() + def test_collect_error(self, testdir): testdir.makepyfile("syntax error") result, dom = runandparse(testdir) --- a/py/_plugin/pytest_junitxml.py +++ b/py/_plugin/pytest_junitxml.py @@ -56,10 +56,15 @@ class LogXML(object): def append_failure(self, report): self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) - self.appendlog('%s', - report.longrepr) + if "xfail" in report.keywords: + self.appendlog( + '') + self.skipped += 1 + else: + self.appendlog('%s', + report.longrepr) + self.failed += 1 self._closetestcase() - self.failed += 1 def _opentestcase_collectfailure(self, report): node = report.collector @@ -95,7 +100,12 @@ class LogXML(object): def append_skipped(self, report): self._opentestcase(report) - self.appendlog("") + if "xfail" in report.keywords: + self.appendlog( + '%s', + report.keywords['xfail']) + else: + self.appendlog("") self._closetestcase() self.skipped += 1 --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -225,7 +225,7 @@ def pytest_report_teststatus(report): if report.skipped: return "xfailed", "x", "xfail" elif report.failed: - return "xpassed", "P", "XPASS" + return "xpassed", "X", "XPASS" # called by the terminalreporter instance/plugin def pytest_terminal_summary(terminalreporter): @@ -242,7 +242,7 @@ def pytest_terminal_summary(terminalrepo for char in tr.reportchars: if char == "x": show_xfailed(terminalreporter, lines) - elif char == "P": + elif char == "X": show_xpassed(terminalreporter, lines) elif char == "f": show_failed(terminalreporter, lines) --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -162,7 +162,7 @@ class TestXFail: def test_that(): assert 1 """) - result = testdir.runpytest(p, '-rP') + result = testdir.runpytest(p, '-rX') result.stdout.fnmatch_lines([ "*XPASS*test_that*", "*1 xpassed*" @@ -331,7 +331,7 @@ def test_reportchars(testdir): def test_4(): py.test.skip("four") """) - result = testdir.runpytest("-rfxPs") + result = testdir.runpytest("-rfxXs") result.stdout.fnmatch_lines([ "FAIL*test_1*", "XFAIL*test_2*", --- a/py/_plugin/pytest_resultlog.py +++ b/py/_plugin/pytest_resultlog.py @@ -73,7 +73,7 @@ class ResultLog(object): code = report.shortrepr if code == 'x': longrepr = str(report.longrepr) - elif code == 'P': + elif code == 'X': longrepr = '' elif report.passed: longrepr = "" --- a/testing/plugin/test_pytest_resultlog.py +++ b/testing/plugin/test_pytest_resultlog.py @@ -126,7 +126,7 @@ class TestWithFunctionIntegration: tb = "".join(lines[8:14]) assert tb.find('raise ValueError("XFAIL")') != -1 - assert lines[14].startswith('P ') + assert lines[14].startswith('X ') assert len(lines) == 15 def test_internal_exception(self): From commits-noreply at bitbucket.org Fri May 21 16:43:37 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 21 May 2010 14:43:37 +0000 (UTC) Subject: [py-svn] py-trunk commit 4f00006a2434: fix issue94 make reporting more robust against bogus source code Message-ID: <20100521144337.80F277EF8C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274452966 -7200 # Node ID 4f00006a24344b9e763e92d3558d18fc89fff456 # Parent 9eb1cef24b56e4f61772da8776025475ca903ac9 fix issue94 make reporting more robust against bogus source code (and internally be more careful when presenting unexpected byte sequences) also make py.code.Source accept a list of lines directly. --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -110,7 +110,7 @@ class TmpTestdir: def _makefile(self, ext, args, kwargs): items = list(kwargs.items()) if args: - source = "\n".join(map(str, args)) + source = "\n".join(map(str, args)) + "\n" basename = self.request.function.__name__ items.insert(0, (basename, source)) ret = None @@ -294,8 +294,10 @@ class TmpTestdir: ret = popen.wait() f1.close() f2.close() - out = p1.read("rb").decode("utf-8").splitlines() - err = p2.read("rb").decode("utf-8").splitlines() + out = p1.read("rb") + out = getdecoded(out).splitlines() + err = p2.read("rb") + err = getdecoded(err).splitlines() def dump_lines(lines, fp): try: for line in lines: @@ -360,6 +362,13 @@ class TmpTestdir: child.timeout = expect_timeout return child +def getdecoded(out): + try: + return out.decode("utf-8") + except UnicodeDecodeError: + return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( + py.io.saferepr(out),) + class PseudoPlugin: def __init__(self, vars): self.__dict__.update(vars) --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -36,6 +36,19 @@ def test_source_from_function(): source = py.code.Source(test_source_str_function) assert str(source).startswith('def test_source_str_function():') +def test_source_from_method(): + class TestClass: + def test_method(self): + pass + source = py.code.Source(TestClass().test_method) + assert source.lines == ["def test_method(self):", + " pass"] + +def test_source_from_lines(): + lines = ["a \n", "b\n", "c"] + source = py.code.Source(lines) + assert source.lines == ['a ', 'b', 'c'] + def test_source_from_inner_function(): def f(): pass @@ -92,6 +105,7 @@ def test_isparseable(): assert Source(" \nif 1:\n pass").isparseable() assert not Source("if 1:\n").isparseable() assert not Source(" \nif 1:\npass").isparseable() + assert not Source(chr(0)).isparseable() class TestAccesses: source = Source("""\ --- a/py/_code/source.py +++ b/py/_code/source.py @@ -26,6 +26,8 @@ class Source(object): partlines = [] if isinstance(part, Source): partlines = part.lines + elif isinstance(part, (tuple, list)): + partlines = [x.rstrip("\n") for x in part] elif isinstance(part, py.builtin._basestring): partlines = part.split('\n') if rstrip: @@ -172,7 +174,9 @@ class Source(object): try: #compile(source+'\n', "x", "exec") syntax_checker(source+'\n') - except SyntaxError: + except KeyboardInterrupt: + raise + except Exception: return False else: return True --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,9 @@ Changes between 1.3.0 and 1.3.1 declarative approach with the @py.test.mark.xfail cannot be used as it would mark all configurations as xfail. +- fix issue94: make reporting more robust against bogus source code + (and internally be more careful when presenting unexpected byte sequences) + - improve and refine letter reporting in the progress bar: . pass f failed test From commits-noreply at bitbucket.org Fri May 21 18:08:44 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 21 May 2010 16:08:44 +0000 (UTC) Subject: [py-svn] py-trunk commit a6cc8c8e751d: note that issue99 is also fixed Message-ID: <20100521160844.68D697EF7C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274453475 -7200 # Node ID a6cc8c8e751d15425ab899c512a8a6801f678989 # Parent 4f00006a24344b9e763e92d3558d18fc89fff456 note that issue99 is also fixed --- a/CHANGELOG +++ b/CHANGELOG @@ -29,7 +29,8 @@ Changes between 1.3.0 and 1.3.1 You can use any combination of 'fsxX' with the '-r' extended reporting option. The xfail/xpass results will show up as - skipped tests in the junitxml output. + skipped tests in the junitxml output - which also fixes + issue99. - make py.test.cmdline.main() return the exitstatus instead of raising (which is still done by py.cmdline.pytest()) From commits-noreply at bitbucket.org Fri May 21 18:08:46 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 21 May 2010 16:08:46 +0000 (UTC) Subject: [py-svn] py-trunk commit f5b6aad2ea2d: fix issue89 - allow py.test.mark decorators to be used with classes Message-ID: <20100521160846.61F3C7EF7D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274458307 -7200 # Node ID f5b6aad2ea2dc0e4822d58c1ac92cef315480d07 # Parent a6cc8c8e751d15425ab899c512a8a6801f678989 fix issue89 - allow py.test.mark decorators to be used with classes (if you are using >=python2.6) also allow to have multiple markers applied at class level and test and fix a bug with chained skip/xfail decorators: if any of the conditions is true a test will be skipped/xfailed with a explanation which condition evaluated to true. --- a/doc/test/plugin/skipping.txt +++ b/doc/test/plugin/skipping.txt @@ -65,6 +65,18 @@ for skipping all methods of a test class # The ``pytestmark`` decorator will be applied to each test function. +If your code targets python2.6 or above you can also use the +skipif decorator with classes:: + + @py.test.mark.skipif("sys.platform == 'win32'") + class TestPosixCalls: + + def test_function(self): + # will not be setup or run under 'win32' platform + # + +It is fine in both situations to use multiple "skipif" decorators +on a single function. .. _`whole class- or module level`: mark.html#scoped-marking --- a/doc/test/plugin/mark.txt +++ b/doc/test/plugin/mark.txt @@ -39,38 +39,43 @@ and later access it with ``test_receive. .. _`scoped-marking`: -Marking classes or modules +Marking whole classes or modules ---------------------------------------------------- -To mark all methods of a class set a ``pytestmark`` attribute like this:: +If you are programming with Python2.6 you may use ``py.test.mark`` decorators +with classes to apply markers to all its test methods:: + + @py.test.mark.webtest + class TestClass: + def test_startup(self): + ... + +This is equivalent to directly applying the decorator to the +``test_startup`` function. + +To remain compatible with Python2.5 you can instead set a +``pytestmark`` attribute on a TestClass like this:: import py class TestClass: pytestmark = py.test.mark.webtest -You can re-use the same markers that you would use for decorating -a function - in fact this marker decorator will be applied -to all test methods of the class. +or if you need to use multiple markers:: + + import py + + class TestClass: + pytestmark = [py.test.mark.webtest, pytest.mark.slowtest] You can also set a module level marker:: import py pytestmark = py.test.mark.webtest -in which case then the marker decorator will be applied to all functions and +in which case then it will be applied to all functions and methods defined in the module. -The order in which marker functions are called is this:: - - per-function (upon import of module already) - per-class - per-module - -Later called markers may overwrite previous key-value settings. -Positional arguments are all appended to the same 'args' list -of the Marker object. - Using "-k MARKNAME" to select tests ---------------------------------------------------- --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,14 @@ Changes between 1.3.0 and 1.3.1 to the underlying capturing functionality to avoid race conditions). +- fix issue89 - allow py.test.mark decorators to be used on classes + (class decorators were introduced with python2.6) + also allow to have multiple markers applied at class/module level + +- fix chaining of conditional skipif/xfail decorators - so it works now + as expected to use multiple @py.test.mark.skipif(condition) decorators, + including specific reporting which of the conditions lead to skipping. + - fix issue95: late-import zlib so that it's not required for general py.test startup. --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -60,6 +60,19 @@ for skipping all methods of a test class # The ``pytestmark`` decorator will be applied to each test function. +If your code targets python2.6 or above you can equivalently use +the skipif decorator on classes:: + + @py.test.mark.skipif("sys.platform == 'win32'") + class TestPosixCalls: + + def test_function(self): + # will not be setup or run under 'win32' platform + # + +It is fine in general to apply multiple "skipif" decorators +on a single function - this means that if any of the conditions +apply the function will be skipped. .. _`whole class- or module level`: mark.html#scoped-marking @@ -144,17 +157,20 @@ class MarkEvaluator: def istrue(self): if self.holder: d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config} - self.result = True - for expr in self.holder.args: - self.expr = expr - if isinstance(expr, str): - result = cached_eval(self.item.config, expr, d) - else: - result = expr - if not result: - self.result = False + if self.holder.args: + self.result = False + for expr in self.holder.args: self.expr = expr - break + if isinstance(expr, str): + result = cached_eval(self.item.config, expr, d) + else: + result = expr + if result: + self.result = True + self.expr = expr + break + else: + self.result = True return getattr(self, 'result', False) def get(self, attr, default=None): --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -52,6 +52,39 @@ class TestEvaluator: assert expl == "hello world" assert ev.get("attr") == 2 + def test_marked_one_arg_twice(self, testdir): + lines = [ + '''@py.test.mark.skipif("not hasattr(os, 'murks')")''', + '''@py.test.mark.skipif("hasattr(os, 'murks')")''' + ] + for i in range(0, 2): + item = testdir.getitem(""" + import py + %s + %s + def test_func(): + pass + """ % (lines[i], lines[(i+1) %2])) + ev = MarkEvaluator(item, 'skipif') + assert ev + assert ev.istrue() + expl = ev.getexplanation() + assert expl == "condition: not hasattr(os, 'murks')" + + def test_marked_one_arg_twice2(self, testdir): + item = testdir.getitem(""" + import py + @py.test.mark.skipif("hasattr(os, 'murks')") + @py.test.mark.skipif("not hasattr(os, 'murks')") + def test_func(): + pass + """) + ev = MarkEvaluator(item, 'skipif') + assert ev + assert ev.istrue() + expl = ev.getexplanation() + assert expl == "condition: not hasattr(os, 'murks')" + def test_skipif_class(self, testdir): item, = testdir.getitems(""" import py --- a/py/_plugin/pytest_mark.py +++ b/py/_plugin/pytest_mark.py @@ -34,38 +34,43 @@ and later access it with ``test_receive. .. _`scoped-marking`: -Marking classes or modules +Marking whole classes or modules ---------------------------------------------------- -To mark all methods of a class set a ``pytestmark`` attribute like this:: +If you are programming with Python2.6 you may use ``py.test.mark`` decorators +with classes to apply markers to all its test methods:: + + @py.test.mark.webtest + class TestClass: + def test_startup(self): + ... + +This is equivalent to directly applying the decorator to the +``test_startup`` function. + +To remain compatible with Python2.5 you can instead set a +``pytestmark`` attribute on a TestClass like this:: import py class TestClass: pytestmark = py.test.mark.webtest -You can re-use the same markers that you would use for decorating -a function - in fact this marker decorator will be applied -to all test methods of the class. +or if you need to use multiple markers:: + + import py + + class TestClass: + pytestmark = [py.test.mark.webtest, pytest.mark.slowtest] You can also set a module level marker:: import py pytestmark = py.test.mark.webtest -in which case then the marker decorator will be applied to all functions and +in which case then it will be applied to all functions and methods defined in the module. -The order in which marker functions are called is this:: - - per-function (upon import of module already) - per-class - per-module - -Later called markers may overwrite previous key-value settings. -Positional arguments are all appended to the same 'args' list -of the Marker object. - Using "-k MARKNAME" to select tests ---------------------------------------------------- @@ -105,15 +110,23 @@ class MarkDecorator: """ if passed a single callable argument: decorate it with mark info. otherwise add *args/**kwargs in-place to mark information. """ if args: - if len(args) == 1 and hasattr(args[0], '__call__'): - func = args[0] - holder = getattr(func, self.markname, None) - if holder is None: - holder = MarkInfo(self.markname, self.args, self.kwargs) - setattr(func, self.markname, holder) + func = args[0] + if len(args) == 1 and hasattr(func, '__call__') or \ + hasattr(func, '__bases__'): + if hasattr(func, '__bases__'): + l = func.__dict__.setdefault("pytestmark", []) + if not isinstance(l, list): + func.pytestmark = [l, self] + else: + l.append(self) else: - holder.kwargs.update(self.kwargs) - holder.args.extend(self.args) + holder = getattr(func, self.markname, None) + if holder is None: + holder = MarkInfo(self.markname, self.args, self.kwargs) + setattr(func, self.markname, holder) + else: + holder.kwargs.update(self.kwargs) + holder.args.extend(self.args) return func else: self.args.extend(args) @@ -147,6 +160,10 @@ def pytest_pycollect_makeitem(__multical func = getattr(func, 'im_func', func) # py2 for parent in [x for x in (mod, cls) if x]: marker = getattr(parent.obj, 'pytestmark', None) - if isinstance(marker, MarkDecorator): - marker(func) + if marker is not None: + if not isinstance(marker, list): + marker = [marker] + for mark in marker: + if isinstance(mark, MarkDecorator): + mark(func) return item --- a/testing/plugin/test_pytest_mark.py +++ b/testing/plugin/test_pytest_mark.py @@ -68,11 +68,41 @@ class TestFunctional: keywords = item.readkeywords() assert 'hello' in keywords - def test_mark_per_class(self, testdir): + def test_marklist_per_class(self, testdir): modcol = testdir.getmodulecol(""" import py class TestClass: - pytestmark = py.test.mark.hello + pytestmark = [py.test.mark.hello, py.test.mark.world] + def test_func(self): + assert TestClass.test_func.hello + assert TestClass.test_func.world + """) + clscol = modcol.collect()[0] + item = clscol.collect()[0].collect()[0] + keywords = item.readkeywords() + assert 'hello' in keywords + + def test_marklist_per_module(self, testdir): + modcol = testdir.getmodulecol(""" + import py + pytestmark = [py.test.mark.hello, py.test.mark.world] + class TestClass: + def test_func(self): + assert TestClass.test_func.hello + assert TestClass.test_func.world + """) + clscol = modcol.collect()[0] + item = clscol.collect()[0].collect()[0] + keywords = item.readkeywords() + assert 'hello' in keywords + assert 'world' in keywords + + @py.test.mark.skipif("sys.version_info < (2,6)") + def test_mark_per_class_decorator(self, testdir): + modcol = testdir.getmodulecol(""" + import py + @py.test.mark.hello + class TestClass: def test_func(self): assert TestClass.test_func.hello """) @@ -81,6 +111,23 @@ class TestFunctional: keywords = item.readkeywords() assert 'hello' in keywords + @py.test.mark.skipif("sys.version_info < (2,6)") + def test_mark_per_class_decorator_plus_existing_dec(self, testdir): + modcol = testdir.getmodulecol(""" + import py + @py.test.mark.hello + class TestClass: + pytestmark = py.test.mark.world + def test_func(self): + assert TestClass.test_func.hello + assert TestClass.test_func.world + """) + clscol = modcol.collect()[0] + item = clscol.collect()[0].collect()[0] + keywords = item.readkeywords() + assert 'hello' in keywords + assert 'world' in keywords + def test_merging_markers(self, testdir): p = testdir.makepyfile(""" import py From commits-noreply at bitbucket.org Sat May 22 10:09:36 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 08:09:36 +0000 (UTC) Subject: [py-svn] py-trunk commit bda43cc4d88e: update docs: mention that py.test.xfail is there Message-ID: <20100522080936.4B9987EF63@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274458576 -7200 # Node ID bda43cc4d88ed9108f0452fd4a6b775eaabe670e # Parent f5b6aad2ea2dc0e4822d58c1ac92cef315480d07 update docs: mention that py.test.xfail is there --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -106,6 +106,17 @@ To specify an explicit reason to be show @py.test.mark.xfail(..., reason="my reason") +imperative xfail from within a test or setup function +------------------------------------------------------ + +If you cannot declare xfail-conditions at import time +you can also imperatively produce an XFail-outcome from +within test or setup code. Example:: + + def test_function(): + if not valid_config(): + py.test.xfail("unsuppored configuration") + skipping on a missing import dependency -------------------------------------------------- From commits-noreply at bitbucket.org Sat May 22 10:09:38 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 08:09:38 +0000 (UTC) Subject: [py-svn] py-trunk commit 665908f06c93: fix for python3 - class.__dict__ is now a dict_proxy which doesn't have setdefault() anymore. Message-ID: <20100522080938.2C6B47EF7E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274515977 -7200 # Node ID 665908f06c93ee034bf27b13336091a2e230563b # Parent bda43cc4d88ed9108f0452fd4a6b775eaabe670e fix for python3 - class.__dict__ is now a dict_proxy which doesn't have setdefault() anymore. --- a/py/_plugin/pytest_mark.py +++ b/py/_plugin/pytest_mark.py @@ -44,11 +44,13 @@ with classes to apply markers to all its class TestClass: def test_startup(self): ... + def test_startup_and_more(self): + ... This is equivalent to directly applying the decorator to the -``test_startup`` function. +two test functions. -To remain compatible with Python2.5 you can instead set a +To remain compatible with Python2.5 you can also set a ``pytestmark`` attribute on a TestClass like this:: import py @@ -56,7 +58,7 @@ To remain compatible with Python2.5 you class TestClass: pytestmark = py.test.mark.webtest -or if you need to use multiple markers:: +or if you need to use multiple markers you can use a list:: import py @@ -68,7 +70,7 @@ You can also set a module level marker:: import py pytestmark = py.test.mark.webtest -in which case then it will be applied to all functions and +in which case it will be applied to all functions and methods defined in the module. Using "-k MARKNAME" to select tests @@ -114,11 +116,14 @@ class MarkDecorator: if len(args) == 1 and hasattr(func, '__call__') or \ hasattr(func, '__bases__'): if hasattr(func, '__bases__'): - l = func.__dict__.setdefault("pytestmark", []) - if not isinstance(l, list): - func.pytestmark = [l, self] - else: - l.append(self) + if hasattr(func, 'pytestmark'): + l = func.pytestmark + if not isinstance(l, list): + func.pytestmark = [l, self] + else: + l.append(self) + else: + func.pytestmark = [self] else: holder = getattr(func, self.markname, None) if holder is None: From commits-noreply at bitbucket.org Sat May 22 16:15:51 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 14:15:51 +0000 (UTC) Subject: [py-svn] py-trunk commit dee5ceb16f70: terser reporting header Message-ID: <20100522141551.0A4487EEF7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274529541 -7200 # Node ID dee5ceb16f705c3e83fd432a71bbf1020252496a # Parent 665908f06c93ee034bf27b13336091a2e230563b terser reporting header --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -525,7 +525,7 @@ class TestTerminalFunctional: verinfo = ".".join(map(str, py.std.sys.version_info[:3])) result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "python: platform %s -- Python %s*" %( + "platform %s -- Python %s*" %( py.std.sys.platform, verinfo), # , py.std.sys.executable), "*test_header_trailer_info.py .", "=* 1 passed in *.[0-9][0-9] seconds *=", --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -284,7 +284,7 @@ class TerminalReporter: self._sessionstarttime = py.std.time.time() verinfo = ".".join(map(str, sys.version_info[:3])) - msg = "python: platform %s -- Python %s" % (sys.platform, verinfo) + msg = "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): msg += " -- " + str(sys.executable) From commits-noreply at bitbucket.org Sat May 22 16:16:01 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 14:16:01 +0000 (UTC) Subject: [py-svn] py-trunk commit 998d3278c0e1: refine and structure CHANGELOG Message-ID: <20100522141601.4E6437EF84@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274530381 -7200 # Node ID 998d3278c0e1425e618d0ea22b681048acdb2252 # Parent dee5ceb16f705c3e83fd432a71bbf1020252496a refine and structure CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,23 +1,10 @@ Changes between 1.3.0 and 1.3.1 ================================================== -- fix issue96: make capturing more resilient against Control-C - interruptions (involved somewhat substantial refactoring - to the underlying capturing functionality to avoid race - conditions). +New features +++++++++++++++++++ -- fix issue89 - allow py.test.mark decorators to be used on classes - (class decorators were introduced with python2.6) - also allow to have multiple markers applied at class/module level - -- fix chaining of conditional skipif/xfail decorators - so it works now - as expected to use multiple @py.test.mark.skipif(condition) decorators, - including specific reporting which of the conditions lead to skipping. - -- fix issue95: late-import zlib so that it's not required - for general py.test startup. - -- fix issue91: introduce new py.test.xfail(reason) helper +- issue91: introduce new py.test.xfail(reason) helper to imperatively mark a test as expected to fail. Can be used from within setup and test functions. This is useful especially for parametrized tests when certain @@ -25,8 +12,10 @@ Changes between 1.3.0 and 1.3.1 declarative approach with the @py.test.mark.xfail cannot be used as it would mark all configurations as xfail. -- fix issue94: make reporting more robust against bogus source code - (and internally be more careful when presenting unexpected byte sequences) +- issue89: allow py.test.mark decorators to be used on classes + (class decorators were introduced with python2.6) and + also allow to have multiple markers applied at class/module level + by specifying a list. - improve and refine letter reporting in the progress bar: . pass @@ -40,12 +29,13 @@ Changes between 1.3.0 and 1.3.1 skipped tests in the junitxml output - which also fixes issue99. -- make py.test.cmdline.main() return the exitstatus - instead of raising (which is still done by py.cmdline.pytest()) - and make it so that py.test.cmdline.main() can be called - multiple times, at least as far as py.test's internal - state is concerned - previously it would raise an exception - on the second time. +- make py.test.cmdline.main() return the exitstatus instead of raising + SystemExit and also allow it to be called multiple times. This of + course requires that your application and tests are properly teared + down and don't have global state. + +Fixes / Maintenance +++++++++++++++++++++++ - improve tracebacks presentation: - raises shows shorter more relevant tracebacks @@ -53,7 +43,23 @@ Changes between 1.3.0 and 1.3.1 - improve support for raises and other dynamically compiled code by manipulating python's linecache.cache instead of the previous rather hacky way of creating custom code objects. This makes - it seemlessly work on Jython and PyPy at least. + it seemlessly work on Jython and PyPy where it previously didn't. + +- fix issue96: make capturing more resilient against Control-C + interruptions (involved somewhat substantial refactoring + to the underlying capturing functionality to avoid race + conditions). + +- fix chaining of conditional skipif/xfail decorators - so it works now + as expected to use multiple @py.test.mark.skipif(condition) decorators, + including specific reporting which of the conditions lead to skipping. + +- fix issue95: late-import zlib so that it's not required + for general py.test startup. + +- fix issue94: make reporting more robust against bogus source code + (and internally be more careful when presenting unexpected byte sequences) + Changes between 1.2.1 and 1.3.0 ================================================== From commits-noreply at bitbucket.org Sat May 22 16:16:05 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 14:16:05 +0000 (UTC) Subject: [py-svn] py-trunk commit bb9c3511e28c: * improve and test --tb=short reporting Message-ID: <20100522141605.745807EF85@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274537904 -7200 # Node ID bb9c3511e28c0b886df4ddc31573e105aea08cf4 # Parent 998d3278c0e1425e618d0ea22b681048acdb2252 * improve and test --tb=short reporting * show --tb=short tracebacks for importing test modules --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -188,7 +188,11 @@ class CollectReport(BaseReport): self.passed = True self.result = result else: - self.longrepr = self.collector._repr_failure_py(excinfo) + style = "short" + if collector.config.getvalue("fulltrace"): + style = "long" + self.longrepr = self.collector._repr_failure_py(excinfo, + style=style) if excinfo.errisinstance(py.test.skip.Exception): self.skipped = True self.reason = str(excinfo.value) --- a/CHANGELOG +++ b/CHANGELOG @@ -37,7 +37,9 @@ New features Fixes / Maintenance ++++++++++++++++++++++ -- improve tracebacks presentation: +- improved traceback presentation: + - improved and unified reporting for "--tb=short" option + - Errors during test module imports are much shorter, (using --tb=short style) - raises shows shorter more relevant tracebacks - improve support for raises and other dynamically compiled code by --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -626,6 +626,35 @@ def test_terminalreporter_reportopt_conf "*1 passed*" ]) +def test_tbstyle_short(testdir): + p = testdir.makepyfile(""" + def pytest_funcarg__arg(request): + return 42 + def test_opt(arg): + x = 0 + assert x + """) + result = testdir.runpytest("--tb=short") + s = result.stdout.str() + assert 'arg = 42' not in s + assert 'x = 0' not in s + result.stdout.fnmatch_lines([ + "*%s:5*" % p.basename, + ">*assert x", + "E*assert*", + ]) + result = testdir.runpytest() + s = result.stdout.str() + assert 'x = 0' in s + assert 'assert x' in s + +def test_trace_reporting(testdir): + result = testdir.runpytest("--traceconfig") + result.stdout.fnmatch_lines([ + "*active plugins*" + ]) + assert result.ret == 0 + def test_trace_reporting(testdir): result = testdir.runpytest("--traceconfig") result.stdout.fnmatch_lines([ --- a/py/_code/code.py +++ b/py/_code/code.py @@ -416,7 +416,7 @@ class FormattedExcinfo(object): args.append((argname, self._saferepr(argvalue))) return ReprFuncArgs(args) - def get_source(self, source, line_index=-1, excinfo=None): + def get_source(self, source, line_index=-1, excinfo=None, short=False): """ return formatted and marked up source lines. """ lines = [] if source is None: @@ -428,6 +428,8 @@ class FormattedExcinfo(object): if i == line_index: prefix = self.flow_marker + " " else: + if short: + continue prefix = " " line = prefix + source[i] lines.append(line) @@ -482,24 +484,26 @@ class FormattedExcinfo(object): line_index = entry.lineno - max(entry.getfirstlinesource(), 0) lines = [] - if self.style == "long": - reprargs = self.repr_args(entry) - lines.extend(self.get_source(source, line_index, excinfo)) - message = excinfo and excinfo.typename or "" + if self.style in ("short", "long"): + short = self.style == "short" + reprargs = None + if not short: + reprargs = self.repr_args(entry) + s = self.get_source(source, line_index, excinfo, short=short) + lines.extend(s) + if short: + message = "in %s" %(entry.name) + else: + message = excinfo and excinfo.typename or "" path = self._makepath(entry.path) filelocrepr = ReprFileLocation(path, entry.lineno+1, message) - localsrepr = self.repr_locals(entry.locals) - return ReprEntry(lines, reprargs, localsrepr, filelocrepr) - else: - if self.style == "short": - line = source[line_index].lstrip() - basename = os.path.basename(entry.frame.code.filename) - lines.append(' File "%s", line %d, in %s' % ( - basename, entry.lineno+1, entry.name)) - lines.append(" " + line) - if excinfo: - lines.extend(self.get_exconly(excinfo, indent=4)) - return ReprEntry(lines, None, None, None) + localsrepr = None + if not short: + localsrepr = self.repr_locals(entry.locals) + return ReprEntry(lines, reprargs, localsrepr, filelocrepr, short) + if excinfo: + lines.extend(self.get_exconly(excinfo, indent=4)) + return ReprEntry(lines, None, None, None, False) def _makepath(self, path): if not self.abspath: @@ -595,13 +599,21 @@ class ReprTraceback(TerminalRepr): class ReprEntry(TerminalRepr): localssep = "_ " - def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr): + def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, short): self.lines = lines self.reprfuncargs = reprfuncargs self.reprlocals = reprlocals self.reprfileloc = filelocrepr + self.short = short def toterminal(self, tw): + if self.short: + self.reprfileloc.toterminal(tw) + for line in self.lines: + red = line.startswith("E ") + tw.line(line, bold=True, red=red) + #tw.line("") + return if self.reprfuncargs: self.reprfuncargs.toterminal(tw) for line in self.lines: --- a/testing/test_pycollect.py +++ b/testing/test_pycollect.py @@ -484,3 +484,28 @@ class TestTracebackCutting: assert out.find("conftest.py:2: ValueError") != -1 numentries = out.count("_ _ _ _") # separator for traceback entries assert numentries >3 + + def test_traceback_error_during_import(self, testdir): + testdir.makepyfile(""" + x = 1 + x = 2 + x = 17 + asd + """) + result = testdir.runpytest() + assert result.ret != 0 + out = result.stdout.str() + assert "x = 1" not in out + assert "x = 2" not in out + result.stdout.fnmatch_lines([ + ">*asd*", + "E*NameError*", + ]) + result = testdir.runpytest("--fulltrace") + out = result.stdout.str() + assert "x = 1" in out + assert "x = 2" in out + result.stdout.fnmatch_lines([ + ">*asd*", + "E*NameError*", + ]) --- a/py/_test/pycollect.py +++ b/py/_test/pycollect.py @@ -253,7 +253,7 @@ class FunctionMixin(PyobjMixin): traceback = ntraceback.filter() return traceback - def _repr_failure_py(self, excinfo): + def _repr_failure_py(self, excinfo, style="long"): if excinfo.errisinstance(funcargs.FuncargRequest.LookupError): fspath, lineno, msg = self.reportinfo() lines, _ = inspect.getsourcelines(self.obj) @@ -261,11 +261,13 @@ class FunctionMixin(PyobjMixin): if line.strip().startswith('def'): return FuncargLookupErrorRepr(fspath, lineno, lines[:i+1], str(excinfo.value)) - return super(FunctionMixin, self)._repr_failure_py(excinfo) + return super(FunctionMixin, self)._repr_failure_py(excinfo, + style=style) def repr_failure(self, excinfo, outerr=None): assert outerr is None, "XXX outerr usage is deprecated" - return self._repr_failure_py(excinfo) + return self._repr_failure_py(excinfo, + style=self.config.getvalue("tbstyle")) shortfailurerepr = "F" --- a/py/_test/collect.py +++ b/py/_test/collect.py @@ -172,14 +172,15 @@ class Node(object): def _prunetraceback(self, traceback): return traceback - def _repr_failure_py(self, excinfo): + def _repr_failure_py(self, excinfo, style=None): excinfo.traceback = self._prunetraceback(excinfo.traceback) # XXX should excinfo.getrepr record all data and toterminal() # process it? - if self.config.option.tbstyle == "short": - style = "short" - else: - style = "long" + if style is None: + if self.config.option.tbstyle == "short": + style = "short" + else: + style = "long" return excinfo.getrepr(funcargs=True, showlocals=self.config.option.showlocals, style=style) --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -463,16 +463,18 @@ raise ValueError() reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) lines = reprtb.lines basename = py.path.local(mod.__file__).basename - assert lines[0] == ' File "%s", line 5, in entry' % basename - assert lines[1] == ' func1()' + assert lines[0] == '> func1()' + assert basename in str(reprtb.reprfileloc.path) + assert reprtb.reprfileloc.lineno == 5 # test last entry p = FormattedExcinfo(style="short") reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) lines = reprtb.lines - assert lines[0] == ' File "%s", line 3, in func1' % basename - assert lines[1] == ' raise ValueError("hello")' - assert lines[2] == 'E ValueError: hello' + assert lines[0] == '> raise ValueError("hello")' + assert lines[1] == 'E ValueError: hello' + assert basename in str(reprtb.reprfileloc.path) + assert reprtb.reprfileloc.lineno == 3 def test_repr_tracebackentry_no(self, importasmod): mod = importasmod(""" @@ -525,12 +527,10 @@ raise ValueError() last_lines = last_reprtb.lines monkeypatch.undo() basename = py.path.local(mod.__file__).basename - assert lines[0] == ' File "%s", line 5, in entry' % basename - assert lines[1] == ' func1()' + assert lines[0] == '> func1()' - assert last_lines[0] == ' File "%s", line 3, in func1' % basename - assert last_lines[1] == ' raise ValueError("hello")' - assert last_lines[2] == 'E ValueError: hello' + assert last_lines[0] == '> raise ValueError("hello")' + assert last_lines[1] == 'E ValueError: hello' def test_repr_traceback_and_excinfo(self, importasmod): mod = importasmod(""" From commits-noreply at bitbucket.org Sat May 22 17:17:01 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 15:17:01 +0000 (UTC) Subject: [py-svn] py-trunk commit 2315e94e1bb8: when --runxfail is supplied also show tracebacks when running a test that Message-ID: <20100522151701.616B27EF0C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274540929 -7200 # Node ID 2315e94e1bb8202743844ed12654556f3b0e0dc4 # Parent bb9c3511e28c0b886df4ddc31573e105aea08cf4 when --runxfail is supplied also show tracebacks when running a test that calls py.test.xfail --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -216,6 +216,11 @@ class TestXFail: result.stdout.fnmatch_lines([ "*XFAIL*test_this*reason:*hello*", ]) + result = testdir.runpytest(p, "--runxfail") + result.stdout.fnmatch_lines([ + "*def test_this():*", + "*py.test.xfail*", + ]) def test_xfail_imperative_in_setup_function(self, testdir): p = testdir.makepyfile(""" @@ -234,6 +239,11 @@ class TestXFail: result.stdout.fnmatch_lines([ "*XFAIL*test_this*reason:*hello*", ]) + result = testdir.runpytest(p, "--runxfail") + result.stdout.fnmatch_lines([ + "*def setup_function(function):*", + "*py.test.xfail*", + ]) --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -218,11 +218,12 @@ def pytest_runtest_makereport(__multical if not evalxfail: return if call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception): - rep = __multicall__.execute() - rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg - rep.skipped = True - rep.failed = False - return rep + if not item.config.getvalue("runxfail"): + rep = __multicall__.execute() + rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg + rep.skipped = True + rep.failed = False + return rep if call.when == "setup": rep = __multicall__.execute() if rep.skipped and evalxfail.istrue(): From commits-noreply at bitbucket.org Sat May 22 17:17:24 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 15:17:24 +0000 (UTC) Subject: [py-svn] py-trunk commit c3f32c3ed8a4: py-1.3.1 release prep and version bumping Message-ID: <20100522151724.DD5CF7EF0C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274541090 -7200 # Node ID c3f32c3ed8a4f63b58711cbe9e696034b90160e7 # Parent 2315e94e1bb8202743844ed12654556f3b0e0dc4 py-1.3.1 release prep and version bumping --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= '1.3.1a1', + version= '1.3.1', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], --- /dev/null +++ b/doc/announce/release-1.3.1.txt @@ -0,0 +1,89 @@ +py.test/pylib 1.3.1: new py.test.xfail, better reporting, fixes +=========================================================================== + +The 1.3.1 release introduces several bug fixes and brings shorter, +more concise tracebacks in some cases where it displayed a lot of info. +It also further improves compatibility on Jython and PyPy where now virtually +all py.test features are supported. If you used 1.2.1 or 1.3.0 you should +be able to upgrade to 1.3.1 without changes to your test source code. +See the below CHANGELOG entry below for more details and +http://pylib.org/install.html for installation instructions. + +py.test is an advanced automated testing tool working with Python2, +Python3, Jython and PyPy versions on all major operating systems. It +offers a no-boilerplate testing approach and has inspired other testing +tools and enhancements in the standard Python library for more than five +years. It has a simple and extensive plugin architecture, configurable +reporting and provides unique ways to make it fit to your testing +process and needs. + +See http://pytest.org for more info. + +cheers and have fun, + +holger krekel + +Changes between 1.3.0 and 1.3.1 +================================================== + +New features +++++++++++++++++++ + +- issue91: introduce new py.test.xfail(reason) helper + to imperatively mark a test as expected to fail. Can + be used from within setup and test functions. This is + useful especially for parametrized tests when certain + configurations are expected-to-fail. In this case the + declarative approach with the @py.test.mark.xfail cannot + be used as it would mark all configurations as xfail. + +- issue89: allow py.test.mark decorators to be used on classes + (class decorators were introduced with python2.6) and + also allow to have multiple markers applied at class/module level + by specifying a list. + +- improve and refine letter reporting in the progress bar: + . pass + f failed test + s skipped tests (reminder: use for dependency/platform mismatch only) + x xfailed test (test that was expected to fail) + X xpassed test (test that was expected to fail but passed) + + You can use any combination of 'fsxX' with the '-r' extended + reporting option. The xfail/xpass results will show up as + skipped tests in the junitxml output - which also fixes + issue99. + +- make py.test.cmdline.main() return the exitstatus instead of raising + SystemExit and also allow it to be called multiple times. This of + course requires that your application and tests are properly teared + down and don't have global state. + +Fixes / Maintenance +++++++++++++++++++++++ + +- improved traceback presentation: + - improved and unified reporting for "--tb=short" option + - Errors during test module imports are much shorter, (using --tb=short style) + - raises shows shorter more relevant tracebacks + +- improve support for raises and other dynamically compiled code by + manipulating python's linecache.cache instead of the previous + rather hacky way of creating custom code objects. This makes + it seemlessly work on Jython and PyPy where it previously didn't. + +- fix issue96: make capturing more resilient against Control-C + interruptions (involved somewhat substantial refactoring + to the underlying capturing functionality to avoid race + conditions). + +- fix chaining of conditional skipif/xfail decorators - so it works now + as expected to use multiple @py.test.mark.skipif(condition) decorators, + including specific reporting which of the conditions lead to skipping. + +- fix issue95: late-import zlib so that it's not required + for general py.test startup. + +- fix issue94: make reporting more robust against bogus source code + (and internally be more careful when presenting unexpected byte sequences) + --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -__version__ = version = "1.3.1a1" +__version__ = version = "1.3.1" import py.apipkg From commits-noreply at bitbucket.org Sat May 22 17:56:23 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 22 May 2010 15:56:23 +0000 (UTC) Subject: [py-svn] pytest-coverage commit a0eb9054e60a: fixing tests, updating README and pytest_coverage module doc string Message-ID: <20100522155623.A8D3E7EF86@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-coverage # URL http://bitbucket.org/prologic/pytest-coverage/overview # User holger krekel # Date 1274543343 -7200 # Node ID a0eb9054e60abded2f86bf051f9d819b03449a7e # Parent 49dc7ecad578e96e53fbf1e96161fc24897400db fixing tests, updating README and pytest_coverage module doc string --- a/test_pytest_coverage.py +++ b/test_pytest_coverage.py @@ -18,7 +18,7 @@ def test_functional(testdir): print type(result.stdout) print repr(result.stdout) print - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ '*Processing Coverage*', 'test_functional*4*3*75%*', ]) @@ -31,7 +31,7 @@ def test_coverage_is_not_imported(testdi """) result = testdir.runpytest() assert result.ret == 0 - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ '*1 passed*', ]) @@ -45,7 +45,7 @@ def test_cover_package(testdir): result = testdir.runpytest('--cover=test_cover_package', '--cover-report=report') assert result.ret == 0 - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ '*Processing Coverage*', 'test_cover_package*4*3*75%*', ]) --- a/README.txt +++ b/README.txt @@ -7,12 +7,8 @@ issue:: python setup.py install -which will install dependencies as well. +which will install dependencies (Ned's coverage module) as well. For more info on py.test go here: http://pytest.org - -Have fun, - -holger krekel --- a/pytest_coverage.py +++ b/pytest_coverage.py @@ -1,15 +1,32 @@ """ -Write and report coverage data with the 'coverage' package. -Original code by Ross Lawley. +Write and report coverage data with the 'coverage' package. -Requires Ned Batchelder's excellent coverage: -http://nedbatchelder.com/code/coverage/ +Note: Original code by Ross Lawley. + +Install +-------------- + +Use pip to (un)install:: + + pip install pytest-coverage + pip uninstall pytest-coverage + +or alternatively use easy_install to install:: + + easy_install pytest-coverage + + +Usage +------------- + +To get full test coverage reports for a particular package type:: + + py.test --cover-report=report + """ import sys -from coverage import coverage - def pytest_addoption(parser): group = parser.getgroup('Coverage options') group.addoption('--cover', action='append', default=[], @@ -48,6 +65,7 @@ class DoCoverage: def __init__(self, config): self.config = config + from coverage import coverage self._coverage = coverage() self._coverage.use_cache(False) From commits-noreply at bitbucket.org Tue May 25 11:58:56 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 09:58:56 +0000 (UTC) Subject: [py-svn] pytest-xdist commit d3fc793b801d: Added tag 1.2 for changeset 56d8e5280be2 Message-ID: <20100525095856.540EB7EF7A@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274781749 -7200 # Node ID d3fc793b801dbf898cce33b301c125828cfe7926 # Parent dd7765376b0ee5e7db1663f95d08df543b01ce1e Added tag 1.2 for changeset 56d8e5280be2 --- a/.hgtags +++ b/.hgtags @@ -1,2 +1,3 @@ 42c6503ee48fae9c4c96d406afb12bfc86f15803 1.0 eca7ce17eabf296983c36812c8b8be901e7055a3 1.1 +56d8e5280be224a0ad3220a9deed55334710bd23 1.2 From commits-noreply at bitbucket.org Tue May 25 12:26:50 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 10:26:50 +0000 (UTC) Subject: [py-svn] py-trunk commit 2fc9929ffdc3: internal test runs: do inline_run() without io-capturing Message-ID: <20100525102650.59D4A7EF1F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274783091 -7200 # Node ID 2fc9929ffdc345c649aaa55ffa6c5a6e1a00b96a # Parent c3f32c3ed8a4f63b58711cbe9e696034b90160e7 internal test runs: do inline_run() without io-capturing as this nested capturing can leave open FDs which breaks larger test runs. also introduce an internal option "--lsof" for checking the number of file descriptors --- a/conftest.py +++ b/conftest.py @@ -7,7 +7,9 @@ collect_ignore = ['build', 'doc/_build'] rsyncdirs = ['conftest.py', 'bin', 'py', 'doc', 'testing'] -import py +import os, py +pid = os.getpid() + def pytest_addoption(parser): group = parser.getgroup("pylib", "py lib testing options") group.addoption('--sshhost', @@ -16,7 +18,26 @@ def pytest_addoption(parser): group.addoption('--runslowtests', action="store_true", dest="runslowtests", default=False, help=("run slow tests")) + group.addoption('--lsof', + action="store_true", dest="lsof", default=False, + help=("run FD checks if lsof is available")) +def pytest_configure(config): + if config.getvalue("lsof"): + try: + out = py.process.cmdexec("lsof -p %d" % pid) + except py.process.cmdexec.Error: + pass + else: + config._numfiles = len([x for x in out.split("\n") if "REG" in x]) + +def pytest_unconfigure(config, __multicall__): + if not hasattr(config, '_numfiles'): + return + __multicall__.execute() + out2 = py.process.cmdexec("lsof -p %d" % pid) + len2 = len([x for x in out2.split("\n") if "REG" in x]) + assert len2 < config._numfiles + 7, out2 def pytest_funcarg__sshhost(request): val = request.config.getvalue("sshhost") --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -185,6 +185,7 @@ class TmpTestdir: return reports[0] def inline_run(self, *args): + args = ("-s", ) + args # otherwise FD leakage config = self.parseconfig(*args) config.pluginmanager.do_configure(config) session = config.initsession() From commits-noreply at bitbucket.org Tue May 25 16:56:36 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 14:56:36 +0000 (UTC) Subject: [py-svn] py-trunk commit 1011232f2bc9: merge changes Message-ID: <20100525145636.B83407EF86@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274799330 -7200 # Node ID 1011232f2bc93c440a0b8ff00c2a64172a00f17b # Parent e943e7b50b7d4c6aa21f6fc341b7dc6801c696b9 # Parent 2fc9929ffdc345c649aaa55ffa6c5a6e1a00b96a merge changes From commits-noreply at bitbucket.org Tue May 25 16:56:38 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 14:56:38 +0000 (UTC) Subject: [py-svn] py-trunk commit fcfd21755429: fix issue102 by introducing a --maxfailures=NUM option Message-ID: <20100525145638.B6F9C7EF87@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274799129 -7200 # Node ID fcfd21755429fbc36b0b5da7346ae8ec5d693d62 # Parent 1011232f2bc93c440a0b8ff00c2a64172a00f17b fix issue102 by introducing a --maxfailures=NUM option also print an informative line about "stopped/interrupted" test runs near the end. --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -347,7 +347,7 @@ class TestCaptureFuncarg: """) result = testdir.runpytest(p) result.stdout.fnmatch_lines([ - "*KEYBOARD INTERRUPT*" + "*KeyboardInterrupt*" ]) assert result.ret == 2 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -86,6 +86,16 @@ class SessionTests: assert failed == 1 assert passed == skipped == 0 + def test_maxfail(self, testdir): + reprec = testdir.inline_runsource(""" + def test_one(): assert 0 + def test_two(): assert 0 + def test_three(): assert 0 + """, '--maxfail=2') + passed, skipped, failed = reprec.countoutcomes() + assert failed == 2 + assert passed == skipped == 0 + def test_broken_repr(self, testdir): p = testdir.makepyfile(""" import py --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -312,12 +312,14 @@ class TerminalReporter: self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) def _report_keyboardinterrupt(self): - self.write_sep("!", "KEYBOARD INTERRUPT") excrepr = self._keyboardinterrupt_memo - if self.config.option.verbose: - excrepr.toterminal(self._tw) - else: - excrepr.reprcrash.toterminal(self._tw) + msg = excrepr.reprcrash.message + self.write_sep("!", msg) + if "KeyboardInterrupt" in msg: + if self.config.getvalue("fulltrace"): + excrepr.toterminal(self._tw) + else: + excrepr.reprcrash.toterminal(self._tw) def _getcrashline(self, report): try: --- a/py/_plugin/pytest_default.py +++ b/py/_plugin/pytest_default.py @@ -62,9 +62,12 @@ def pytest_report_iteminfo(item): def pytest_addoption(parser): group = parser.getgroup("general", "running and selection options") - group._addoption('-x', '--exitfirst', - action="store_true", dest="exitfirst", default=False, + group._addoption('-x', '--exitfirst', action="store_true", default=False, + dest="exitfirst", help="exit instantly on first error or failed test."), + group._addoption('--maxfail', metavar="num", + action="store", type="int", dest="maxfail", default=0, + help="exit after first num failures or errors.") group._addoption('-k', action="store", dest="keyword", default='', help="only run test items matching the given " @@ -89,6 +92,9 @@ def pytest_addoption(parser): def pytest_configure(config): setsession(config) + # compat + if config.getvalue("exitfirst"): + config.option.maxfail = 1 def setsession(config): val = config.getvalue --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,12 @@ New features declarative approach with the @py.test.mark.xfail cannot be used as it would mark all configurations as xfail. +- issue102: introduce new --maxfail=NUM option to stop + test runs after NUM failures. This is a generalization + of the '-x' or '--exitfirst' option which is now equivalent + to '--maxfail=1'. Both '-x' and '--maxfail' will + now also print a line near the end indicating the Interruption. + - issue89: allow py.test.mark decorators to be used on classes (class decorators were introduced with python2.6) and also allow to have multiple markers applied at class/module level @@ -41,6 +47,7 @@ Fixes / Maintenance - improved and unified reporting for "--tb=short" option - Errors during test module imports are much shorter, (using --tb=short style) - raises shows shorter more relevant tracebacks + - --fulltrace now more systematically makes traces longer / inhibits cutting - improve support for raises and other dynamically compiled code by manipulating python's linecache.cache instead of the previous --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -17,9 +17,10 @@ def basic_run_report(item): return runner.call_and_report(item, "call", log=False) class Option: - def __init__(self, verbose=False, dist=None): + def __init__(self, verbose=False, dist=None, fulltrace=False): self.verbose = verbose self.dist = dist + self.fulltrace = fulltrace def _getcmdargs(self): l = [] if self.verbose: @@ -27,6 +28,8 @@ class Option: if self.dist: l.append('--dist=%s' % self.dist) l.append('--tx=popen') + if self.fulltrace: + l.append('--fulltrace') return l def _getcmdstring(self): return " ".join(self._getcmdargs()) @@ -35,6 +38,7 @@ def pytest_generate_tests(metafunc): if "option" in metafunc.funcargnames: metafunc.addcall(id="default", param=Option(verbose=False)) metafunc.addcall(id="verbose", param=Option(verbose=True)) + metafunc.addcall(id="fulltrace", param=Option(fulltrace=True)) if not getattr(metafunc.function, 'nodist', False): metafunc.addcall(id="verbose-dist", param=Option(dist='each', verbose=True)) @@ -284,11 +288,28 @@ class TestTerminal: "E assert 0", "*_keyboard_interrupt.py:6: KeyboardInterrupt*", ]) - if option.verbose: + if option.fulltrace: result.stdout.fnmatch_lines([ "*raise KeyboardInterrupt # simulating the user*", ]) - result.stdout.fnmatch_lines(['*KEYBOARD INTERRUPT*']) + result.stdout.fnmatch_lines(['*KeyboardInterrupt*']) + + def test_maxfailures(self, testdir, option): + p = testdir.makepyfile(""" + def test_1(): + assert 0 + def test_2(): + assert 0 + def test_3(): + assert 0 + """) + result = testdir.runpytest("--maxfail=2", *option._getcmdargs()) + result.stdout.fnmatch_lines([ + "*def test_1():*", + "*def test_2():*", + "*!! Interrupted: stopping after 2 failures*!!*", + "*2 failed*", + ]) def test_pytest_report_header(self, testdir): testdir.makeconftest(""" --- a/py/_plugin/pytest_helpconfig.py +++ b/py/_plugin/pytest_helpconfig.py @@ -45,7 +45,7 @@ def pytest_configure(__multicall__, conf options = [opt for opt in options if opt._long_opts] options.sort(key=lambda x: x._long_opts) for opt in options: - if not opt._long_opts: + if not opt._long_opts or not opt.dest: continue optstrings = list(opt._long_opts) # + list(opt._short_opts) optstrings = filter(None, optstrings) --- a/py/_test/session.py +++ b/py/_test/session.py @@ -20,11 +20,14 @@ Collector = py.test.collect.Collector class Session(object): nodeid = "" + class Interrupted(KeyboardInterrupt): + """ signals an interrupted test run. """ + def __init__(self, config): self.config = config self.pluginmanager = config.pluginmanager # shortcut self.pluginmanager.register(self) - self._testsfailed = False + self._testsfailed = 0 self._nomatch = False self.shouldstop = False @@ -52,7 +55,7 @@ class Session(object): yield x self.config.hook.pytest_collectreport(report=rep) if self.shouldstop: - break + raise self.Interrupted(self.shouldstop) def filteritems(self, colitems): """ return items to process (some may be deselected)""" @@ -86,9 +89,11 @@ class Session(object): def pytest_runtest_logreport(self, report): if report.failed: - self._testsfailed = True - if self.config.option.exitfirst: - self.shouldstop = True + self._testsfailed += 1 + maxfail = self.config.getvalue("maxfail") + if maxfail and self._testsfailed >= maxfail: + self.shouldstop = "stopping after %d failures" % ( + self._testsfailed) pytest_collectreport = pytest_runtest_logreport def sessionfinishes(self, exitstatus): @@ -122,7 +127,8 @@ class Session(object): def _mainloop(self, colitems): for item in self.collect(colitems): - if self.shouldstop: - break if not self.config.option.collectonly: item.config.hook.pytest_runtest_protocol(item=item) + if self.shouldstop: + raise self.Interrupted(self.shouldstop) + From commits-noreply at bitbucket.org Tue May 25 16:56:34 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 14:56:34 +0000 (UTC) Subject: [py-svn] py-trunk commit e943e7b50b7d: adding xfail example Message-ID: <20100525145634.B27327EF83@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274541751 -7200 # Node ID e943e7b50b7d4c6aa21f6fc341b7dc6801c696b9 # Parent c3f32c3ed8a4f63b58711cbe9e696034b90160e7 adding xfail example --- /dev/null +++ b/doc/example/xfail_demo.py @@ -0,0 +1,16 @@ +import py + + at py.test.mark.xfail +def test_hello(): + assert 0 + + at py.test.mark.xfail(run=False) +def test_hello2(): + assert 0 + + at py.test.mark.xfail("hasattr(os, 'sep')") +def test_hello3(): + assert 0 + +def test_hello5(): + py.test.xfail("reason") From commits-noreply at bitbucket.org Tue May 25 16:56:52 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 14:56:52 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 1392e029c567: adapt for py-trunk/1.3.1 changes wrt --exitfirst -> --maxfailures Message-ID: <20100525145652.3EA647EF02@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274799548 -7200 # Node ID 1392e029c5674cec9a5eede65c0d5c53f124b79e # Parent d3fc793b801dbf898cce33b301c125828cfe7926 adapt for py-trunk/1.3.1 changes wrt --exitfirst -> --maxfailures --- a/testing/test_dsession.py +++ b/testing/test_dsession.py @@ -259,7 +259,7 @@ class TestDSession: def test_pass(): pass """) - modcol.config.option.exitfirst = True + modcol.config.option.maxfail = 1 session = DSession(modcol.config) node = MockNode() session.addnode(node) @@ -271,12 +271,44 @@ class TestDSession: # run tests ourselves and produce reports ev1 = run(items[0], node, "fail") ev2 = run(items[1], node, None) + session.queueevent("pytest_runtest_logreport", report=ev1) + session.queueevent("pytest_runtest_logreport", report=ev2) + # now call the loop + loopstate = session._initloopstate(items) + py.test.raises(session.Interrupted, "session.loop_once(loopstate)") + assert loopstate.testsfailed + #assert loopstate.shuttingdown + + def test_maxfail(self, testdir): + modcol = testdir.getmodulecol(""" + def test_fail1(): + assert 0 + def test_fail2(): + assert 0 + def test_pass(): + pass + """) + modcol.config.option.maxfail = 2 + session = DSession(modcol.config) + node = MockNode() + session.addnode(node) + items = modcol.config.hook.pytest_make_collect_report(collector=modcol).result + + # trigger testing - this sends tests to the node + session.triggertesting(items) + + # run tests ourselves and produce reports + ev1 = run(items[0], node, "fail") + ev2 = run(items[1], node, "fail") session.queueevent("pytest_runtest_logreport", report=ev1) # a failing one session.queueevent("pytest_runtest_logreport", report=ev2) # now call the loop loopstate = session._initloopstate(items) - from xdist.dsession import ExitFirstInterrupt - py.test.raises(ExitFirstInterrupt, "session.loop_once(loopstate)") + try: + session.loop_once(loopstate) + except session.Interrupted: + py.test.fail("raised Interrupted but shouildn't") + py.test.raises(session.Interrupted, "session.loop_once(loopstate)") assert loopstate.testsfailed #assert loopstate.shuttingdown --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup( packages = ['xdist'], entry_points = {'pytest11': ['xdist = xdist.plugin'],}, zip_safe=False, - install_requires = ['execnet>=1.0.6', 'py>=1.3.0'], + install_requires = ['execnet>=1.0.6', 'py>=1.3.1'], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', --- a/xdist/dsession.py +++ b/xdist/dsession.py @@ -20,7 +20,7 @@ class LoopState(object): # waiting for a host to become ready. self.dowork = True self.shuttingdown = False - self.testsfailed = False + self.testsfailed = 0 def __repr__(self): return "" % ( @@ -31,7 +31,7 @@ class LoopState(object): if report.when != "teardown": # otherwise we already managed it self.dsession.removeitem(report.item, report.node) if report.failed: - self.testsfailed = True + self.testsfailed += 1 def pytest_collectreport(self, report): if report.passed: @@ -58,9 +58,6 @@ class LoopState(object): if pending: self.dowork = False # avoid busywait, nodes still have work -class ExitFirstInterrupt(KeyboardInterrupt): - pass - class DSession(session.Session): """ Session drives the collection and running of tests @@ -131,11 +128,14 @@ class DSession(session.Session): call(**kwargs) # termination conditions + maxfail = self.config.getvalue("maxfail") if (not self.node2pending or - (loopstate.testsfailed and self.config.option.exitfirst) or + (loopstate.testsfailed and maxfail and + loopstate.testsfailed >= maxfail) or (not self.item2nodes and not colitems and not self.queue.qsize())): - if self.config.option.exitfirst: - raise ExitFirstInterrupt() + if maxfail and loopstate.testsfailed >= maxfail: + raise self.Interrupted("stopping after %d failures" % ( + loopstate.testsfailed)) self.triggershutdown() loopstate.shuttingdown = True if not self.node2pending: @@ -181,11 +181,8 @@ class DSession(session.Session): break except KeyboardInterrupt: excinfo = py.code.ExceptionInfo() - if excinfo.errisinstance(ExitFirstInterrupt): - exitstatus = session.EXIT_TESTSFAILED - else: - self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) - exitstatus = session.EXIT_INTERRUPTED + self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) + exitstatus = session.EXIT_INTERRUPTED except: self.config.pluginmanager.notify_exception() exitstatus = session.EXIT_INTERNALERROR --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,8 @@ 1.3 - fix --looponfailing - it would not actually run against the fully changed source tree when initial conftest files load application state. +- adapt for py-1.3.1's new --maxfailure option + 1.2 ------------------------- From commits-noreply at bitbucket.org Tue May 25 17:20:37 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 15:20:37 +0000 (UTC) Subject: [py-svn] py-trunk commit 5265cfc1d96f: fix for py3 exception printing logic Message-ID: <20100525152037.137F17EF86@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274801064 -7200 # Node ID 5265cfc1d96f70de514ba9df9e0935ce179f02f2 # Parent fcfd21755429fbc36b0b5da7346ae8ec5d693d62 fix for py3 exception printing logic --- a/py/_test/session.py +++ b/py/_test/session.py @@ -22,6 +22,7 @@ class Session(object): nodeid = "" class Interrupted(KeyboardInterrupt): """ signals an interrupted test run. """ + __module__ = 'builtins' # for py3 def __init__(self, config): self.config = config From commits-noreply at bitbucket.org Tue May 25 20:47:50 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 18:47:50 +0000 (UTC) Subject: [py-svn] py-trunk commit d5eacf390af7: update release announcement Message-ID: <20100525184750.E9DFB7EF8B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274813211 -7200 # Node ID d5eacf390af74553227122b85e20345d47b2f9e6 # Parent 5265cfc1d96f70de514ba9df9e0935ce179f02f2 update release announcement --- a/doc/announce/release-1.3.1.txt +++ b/doc/announce/release-1.3.1.txt @@ -1,12 +1,16 @@ -py.test/pylib 1.3.1: new py.test.xfail, better reporting, fixes +py.test/pylib 1.3.1: new py.test.xfail, --maxfail, better reporting =========================================================================== -The 1.3.1 release introduces several bug fixes and brings shorter, -more concise tracebacks in some cases where it displayed a lot of info. -It also further improves compatibility on Jython and PyPy where now virtually -all py.test features are supported. If you used 1.2.1 or 1.3.0 you should -be able to upgrade to 1.3.1 without changes to your test source code. -See the below CHANGELOG entry below for more details and +The 1.3.1 release introduces a new imperative way to "xfail" a test +and a new option --maxfail=NUM to limit the number of executed tests +in case of failures. This backward-compatible release also brings shorter, +more concise tracebacks in several cases. The underlying code has been +simplified and now is compatible to Jython and PyPy, along with some bug +fixes and refinemnts that have been applied to CPython's code base. +If you used older versions of py.test you should be able to upgrade +to 1.3.1 without changes to your test source code. + +See the below CHANGELOG entry below for more details and http://pylib.org/install.html for installation instructions. py.test is an advanced automated testing tool working with Python2, @@ -37,6 +41,12 @@ New features declarative approach with the @py.test.mark.xfail cannot be used as it would mark all configurations as xfail. +- issue102: introduce new --maxfail=NUM option to stop + test runs after NUM failures. This is a generalization + of the '-x' or '--exitfirst' option which is now equivalent + to '--maxfail=1'. Both '-x' and '--maxfail' will + now also print a line near the end indicating the Interruption. + - issue89: allow py.test.mark decorators to be used on classes (class decorators were introduced with python2.6) and also allow to have multiple markers applied at class/module level @@ -66,6 +76,7 @@ Fixes / Maintenance - improved and unified reporting for "--tb=short" option - Errors during test module imports are much shorter, (using --tb=short style) - raises shows shorter more relevant tracebacks + - --fulltrace now more systematically makes traces longer / inhibits cutting - improve support for raises and other dynamically compiled code by manipulating python's linecache.cache instead of the previous From commits-noreply at bitbucket.org Tue May 25 20:47:52 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 18:47:52 +0000 (UTC) Subject: [py-svn] py-trunk commit 575843976930: Added tag 1.3.1 for changeset d5eacf390af7 Message-ID: <20100525184752.A96FE7EF8E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274813220 -7200 # Node ID 5758439769305e23ba582cbbd96cc66585bd0cba # Parent d5eacf390af74553227122b85e20345d47b2f9e6 Added tag 1.3.1 for changeset d5eacf390af7 --- a/.hgtags +++ b/.hgtags @@ -23,3 +23,4 @@ 319187fcda66714c5eb1353492babeec3d3c826f 4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0 c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1 eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0 +d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1 From commits-noreply at bitbucket.org Tue May 25 21:10:16 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 19:10:16 +0000 (UTC) Subject: [py-svn] py-trunk commit 8b8e7c25a13c: update plugin docs, restructure release announcement a bit Message-ID: <20100525191016.5355F7EF1C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274814103 -7200 # Node ID 8b8e7c25a13cf863f01b2dd955978285ae9daf6a # Parent 5758439769305e23ba582cbbd96cc66585bd0cba update plugin docs, restructure release announcement a bit --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -1,46 +1,46 @@ .. _`helpconfig`: helpconfig.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_recwarn.py .. _`unittest`: unittest.html -.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_monkeypatch.py -.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_genscript.py +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_monkeypatch.py +.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_genscript.py .. _`pastebin`: pastebin.html .. _`skipping`: skipping.html .. _`genscript`: genscript.html .. _`plugins`: index.html .. _`mark`: mark.html .. _`tmpdir`: tmpdir.html -.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_doctest.py +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_doctest.py .. _`capture`: capture.html -.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_nose.py -.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_restdoc.py +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_restdoc.py .. _`restdoc`: restdoc.html .. _`xdist`: xdist.html -.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_pastebin.py -.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_tmpdir.py +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_pastebin.py +.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_tmpdir.py .. _`terminal`: terminal.html -.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_hooklog.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_hooklog.py .. _`capturelog`: capturelog.html .. _`junitxml`: junitxml.html -.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_skipping.py +.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_skipping.py .. _`checkout the py.test development version`: ../../install.html#checkout -.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_helpconfig.py +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html .. _`doctest`: doctest.html -.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_mark.py +.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_mark.py .. _`get in contact`: ../../contact.html -.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_capture.py +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_capture.py .. _`figleaf`: figleaf.html .. _`customize`: ../customize.html .. _`hooklog`: hooklog.html -.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_terminal.py +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_terminal.py .. _`recwarn`: recwarn.html -.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_pdb.py +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_pdb.py .. _`monkeypatch`: monkeypatch.html .. _`coverage`: coverage.html .. _`resultlog`: resultlog.html -.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_junitxml.py +.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_junitxml.py .. _`django`: django.html -.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_unittest.py +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_unittest.py .. _`nose`: nose.html -.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.0/py/_plugin/pytest_resultlog.py +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_resultlog.py .. _`pdb`: pdb.html --- a/doc/test/plugin/mark.txt +++ b/doc/test/plugin/mark.txt @@ -49,11 +49,13 @@ with classes to apply markers to all its class TestClass: def test_startup(self): ... + def test_startup_and_more(self): + ... This is equivalent to directly applying the decorator to the -``test_startup`` function. +two test functions. -To remain compatible with Python2.5 you can instead set a +To remain compatible with Python2.5 you can also set a ``pytestmark`` attribute on a TestClass like this:: import py @@ -61,7 +63,7 @@ To remain compatible with Python2.5 you class TestClass: pytestmark = py.test.mark.webtest -or if you need to use multiple markers:: +or if you need to use multiple markers you can use a list:: import py @@ -73,7 +75,7 @@ You can also set a module level marker:: import py pytestmark = py.test.mark.webtest -in which case then it will be applied to all functions and +in which case it will be applied to all functions and methods defined in the module. Using "-k MARKNAME" to select tests --- a/doc/announce/release-1.3.1.txt +++ b/doc/announce/release-1.3.1.txt @@ -1,19 +1,23 @@ py.test/pylib 1.3.1: new py.test.xfail, --maxfail, better reporting =========================================================================== -The 1.3.1 release introduces a new imperative way to "xfail" a test -and a new option --maxfail=NUM to limit the number of executed tests -in case of failures. This backward-compatible release also brings shorter, -more concise tracebacks in several cases. The underlying code has been -simplified and now is compatible to Jython and PyPy, along with some bug -fixes and refinemnts that have been applied to CPython's code base. -If you used older versions of py.test you should be able to upgrade -to 1.3.1 without changes to your test source code. +The pylib/py.test 1.3.1 release brings: + +- the new imperative ``py.test.xfail()`` helper in order to have a test or + setup function result in an "expected failure" +- a new option ``--maxfail=NUM`` to stop the test run after some failures +- markers/decorators are now applicable to test classes (>=Python2.6) +- improved reporting, shorter tracebacks in several cases +- some simplified internals, more compatibility with Jython and PyPy +- bug fixes and various refinements See the below CHANGELOG entry below for more details and http://pylib.org/install.html for installation instructions. -py.test is an advanced automated testing tool working with Python2, +If you used older versions of py.test you should be able to upgrade +to 1.3.1 without changes to your test source code. + +py.test is an automated testing tool working with Python2, Python3, Jython and PyPy versions on all major operating systems. It offers a no-boilerplate testing approach and has inspired other testing tools and enhancements in the standard Python library for more than five --- a/doc/test/plugin/skipping.txt +++ b/doc/test/plugin/skipping.txt @@ -65,8 +65,8 @@ for skipping all methods of a test class # The ``pytestmark`` decorator will be applied to each test function. -If your code targets python2.6 or above you can also use the -skipif decorator with classes:: +If your code targets python2.6 or above you can equivalently use +the skipif decorator on classes:: @py.test.mark.skipif("sys.platform == 'win32'") class TestPosixCalls: @@ -75,8 +75,9 @@ skipif decorator with classes:: # will not be setup or run under 'win32' platform # -It is fine in both situations to use multiple "skipif" decorators -on a single function. +It is fine in general to apply multiple "skipif" decorators +on a single function - this means that if any of the conditions +apply the function will be skipped. .. _`whole class- or module level`: mark.html#scoped-marking @@ -110,6 +111,17 @@ To specify an explicit reason to be show @py.test.mark.xfail(..., reason="my reason") +imperative xfail from within a test or setup function +------------------------------------------------------ + +If you cannot declare xfail-conditions at import time +you can also imperatively produce an XFail-outcome from +within test or setup code. Example:: + + def test_function(): + if not valid_config(): + py.test.xfail("unsuppored configuration") + skipping on a missing import dependency -------------------------------------------------- From commits-noreply at bitbucket.org Tue May 25 21:10:18 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 19:10:18 +0000 (UTC) Subject: [py-svn] py-trunk commit 180dce29c237: Added tag 1.3.1 for changeset 8b8e7c25a13c Message-ID: <20100525191018.7F8997EF6D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274814561 -7200 # Node ID 180dce29c237c2bcbd33a7b73f676f3a826613bb # Parent 8b8e7c25a13cf863f01b2dd955978285ae9daf6a Added tag 1.3.1 for changeset 8b8e7c25a13c --- a/.hgtags +++ b/.hgtags @@ -24,3 +24,5 @@ 4fc5212f7626a56b9eb6437b5c673f56dd7eb942 c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1 eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0 d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1 +d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1 +8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1 From commits-noreply at bitbucket.org Tue May 25 21:23:29 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 19:23:29 +0000 (UTC) Subject: [py-svn] pytest-xdist commit e6c4ce20db4b: bump version number Message-ID: <20100525192329.37B337EF6D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274815566 -7200 # Node ID e6c4ce20db4bf65086ff55807a3c306cad7ca393 # Parent 1392e029c5674cec9a5eede65c0d5c53f124b79e bump version number --- a/xdist/__init__.py +++ b/xdist/__init__.py @@ -1,3 +1,3 @@ # -__version__ = "1.2" +__version__ = "1.3" From commits-noreply at bitbucket.org Tue May 25 21:23:27 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 25 May 2010 19:23:27 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 45a2e97933a8: Added tag 1.3 for changeset e6c4ce20db4b Message-ID: <20100525192327.1823F7EF39@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274815569 -7200 # Node ID 45a2e97933a8468a2874b25de9ffd7f9fdac5f06 # Parent e6c4ce20db4bf65086ff55807a3c306cad7ca393 Added tag 1.3 for changeset e6c4ce20db4b --- a/.hgtags +++ b/.hgtags @@ -1,3 +1,4 @@ 42c6503ee48fae9c4c96d406afb12bfc86f15803 1.0 eca7ce17eabf296983c36812c8b8be901e7055a3 1.1 56d8e5280be224a0ad3220a9deed55334710bd23 1.2 +e6c4ce20db4bf65086ff55807a3c306cad7ca393 1.3 From commits-noreply at bitbucket.org Wed May 26 14:51:44 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 26 May 2010 12:51:44 +0000 (UTC) Subject: [py-svn] py-trunk commit 8ad40decae54: add Meme's pytest-cov plugin to the plugin index page Message-ID: <20100526125144.BE4837EF81@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274878107 -7200 # Node ID 8ad40decae54b92b3b70287c321a9853e3f1da01 # Parent 180dce29c237c2bcbd33a7b73f676f3a826613bb add Meme's pytest-cov plugin to the plugin index page --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -38,6 +38,7 @@ .. _`monkeypatch`: monkeypatch.html .. _`coverage`: coverage.html .. _`resultlog`: resultlog.html +.. _`cov`: cov.html .. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_junitxml.py .. _`django`: django.html .. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.1/py/_plugin/pytest_unittest.py --- a/bin-for-dist/makepluginlist.py +++ b/bin-for-dist/makepluginlist.py @@ -5,7 +5,7 @@ WIDTH = 75 plugins = [ ('advanced python testing', 'skipping mark pdb figleaf ' - 'monkeypatch coverage capture capturelog recwarn tmpdir',), + 'monkeypatch coverage cov capture capturelog recwarn tmpdir',), ('distributed testing, CI and deployment', 'xdist pastebin junitxml resultlog genscript',), ('testing domains and conventions codecheckers', @@ -24,6 +24,7 @@ externals = { 'figleaf': None, 'capturelog': None, 'coverage': None, + 'cov': None, 'codecheckers': None, 'django': "for testing django applications", } --- /dev/null +++ b/doc/test/plugin/cov.txt @@ -0,0 +1,250 @@ + +produce code coverage reports using the 'coverage' package, including support for distributed testing. +====================================================================================================== + + +.. contents:: + :local: + +This plugin produces coverage reports using the coverage package. It +supports centralised testing and distributed testing in both load and +each modes. + +All features offered by the coverage package should be available, +either through this plugin or through coverage's own config file. + + +Installation +------------ + +The `pytest-cov pypi`_ package may be installed / uninstalled with pip:: + + pip install pytest-cov + pip uninstall pytest-cov + +Alternatively easy_install can be used:: + + easy_install pytest-cov + +.. _`pytest-cov pypi`: http://pypi.python.org/pypi/pytest-cov/ + + +Usage +----- + +Centralised Testing +~~~~~~~~~~~~~~~~~~~ + +Running centralised testing:: + + py.test --cov myproj tests/ + +Shows a terminal report:: + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Exec Cover Missing + -------------------------------------------------- + myproj/__init__ 2 2 100% + myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370 + myproj/feature4286 94 87 92% 183-188, 197 + -------------------------------------------------- + TOTAL 353 333 94% + + +Distributed Testing +~~~~~~~~~~~~~~~~~~~ + +Distributed testing with dist mode set to load:: + + py.test --cov myproj -n 2 tests/ + +The results from the slaves will be combined like so:: + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Exec Cover Missing + -------------------------------------------------- + myproj/__init__ 2 2 100% + myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370 + myproj/feature4286 94 87 92% 183-188, 197 + -------------------------------------------------- + TOTAL 353 333 94% + + +Distributed testing in each mode:: + + py.test --cov myproj --dist=each + --tx=popen//python=/usr/local/python265/bin/python + --tx=popen//python=/usr/local/python27b1/bin/python + tests/ + +Will produce a report for each slave:: + + -------------------- coverage: platform linux2, python 2.6.5-final-0 --------------------- + Name Stmts Exec Cover Missing + -------------------------------------------------- + myproj/__init__ 2 2 100% + myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370 + myproj/feature4286 94 87 92% 183-188, 197 + -------------------------------------------------- + TOTAL 353 333 94% + --------------------- coverage: platform linux2, python 2.7.0-beta-1 --------------------- + Name Stmts Exec Cover Missing + -------------------------------------------------- + myproj/__init__ 2 2 100% + myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370 + myproj/feature4286 94 87 92% 183-188, 197 + -------------------------------------------------- + TOTAL 353 333 94% + + +Distributed testing in each mode can also produce a single combined +report. This is useful to get coverage information spanning things +such as all python versions:: + + py.test --cov myproj --cov-combine-each --dist=each + --tx=popen//python=/usr/local/python265/bin/python + --tx=popen//python=/usr/local/python27b1/bin/python + tests/ + +Which looks like:: + + ---------------------------------------- coverage ---------------------------------------- + platform linux2, python 2.6.5-final-0 + platform linux2, python 2.7.0-beta-1 + Name Stmts Exec Cover Missing + -------------------------------------------------- + myproj/__init__ 2 2 100% + myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370 + myproj/feature4286 94 87 92% 183-188, 197 + -------------------------------------------------- + TOTAL 353 333 94% + + +Reporting +--------- + +By default a terminal report is output. This report can be disabled +if desired, such as when results are going to a continuous integration +system and the terminal output won't be seen. + +In addition and without rerunning tests it is possible to generate +annotated source code, a html report and an xml report. + +The directories for annotated source code and html reports can be +specified as can the file name for the xml report. + +Since testing often takes a non trivial amount of time at the end of +testing any / all of the reports may be generated. + + +Coverage Data File +------------------ + +During testing there may be many data files with coverage data. These +will have unique suffixes and will be combined at the end of testing. + +Upon completion, for --dist=load (and also for --dist=each when the +--cov-combine-each option is used) there will only be one data file. + +For --dist=each there may be many data files where each one will have +the platform / python version info appended to the name. + +These data files are left at the end of testing so that it is possible +to use normal coverage tools to examine them. + +At the beginning of testing any data files that are about to be used +will first be erased so ensure the data is clean for each test run. + +It is possible to set the name of the data file. If needed the +platform / python version will be appended automatically to this name. + + +Coverage Config File +-------------------- + +Coverage by default will read its own config file. An alternative +file name may be specified or reading config can be disabled entirely. + +Care has been taken to ensure that the coverage env vars and config +file options work the same under this plugin as they do under coverage +itself. + +Since options may be specified in different ways the order of +precedence between pytest-cov and coverage from highest to lowest is: + +1. pytest command line +2. pytest env var +3. pytest conftest +4. coverage env var +5. coverage config file +6. coverage default + + +Limitations +----------- + +For distributed testing the slaves must have the pytest-cov package +installed. This is needed since the plugin must be registered through +setuptools / distribute for pytest to start the plugin on the slave. + + +Acknowledgements +---------------- + +Holger Krekel for pytest with its distributed testing support. + +Ned Batchelder for coverage and its ability to combine the coverage +results of parallel runs. + +Whilst this plugin has been built fresh from the ground up to support +distributed testing it has been influenced by the work done on +pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and +nose-cover (Jason Pellerin) which are other coverage plugins for +pytest and nose respectively. + +No doubt others have contributed to these tools as well. + +command line options +-------------------- + + +``--cov-on`` + enable coverage, only needed if not specifying any --cov options +``--cov=package`` + collect coverage for the specified package (multi-allowed) +``--cov-no-terminal`` + disable printing a report on the terminal +``--cov-annotate`` + generate an annotated source code report +``--cov-html`` + generate a html report +``--cov-xml`` + generate an xml report +``--cov-annotate-dir=dir`` + directory for the annotate report, default: %default +``--cov-html-dir=dir`` + directory for the html report, default: coverage_html +``--cov-xml-file=path`` + file for the xml report, default: coverage.xml +``--cov-data-file=path`` + file containing coverage data, default: .coverage +``--cov-combine-each`` + for dist=each mode produce a single combined report +``--cov-branch`` + enable branch coverage +``--cov-pylib`` + enable python library coverage +``--cov-timid`` + enable slower and simpler tracing +``--cov-no-missing-lines`` + disable showing missing lines, only relevant to the terminal report +``--cov-no-missing-files`` + disable showing message about missing source files +``--cov-omit=prefix1,prefix2,...`` + ignore files with these prefixes +``--cov-no-config`` + disable coverage reading its config file +``--cov-config-file=path`` + config file for coverage, default: %default + +.. include:: links.txt --- a/doc/test/plugin/index.txt +++ b/doc/test/plugin/index.txt @@ -14,6 +14,8 @@ monkeypatch_ safely patch object attribu coverage_ (external) Write and report coverage data with the 'coverage' package. +cov_ (external) produce code coverage reports using the 'coverage' package, including support for distributed testing. + capture_ configurable per-test stdout/stderr capturing mechanisms. capturelog_ (external) capture output of logging module. From commits-noreply at bitbucket.org Wed May 26 18:54:18 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 26 May 2010 16:54:18 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 14d2f8b74c94: add a test for issue57 which currently needs to be fixed on py-trunk though Message-ID: <20100526165418.E7A447EF7C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1274892778 -7200 # Node ID 14d2f8b74c944dde004c3d18f03287659adcb667 # Parent 45a2e97933a8468a2874b25de9ffd7f9fdac5f06 add a test for issue57 which currently needs to be fixed on py-trunk though --- a/testing/test_remote.py +++ b/testing/test_remote.py @@ -146,3 +146,16 @@ class TestLooponFailing: child.expect(".*1 passed.*") child.kill(15) + def test_looponfail_xfail_passes(self, testdir): + p = testdir.makepyfile(""" + import py + @py.test.mark.xfail + def test_one(): + pass + """) + child = testdir.spawn_pytest("-f %s" % p) + child.expect("1 xpass") + child.expect("### LOOPONFAILING ####") + child.expect("waiting for changes") + child.kill(15) + From commits-noreply at bitbucket.org Wed May 26 18:54:57 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 26 May 2010 16:54:57 +0000 (UTC) Subject: [py-svn] py-trunk commit ae39db5c4322: fix issue 57 - make --looponfail work with xpassing tests Message-ID: <20100526165457.DE27A7EF81@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1274892950 -7200 # Node ID ae39db5c43222c4c87a2d59982e8a1a66bcaa303 # Parent 8ad40decae54b92b3b70287c321a9853e3f1da01 fix issue 57 - make --looponfail work with xpassing tests --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -207,7 +207,8 @@ class TerminalReporter: self.write_sep("#", "LOOPONFAILING", red=True) for report in failreports: loc = self._getcrashline(report) - self.write_line(loc, red=True) + if loc: + self.write_line(loc, red=True) self.write_sep("#", "waiting for changes") for rootdir in rootdirs: self.write_line("### Watching: %s" %(rootdir,), bold=True) @@ -325,7 +326,10 @@ class TerminalReporter: try: return report.longrepr.reprcrash except AttributeError: - return str(report.longrepr)[:50] + try: + return str(report.longrepr)[:50] + except AttributeError: + return "" def _reportinfoline(self, item): collect_fspath = self._getfspath(item) --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -133,17 +133,24 @@ class TestTerminal: def test_looponfailreport(self, testdir, linecomp): modcol = testdir.getmodulecol(""" + import py def test_fail(): assert 0 def test_fail2(): raise ValueError() + @py.test.mark.xfail + def test_xfail(): + assert 0 + @py.test.mark.xfail + def test_xpass(): + assert 1 """) rep = TerminalReporter(modcol.config, file=linecomp.stringio) reports = [basic_run_report(x) for x in modcol.collect()] rep.pytest_looponfailinfo(reports, [modcol.config.topdir]) linecomp.assert_contains_lines([ - "*test_looponfailreport.py:2: assert 0", - "*test_looponfailreport.py:4: ValueError*", + "*test_looponfailreport.py:3: assert 0", + "*test_looponfailreport.py:5: ValueError*", "*waiting*", "*%s*" % (modcol.config.topdir), ]) --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= '1.3.1', + version= '1.3.2a1', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -__version__ = version = "1.3.1" +__version__ = version = "1.3.2a1" import py.apipkg --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +Changes between 1.3.1 and 1.3.x +================================================== + +New features +++++++++++++++++++ + +Bug fixes +++++++++++++++++++ + +- fix issue57 -f|--looponfail to work with xpassing tests + Changes between 1.3.0 and 1.3.1 ================================================== From commits-noreply at bitbucket.org Sat May 29 09:11:40 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 29 May 2010 07:11:40 +0000 (UTC) Subject: [py-svn] py-trunk commit 5202a58a1066: fix name of fedora package, thanks thm Message-ID: <20100529071140.33A7B7EEE7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1275117350 -7200 # Node ID 5202a58a10660fd2e258f4e470bad71ed3aeb680 # Parent ae39db5c43222c4c87a2d59982e8a1a66bcaa303 fix name of fedora package, thanks thm --- a/doc/install.txt +++ b/doc/install.txt @@ -15,7 +15,7 @@ py.test/pylib installation info in a nut **Distribution names**: * PyPI name: ``py`` (see `index page`_ for versions) -* redhat fedora: ``pylib`` +* redhat fedora: ``python-py`` * debian: ``python-codespeak-lib`` * gentoo: ``pylib``