diff --git a/AUTHORS b/AUTHORS index c2400f508..b09516a1d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,6 +16,7 @@ Antony Lee Armin Rigo Aron Curzon Aviv Palivoda +Barney Gale Ben Webb Benjamin Peterson Bernard Pratz @@ -117,6 +118,7 @@ Piotr Banaszkiewicz Punyashloka Biswal Quentin Pradet Ralf Schmitt +Ran Benita Raphael Pierzina Raquel Alegre Roberto Polli @@ -141,5 +143,6 @@ Trevor Bekolay Tyler Goodlet Vasily Kuznetsov Victor Uriarte +Vidar T. Fauske Wouter van Ackooy Xuecong Liao diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5b47ea60a..f33663b2a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,19 +4,36 @@ * Fix regression, pytest now skips unittest correctly if run with ``--pdb`` (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. +* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_). + Thanks to `@bluetech`_. + +* + * Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. + +* + +* Skipping plugin now also works with test items generated by custom collectors (`#2231`_). + Thanks to `@vidartf`_. + +* + +* Conditionless ``xfail`` markers no longer rely on the underlying test item + being an instance of ``PyobjMixin``, and can therefore apply to tests not + collected by the built-in python test collector. Thanks `@barneygale`_ for the + PR. * -* - -* - +.. _@bluetech: https://github.com/bluetech .. _@gst: https://github.com/gst +.. _@vidartf: https://github.com/vidartf .. _#2137: https://github.com/pytest-dev/pytest/issues/2137 .. _#2160: https://github.com/pytest-dev/pytest/issues/2160 +.. _#2231: https://github.com/pytest-dev/pytest/issues/2231 +.. _#2234: https://github.com/pytest-dev/pytest/issues/2234 .. _PEP-479: https://www.python.org/dev/peps/pep-0479/ @@ -53,6 +70,7 @@ terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR. +.. _@barneygale: https://github.com/barneygale .. _@lesteve: https://github.com/lesteve .. _@malinoff: https://github.com/malinoff .. _@pelme: https://github.com/pelme diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 28bcd4d8d..248708f6e 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -14,6 +14,7 @@ from _pytest.compat import ( getfslineno, get_real_func, is_generator, isclass, getimfunc, getlocation, getfuncargnames, + safe_getattr, ) def pytest_sessionstart(session): @@ -124,8 +125,6 @@ def getfixturemarker(obj): exceptions.""" try: return getattr(obj, "_pytestfixturefunction", None) - except KeyboardInterrupt: - raise except Exception: # some objects raise errors like request (from flask import request) # we don't expect them to be fixture functions @@ -1068,7 +1067,9 @@ class FixtureManager: self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): - obj = getattr(holderobj, name, None) + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getatt() ignores such exceptions. + obj = safe_getattr(holderobj, name, None) # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # or are "@pytest.fixture" marked marker = getfixturemarker(obj) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 91a34169f..0af5573ac 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -112,14 +112,14 @@ class MarkEvaluator: def _getglobals(self): d = {'os': os, 'sys': sys, 'config': self.item.config} - d.update(self.item.obj.__globals__) + if hasattr(self.item, 'obj'): + d.update(self.item.obj.__globals__) return d def _istrue(self): if hasattr(self, 'result'): return self.result if self.holder: - d = self._getglobals() if self.holder.args or 'condition' in self.holder.kwargs: self.result = False # "holder" might be a MarkInfo or a MarkDecorator; only @@ -135,6 +135,7 @@ class MarkEvaluator: for expr in args: self.expr = expr if isinstance(expr, py.builtin._basestring): + d = self._getglobals() result = cached_eval(self.item.config, expr, d) else: if "reason" not in kwargs: diff --git a/testing/python/collect.py b/testing/python/collect.py index 1e69f2da9..cce934ddc 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -166,6 +166,16 @@ class TestClass: "because it has a __new__ constructor*" ) + def test_issue2234_property(self, testdir): + testdir.makepyfile(""" + class TestCase(object): + @property + def prop(self): + raise NotImplementedError() + """) + result = testdir.runpytest() + assert result.ret == EXIT_NOTESTSCOLLECTED + class TestGenerator: def test_generative_functions(self, testdir): diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 2e7868d3a..ac4412fcb 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -969,3 +969,26 @@ def test_module_level_skip_error(testdir): result.stdout.fnmatch_lines( "*Using pytest.skip outside of a test is not allowed*" ) + + +def test_mark_xfail_item(testdir): + # Ensure pytest.mark.xfail works with non-Python Item + testdir.makeconftest(""" + import pytest + + class MyItem(pytest.Item): + nodeid = 'foo' + def setup(self): + marker = pytest.mark.xfail(True, reason="Expected failure") + self.add_marker(marker) + def runtest(self): + assert False + + def pytest_collect_file(path, parent): + return MyItem("foo", parent) + """) + result = testdir.inline_run() + passed, skipped, failed = result.listoutcomes() + assert not failed + xfailed = [r for r in skipped if hasattr(r, 'wasxfail')] + assert xfailed