From 6b135c83be8cef769dbc860a4706a95603d01d09 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Wed, 22 Jun 2016 12:21:51 +0200 Subject: [PATCH 001/153] Initial commit. --- .gitignore | 6 ++++++ LICENSE | 19 ++++++++++++++++++ README.rst | 37 ++++++++++++++++++++++++++++++++++ pytest_warnings.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 17 ++++++++++++++++ test_warnings.py | 27 +++++++++++++++++++++++++ test_warnings2.py | 20 +++++++++++++++++++ tox.ini | 17 ++++++++++++++++ 8 files changed, 193 insertions(+) create mode 100644 .gitignore create mode 100755 LICENSE create mode 100644 README.rst create mode 100644 pytest_warnings.py create mode 100644 setup.py create mode 100644 test_warnings.py create mode 100644 test_warnings2.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..dac3fe1aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/.cache/ +/.tox/ +/bin/ +/include/ +/lib/ +/pip-selfcheck.json diff --git a/LICENSE b/LICENSE new file mode 100755 index 000000000..8a30978e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + diff --git a/README.rst b/README.rst new file mode 100644 index 000000000..02389db73 --- /dev/null +++ b/README.rst @@ -0,0 +1,37 @@ +pytest-warnings +=============== + +py.test plugin to list Python warnings in pytest report + + +Usage +----- + +install via:: + + pip install pytest-warnings + +if you then type:: + + py.test -rw + +any warnings in your code are reported in the pytest report. +You can use the ``-W`` option or ``--pythonwarnings`` exactly like for the ``python`` executable. + +The following example ignores all warnings, but prints DeprecationWarnings once per occurrence:: + + py.test -rw -W ignore -W once::DeprecationWarning + +You can also turn warnings into actual errors:: + + py.test -W error + + +Changes +======= + +0.1 - Unreleased +---------------- + +- Initial release. + [fschulze (Florian Schulze)] diff --git a/pytest_warnings.py b/pytest_warnings.py new file mode 100644 index 000000000..1813cf519 --- /dev/null +++ b/pytest_warnings.py @@ -0,0 +1,50 @@ +from _pytest.recwarn import RecordedWarning, WarningsRecorder +import inspect +import os +import pytest +import warnings + + +def pytest_addoption(parser): + group = parser.getgroup("pytest-warnings") + group.addoption( + '-W', '--pythonwarnings', action='append', + help="set which warnings to report, see ...") + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item): + wrec = WarningsRecorder() + + def showwarning(message, category, filename, lineno, file=None, line=None): + frame = inspect.currentframe() + if '/_pytest/recwarn' in frame.f_back.f_code.co_filename: + # we are in test recorder, so this warning is already handled + return + wrec._list.append(RecordedWarning( + message, category, filename, lineno, file, line)) + # still perform old showwarning functionality + wrec._showwarning( + message, category, filename, lineno, file=file, line=line) + + args = item.config.getoption('pythonwarnings') or [] + with wrec: + _showwarning = wrec._showwarning + warnings.showwarning = showwarning + wrec._module.simplefilter('once') + for arg in args: + wrec._module._setoption(arg) + yield + wrec._showwarning = _showwarning + + for warning in wrec.list: + msg = warnings.formatwarning( + warning.message, warning.category, + os.path.relpath(warning.filename), warning.lineno, warning.line) + fslocation = getattr(item, "location", None) + if fslocation is None: + fslocation = getattr(item, "fspath", None) + else: + fslocation = "%s:%s" % fslocation[:2] + fslocation = "in %s the following warning was recorded:\n" % fslocation + item.config.warn("W0", msg, fslocation=fslocation) diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..fd478664c --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup + + +setup( + name="pytest-warnings", + description='pytest plugin to list Python warnings in pytest report', + long_description=open("README.rst").read(), + license="MIT license", + version='0.1.0', + author='Florian Schulze', + author_email='florian.schulze@gmx.net', + url='https://github.com/fschulze/pytest-warnings', + py_modules=["pytest_warnings"], + entry_points={'pytest11': ['pytest_warnings = pytest_warnings']}, + install_requires=['pytest'], + classifiers=[ + "Framework :: Pytest"]) diff --git a/test_warnings.py b/test_warnings.py new file mode 100644 index 000000000..62167992f --- /dev/null +++ b/test_warnings.py @@ -0,0 +1,27 @@ +import pytest +import warnings + + +def test_warnings(): + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + + +def test_warnings1(): + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + + +def test_warn(): + with pytest.warns(DeprecationWarning): + warnings.warn("Bar", DeprecationWarning) diff --git a/test_warnings2.py b/test_warnings2.py new file mode 100644 index 000000000..d4c9be0ea --- /dev/null +++ b/test_warnings2.py @@ -0,0 +1,20 @@ +def test_warnings(): + import warnings + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + + +def test_warnings1(): + import warnings + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", RuntimeWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) + warnings.warn("Foo", DeprecationWarning) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..977819b65 --- /dev/null +++ b/tox.ini @@ -0,0 +1,17 @@ +[tox] +envlist = py27,py33,py34,py35 + +[testenv] +usedevelop = true +deps = + pytest + pytest-flakes + pytest-pep8 + coverage +commands = + {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} + +[pytest] +addopts = --flakes --pep8 +pep8ignore = E501 +norecursedirs = bin lib include Scripts .* From b9c4ecf5a86aa9054234f4f2f7b99a7030b5b1d7 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 27 Jun 2016 11:32:38 +0200 Subject: [PATCH 002/153] Add MANIFEST.in. --- MANIFEST.in | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..fb7dc7474 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst +include tox.ini +include LICENSE +include test*.py From 3feee0c48386f172effd8056361cce4a1b87ef4e Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 27 Jun 2016 11:32:54 +0200 Subject: [PATCH 003/153] Prepare 0.1 release. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 02389db73..aa8146bed 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ You can also turn warnings into actual errors:: Changes ======= -0.1 - Unreleased +0.1 - 2016-06-27 ---------------- - Initial release. From bc5a8c761e596fbfc661adb26889951140622203 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 27 Jun 2016 11:33:36 +0200 Subject: [PATCH 004/153] Fix version in readme. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index aa8146bed..483d258ea 100644 --- a/README.rst +++ b/README.rst @@ -30,8 +30,8 @@ You can also turn warnings into actual errors:: Changes ======= -0.1 - 2016-06-27 ----------------- +0.1.0 - 2016-06-27 +------------------ - Initial release. [fschulze (Florian Schulze)] From db922403cc6ec06a0cab54b59f5e65ea8ced215c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 19 Aug 2016 17:40:43 -0300 Subject: [PATCH 005/153] Bump version to 3.1.0.dev --- CHANGELOG.rst | 12 +++++++++++- _pytest/__init__.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e8ad90ff7..48262f8c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,4 +1,4 @@ -3.0.1.dev +3.1.0.dev ========= * @@ -10,6 +10,16 @@ * +3.0.1.dev +========= + +* + +* + +* + +* 3.0.0 ===== diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 82f668dcf..989d8a975 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '3.0.1.dev' +__version__ = '3.1.0.dev' From c5675d3efc38ff20eed41a13ee2f27691a6144eb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Aug 2016 23:42:16 -0300 Subject: [PATCH 006/153] Remove merge-conflict marker merged by accident Unfortunately rst-lint didn't catch that --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bcf1984c9..0d244e020 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -46,7 +46,6 @@ .. _#1832: https://github.com/pytest-dev/pytest/issues/1832 .. _#1849: https://github.com/pytest-dev/pytest/issues/1849 ->>>>>>> master 3.0.0 ===== From 1b259f70f3e0eac4eb0af441b7a18779b2c4ac58 Mon Sep 17 00:00:00 2001 From: John Towler Date: Thu, 25 Aug 2016 13:08:51 -0700 Subject: [PATCH 007/153] Testcase reports with a url attribute will now properly write this to junitxml --- CHANGELOG.rst | 5 ++++- _pytest/junitxml.py | 2 ++ testing/test_junitxml.py | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 447b03b94..8abe4c48c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,9 +9,12 @@ * Add ``buffer`` attribute to stdin stub class ``pytest.capture.DontReadFromInput`` Thanks `@joguSD`_ for the PR. -* +* Testcase reports with a url attribute will now properly write this to junitxml. + Thanks `@fushi`_ for the PR + .. _@joguSD: https://github.com/joguSD +.. _@fushi: https://github.com/fushi .. _#1857: https://github.com/pytest-dev/pytest/issues/1857 diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index da190ff21..88e4f5282 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -102,6 +102,8 @@ class _NodeReporter(object): } if testreport.location[1] is not None: attrs["line"] = testreport.location[1] + if hasattr(testreport, "url"): + attrs["url"] = testreport.url self.attrs = attrs def to_xml(self): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index aec2741f1..382d7c137 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -922,3 +922,28 @@ def test_global_properties(testdir): actual[k] = v assert actual == expected + + +def test_url_property(testdir): + test_url = "http://www.github.com/pytest-dev" + path = testdir.tmpdir.join("test_url_property.xml") + log = LogXML(str(path), None) + from _pytest.runner import BaseReport + + class Report(BaseReport): + longrepr = "FooBarBaz" + sections = [] + nodeid = "something" + location = 'tests/filename.py', 42, 'TestClass.method' + url = test_url + + test_report = Report() + + log.pytest_sessionstart() + node_reporter = log._opentestcase(test_report) + node_reporter.append_failure(test_report) + log.pytest_sessionfinish() + + test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0] + + assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" \ No newline at end of file From e572c16d3f1f619b94137ca8226acba83054d566 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 27 Aug 2016 12:53:18 +0200 Subject: [PATCH 008/153] remove memoizedcall in Module --- _pytest/python.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/python.py b/_pytest/python.py index 2ab1de6b0..2c0b02ff2 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -397,8 +397,9 @@ def transfer_markers(funcobj, cls, mod): class Module(pytest.File, PyCollector): """ Collector for test classes and functions. """ + def _getobj(self): - return self._memoizedcall('_obj', self._importtestmodule) + return self._importtestmodule() def collect(self): self.session._fixturemanager.parsefactories(self) From e1674f60e75da82dafd0685f32b97af4967d971c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 27 Aug 2016 13:04:00 +0200 Subject: [PATCH 009/153] remove memocollect anmd memoized_call --- _pytest/main.py | 4 ---- _pytest/pytester.py | 8 +++++++- _pytest/runner.py | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 5771a1699..3383400e1 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -455,10 +455,6 @@ class Collector(Node): return str(exc.args[0]) return self._repr_failure_py(excinfo, style="short") - def _memocollect(self): - """ internal helper method to cache results of calling collect(). """ - return self._memoizedcall('_collected', lambda: list(self.collect())) - def _prunetraceback(self, excinfo): if hasattr(self, 'fspath'): traceback = excinfo.traceback diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 651160cc7..da8996a04 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -10,6 +10,8 @@ import time import traceback from fnmatch import fnmatch +from weakref import WeakKeyDictionary + from py.builtin import print_ from _pytest._code import Source @@ -401,6 +403,7 @@ class Testdir: def __init__(self, request, tmpdir_factory): self.request = request + self._mod_collections = WeakKeyDictionary() # XXX remove duplication with tmpdir plugin basetmp = tmpdir_factory.ensuretemp("testdir") name = request.function.__name__ @@ -856,6 +859,7 @@ class Testdir: self.makepyfile(__init__ = "#") self.config = config = self.parseconfigure(path, *configargs) node = self.getnode(config, path) + return node def collect_by_name(self, modcol, name): @@ -870,7 +874,9 @@ class Testdir: :param name: The name of the node to return. """ - for colitem in modcol._memocollect(): + if modcol not in self._mod_collections: + self._mod_collections[modcol] = list(modcol.collect()) + for colitem in self._mod_collections[modcol]: if colitem.name == name: return colitem diff --git a/_pytest/runner.py b/_pytest/runner.py index d1a155415..6f1759f14 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -330,7 +330,9 @@ class TeardownErrorReport(BaseReport): self.__dict__.update(extra) def pytest_make_collect_report(collector): - call = CallInfo(collector._memocollect, "memocollect") + call = CallInfo( + lambda: list(collector.collect()), + 'collect') longrepr = None if not call.excinfo: outcome = "passed" @@ -568,4 +570,3 @@ def importorskip(modname, minversion=None): raise Skipped("module %r has __version__ %r, required is: %r" %( modname, verattr, minversion), allow_module_level=True) return mod - From 28b1896e9a9aebb4e73d23498977b6c859420445 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 5 Sep 2016 15:29:09 +0200 Subject: [PATCH 010/153] Remove BuiltinAssertionError We used to have this when we where patching the real Python AssertionError for use with reinterpret, but reinterpret is now gone so we no longer need this as it is not used by rewrite. --- _pytest/assertion/util.py | 2 +- _pytest/python.py | 6 ------ testing/python/raises.py | 8 -------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py index 2481cf34c..29ae51186 100644 --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -8,7 +8,7 @@ try: except ImportError: Sequence = list -BuiltinAssertionError = py.builtin.builtins.AssertionError + u = py.builtin._totext # The _reprcompare attribute on the util module is used by the new assertion diff --git a/_pytest/python.py b/_pytest/python.py index 4d2155aee..af9d1e00e 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1146,12 +1146,6 @@ def raises(expected_exception, *args, **kwargs): """ __tracebackhide__ = True - if expected_exception is AssertionError: - # we want to catch a AssertionError - # replace our subclass with the builtin one - # see https://github.com/pytest-dev/pytest/issues/176 - from _pytest.assertion.util import BuiltinAssertionError \ - as expected_exception msg = ("exceptions must be old-style classes or" " derived from BaseException, not %s") if isinstance(expected_exception, tuple): diff --git a/testing/python/raises.py b/testing/python/raises.py index 59fd622fd..aee3715f1 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -26,14 +26,6 @@ class TestRaises: except pytest.raises.Exception: pass - def test_raises_flip_builtin_AssertionError(self): - # we replace AssertionError on python level - # however c code might still raise the builtin one - from _pytest.assertion.util import BuiltinAssertionError # noqa - pytest.raises(AssertionError,""" - raise BuiltinAssertionError - """) - def test_raises_as_contextmanager(self, testdir): testdir.makepyfile(""" from __future__ import with_statement From 10094a3f091ab64a608bc5107e91cafc9faa0c4c Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 8 Sep 2016 09:52:22 +0200 Subject: [PATCH 011/153] use consistent inner repressentation for marks --- _pytest/main.py | 2 +- _pytest/mark.py | 83 ++++++++++++++++++++++++-------------------- _pytest/skipping.py | 8 ++--- testing/test_mark.py | 4 +-- 4 files changed, 52 insertions(+), 45 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 5771a1699..f9298aa0c 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -356,7 +356,7 @@ class Node(object): """ from _pytest.mark import MarkDecorator if isinstance(marker, py.builtin._basestring): - marker = MarkDecorator(marker) + marker = getattr(pytest.mark, marker) elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker.name] = marker diff --git a/_pytest/mark.py b/_pytest/mark.py index 40c998c3e..d18654fb6 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -1,5 +1,11 @@ """ generic mechanism for marking and selecting python functions. """ import inspect +from collections import namedtuple +from operator import attrgetter +from itertools import imap + +def alias(name): + return property(attrgetter(name), doc='alias for ' + name) class MarkerError(Exception): @@ -182,7 +188,7 @@ class MarkGenerator: raise AttributeError("Marker name must NOT start with underscore") if hasattr(self, '_config'): self._check(name) - return MarkDecorator(name) + return MarkDecorator(Mark(name, (), {})) def _check(self, name): try: @@ -235,19 +241,20 @@ class MarkDecorator: additional keyword or positional arguments. """ - def __init__(self, name, args=None, kwargs=None): - self.name = name - self.args = args or () - self.kwargs = kwargs or {} + def __init__(self, mark): + assert isinstance(mark, Mark), repr(mark) + self.mark = mark + + name = alias('mark.name') + args = alias('mark.args') + kwargs = alias('mark.kwargs') @property def markname(self): return self.name # for backward-compat (2.4.1 had this attr) def __repr__(self): - d = self.__dict__.copy() - name = d.pop('name') - return "" % (name, d) + return "" % self.mark def __call__(self, *args, **kwargs): """ if passed a single callable argument: decorate it with mark info. @@ -270,17 +277,14 @@ class MarkDecorator: else: holder = getattr(func, self.name, None) if holder is None: - holder = MarkInfo( - self.name, self.args, self.kwargs - ) + holder = MarkInfo(self.mark) setattr(func, self.name, holder) else: - holder.add(self.args, self.kwargs) + holder.add_mark(self.mark) return func - kw = self.kwargs.copy() - kw.update(kwargs) - args = self.args + args - return self.__class__(self.name, args=args, kwargs=kw) + + mark = Mark(self.name, args, kwargs) + return self.__class__(self.mark.combined_with(mark)) def extract_argvalue(maybe_marked_args): @@ -291,36 +295,41 @@ def extract_argvalue(maybe_marked_args): newmarks = {} argval = maybe_marked_args while isinstance(argval, MarkDecorator): - newmark = MarkDecorator(argval.markname, - argval.args[:-1], argval.kwargs) - newmarks[newmark.markname] = newmark + newmark = MarkDecorator(Mark( + argval.markname, argval.args[:-1], argval.kwargs)) + newmarks[newmark.name] = newmark argval = argval.args[-1] return argval, newmarks -class MarkInfo: +class Mark(namedtuple('Mark', 'name, args, kwargs')): + + def combined_with(self, other): + assert self.name == other.name + return Mark( + self.name, self.args + other.args, + dict(self.kwargs, **other.kwargs)) + + +class MarkInfo(object): """ Marking object created by :class:`MarkDecorator` instances. """ - def __init__(self, name, args, kwargs): - #: name of attribute - self.name = name - #: positional argument list, empty if none specified - self.args = args - #: keyword argument dictionary, empty if nothing specified - self.kwargs = kwargs.copy() - self._arglist = [(args, kwargs.copy())] + def __init__(self, mark): + assert isinstance(mark, Mark), repr(mark) + self.combined = mark + self._marks = [mark] + + name = alias('combined.name') + args = alias('combined.args') + kwargs = alias('combined.kwargs') def __repr__(self): - return "" % ( - self.name, self.args, self.kwargs - ) + return "".format(self.combined) - def add(self, args, kwargs): + def add_mark(self, mark): """ add a MarkInfo with the given args and kwargs. """ - self._arglist.append((args, kwargs)) - self.args += args - self.kwargs.update(kwargs) + self._marks.append(mark) + self.combined = self.combined.combined_with(mark) def __iter__(self): """ yield MarkInfo objects each relating to a marking-call. """ - for args, kwargs in self._arglist: - yield MarkInfo(self.name, args, kwargs) + return imap(MarkInfo, self._marks) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 2f000b7b9..3ef78b74d 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -121,11 +121,9 @@ class MarkEvaluator: # "holder" might be a MarkInfo or a MarkDecorator; only # MarkInfo keeps track of all parameters it received in an # _arglist attribute - if hasattr(self.holder, '_arglist'): - arglist = self.holder._arglist - else: - arglist = [(self.holder.args, self.holder.kwargs)] - for args, kwargs in arglist: + marks = getattr(self.holder, '_marks', None) \ + or [self.holder.mark] + for _, args, kwargs in marks: if 'condition' in kwargs: args = (kwargs['condition'],) for expr in args: diff --git a/testing/test_mark.py b/testing/test_mark.py index e0bf3c3c8..ca717c7c6 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -5,8 +5,8 @@ from _pytest.mark import MarkGenerator as Mark class TestMark: def test_markinfo_repr(self): - from _pytest.mark import MarkInfo - m = MarkInfo("hello", (1,2), {}) + from _pytest.mark import MarkInfo, Mark + m = MarkInfo(Mark("hello", (1,2), {})) repr(m) def test_pytest_exists_in_namespace_all(self): From 1812387bf0c2cb36630afa3942f553c9540a5f47 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 8 Sep 2016 10:03:45 +0200 Subject: [PATCH 012/153] Mark: fix python 3 compatibility --- _pytest/compat.py | 6 ++++-- _pytest/mark.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 1d8c2f331..d5f7ffe56 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -111,7 +111,7 @@ if sys.version_info[:2] == (2, 6): if _PY3: import codecs - + imap = map STRING_TYPES = bytes, str def _escape_strings(val): @@ -145,6 +145,8 @@ if _PY3: else: STRING_TYPES = bytes, str, unicode + from itertools import imap + def _escape_strings(val): """In py2 bytes and str are the same type, so return if it's a bytes object, return it unchanged if it is a full ascii string, @@ -213,4 +215,4 @@ def _is_unittest_unexpected_success_a_failure(): Changed in version 3.4: Returns False if there were any unexpectedSuccesses from tests marked with the expectedFailure() decorator. """ - return sys.version_info >= (3, 4) \ No newline at end of file + return sys.version_info >= (3, 4) diff --git a/_pytest/mark.py b/_pytest/mark.py index d18654fb6..3c97dc153 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -2,7 +2,7 @@ import inspect from collections import namedtuple from operator import attrgetter -from itertools import imap +from .compat import imap def alias(name): return property(attrgetter(name), doc='alias for ' + name) From 45524241a5e9ac349961a803907c1394ad289390 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 8 Sep 2016 10:16:45 +0200 Subject: [PATCH 013/153] mark: fix introduced linting error --- _pytest/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index d5f7ffe56..16505c31c 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -145,7 +145,7 @@ if _PY3: else: STRING_TYPES = bytes, str, unicode - from itertools import imap + from itertools import imap # NOQA def _escape_strings(val): """In py2 bytes and str are the same type, so return if it's a bytes From dd64d823b90f87fca2bb77a61da0912d91f8d7ea Mon Sep 17 00:00:00 2001 From: Matthew Duck Date: Mon, 19 Sep 2016 12:17:26 +0100 Subject: [PATCH 014/153] Don't display dict common items if verbosity=1 Part one of https://github.com/pytest-dev/pytest/issues/1512. If verbosity=1, assertion explanations are truncated at 10 lines. In this situation, it's more important to tell the user which dictionary items are different than which are the same. --- _pytest/assertion/util.py | 4 ++-- testing/test_assertion.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py index 29ae51186..709fe3b4d 100644 --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -256,8 +256,8 @@ def _compare_eq_dict(left, right, verbose=False): explanation = [] common = set(left).intersection(set(right)) same = dict((k, left[k]) for k in common if left[k] == right[k]) - if same and not verbose: - explanation += [u('Omitting %s identical items, use -v to show') % + if same and verbose < 2: + explanation += [u('Omitting %s identical items, use -vv to show') % len(same)] elif same: explanation += [u('Common items:')] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index d49815181..93e8847fa 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -351,8 +351,16 @@ class TestAssert_reprcompare: for line in lines[1:]: assert 'b' not in line - def test_dict_omitting_verbose(self): - lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1}, verbose=True) + def test_dict_omitting_with_verbosity_1(self): + """ Ensure differing items are visible for verbosity=1 (#1512) """ + lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1}, verbose=1) + assert lines[1].startswith('Omitting 1 identical item') + assert lines[2].startswith('Differing items') + assert lines[3] == "{'a': 0} != {'a': 1}" + assert 'Common items' not in lines + + def test_dict_omitting_with_verbosity_2(self): + lines = callequal({'a': 0, 'b': 1}, {'a': 1, 'b': 1}, verbose=2) assert lines[1].startswith('Common items:') assert 'Omitting' not in lines[1] assert lines[2] == "{'b': 1}" From 999e7c65417f1e97fc89bf66e0da4c5cd84442ec Mon Sep 17 00:00:00 2001 From: Matthew Duck Date: Mon, 19 Sep 2016 14:19:34 +0100 Subject: [PATCH 015/153] Tidy formatting of assertion truncation Part two of https://github.com/pytest-dev/pytest/issues/1512. Update the format of the truncation message to help make it clear that pytest truncates the entire assertion output when verbosity < 2. --- _pytest/assertion/__init__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index fd1ebe2c1..e7f0e58ed 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -131,14 +131,21 @@ def pytest_runtest_setup(item): config=item.config, op=op, left=left, right=right) for new_expl in hook_result: if new_expl: + + # Truncate lines if required if (sum(len(p) for p in new_expl[1:]) > 80*8 and item.config.option.verbose < 2 and not _running_on_ci()): show_max = 10 - truncated_lines = len(new_expl) - show_max - new_expl[show_max:] = [py.builtin._totext( - 'Detailed information truncated (%d more lines)' - ', use "-vv" to show' % truncated_lines)] + truncated_count = len(new_expl) - show_max + new_expl[show_max - 1] += " ..." + new_expl[show_max:] = [ + py.builtin._totext(""), + py.builtin._totext('...Full output truncated (%d more lines)' + ', use "-vv" to show' % truncated_count + ), + ] + new_expl = [line.replace("\n", "\\n") for line in new_expl] res = py.builtin._totext("\n~").join(new_expl) if item.config.getvalue("assertmode") == "rewrite": From 4df74a5cfb20c0898f046315dd5b1d0053922062 Mon Sep 17 00:00:00 2001 From: Matthew Duck Date: Mon, 19 Sep 2016 14:35:00 +0100 Subject: [PATCH 016/153] Add AUTHORS and CHANGELOG for #1512 --- AUTHORS | 1 + CHANGELOG.rst | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index c74aa4af6..a53455aff 100644 --- a/AUTHORS +++ b/AUTHORS @@ -89,6 +89,7 @@ Martijn Faassen Martin K. Scherer Martin Prusse Matt Bachmann +Matt Duck Matt Williams Matthias Hafner mbyt diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7d5bee5a8..1c75e4335 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,15 +4,19 @@ * Testcase reports with a url attribute will now properly write this to junitxml. Thanks `@fushi`_ for the PR -* - -* +* Remove common items from dict comparision output when verbosity=1. Also update + the truncation message to make it clearer that pytest truncates all + assertion messages if verbosity < 2 (`#1512`_). + Thanks `@mattduck`_ for the PR * * .. _@fushi: https://github.com/fushi +.. _@mattduck: https://github.com/mattduck + +.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 3.0.2 From 0ac85218d1b761f09d67eb55e10d66575285c99c Mon Sep 17 00:00:00 2001 From: David Szotten Date: Mon, 19 Sep 2016 15:43:54 +0000 Subject: [PATCH 017/153] allow pdbcls without implying usepdb --- _pytest/debugging.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 299b05028..685da8200 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -20,15 +20,15 @@ def pytest_namespace(): return {'set_trace': pytestPDB().set_trace} def pytest_configure(config): - if config.getvalue("usepdb") or config.getvalue("usepdb_cls"): + if config.getvalue("usepdb_cls"): + modname, classname = config.getvalue("usepdb_cls").split(":") + __import__(modname) + pdb_cls = getattr(sys.modules[modname], classname) + else: + pdb_cls = pdb.Pdb + + if config.getvalue("usepdb"): config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') - if config.getvalue("usepdb_cls"): - modname, classname = config.getvalue("usepdb_cls").split(":") - __import__(modname) - pdb_cls = getattr(sys.modules[modname], classname) - else: - pdb_cls = pdb.Pdb - pytestPDB._pdb_cls = pdb_cls old = (pdb.set_trace, pytestPDB._pluginmanager) def fin(): @@ -38,6 +38,7 @@ def pytest_configure(config): pdb.set_trace = pytest.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config + pytestPDB._pdb_cls = pdb_cls config._cleanup.append(fin) class pytestPDB: From c7b4b8cf6fe3b2bb608f392839a2638857d6509a Mon Sep 17 00:00:00 2001 From: David Szotten Date: Mon, 19 Sep 2016 16:05:57 +0000 Subject: [PATCH 018/153] test --- testing/test_pdb.py | 46 ++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index d79d71262..15cb40eb6 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -12,6 +12,26 @@ def runpdb_and_get_report(testdir, source): return reports[1] +@pytest.fixture +def custom_pdb_calls(): + called = [] + + # install dummy debugger class and track which methods were called on it + class _CustomPdb: + def __init__(self, *args, **kwargs): + called.append("init") + + def reset(self): + called.append("reset") + + def interaction(self, *args): + called.append("interaction") + + _pytest._CustomPdb = _CustomPdb + return called + + + class TestPDB: @pytest.fixture @@ -334,22 +354,18 @@ class TestPDB: if child.isalive(): child.wait() - def test_pdb_custom_cls(self, testdir): - called = [] + def test_pdb_custom_cls(self, testdir, custom_pdb_calls): + p1 = testdir.makepyfile("""xxx """) + result = testdir.runpytest_inprocess( + "--pdb", "--pdbcls=_pytest:_CustomPdb", p1) + result.stdout.fnmatch_lines([ + "*NameError*xxx*", + "*1 error*", + ]) + assert custom_pdb_calls == ["init", "reset", "interaction"] - # install dummy debugger class and track which methods were called on it - class _CustomPdb: - def __init__(self, *args, **kwargs): - called.append("init") - - def reset(self): - called.append("reset") - - def interaction(self, *args): - called.append("interaction") - - _pytest._CustomPdb = _CustomPdb + def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls): p1 = testdir.makepyfile("""xxx """) result = testdir.runpytest_inprocess( "--pdbcls=_pytest:_CustomPdb", p1) @@ -357,4 +373,4 @@ class TestPDB: "*NameError*xxx*", "*1 error*", ]) - assert called == ["init", "reset", "interaction"] + assert custom_pdb_calls == [] From d75748ef6f158aca7e253d0632615bc67072d946 Mon Sep 17 00:00:00 2001 From: David Szotten Date: Wed, 21 Sep 2016 08:44:39 +0000 Subject: [PATCH 019/153] test for calling set_trace with custom pdb cls --- testing/test_pdb.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 15cb40eb6..f164f9691 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -374,3 +374,22 @@ class TestPDB: "*1 error*", ]) assert custom_pdb_calls == [] + + def test_pdb_custom_cls_with_settrace(self, testdir, monkeypatch): + testdir.makepyfile(custom_pdb=""" + class CustomPdb: + def set_trace(*args, **kwargs): + print 'custom set_trace>' + """) + p1 = testdir.makepyfile(""" + import pytest + + def test_foo(): + pytest.set_trace() + """) + monkeypatch.setenv('PYTHONPATH', str(testdir.tmpdir)) + child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1)) + + child.expect('custom set_trace>') + if child.isalive(): + child.wait() From 3fce78498fad121c75ec23fce7da06cd1c3470d2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 25 Sep 2016 20:31:03 -0300 Subject: [PATCH 020/153] Mention #1952 on the CHANGELOG Also created "New Features" and "Changes" sections. --- CHANGELOG.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1c75e4335..e10e19e4f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,19 @@ 3.1.0.dev ========= + +New Features +------------ + + +* + +* + + +Changes +------- + * Testcase reports with a url attribute will now properly write this to junitxml. Thanks `@fushi`_ for the PR @@ -9,14 +22,20 @@ assertion messages if verbosity < 2 (`#1512`_). Thanks `@mattduck`_ for the PR +* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use + ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for + the PR (`#1952`_). + * * +.. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 +.. _#1952: https://github.com/pytest-dev/pytest/pull/1952 3.0.2 From 8985c0be3ec5294deb173d4161e2125adab18f37 Mon Sep 17 00:00:00 2001 From: Vlad Dragos Date: Mon, 26 Sep 2016 13:15:35 +0300 Subject: [PATCH 021/153] Change exception raised by DontReadFromInput.fileno() from ValueError to io.UnsupportedOperation --- _pytest/capture.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index 9f60db6ac..f4b6e64c3 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -7,6 +7,7 @@ from __future__ import with_statement import contextlib import sys import os +from io import UnsupportedOperation from tempfile import TemporaryFile import py @@ -447,7 +448,7 @@ class DontReadFromInput: __iter__ = read def fileno(self): - raise ValueError("redirected Stdin is pseudofile, has no fileno()") + raise UnsupportedOperation("redirected Stdin is pseudofile, has no fileno()") def isatty(self): return False From ff492ca73fc038fcbe7f233594a9a19e4dbbe988 Mon Sep 17 00:00:00 2001 From: Vlad Dragos Date: Mon, 26 Sep 2016 13:27:41 +0300 Subject: [PATCH 022/153] Thanked my self in the change log :) --- AUTHORS | 1 + CHANGELOG.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index d8c431dc0..3d8dac890 100644 --- a/AUTHORS +++ b/AUTHORS @@ -129,5 +129,6 @@ Tom Viner Trevor Bekolay Tyler Goodlet Vasily Kuznetsov +Vlad Dragos Wouter van Ackooy Xuecong Liao diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a67ec4eee..b3ecf9a3e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,8 @@ * Explain a bad scope value passed to ``@fixture`` declarations or a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR. +* Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError`` + to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR. .. _@philpep: https://github.com/philpep .. _@raquel-ucl: https://github.com/raquel-ucl From 3444796f3e4ba147ccef4df735a8a09ea62b9e48 Mon Sep 17 00:00:00 2001 From: Vlad Dragos Date: Mon, 26 Sep 2016 13:59:28 +0300 Subject: [PATCH 023/153] Fix formating error. --- _pytest/capture.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index f4b6e64c3..b467dc17b 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -448,7 +448,8 @@ class DontReadFromInput: __iter__ = read def fileno(self): - raise UnsupportedOperation("redirected Stdin is pseudofile, has no fileno()") + raise UnsupportedOperation("redirected stdin is pseudofile, " + "has no fileno()") def isatty(self): return False From acac78adc0a5145e2b89c8db74ac8c8b0bc0c136 Mon Sep 17 00:00:00 2001 From: Vlad Dragos Date: Mon, 26 Sep 2016 16:09:25 +0300 Subject: [PATCH 024/153] Add link to profile. --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b3ecf9a3e..0cfe42c79 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -26,6 +26,7 @@ .. _@raquel-ucl: https://github.com/raquel-ucl .. _@axil: https://github.com/axil .. _@tgoodlet: https://github.com/tgoodlet +.. _@vlad-dragos: https://github.com/vlad-dragos .. _#1905: https://github.com/pytest-dev/pytest/issues/1905 .. _#1934: https://github.com/pytest-dev/pytest/issues/1934 From b629da424efa894a37c9d49eb94b14803afa8332 Mon Sep 17 00:00:00 2001 From: Matthew Duck Date: Thu, 22 Sep 2016 00:06:45 +0100 Subject: [PATCH 025/153] Restructure truncation of assertion messages This addresses ref https://github.com/pytest-dev/pytest/issues/1954. The current truncation for assertion explanations does not deal with long lines properly: - Previously if lines were too long it would display a "-n more lines" message. - 999e7c65417f1e97fc89bf66e0da4c5cd84442ec introduced a bug where long lines can cause index errors if there are < 10 lines. Extract the truncation logic into its own file and ensure it can deal with long lines properly. --- _pytest/assertion/__init__.py | 27 +------ _pytest/assertion/truncate.py | 102 ++++++++++++++++++++++++ testing/test_assertion.py | 141 +++++++++++++++++++++++++--------- 3 files changed, 212 insertions(+), 58 deletions(-) create mode 100644 _pytest/assertion/truncate.py diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index e7f0e58ed..0cdb56e07 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -7,6 +7,7 @@ import sys from _pytest.assertion import util from _pytest.assertion import rewrite +from _pytest.assertion import truncate def pytest_addoption(parser): @@ -98,12 +99,6 @@ def pytest_collection(session): assertstate.hook.set_session(session) -def _running_on_ci(): - """Check if we're currently running on a CI system.""" - env_vars = ['CI', 'BUILD_NUMBER'] - return any(var in os.environ for var in env_vars) - - def pytest_runtest_setup(item): """Setup the pytest_assertrepr_compare hook @@ -117,8 +112,8 @@ def pytest_runtest_setup(item): This uses the first result from the hook and then ensures the following: - * Overly verbose explanations are dropped unless -vv was used or - running on a CI. + * Overly verbose explanations are truncated unless configured otherwise + (eg. if running in verbose mode). * Embedded newlines are escaped to help util.format_explanation() later. * If the rewrite mode is used embedded %-characters are replaced @@ -131,21 +126,7 @@ def pytest_runtest_setup(item): config=item.config, op=op, left=left, right=right) for new_expl in hook_result: if new_expl: - - # Truncate lines if required - if (sum(len(p) for p in new_expl[1:]) > 80*8 and - item.config.option.verbose < 2 and - not _running_on_ci()): - show_max = 10 - truncated_count = len(new_expl) - show_max - new_expl[show_max - 1] += " ..." - new_expl[show_max:] = [ - py.builtin._totext(""), - py.builtin._totext('...Full output truncated (%d more lines)' - ', use "-vv" to show' % truncated_count - ), - ] - + new_expl = truncate.truncate_if_required(new_expl, item) new_expl = [line.replace("\n", "\\n") for line in new_expl] res = py.builtin._totext("\n~").join(new_expl) if item.config.getvalue("assertmode") == "rewrite": diff --git a/_pytest/assertion/truncate.py b/_pytest/assertion/truncate.py new file mode 100644 index 000000000..3c031b11f --- /dev/null +++ b/_pytest/assertion/truncate.py @@ -0,0 +1,102 @@ +""" +Utilities for truncating assertion output. + +Current default behaviour is to truncate assertion explanations at +~8 terminal lines, unless running in "-vv" mode or running on CI. +""" + +import os + +import py + + +DEFAULT_MAX_LINES = 8 +DEFAULT_MAX_CHARS = 8 * 80 +USAGE_MSG = "use '-vv' to show" + + +def truncate_if_required(explanation, item, max_length=None): + """ + Truncate this assertion explanation if the given test item is eligible. + """ + if _should_truncate_item(item): + return _truncate_explanation(explanation) + return explanation + + +def _should_truncate_item(item): + """ + Whether or not this test item is eligible for truncation. + """ + verbose = item.config.option.verbose + return verbose < 2 and not _running_on_ci() + + +def _running_on_ci(): + """Check if we're currently running on a CI system.""" + env_vars = ['CI', 'BUILD_NUMBER'] + return any(var in os.environ for var in env_vars) + + +def _truncate_explanation(input_lines, max_lines=None, max_chars=None): + """ + Truncate given list of strings that makes up the assertion explanation. + + Truncates to either 8 lines, or 640 characters - whichever the input reaches + first. The remaining lines will be replaced by a usage message. + """ + + if max_lines is None: + max_lines = DEFAULT_MAX_LINES + if max_chars is None: + max_chars = DEFAULT_MAX_CHARS + + # Check if truncation required + input_char_count = len("".join(input_lines)) + if len(input_lines) <= max_lines and input_char_count <= max_chars: + return input_lines + + # Truncate first to max_lines, and then truncate to max_chars if max_chars + # is exceeded. + truncated_explanation = input_lines[:max_lines] + truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars) + + # Add ellipsis to final line + truncated_explanation[-1] = truncated_explanation[-1] + "..." + + # Append useful message to explanation + truncated_line_count = len(input_lines) - len(truncated_explanation) + truncated_line_count += 1 # Account for the part-truncated final line + msg = '...Full output truncated' + if truncated_line_count == 1: + msg += ' ({0} line hidden)'.format(truncated_line_count) + else: + msg += ' ({0} lines hidden)'.format(truncated_line_count) + msg += ", {0}" .format(USAGE_MSG) + truncated_explanation.extend([ + py.builtin._totext(""), + py.builtin._totext(msg), + ]) + return truncated_explanation + + +def _truncate_by_char_count(input_lines, max_chars): + # Check if truncation required + if len("".join(input_lines)) <= max_chars: + return input_lines + + # Find point at which input length exceeds total allowed length + iterated_char_count = 0 + for iterated_index, input_line in enumerate(input_lines): + if iterated_char_count + len(input_line) > max_chars: + break + iterated_char_count += len(input_line) + + # Create truncated explanation with modified final line + truncated_result = input_lines[:iterated_index] + final_line = input_lines[iterated_index] + if final_line: + final_line_truncate_point = max_chars - iterated_char_count + final_line = final_line[:final_line_truncate_point] + truncated_result.append(final_line) + return truncated_result diff --git a/testing/test_assertion.py b/testing/test_assertion.py index c6afab014..24b3e719b 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -6,6 +6,7 @@ import _pytest.assertion as plugin import py import pytest from _pytest.assertion import util +from _pytest.assertion import truncate PY3 = sys.version_info >= (3, 0) @@ -572,6 +573,111 @@ class TestFormatExplanation: assert util.format_explanation(expl) == res +class TestTruncateExplanation: + + """ Confirm assertion output is truncated as expected """ + + # The number of lines in the truncation explanation message. Used + # to calculate that results have the expected length. + LINES_IN_TRUNCATION_MSG = 2 + + def test_doesnt_truncate_when_input_is_empty_list(self): + expl = [] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) + assert result == expl + + def test_doesnt_truncate_at_when_input_is_5_lines_and_LT_max_chars(self): + expl = ['a' * 100 for x in range(5)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8*80) + assert result == expl + + def test_truncates_at_8_lines_when_given_list_of_empty_strings(self): + expl = ['' for x in range(50)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) + assert result != expl + assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG + assert "Full output truncated" in result[-1] + assert "43 lines hidden" in result[-1] + last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1] + assert last_line_before_trunc_msg.endswith("...") + + def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self): + expl = ['a' for x in range(100)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8*80) + assert result != expl + assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG + assert "Full output truncated" in result[-1] + assert "93 lines hidden" in result[-1] + last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1] + assert last_line_before_trunc_msg.endswith("...") + + def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self): + expl = ['a' * 80 for x in range(16)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8*80) + assert result != expl + assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG + assert "Full output truncated" in result[-1] + assert "9 lines hidden" in result[-1] + last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1] + assert last_line_before_trunc_msg.endswith("...") + + def test_truncates_at_4_lines_when_first_4_lines_are_GT_max_chars(self): + expl = ['a' * 250 for x in range(10)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=999) + assert result != expl + assert len(result) == 4 + self.LINES_IN_TRUNCATION_MSG + assert "Full output truncated" in result[-1] + assert "7 lines hidden" in result[-1] + last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1] + assert last_line_before_trunc_msg.endswith("...") + + def test_truncates_at_1_line_when_first_line_is_GT_max_chars(self): + expl = ['a' * 250 for x in range(1000)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) + assert result != expl + assert len(result) == 1 + self.LINES_IN_TRUNCATION_MSG + assert "Full output truncated" in result[-1] + assert "1000 lines hidden" in result[-1] + last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1] + assert last_line_before_trunc_msg.endswith("...") + + def test_full_output_truncated(self, monkeypatch, testdir): + """ Test against full runpytest() output. """ + + line_count = 7 + line_len = 100 + expected_truncated_lines = 2 + testdir.makepyfile(r""" + def test_many_lines(): + a = list([str(i)[0] * %d for i in range(%d)]) + b = a[::2] + a = '\n'.join(map(str, a)) + b = '\n'.join(map(str, b)) + assert a == b + """ % (line_len, line_count)) + monkeypatch.delenv('CI', raising=False) + + result = testdir.runpytest() + # without -vv, truncate the message showing a few diff lines only + result.stdout.fnmatch_lines([ + "*- 1*", + "*- 3*", + "*- 5*", + "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, + ]) + + result = testdir.runpytest('-vv') + result.stdout.fnmatch_lines([ + "*- %d*" % 5, + ]) + + monkeypatch.setenv('CI', '1') + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*- %d*" % 5, + ]) + + def test_python25_compile_issue257(testdir): testdir.makepyfile(""" def test_rewritten(): @@ -631,40 +737,6 @@ def test_sequence_comparison_uses_repr(testdir): ]) -def test_assert_compare_truncate_longmessage(monkeypatch, testdir): - testdir.makepyfile(r""" - def test_long(): - a = list(range(200)) - b = a[::2] - a = '\n'.join(map(str, a)) - b = '\n'.join(map(str, b)) - assert a == b - """) - monkeypatch.delenv('CI', raising=False) - - result = testdir.runpytest() - # without -vv, truncate the message showing a few diff lines only - result.stdout.fnmatch_lines([ - "*- 1", - "*- 3", - "*- 5", - "*- 7", - "*truncated (193 more lines)*use*-vv*", - ]) - - - result = testdir.runpytest('-vv') - result.stdout.fnmatch_lines([ - "*- 197", - ]) - - monkeypatch.setenv('CI', '1') - result = testdir.runpytest() - result.stdout.fnmatch_lines([ - "*- 197", - ]) - - def test_assertrepr_loaded_per_dir(testdir): testdir.makepyfile(test_base=['def test_base(): assert 1 == 2']) a = testdir.mkdir('a') @@ -883,4 +955,3 @@ def test_issue_1944(testdir): result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 error*"]) assert "AttributeError: 'Module' object has no attribute '_obj'" not in result.stdout.str() - From 0061d9bd3ded0c1580211cb01d11ce38fe40b342 Mon Sep 17 00:00:00 2001 From: Matthew Duck Date: Tue, 11 Oct 2016 00:17:15 +0100 Subject: [PATCH 026/153] Fix flake8 (unused import, trailng whitespace) --- _pytest/assertion/__init__.py | 1 - testing/test_assertion.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index 0cdb56e07..46a5b67e6 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -2,7 +2,6 @@ support for presenting detailed information in failing assertions. """ import py -import os import sys from _pytest.assertion import util diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 24b3e719b..1c47440c3 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -579,7 +579,7 @@ class TestTruncateExplanation: # The number of lines in the truncation explanation message. Used # to calculate that results have the expected length. - LINES_IN_TRUNCATION_MSG = 2 + LINES_IN_TRUNCATION_MSG = 2 def test_doesnt_truncate_when_input_is_empty_list(self): expl = [] From acee88a118544b74a4cbb9f57ff6a6a8ffb70516 Mon Sep 17 00:00:00 2001 From: Matthew Duck Date: Sun, 16 Oct 2016 20:54:51 +0100 Subject: [PATCH 027/153] Fix truncation tests: pyruntest() match Fix issue with truncation tests for -vv and CI, where the test for non-truncated output would return a positive match even on truncated output. --- testing/test_assertion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 1c47440c3..dcb0fbf35 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -668,13 +668,13 @@ class TestTruncateExplanation: result = testdir.runpytest('-vv') result.stdout.fnmatch_lines([ - "*- %d*" % 5, + "* 6*", ]) monkeypatch.setenv('CI', '1') result = testdir.runpytest() result.stdout.fnmatch_lines([ - "*- %d*" % 5, + "* 6*", ]) From bc94a51a965ef20e9a66fc678718ce20746a1a0b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 17 Oct 2016 11:34:30 -0700 Subject: [PATCH 028/153] Add a warning option which does not escape its arguments. This is useful when to use regular expressions, like for example ignore a bunch of dynamic messages --filterwarnigns 'ignore:Please use assert.* instead.:' Or ignore all the warnings in a sub-backage --filterwarnigns 'ignore:::package.submodule.*' This is also available in the ini file as the filterwarnigns options --- README.rst | 21 ++++++++++++++ helper_test_a.py | 10 +++++++ helper_test_b.py | 11 ++++++++ pytest_warnings.py | 35 +++++++++++++++++++++++- test_warnings.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 helper_test_a.py create mode 100644 helper_test_b.py diff --git a/README.rst b/README.rst index 483d258ea..def6147ca 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,27 @@ You can also turn warnings into actual errors:: py.test -W error +Advance usage +============= + +You can get more fine grained filtering of warnings by using the +``filterwarnings`` configuration option. + +``filterwarnings`` works like the python's ``-W`` flag except it will not +escape special characters. + +Example +------- + +.. code:: + + # pytest.ini + [pytest] + filterwarnings= default + ignore:.*is deprecated.*:Warning + error::DeprecationWarning:importlib.* + + Changes ======= diff --git a/helper_test_a.py b/helper_test_a.py new file mode 100644 index 000000000..ba88aa31d --- /dev/null +++ b/helper_test_a.py @@ -0,0 +1,10 @@ +import warnings + + +def deprecated_a(): + """ + A warning triggered in __this__ module for testing. + """ + globals()['__warningregistry__'] = {} + warnings.warn("This is deprecated message_a", + DeprecationWarning, stacklevel=0) diff --git a/helper_test_b.py b/helper_test_b.py new file mode 100644 index 000000000..3c00a6114 --- /dev/null +++ b/helper_test_b.py @@ -0,0 +1,11 @@ +import warnings + + +def user_warning_b(): + """ + A warning triggered in __this__ module for testing. + """ + # reset the "once" filters + # globals()['__warningregistry__'] = {} + warnings.warn("This is deprecated message_b different from a", + UserWarning, stacklevel=1) diff --git a/pytest_warnings.py b/pytest_warnings.py index 1813cf519..84f64ea92 100644 --- a/pytest_warnings.py +++ b/pytest_warnings.py @@ -5,11 +5,39 @@ import pytest import warnings +def _setoption(wmod, arg): + """ + Copy of the warning._setoption function but does not escape arguments. + """ + parts = arg.split(':') + if len(parts) > 5: + raise wmod._OptionError("too many fields (max 5): %r" % (arg,)) + while len(parts) < 5: + parts.append('') + action, message, category, module, lineno = [s.strip() + for s in parts] + action = wmod._getaction(action) + category = wmod._getcategory(category) + if lineno: + try: + lineno = int(lineno) + if lineno < 0: + raise ValueError + except (ValueError, OverflowError): + raise wmod._OptionError("invalid lineno %r" % (lineno,)) + else: + lineno = 0 + wmod.filterwarnings(action, message, category, module, lineno) + + def pytest_addoption(parser): group = parser.getgroup("pytest-warnings") group.addoption( '-W', '--pythonwarnings', action='append', - help="set which warnings to report, see ...") + help="set which warnings to report, see -W option of python itself.") + parser.addini("filterwarnings", type="linelist", + help="Each line specifies warning filter pattern which would be passed" + "to warnings.filterwarnings. Process after -W and --pythonwarnings.") @pytest.hookimpl(hookwrapper=True) @@ -28,12 +56,17 @@ def pytest_runtest_call(item): message, category, filename, lineno, file=file, line=line) args = item.config.getoption('pythonwarnings') or [] + inifilters = item.config.getini("filterwarnings") with wrec: _showwarning = wrec._showwarning warnings.showwarning = showwarning wrec._module.simplefilter('once') for arg in args: wrec._module._setoption(arg) + + for arg in inifilters: + _setoption(wrec._module, arg) + yield wrec._showwarning = _showwarning diff --git a/test_warnings.py b/test_warnings.py index 62167992f..bc65220c7 100644 --- a/test_warnings.py +++ b/test_warnings.py @@ -1,6 +1,10 @@ import pytest import warnings +from pytest_warnings import _setoption +from helper_test_a import deprecated_a +from helper_test_b import user_warning_b + def test_warnings(): warnings.warn("Foo", DeprecationWarning) @@ -25,3 +29,67 @@ def test_warnings1(): def test_warn(): with pytest.warns(DeprecationWarning): warnings.warn("Bar", DeprecationWarning) + + +# This section test the ability to filter selectively warnings using regular +# expressions on messages. + +def test_filters_setoption(): + "A alone works" + + with pytest.warns(DeprecationWarning): + deprecated_a() + + +def test_filters_setoption_2(): + "B alone works" + + with pytest.warns(UserWarning) as record: + user_warning_b() + + assert len(record) == 1 + + +def test_filters_setoption_3(): + "A and B works" + + with pytest.warns(None) as record: + user_warning_b() + deprecated_a() + assert len(record) == 2 + + +def test_filters_setoption_4(): + "A works, B is filtered" + + with pytest.warns(None) as record: + _setoption(warnings, 'ignore:.*message_a.*') + deprecated_a() + user_warning_b() + + assert len(record) == 1, "Only `A` should be filtered out" + + +def test_filters_setoption_4b(): + "A works, B is filtered" + + with pytest.warns(None) as record: + _setoption(warnings, 'ignore:.*message_b.*') + _setoption(warnings, 'ignore:.*message_a.*') + _setoption(warnings, 'always:::.*helper_test_a.*') + deprecated_a() + user_warning_b() + + assert len(record) == 1, "`A` and `B` should be visible, second filter reenable A" + + +def test_filters_setoption_5(): + "B works, A is filtered" + + with pytest.warns(None) as records: + _setoption(warnings, 'always:::.*helper_test_a.*') + _setoption(warnings, 'ignore::UserWarning') + deprecated_a() + user_warning_b() + + assert len(records) == 1, "Only `B` should be filtered out" From f229b573fa57d69fa55ad3280f03c5a3525aa9bc Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 24 Oct 2016 12:08:00 +0200 Subject: [PATCH 029/153] Bump version, add changelog entry and move stuff around for added coverage reporting. --- .gitignore | 3 +++ MANIFEST.in | 2 +- README.rst | 7 +++++++ pytest_warnings.py => pytest_warnings/__init__.py | 0 setup.py | 4 ++-- helper_test_a.py => tests/helper_test_a.py | 0 helper_test_b.py => tests/helper_test_b.py | 0 test_warnings.py => tests/test_warnings.py | 0 test_warnings2.py => tests/test_warnings2.py | 0 tox.ini | 3 ++- 10 files changed, 15 insertions(+), 4 deletions(-) rename pytest_warnings.py => pytest_warnings/__init__.py (100%) rename helper_test_a.py => tests/helper_test_a.py (100%) rename helper_test_b.py => tests/helper_test_b.py (100%) rename test_warnings.py => tests/test_warnings.py (100%) rename test_warnings2.py => tests/test_warnings2.py (100%) diff --git a/.gitignore b/.gitignore index dac3fe1aa..80b4d47de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /.cache/ +/.coverage /.tox/ /bin/ +/dist/ +/htmlcov/ /include/ /lib/ /pip-selfcheck.json diff --git a/MANIFEST.in b/MANIFEST.in index fb7dc7474..1652af658 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include README.rst include tox.ini include LICENSE -include test*.py +include tests/*.py diff --git a/README.rst b/README.rst index def6147ca..63e5feff3 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,13 @@ Example Changes ======= +0.2.0 - Unreleased +------------------ + +- Add ``filterwarnings`` option. + [Carreau (Matthias Bussonnier)] + + 0.1.0 - 2016-06-27 ------------------ diff --git a/pytest_warnings.py b/pytest_warnings/__init__.py similarity index 100% rename from pytest_warnings.py rename to pytest_warnings/__init__.py diff --git a/setup.py b/setup.py index fd478664c..ea4d193fd 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,11 @@ setup( description='pytest plugin to list Python warnings in pytest report', long_description=open("README.rst").read(), license="MIT license", - version='0.1.0', + version='0.2.0', author='Florian Schulze', author_email='florian.schulze@gmx.net', url='https://github.com/fschulze/pytest-warnings', - py_modules=["pytest_warnings"], + packages=['pytest_warnings'], entry_points={'pytest11': ['pytest_warnings = pytest_warnings']}, install_requires=['pytest'], classifiers=[ diff --git a/helper_test_a.py b/tests/helper_test_a.py similarity index 100% rename from helper_test_a.py rename to tests/helper_test_a.py diff --git a/helper_test_b.py b/tests/helper_test_b.py similarity index 100% rename from helper_test_b.py rename to tests/helper_test_b.py diff --git a/test_warnings.py b/tests/test_warnings.py similarity index 100% rename from test_warnings.py rename to tests/test_warnings.py diff --git a/test_warnings2.py b/tests/test_warnings2.py similarity index 100% rename from test_warnings2.py rename to tests/test_warnings2.py diff --git a/tox.ini b/tox.ini index 977819b65..ec2e5622d 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py27,py33,py34,py35 usedevelop = true deps = pytest + pytest-cov pytest-flakes pytest-pep8 coverage @@ -12,6 +13,6 @@ commands = {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} [pytest] -addopts = --flakes --pep8 +addopts = --flakes --pep8 --cov pytest_warnings --cov tests --no-cov-on-fail pep8ignore = E501 norecursedirs = bin lib include Scripts .* From ce138060acd9107efac1365a96b14ee8e839c0d9 Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 24 Oct 2016 12:09:49 +0200 Subject: [PATCH 030/153] Prepare pytest-warnings 0.2.0. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 63e5feff3..86d1d0985 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ Example Changes ======= -0.2.0 - Unreleased +0.2.0 - 2016-10-24 ------------------ - Add ``filterwarnings`` option. From 6ec0c3f3690efb500155a44b65c0f1719dad905b Mon Sep 17 00:00:00 2001 From: Florian Schulze Date: Mon, 24 Oct 2016 12:10:49 +0200 Subject: [PATCH 031/153] Bump. --- README.rst | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 86d1d0985..c598259c3 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,11 @@ Example Changes ======= +0.3.0 - Unreleased +------------------ + + + 0.2.0 - 2016-10-24 ------------------ diff --git a/setup.py b/setup.py index ea4d193fd..19a4c6e80 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( description='pytest plugin to list Python warnings in pytest report', long_description=open("README.rst").read(), license="MIT license", - version='0.2.0', + version='0.3.0.dev0', author='Florian Schulze', author_email='florian.schulze@gmx.net', url='https://github.com/fschulze/pytest-warnings', From e9668d75b80057441b53c65c84c9a6351b6c87cb Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 19 Oct 2016 08:57:24 +0200 Subject: [PATCH 032/153] turn RecordedWarning into a namedtuple fixes #2013 --- _pytest/recwarn.py | 15 +++++---------- testing/test_recwarn.py | 3 +++ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index a89474c03..0f6f93571 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -1,5 +1,4 @@ """ recording warnings during test function execution. """ - import inspect import _pytest._code @@ -7,6 +6,7 @@ import py import sys import warnings import pytest +from collections import namedtuple @pytest.yield_fixture @@ -110,15 +110,10 @@ def warns(expected_warning, *args, **kwargs): return func(*args[1:], **kwargs) -class RecordedWarning(object): - def __init__(self, message, category, filename, lineno, file, line): - self.message = message - self.category = category - self.filename = filename - self.lineno = lineno - self.file = file - self.line = line - +RecordedWarning = namedtuple('RecordedWarning', ( + 'message', 'category', 'filename', 'lineno', 'file', 'line', +)) + class WarningsRecorder(object): """A context manager to record raised warnings. diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 87e5846c2..36be5d0d2 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -203,6 +203,9 @@ class TestWarns(object): assert len(record) == 1 assert str(record[0].message) == "user" + print(repr(record[0])) + assert str(record[0].message) in repr(record[0]) + def test_record_only(self): with pytest.warns(None) as record: warnings.warn("user", UserWarning) From b3c337db005770d15f5717b4ec27b5ccbce751a3 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 24 Oct 2016 15:28:35 +0200 Subject: [PATCH 033/153] add changelog entry and documentation note about RecordedWarning --- CHANGELOG.rst | 4 ++++ doc/en/recwarn.rst | 3 +++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 29fba4ac0..b16f2c2cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,6 +28,9 @@ Changes to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR. +* fix `#2013`_: turn RecordedWarning into namedtupe, + to give it a comprehensible repr while preventing unwarranted modification + .. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck @@ -35,6 +38,7 @@ Changes .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1952: https://github.com/pytest-dev/pytest/pull/1952 +.. _#2013: https://github.com/pytest-dev/pytest/issues/2013 3.0.4.dev diff --git a/doc/en/recwarn.rst b/doc/en/recwarn.rst index 735006016..3eb333912 100644 --- a/doc/en/recwarn.rst +++ b/doc/en/recwarn.rst @@ -92,6 +92,9 @@ Each recorded warning has the attributes ``message``, ``category``, class of the warning. The ``message`` is the warning itself; calling ``str(message)`` will return the actual message of the warning. +.. note:: + :class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1 + .. note:: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated differently; see :ref:`ensuring_function_triggers`. From e31421a5d242739f44531cfc6525984c5f99046d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2016 07:27:26 -0200 Subject: [PATCH 034/153] Moving all stuff to a subdirectory to try to retain history --- .gitignore => warnings-root/.gitignore | 0 LICENSE => warnings-root/LICENSE | 0 MANIFEST.in => warnings-root/MANIFEST.in | 0 README.rst => warnings-root/README.rst | 0 {pytest_warnings => warnings-root/pytest_warnings}/__init__.py | 0 setup.py => warnings-root/setup.py | 0 {tests => warnings-root/tests}/helper_test_a.py | 0 {tests => warnings-root/tests}/helper_test_b.py | 0 {tests => warnings-root/tests}/test_warnings.py | 0 {tests => warnings-root/tests}/test_warnings2.py | 0 tox.ini => warnings-root/tox.ini | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename .gitignore => warnings-root/.gitignore (100%) rename LICENSE => warnings-root/LICENSE (100%) mode change 100755 => 100644 rename MANIFEST.in => warnings-root/MANIFEST.in (100%) rename README.rst => warnings-root/README.rst (100%) rename {pytest_warnings => warnings-root/pytest_warnings}/__init__.py (100%) rename setup.py => warnings-root/setup.py (100%) rename {tests => warnings-root/tests}/helper_test_a.py (100%) rename {tests => warnings-root/tests}/helper_test_b.py (100%) rename {tests => warnings-root/tests}/test_warnings.py (100%) rename {tests => warnings-root/tests}/test_warnings2.py (100%) rename tox.ini => warnings-root/tox.ini (100%) diff --git a/.gitignore b/warnings-root/.gitignore similarity index 100% rename from .gitignore rename to warnings-root/.gitignore diff --git a/LICENSE b/warnings-root/LICENSE old mode 100755 new mode 100644 similarity index 100% rename from LICENSE rename to warnings-root/LICENSE diff --git a/MANIFEST.in b/warnings-root/MANIFEST.in similarity index 100% rename from MANIFEST.in rename to warnings-root/MANIFEST.in diff --git a/README.rst b/warnings-root/README.rst similarity index 100% rename from README.rst rename to warnings-root/README.rst diff --git a/pytest_warnings/__init__.py b/warnings-root/pytest_warnings/__init__.py similarity index 100% rename from pytest_warnings/__init__.py rename to warnings-root/pytest_warnings/__init__.py diff --git a/setup.py b/warnings-root/setup.py similarity index 100% rename from setup.py rename to warnings-root/setup.py diff --git a/tests/helper_test_a.py b/warnings-root/tests/helper_test_a.py similarity index 100% rename from tests/helper_test_a.py rename to warnings-root/tests/helper_test_a.py diff --git a/tests/helper_test_b.py b/warnings-root/tests/helper_test_b.py similarity index 100% rename from tests/helper_test_b.py rename to warnings-root/tests/helper_test_b.py diff --git a/tests/test_warnings.py b/warnings-root/tests/test_warnings.py similarity index 100% rename from tests/test_warnings.py rename to warnings-root/tests/test_warnings.py diff --git a/tests/test_warnings2.py b/warnings-root/tests/test_warnings2.py similarity index 100% rename from tests/test_warnings2.py rename to warnings-root/tests/test_warnings2.py diff --git a/tox.ini b/warnings-root/tox.ini similarity index 100% rename from tox.ini rename to warnings-root/tox.ini From 1da19064839d7773ddc01489ae237c389cf35514 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2016 07:38:12 -0200 Subject: [PATCH 035/153] Rename code to _pytest.warnings and delete old files from the repository --- .../__init__.py => _pytest/warnings.py | 0 warnings-root/.gitignore | 9 -- warnings-root/LICENSE | 19 ---- warnings-root/MANIFEST.in | 4 - warnings-root/README.rst | 70 -------------- warnings-root/setup.py | 17 ---- warnings-root/tests/helper_test_a.py | 10 -- warnings-root/tests/helper_test_b.py | 11 --- warnings-root/tests/test_warnings.py | 95 ------------------- warnings-root/tests/test_warnings2.py | 20 ---- warnings-root/tox.ini | 18 ---- 11 files changed, 273 deletions(-) rename warnings-root/pytest_warnings/__init__.py => _pytest/warnings.py (100%) delete mode 100644 warnings-root/.gitignore delete mode 100644 warnings-root/LICENSE delete mode 100644 warnings-root/MANIFEST.in delete mode 100644 warnings-root/README.rst delete mode 100644 warnings-root/setup.py delete mode 100644 warnings-root/tests/helper_test_a.py delete mode 100644 warnings-root/tests/helper_test_b.py delete mode 100644 warnings-root/tests/test_warnings.py delete mode 100644 warnings-root/tests/test_warnings2.py delete mode 100644 warnings-root/tox.ini diff --git a/warnings-root/pytest_warnings/__init__.py b/_pytest/warnings.py similarity index 100% rename from warnings-root/pytest_warnings/__init__.py rename to _pytest/warnings.py diff --git a/warnings-root/.gitignore b/warnings-root/.gitignore deleted file mode 100644 index 80b4d47de..000000000 --- a/warnings-root/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/.cache/ -/.coverage -/.tox/ -/bin/ -/dist/ -/htmlcov/ -/include/ -/lib/ -/pip-selfcheck.json diff --git a/warnings-root/LICENSE b/warnings-root/LICENSE deleted file mode 100644 index 8a30978e3..000000000 --- a/warnings-root/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - diff --git a/warnings-root/MANIFEST.in b/warnings-root/MANIFEST.in deleted file mode 100644 index 1652af658..000000000 --- a/warnings-root/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include README.rst -include tox.ini -include LICENSE -include tests/*.py diff --git a/warnings-root/README.rst b/warnings-root/README.rst deleted file mode 100644 index c598259c3..000000000 --- a/warnings-root/README.rst +++ /dev/null @@ -1,70 +0,0 @@ -pytest-warnings -=============== - -py.test plugin to list Python warnings in pytest report - - -Usage ------ - -install via:: - - pip install pytest-warnings - -if you then type:: - - py.test -rw - -any warnings in your code are reported in the pytest report. -You can use the ``-W`` option or ``--pythonwarnings`` exactly like for the ``python`` executable. - -The following example ignores all warnings, but prints DeprecationWarnings once per occurrence:: - - py.test -rw -W ignore -W once::DeprecationWarning - -You can also turn warnings into actual errors:: - - py.test -W error - - -Advance usage -============= - -You can get more fine grained filtering of warnings by using the -``filterwarnings`` configuration option. - -``filterwarnings`` works like the python's ``-W`` flag except it will not -escape special characters. - -Example -------- - -.. code:: - - # pytest.ini - [pytest] - filterwarnings= default - ignore:.*is deprecated.*:Warning - error::DeprecationWarning:importlib.* - - -Changes -======= - -0.3.0 - Unreleased ------------------- - - - -0.2.0 - 2016-10-24 ------------------- - -- Add ``filterwarnings`` option. - [Carreau (Matthias Bussonnier)] - - -0.1.0 - 2016-06-27 ------------------- - -- Initial release. - [fschulze (Florian Schulze)] diff --git a/warnings-root/setup.py b/warnings-root/setup.py deleted file mode 100644 index 19a4c6e80..000000000 --- a/warnings-root/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup - - -setup( - name="pytest-warnings", - description='pytest plugin to list Python warnings in pytest report', - long_description=open("README.rst").read(), - license="MIT license", - version='0.3.0.dev0', - author='Florian Schulze', - author_email='florian.schulze@gmx.net', - url='https://github.com/fschulze/pytest-warnings', - packages=['pytest_warnings'], - entry_points={'pytest11': ['pytest_warnings = pytest_warnings']}, - install_requires=['pytest'], - classifiers=[ - "Framework :: Pytest"]) diff --git a/warnings-root/tests/helper_test_a.py b/warnings-root/tests/helper_test_a.py deleted file mode 100644 index ba88aa31d..000000000 --- a/warnings-root/tests/helper_test_a.py +++ /dev/null @@ -1,10 +0,0 @@ -import warnings - - -def deprecated_a(): - """ - A warning triggered in __this__ module for testing. - """ - globals()['__warningregistry__'] = {} - warnings.warn("This is deprecated message_a", - DeprecationWarning, stacklevel=0) diff --git a/warnings-root/tests/helper_test_b.py b/warnings-root/tests/helper_test_b.py deleted file mode 100644 index 3c00a6114..000000000 --- a/warnings-root/tests/helper_test_b.py +++ /dev/null @@ -1,11 +0,0 @@ -import warnings - - -def user_warning_b(): - """ - A warning triggered in __this__ module for testing. - """ - # reset the "once" filters - # globals()['__warningregistry__'] = {} - warnings.warn("This is deprecated message_b different from a", - UserWarning, stacklevel=1) diff --git a/warnings-root/tests/test_warnings.py b/warnings-root/tests/test_warnings.py deleted file mode 100644 index bc65220c7..000000000 --- a/warnings-root/tests/test_warnings.py +++ /dev/null @@ -1,95 +0,0 @@ -import pytest -import warnings - -from pytest_warnings import _setoption -from helper_test_a import deprecated_a -from helper_test_b import user_warning_b - - -def test_warnings(): - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - - -def test_warnings1(): - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - - -def test_warn(): - with pytest.warns(DeprecationWarning): - warnings.warn("Bar", DeprecationWarning) - - -# This section test the ability to filter selectively warnings using regular -# expressions on messages. - -def test_filters_setoption(): - "A alone works" - - with pytest.warns(DeprecationWarning): - deprecated_a() - - -def test_filters_setoption_2(): - "B alone works" - - with pytest.warns(UserWarning) as record: - user_warning_b() - - assert len(record) == 1 - - -def test_filters_setoption_3(): - "A and B works" - - with pytest.warns(None) as record: - user_warning_b() - deprecated_a() - assert len(record) == 2 - - -def test_filters_setoption_4(): - "A works, B is filtered" - - with pytest.warns(None) as record: - _setoption(warnings, 'ignore:.*message_a.*') - deprecated_a() - user_warning_b() - - assert len(record) == 1, "Only `A` should be filtered out" - - -def test_filters_setoption_4b(): - "A works, B is filtered" - - with pytest.warns(None) as record: - _setoption(warnings, 'ignore:.*message_b.*') - _setoption(warnings, 'ignore:.*message_a.*') - _setoption(warnings, 'always:::.*helper_test_a.*') - deprecated_a() - user_warning_b() - - assert len(record) == 1, "`A` and `B` should be visible, second filter reenable A" - - -def test_filters_setoption_5(): - "B works, A is filtered" - - with pytest.warns(None) as records: - _setoption(warnings, 'always:::.*helper_test_a.*') - _setoption(warnings, 'ignore::UserWarning') - deprecated_a() - user_warning_b() - - assert len(records) == 1, "Only `B` should be filtered out" diff --git a/warnings-root/tests/test_warnings2.py b/warnings-root/tests/test_warnings2.py deleted file mode 100644 index d4c9be0ea..000000000 --- a/warnings-root/tests/test_warnings2.py +++ /dev/null @@ -1,20 +0,0 @@ -def test_warnings(): - import warnings - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - - -def test_warnings1(): - import warnings - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", RuntimeWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) - warnings.warn("Foo", DeprecationWarning) diff --git a/warnings-root/tox.ini b/warnings-root/tox.ini deleted file mode 100644 index ec2e5622d..000000000 --- a/warnings-root/tox.ini +++ /dev/null @@ -1,18 +0,0 @@ -[tox] -envlist = py27,py33,py34,py35 - -[testenv] -usedevelop = true -deps = - pytest - pytest-cov - pytest-flakes - pytest-pep8 - coverage -commands = - {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} - -[pytest] -addopts = --flakes --pep8 --cov pytest_warnings --cov tests --no-cov-on-fail -pep8ignore = E501 -norecursedirs = bin lib include Scripts .* From 26ca5a702e2655302a1aeec08a82c8f474f8425f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2016 08:26:43 -0200 Subject: [PATCH 036/153] Add tests and integrated the original code into the core --- _pytest/config.py | 2 +- testing/test_warnings.py | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 testing/test_warnings.py diff --git a/_pytest/config.py b/_pytest/config.py index 5df198e21..04ebfec03 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -74,7 +74,7 @@ default_plugins = ( "mark main terminal runner python fixtures debugging unittest capture skipping " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " "junitxml resultlog doctest cacheprovider freeze_support " - "setuponly setupplan").split() + "setuponly setupplan warnings").split() builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") diff --git a/testing/test_warnings.py b/testing/test_warnings.py new file mode 100644 index 000000000..34badaee8 --- /dev/null +++ b/testing/test_warnings.py @@ -0,0 +1,60 @@ +import pytest + + +@pytest.fixture +def pyfile_with_warnings(testdir): + testdir.makepyfile(''' + import warnings + def test_func(): + warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) + warnings.warn(DeprecationWarning("functionality is deprecated")) + ''') + + +def test_normal_flow(testdir, pyfile_with_warnings): + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines([ + '*== pytest-warning summary ==*', + + 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', + ' test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', + ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', + + 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', + ' test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', + ' warnings.warn(DeprecationWarning("functionality is deprecated"))', + '* 1 passed, 2 pytest-warnings*', + ]) + + +@pytest.mark.parametrize('method', ['cmdline', 'ini']) +def test_as_errors(testdir, pyfile_with_warnings, method): + args = ('-W', 'error') if method == 'cmdline' else () + if method == 'ini': + testdir.makeini(''' + [pytest] + filterwarnings= error + ''') + result = testdir.runpytest_subprocess(*args) + result.stdout.fnmatch_lines([ + 'E PendingDeprecationWarning: functionality is pending deprecation', + 'test_as_errors.py:3: PendingDeprecationWarning', + '* 1 failed in *', + ]) + + +@pytest.mark.parametrize('method', ['cmdline', 'ini']) +def test_ignore(testdir, pyfile_with_warnings, method): + args = ('-W', 'ignore') if method == 'cmdline' else () + if method == 'ini': + testdir.makeini(''' + [pytest] + filterwarnings= ignore + ''') + + result = testdir.runpytest_subprocess(*args) + result.stdout.fnmatch_lines([ + '* 1 passed in *', + ]) + assert 'pytest-warning summary' not in result.stdout.str() + From ed977513ec6af2e9015e6f6eee6e6b2a0b48e2c5 Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Tue, 29 Nov 2016 11:51:56 +0100 Subject: [PATCH 037/153] Added a console option to specify the encoding to use for doctest files. Defaults to UTF-8. --- _pytest/doctest.py | 6 +++++- doc/en/doctest.rst | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index f4782dded..05acc7c59 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -43,6 +43,10 @@ def pytest_addoption(parser): action="store_true", default=False, help="ignore doctest ImportErrors", dest="doctest_ignore_import_errors") + group.addoption("--doctest-encoding", + type=str.lower, default="utf-8", + help="choose the encoding to use for doctest files", + dest="doctestencoding") def pytest_collect_file(path, parent): @@ -171,7 +175,7 @@ class DoctestTextfile(pytest.Module): # inspired by doctest.testfile; ideally we would use it directly, # but it doesn't support passing a custom checker - text = self.fspath.read() + text = self.fspath.read_text(self.config.getoption("doctestencoding")) filename = str(self.fspath) name = self.fspath.basename globs = {'__name__': '__main__'} diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 5a1122515..93d7c30c4 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -11,6 +11,13 @@ can change the pattern by issuing:: on the command line. Since version ``2.9``, ``--doctest-glob`` can be given multiple times in the command-line. +You can specify the encoding that will be used for those doctest files +using the ``--doctest-encoding`` command line option:: + + pytest --doctest-encoding='ascii' + +The default encoding is UTF-8. + You can also trigger running of doctests from docstrings in all python modules (including regular python test modules):: @@ -52,9 +59,9 @@ then you can just invoke ``pytest`` without command line options:: platform linux -- Python 3.5.2, pytest-3.0.4, py-1.4.31, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 1 items - + mymodule.py . - + ======= 1 passed in 0.12 seconds ======== It is possible to use fixtures using the ``getfixture`` helper:: From d254c6b0aeb872a791889d5e3497fa3ca679c482 Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Tue, 29 Nov 2016 12:18:41 +0100 Subject: [PATCH 038/153] Added some tests for --docstring-encoding option. Added option to specify encoding for internal testdir._makefile() for the tests. --- _pytest/pytester.py | 4 ++-- testing/test_doctest.py | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index d19ff1ec6..37348c26e 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -471,7 +471,7 @@ class Testdir: if not hasattr(self, '_olddir'): self._olddir = old - def _makefile(self, ext, args, kwargs): + def _makefile(self, ext, args, kwargs, encoding="utf-8"): items = list(kwargs.items()) if args: source = py.builtin._totext("\n").join( @@ -490,7 +490,7 @@ class Testdir: source_unicode = "\n".join([my_totext(line) for line in source.lines]) source = py.builtin._totext(source_unicode) - content = source.strip().encode("utf-8") # + "\n" + content = source.strip().encode(encoding) # + "\n" #content = content.rstrip() + "\n" p.write(content, "wb") if ret is None: diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 4ea2cc58e..4ecd5744b 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -129,6 +129,52 @@ class TestDoctests: '*1 passed*', ]) + def test_encoding_ascii(self, testdir): + """Test support for --doctest-encoding option. + """ + testdir._makefile(".txt", [""" + >>> 1 + 1 + """], {}, encoding='ascii') + + result = testdir.runpytest("--doctest-encoding=ascii") + + result.stdout.fnmatch_lines([ + '*1 passed*', + ]) + + def test_encoding_latin1(self, testdir): + """Test support for --doctest-encoding option. + """ + testdir._makefile(".txt", [""" + >>> 'üäö' + 'üäö' + """], {}, encoding='latin1') + + result = testdir.runpytest("--doctest-encoding=latin1") + + result.stdout.fnmatch_lines([ + '*1 passed*', + ]) + + def test_encoding_utf8(self, testdir): + """Test support for --doctest-encoding option. + """ + testdir.maketxtfile(""" + >>> 'üäö' + 'üäö' + """) + + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*1 passed*', + ]) + + result = testdir.runpytest("--doctest-encoding=utf-8") + result.stdout.fnmatch_lines([ + '*1 passed*', + ]) + def test_doctest_unexpected_exception(self, testdir): testdir.maketxtfile(""" >>> i = 0 From 929912de2920fdf82a2b5303e9585803368234cd Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Tue, 29 Nov 2016 14:51:17 +0100 Subject: [PATCH 039/153] Changed the tests to pass on python 2 as well. --- testing/test_doctest.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 4ecd5744b..d48e58042 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -146,9 +146,9 @@ class TestDoctests: def test_encoding_latin1(self, testdir): """Test support for --doctest-encoding option. """ - testdir._makefile(".txt", [""" - >>> 'üäö' - 'üäö' + testdir._makefile(".txt", [u""" + >>> len(u'üäö') + 3 """], {}, encoding='latin1') result = testdir.runpytest("--doctest-encoding=latin1") @@ -160,9 +160,9 @@ class TestDoctests: def test_encoding_utf8(self, testdir): """Test support for --doctest-encoding option. """ - testdir.maketxtfile(""" - >>> 'üäö' - 'üäö' + testdir.maketxtfile(u""" + >>> len(u'üäö') + 3 """) result = testdir.runpytest() From c043bbb8545eecf991cda9bfab95b78bae9ecd08 Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Wed, 30 Nov 2016 11:43:33 +0100 Subject: [PATCH 040/153] Changed the doctest_encoding option to an ini option. Parametrized the tests for it. --- _pytest/doctest.py | 9 +++---- testing/test_doctest.py | 57 ++++++++++++++--------------------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 05acc7c59..4ee21b12d 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -25,6 +25,7 @@ DOCTEST_REPORT_CHOICES = ( def pytest_addoption(parser): parser.addini('doctest_optionflags', 'option flags for doctests', type="args", default=["ELLIPSIS"]) + parser.addini("doctest_encoding", 'encoding used for doctest files', default="utf-8") group = parser.getgroup("collect") group.addoption("--doctest-modules", action="store_true", default=False, @@ -43,10 +44,6 @@ def pytest_addoption(parser): action="store_true", default=False, help="ignore doctest ImportErrors", dest="doctest_ignore_import_errors") - group.addoption("--doctest-encoding", - type=str.lower, default="utf-8", - help="choose the encoding to use for doctest files", - dest="doctestencoding") def pytest_collect_file(path, parent): @@ -166,7 +163,6 @@ def get_optionflags(parent): flag_acc |= flag_lookup_table[flag] return flag_acc - class DoctestTextfile(pytest.Module): obj = None @@ -175,7 +171,8 @@ class DoctestTextfile(pytest.Module): # inspired by doctest.testfile; ideally we would use it directly, # but it doesn't support passing a custom checker - text = self.fspath.read_text(self.config.getoption("doctestencoding")) + encoding = self.config.getini("doctest_encoding") + text = self.fspath.read_text(encoding) filename = str(self.fspath) name = self.fspath.basename globs = {'__name__': '__main__'} diff --git a/testing/test_doctest.py b/testing/test_doctest.py index d48e58042..02f4c3b26 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -129,48 +129,29 @@ class TestDoctests: '*1 passed*', ]) - def test_encoding_ascii(self, testdir): - """Test support for --doctest-encoding option. + @pytest.mark.parametrize( + ' test_string, encoding', + [ + (u'foo', 'ascii'), + (u'öäü', 'latin1'), + (u'öäü', 'utf-8') + ] + ) + def test_encoding(self, testdir, test_string, encoding): + """Test support for doctest_encoding ini option. """ - testdir._makefile(".txt", [""" - >>> 1 - 1 - """], {}, encoding='ascii') - - result = testdir.runpytest("--doctest-encoding=ascii") - - result.stdout.fnmatch_lines([ - '*1 passed*', - ]) - - def test_encoding_latin1(self, testdir): - """Test support for --doctest-encoding option. - """ - testdir._makefile(".txt", [u""" - >>> len(u'üäö') - 3 - """], {}, encoding='latin1') - - result = testdir.runpytest("--doctest-encoding=latin1") - - result.stdout.fnmatch_lines([ - '*1 passed*', - ]) - - def test_encoding_utf8(self, testdir): - """Test support for --doctest-encoding option. - """ - testdir.maketxtfile(u""" - >>> len(u'üäö') - 3 - """) + testdir.makeini(""" + [pytest] + doctest_encoding={0} + """.format(encoding)) + doctest = u""" + >>> u"{}" + {} + """.format(test_string, repr(test_string)) + testdir._makefile(".txt", [doctest], {}, encoding=encoding) result = testdir.runpytest() - result.stdout.fnmatch_lines([ - '*1 passed*', - ]) - result = testdir.runpytest("--doctest-encoding=utf-8") result.stdout.fnmatch_lines([ '*1 passed*', ]) From 1f62e5b5a0fd0ec8ab13e409eb2cd5e198ec88a0 Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Wed, 30 Nov 2016 11:50:11 +0100 Subject: [PATCH 041/153] Added CHANGELOG entry and myself to AUTHORS. --- AUTHORS | 1 + CHANGELOG.rst | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8b07e8bf2..609eb3c77 100644 --- a/AUTHORS +++ b/AUTHORS @@ -86,6 +86,7 @@ Lukas Bednar Luke Murphy Maciek Fijalkowski Maho +Manuel Krebber Marc Schlaich Marcin Bachry Mark Abramowitz diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd5c68123..baecaf80e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,8 @@ New Features ------------ -* +* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. + Thanks `@wheerd`_ for the PR (`#2101`_). * @@ -21,10 +22,10 @@ Changes assertion messages if verbosity < 2 (`#1512`_). Thanks `@mattduck`_ for the PR -* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use +* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for the PR (`#1952`_). -* Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError`` +* Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError`` to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR. @@ -34,11 +35,13 @@ Changes .. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck +.. _@wheerd: https://github.com/wheerd .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1952: https://github.com/pytest-dev/pytest/pull/1952 .. _#2013: https://github.com/pytest-dev/pytest/issues/2013 +.. _#2101: https://github.com/pytest-dev/pytest/pull/2101 3.0.5.dev0 From b7fb9fac91069d2d193b78265567f0d3c5673669 Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Wed, 30 Nov 2016 14:17:54 +0100 Subject: [PATCH 042/153] Fixed the documentation for doctest_encoding. --- doc/en/doctest.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 93d7c30c4..5967ba047 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -12,9 +12,13 @@ on the command line. Since version ``2.9``, ``--doctest-glob`` can be given multiple times in the command-line. You can specify the encoding that will be used for those doctest files -using the ``--doctest-encoding`` command line option:: +using the ``doctest_encoding`` ini option: - pytest --doctest-encoding='ascii' +.. code-block:: ini + + # content of pytest.ini + [pytest] + doctest_encoding = latin1 The default encoding is UTF-8. From f8fef07b4c1399ff84b41a3fa4d28d93cc578fc4 Mon Sep 17 00:00:00 2001 From: Manuel Krebber Date: Wed, 30 Nov 2016 14:19:07 +0100 Subject: [PATCH 043/153] Fixed the tests for python 2.6 --- testing/test_doctest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 02f4c3b26..72ba0a622 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -145,8 +145,8 @@ class TestDoctests: doctest_encoding={0} """.format(encoding)) doctest = u""" - >>> u"{}" - {} + >>> u"{0}" + {1} """.format(test_string, repr(test_string)) testdir._makefile(".txt", [doctest], {}, encoding=encoding) From f5afd8cb541549aaf595dbd7ea730becf23e3ee3 Mon Sep 17 00:00:00 2001 From: Luke Murphy Date: Wed, 30 Nov 2016 12:17:51 +0100 Subject: [PATCH 044/153] Add missing `__test__` check for test discovery. --- CHANGELOG.rst | 6 ++++++ _pytest/compat.py | 2 +- _pytest/python.py | 2 ++ testing/test_collection.py | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd5c68123..7239cd662 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,10 @@ New Features Changes ------- +* It is now possible to skip test classes from being collected by setting a + ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks + to `@syre`_ for the report and `@lwm`_ for the PR. + * Testcase reports with a ``url`` attribute will now properly write this to junitxml. Thanks `@fushi`_ for the PR (`#1874`_). @@ -71,6 +75,7 @@ Changes * +.. _@syre: https://github.com/syre .. _@dupuy: https://bitbucket.org/dupuy/ .. _@lwm: https://github.com/lwm .. _@adler-j: https://github.com/adler-j @@ -83,6 +88,7 @@ Changes .. _#2038: https://github.com/pytest-dev/pytest/issues/2038 .. _#2078: https://github.com/pytest-dev/pytest/issues/2078 .. _#2082: https://github.com/pytest-dev/pytest/issues/2082 +.. _#2007: https://github.com/pytest-dev/pytest/issues/2007 3.0.4 diff --git a/_pytest/compat.py b/_pytest/compat.py index d5fa7aa84..6bed9e92a 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -200,7 +200,7 @@ def safe_getattr(object, name, default): """ Like getattr but return default upon any Exception. Attribute access can potentially fail for 'evil' Python objects. - See issue214 + See issue #214. """ try: return getattr(object, name, default) diff --git a/_pytest/python.py b/_pytest/python.py index 3f01d22ad..0936a6b68 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -503,6 +503,8 @@ def _get_xunit_func(obj, name): class Class(PyCollector): """ Collector for test methods. """ def collect(self): + if not safe_getattr(self.obj, "__test__", True): + return [] if hasinit(self.obj): self.warn("C1", "cannot collect test class %r because it has a " "__init__ constructor" % self.obj.__name__) diff --git a/testing/test_collection.py b/testing/test_collection.py index 9cf4de895..4e44874e1 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -85,6 +85,22 @@ class TestCollector: assert len(nodes) == 1 assert isinstance(nodes[0], pytest.File) + def test_can_skip_class_with_test_attr(self, testdir): + """Assure test class is skipped when using `__test__=False` (See #2007).""" + testdir.makepyfile(""" + class TestFoo(): + __test__ = False + def __init__(self): + pass + def test_foo(): + assert True + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + 'collected 0 items', + '*no tests ran in*', + ]) + class TestCollectFS: def test_ignored_certain_directories(self, testdir): tmpdir = testdir.tmpdir From 2edfc805afefbbabdeccdb9b4e9fd6ab89335008 Mon Sep 17 00:00:00 2001 From: Wheerd Date: Wed, 30 Nov 2016 18:01:00 +0100 Subject: [PATCH 045/153] Added "versionadded" for doctest_encoding ini option to docs. --- doc/en/doctest.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 5967ba047..a82e3ed9b 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -11,16 +11,18 @@ can change the pattern by issuing:: on the command line. Since version ``2.9``, ``--doctest-glob`` can be given multiple times in the command-line. -You can specify the encoding that will be used for those doctest files -using the ``doctest_encoding`` ini option: +.. versionadded:: 3.1 -.. code-block:: ini + You can specify the encoding that will be used for those doctest files + using the ``doctest_encoding`` ini option: - # content of pytest.ini - [pytest] - doctest_encoding = latin1 + .. code-block:: ini -The default encoding is UTF-8. + # content of pytest.ini + [pytest] + doctest_encoding = latin1 + + The default encoding is UTF-8. You can also trigger running of doctests from docstrings in all python modules (including regular From a9193a15310138981b2b56f7db69c1c2a6471ea1 Mon Sep 17 00:00:00 2001 From: Rafael Bertoldi Date: Fri, 30 Dec 2016 15:37:09 -0200 Subject: [PATCH 046/153] No longer silently ignore errors in parametrize callable ids --- CHANGELOG.rst | 3 ++ _pytest/python.py | 11 ++++++-- testing/python/metafunc.py | 58 ++++++++++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0efd9c92e..aab0ae6e3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ New Features * Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. Thanks `@wheerd`_ for the PR (`#2101`_). +* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR. + * @@ -39,6 +41,7 @@ Changes .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck .. _@wheerd: https://github.com/wheerd +.. _@fogo: https://github.com/fogo .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 diff --git a/_pytest/python.py b/_pytest/python.py index 205ca472f..fee68251d 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -928,12 +928,17 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): def _idval(val, argname, idx, idfn, config=None): if idfn: + s = None try: s = idfn(val) - if s: - return _escape_strings(s) except Exception: - pass + # See issue https://github.com/pytest-dev/pytest/issues/2169 + import warnings + msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx) + msg += '\nUpdate your code as this will raise an error in pytest-4.0.' + warnings.warn(msg) + if s: + return _escape_strings(s) if config: hook_id = config.hook.pytest_make_parametrize_id(config=config, val=val) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index a7e1d5699..e88107d0b 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -296,18 +296,58 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn_exception(self): from _pytest.python import idmaker + from _pytest.recwarn import WarningsRecorder + + class BadIdsException(Exception): + pass def ids(val): - raise Exception("bad code") + raise BadIdsException("ids raised") - result = idmaker(("a", "b"), [(10.0, IndexError()), - (20, KeyError()), - ("three", [1, 2, 3]), - ], idfn=ids) - assert result == ["10.0-b0", - "20-b1", - "three-b2", - ] + rec = WarningsRecorder() + with rec: + idmaker(("a", "b"), [(10.0, IndexError()), + (20, KeyError()), + ("three", [1, 2, 3]), + ], idfn=ids) + + assert [str(i.message) for i in rec.list] == [ + "Raised while trying to determine id of parameter a at position 0." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter b at position 0." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter a at position 1." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter b at position 1." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter a at position 2." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter b at position 2." + "\nUpdate your code as this will raise an error in pytest-4.0.", + ] + + + def test_parametrize_ids_exception(self, testdir): + """ + :param testdir: the instance of Testdir class, a temporary + test directory. + """ + testdir.makepyfile(""" + import pytest + + def ids(arg): + raise Exception("bad ids") + + @pytest.mark.parametrize("arg", ["a", "b"], ids=ids) + def test_foo(arg): + pass + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "", + " ", + " ", + ]) def test_idmaker_with_ids(self): from _pytest.python import idmaker From 6fd0394c6317dc8acf2d1c58e2bad2379703214a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Tue, 20 Dec 2016 14:57:48 +0100 Subject: [PATCH 047/153] pytest.warns checks for subclass relationship rather than class equality. This makes it more similar to pytest.raises. --- CHANGELOG.rst | 9 +++++++-- _pytest/recwarn.py | 3 ++- testing/test_recwarn.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aab0ae6e3..d0339aaed 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,8 @@ New Features * pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR. -* +* ``pytest.warns`` now checks for subclass relationship rather than + class equality. Thanks `@lesteve`_ for the PR (`#2166`_) Changes @@ -41,7 +42,11 @@ Changes .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck .. _@wheerd: https://github.com/wheerd +<<<<<<< HEAD .. _@fogo: https://github.com/fogo +======= +.. _@lesteve: https://github.com/lesteve +>>>>>>> pytest.warns checks for subclass relationship .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 @@ -49,7 +54,7 @@ Changes .. _#2007: https://github.com/pytest-dev/pytest/issues/2007 .. _#2013: https://github.com/pytest-dev/pytest/issues/2013 .. _#2101: https://github.com/pytest-dev/pytest/pull/2101 - +.. _#2166: https://github.com/pytest-dev/pytest/pull/2166 3.0.6.dev0 (unreleased) ======================= diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 342169693..4fd41fa8a 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -216,7 +216,8 @@ class WarningsChecker(WarningsRecorder): # only check if we're not currently handling an exception if all(a is None for a in exc_info): if self.expected_warning is not None: - if not any(r.category in self.expected_warning for r in self): + if not any(issubclass(r.category, exp_warning) for + exp_warning in self.expected_warning for r in self): __tracebackhide__ = True pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " "The list of emitted warnings is: {1}.".format( diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 6c4d73ff7..ef2ebd74c 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -238,6 +238,16 @@ class TestWarns(object): assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" + def test_record_by_subclass(self): + with pytest.warns(Warning) as record: + warnings.warn("user", UserWarning) + warnings.warn("runtime", RuntimeWarning) + + assert len(record) == 2 + assert str(record[0].message) == "user" + assert str(record[1].message) == "runtime" + + def test_double_test(self, testdir): """If a test is run again, the warning should still be raised""" testdir.makepyfile(''' From 56d1858ea2915e6ba765d6bf337b1b19de832f25 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 28 Dec 2016 22:32:30 -0200 Subject: [PATCH 048/153] Remove duplicate '@lesteve' link from CHANGELOG --- CHANGELOG.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d0339aaed..68457f08c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -42,11 +42,8 @@ Changes .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck .. _@wheerd: https://github.com/wheerd -<<<<<<< HEAD .. _@fogo: https://github.com/fogo -======= .. _@lesteve: https://github.com/lesteve ->>>>>>> pytest.warns checks for subclass relationship .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 From 0bd8159b608c57a1467ee1b67b6170335beb6b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Thu, 29 Dec 2016 09:34:21 +0100 Subject: [PATCH 049/153] Add a test when multiple classes are specified in warns --- testing/test_recwarn.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index ef2ebd74c..8a7edcddb 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -247,6 +247,17 @@ class TestWarns(object): assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" + class MyUserWarning(UserWarning): pass + class MyRuntimeWarning(RuntimeWarning): pass + + with pytest.warns((UserWarning, RuntimeWarning)) as record: + warnings.warn("user", MyUserWarning) + warnings.warn("runtime", MyRuntimeWarning) + + assert len(record) == 2 + assert str(record[0].message) == "user" + assert str(record[1].message) == "runtime" + def test_double_test(self, testdir): """If a test is run again, the warning should still be raised""" From 7930a8373dbc520412a36203ca945e83e98b2a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Thu, 29 Dec 2016 10:18:33 +0100 Subject: [PATCH 050/153] Newline for flake8 --- testing/test_recwarn.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 8a7edcddb..40d11cd04 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -248,6 +248,7 @@ class TestWarns(object): assert str(record[1].message) == "runtime" class MyUserWarning(UserWarning): pass + class MyRuntimeWarning(RuntimeWarning): pass with pytest.warns((UserWarning, RuntimeWarning)) as record: From 9e9547a9e4436bd6e7af2590e6f5bf3b1b024ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Tue, 3 Jan 2017 12:03:26 +0100 Subject: [PATCH 051/153] Simplify condition --- _pytest/recwarn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 4fd41fa8a..9031bdbda 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -216,8 +216,8 @@ class WarningsChecker(WarningsRecorder): # only check if we're not currently handling an exception if all(a is None for a in exc_info): if self.expected_warning is not None: - if not any(issubclass(r.category, exp_warning) for - exp_warning in self.expected_warning for r in self): + if not any(issubclass(r.category, self.expected_warning) + for r in self): __tracebackhide__ = True pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " "The list of emitted warnings is: {1}.".format( From 3494dd06fe3ad8978ecf67e7123969826c5fba08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Tue, 3 Jan 2017 12:49:13 +0100 Subject: [PATCH 052/153] Remove duplicate target in rst --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 68457f08c..ad5a3c9f9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -71,7 +71,6 @@ Changes * -.. _@lesteve: https://github.com/lesteve .. _@malinoff: https://github.com/malinoff .. _@pelme: https://github.com/pelme From d4afa1554b225d11565d5fb81136edea51c54b0d Mon Sep 17 00:00:00 2001 From: mandeep Date: Sun, 8 Jan 2017 22:52:42 -0600 Subject: [PATCH 053/153] Refactored old style classes to new style classes --- _pytest/_argcomplete.py | 2 +- _pytest/assertion/__init__.py | 2 +- _pytest/cacheprovider.py | 2 +- _pytest/capture.py | 12 ++++++------ _pytest/config.py | 10 +++++----- _pytest/debugging.py | 4 ++-- _pytest/fixtures.py | 12 ++++++------ _pytest/main.py | 2 +- _pytest/mark.py | 8 ++++---- _pytest/monkeypatch.py | 4 ++-- _pytest/pytester.py | 22 +++++++++++----------- _pytest/runner.py | 4 ++-- _pytest/skipping.py | 2 +- _pytest/terminal.py | 4 ++-- _pytest/tmpdir.py | 2 +- _pytest/vendored_packages/pluggy.py | 6 +++--- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/_pytest/_argcomplete.py b/_pytest/_argcomplete.py index 3ab679d8b..8fbbf2660 100644 --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -62,7 +62,7 @@ import sys import os from glob import glob -class FastFilesCompleter: +class FastFilesCompleter(object): 'Fast file completer class' def __init__(self, directories=True): self.directories = directories diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index e0fb4284e..fe0653bb7 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -59,7 +59,7 @@ class DummyRewriteHook(object): pass -class AssertionState: +class AssertionState(object): """State for the assertion plugin.""" def __init__(self, config, mode): diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 0657001f2..cd09f4a02 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -87,7 +87,7 @@ class Cache(object): json.dump(value, f, indent=2, sort_keys=True) -class LFPlugin: +class LFPlugin(object): """ Plugin which implements the --lf (run last-failing) option """ def __init__(self, config): self.config = config diff --git a/_pytest/capture.py b/_pytest/capture.py index a90e308ce..3fe1816d8 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -57,7 +57,7 @@ def pytest_load_initial_conftests(early_config, parser, args): sys.stderr.write(err) -class CaptureManager: +class CaptureManager(object): def __init__(self, method): self._method = method @@ -182,7 +182,7 @@ def capfd(request): return c -class CaptureFixture: +class CaptureFixture(object): def __init__(self, captureclass, request): self.captureclass = captureclass self.request = request @@ -315,10 +315,10 @@ class MultiCapture(object): return (self.out.snap() if self.out is not None else "", self.err.snap() if self.err is not None else "") -class NoCapture: +class NoCapture(object): __init__ = start = done = suspend = resume = lambda *args: None -class FDCapture: +class FDCapture(object): """ Capture IO to/from a given os-level filedescriptor. """ def __init__(self, targetfd, tmpfile=None): @@ -394,7 +394,7 @@ class FDCapture: os.write(self.targetfd_save, data) -class SysCapture: +class SysCapture(object): def __init__(self, fd, tmpfile=None): name = patchsysdict[fd] self._old = getattr(sys, name) @@ -432,7 +432,7 @@ class SysCapture: self._old.flush() -class DontReadFromInput: +class DontReadFromInput(object): """Temporary stub class. Ideally when stdin is accessed, the capturing should be turned off, with possibly all data captured so far sent to the screen. This should be configurable, though, diff --git a/_pytest/config.py b/_pytest/config.py index 55326447e..1db281539 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -62,7 +62,7 @@ def main(args=None, plugins=None): sys.stderr.write("ERROR: %s\n" %(msg,)) return 4 -class cmdline: # compatibility namespace +class cmdline(object): # compatibility namespace main = staticmethod(main) @@ -447,7 +447,7 @@ class PytestPluginManager(PluginManager): self.consider_module(mod) -class Parser: +class Parser(object): """ Parser for command line arguments and ini-file values. :ivar extra_info: dict of generic param -> value to display in case @@ -582,7 +582,7 @@ class ArgumentError(Exception): return self.msg -class Argument: +class Argument(object): """class that mimics the necessary behaviour of optparse.Option its currently a least effort implementation @@ -712,7 +712,7 @@ class Argument: return 'Argument({0})'.format(', '.join(args)) -class OptionGroup: +class OptionGroup(object): def __init__(self, name, description="", parser=None): self.name = name self.description = description @@ -838,7 +838,7 @@ class CmdOptions(object): def copy(self): return CmdOptions(self.__dict__) -class Notset: +class Notset(object): def __repr__(self): return "" diff --git a/_pytest/debugging.py b/_pytest/debugging.py index c8d00ad79..c21e0977d 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -43,7 +43,7 @@ def pytest_configure(config): pytestPDB._pdb_cls = pdb_cls config._cleanup.append(fin) -class pytestPDB: +class pytestPDB(object): """ Pseudo PDB that defers to the real pdb. """ _pluginmanager = None _config = None @@ -64,7 +64,7 @@ class pytestPDB: self._pdb_cls().set_trace(frame) -class PdbInvoke: +class PdbInvoke(object): def pytest_exception_interact(self, node, call, report): capman = node.config.pluginmanager.getplugin("capturemanager") if capman: diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 28bcd4d8d..f675217f8 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -222,7 +222,7 @@ def slice_items(items, ignore, scoped_argkeys_cache): -class FuncargnamesCompatAttr: +class FuncargnamesCompatAttr(object): """ helper class so that Metafunc, Function and FixtureRequest don't need to each define the "funcargnames" compatibility attribute. """ @@ -258,7 +258,7 @@ def fillfixtures(function): def get_direct_param_fixture_func(request): return request.param -class FuncFixtureInfo: +class FuncFixtureInfo(object): def __init__(self, argnames, names_closure, name2fixturedefs): self.argnames = argnames self.names_closure = names_closure @@ -456,7 +456,7 @@ class FixtureRequest(FuncargnamesCompatAttr): fixturedef = self._getnextfixturedef(argname) except FixtureLookupError: if argname == "request": - class PseudoFixtureDef: + class PseudoFixtureDef(object): cached_result = (self, [0], None) scope = "function" return PseudoFixtureDef @@ -723,7 +723,7 @@ def call_fixture_func(fixturefunc, request, kwargs): return res -class FixtureDef: +class FixtureDef(object): """ A container for a factory definition. """ def __init__(self, fixturemanager, baseid, argname, func, scope, params, unittest=False, ids=None): @@ -822,7 +822,7 @@ def pytest_fixture_setup(fixturedef, request): return result -class FixtureFunctionMarker: +class FixtureFunctionMarker(object): def __init__(self, scope, params, autouse=False, ids=None, name=None): self.scope = scope self.params = params @@ -909,7 +909,7 @@ def pytestconfig(request): return request.config -class FixtureManager: +class FixtureManager(object): """ pytest fixtures definitions and information is stored and managed from this class. diff --git a/_pytest/main.py b/_pytest/main.py index a2a53cced..3d2d455d1 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -179,7 +179,7 @@ def pytest_ignore_collect(path, config): return False -class FSHookProxy: +class FSHookProxy(object): def __init__(self, fspath, pm, remove_mods): self.fspath = fspath self.pm = pm diff --git a/_pytest/mark.py b/_pytest/mark.py index 5a6926ea0..ef9a94ad9 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -99,7 +99,7 @@ def pytest_collection_modifyitems(items, config): items[:] = remaining -class MarkMapping: +class MarkMapping(object): """Provides a local mapping for markers where item access resolves to True if the marker is present. """ def __init__(self, keywords): @@ -113,7 +113,7 @@ class MarkMapping: return name in self._mymarks -class KeywordMapping: +class KeywordMapping(object): """Provides a local mapping for keywords. Given a list of names, map any substring of one of these names to True. """ @@ -173,7 +173,7 @@ def pytest_configure(config): pytest.mark._config = config -class MarkGenerator: +class MarkGenerator(object): """ Factory for :class:`MarkDecorator` objects - exposed as a ``pytest.mark`` singleton instance. Example:: @@ -210,7 +210,7 @@ def istestfunc(func): return hasattr(func, "__call__") and \ getattr(func, "__name__", "") != "" -class MarkDecorator: +class MarkDecorator(object): """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be :ref:`retrieved by hooks as item keywords `. diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 852e72bed..313ed89be 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -88,7 +88,7 @@ def derive_importpath(import_path, raising): return attr, target -class Notset: +class Notset(object): def __repr__(self): return "" @@ -96,7 +96,7 @@ class Notset: notset = Notset() -class MonkeyPatch: +class MonkeyPatch(object): """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes. """ diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 8b86fc3db..6748fdef4 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -165,7 +165,7 @@ def _pytest(request): """ return PytestArg(request) -class PytestArg: +class PytestArg(object): def __init__(self, request): self.request = request @@ -180,7 +180,7 @@ def get_public_names(l): return [x for x in l if x[0] != "_"] -class ParsedCall: +class ParsedCall(object): def __init__(self, name, kwargs): self.__dict__.update(kwargs) self._name = name @@ -191,7 +191,7 @@ class ParsedCall: return "" %(self._name, d) -class HookRecorder: +class HookRecorder(object): """Record all hooks called in a plugin manager. This wraps all the hook calls in the plugin manager, recording @@ -335,7 +335,7 @@ def testdir(request, tmpdir_factory): rex_outcome = re.compile("(\d+) ([\w-]+)") -class RunResult: +class RunResult(object): """The result of running a command. Attributes: @@ -380,7 +380,7 @@ class RunResult: -class Testdir: +class Testdir(object): """Temporary test directory with tools to test/run pytest itself. This is based on the ``tmpdir`` fixture but provides a number of @@ -707,7 +707,7 @@ class Testdir: rec = [] - class Collect: + class Collect(object): def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) @@ -718,7 +718,7 @@ class Testdir: if len(rec) == 1: reprec = rec.pop() else: - class reprec: + class reprec(object): pass reprec.ret = ret @@ -742,13 +742,13 @@ class Testdir: reprec = self.inline_run(*args, **kwargs) except SystemExit as e: - class reprec: + class reprec(object): ret = e.args[0] except Exception: traceback.print_exc() - class reprec: + class reprec(object): ret = 3 finally: out, err = capture.reset() @@ -1033,7 +1033,7 @@ def getdecoded(out): py.io.saferepr(out),) -class LineComp: +class LineComp(object): def __init__(self): self.stringio = py.io.TextIO() @@ -1049,7 +1049,7 @@ class LineComp: return LineMatcher(lines1).fnmatch_lines(lines2) -class LineMatcher: +class LineMatcher(object): """Flexible matching of text. This is a convenience class to test large texts like the output of diff --git a/_pytest/runner.py b/_pytest/runner.py index 89635a839..f17155dae 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -55,7 +55,7 @@ def pytest_sessionstart(session): def pytest_sessionfinish(session): session._setupstate.teardown_all() -class NodeInfo: +class NodeInfo(object): def __init__(self, location): self.location = location @@ -150,7 +150,7 @@ def call_runtest_hook(item, when, **kwds): ihook = getattr(item.ihook, hookname) return CallInfo(lambda: ihook(item=item, **kwds), when=when) -class CallInfo: +class CallInfo(object): """ Result/Exception info a function invocation. """ #: None or ExceptionInfo object. excinfo = None diff --git a/_pytest/skipping.py b/_pytest/skipping.py index c10fb18c3..cc92012a0 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -72,7 +72,7 @@ def xfail(reason=""): xfail.Exception = XFailed -class MarkEvaluator: +class MarkEvaluator(object): def __init__(self, item, name): self.item = item self.name = name diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 16bf75733..f509283cb 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -80,7 +80,7 @@ def pytest_report_teststatus(report): letter = "f" return report.outcome, letter, report.outcome.upper() -class WarningReport: +class WarningReport(object): def __init__(self, code, message, nodeid=None, fslocation=None): self.code = code self.message = message @@ -88,7 +88,7 @@ class WarningReport: self.fslocation = fslocation -class TerminalReporter: +class TerminalReporter(object): def __init__(self, config, file=None): import _pytest.config self.config = config diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 28a6b0636..565c35cf9 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -6,7 +6,7 @@ import py from _pytest.monkeypatch import MonkeyPatch -class TempdirFactory: +class TempdirFactory(object): """Factory for temporary directories under the common base temp directory. The base directory can be configured using the ``--basetemp`` option. diff --git a/_pytest/vendored_packages/pluggy.py b/_pytest/vendored_packages/pluggy.py index 9c13932b3..a300053a1 100644 --- a/_pytest/vendored_packages/pluggy.py +++ b/_pytest/vendored_packages/pluggy.py @@ -75,7 +75,7 @@ __all__ = ["PluginManager", "PluginValidationError", "HookCallError", _py3 = sys.version_info > (3, 0) -class HookspecMarker: +class HookspecMarker(object): """ Decorator helper class for marking functions as hook specifications. You can instantiate it with a project_name to get a decorator. @@ -113,7 +113,7 @@ class HookspecMarker: return setattr_hookspec_opts -class HookimplMarker: +class HookimplMarker(object): """ Decorator helper class for marking functions as hook implementations. You can instantiate with a project_name to get a decorator. @@ -770,7 +770,7 @@ class _HookCaller(object): proc(res[0]) -class HookImpl: +class HookImpl(object): def __init__(self, plugin, plugin_name, function, hook_impl_opts): self.function = function self.argnames = varnames(self.function) From e412ea1d5a245ecb5f06b17a6ae0f6abdc391418 Mon Sep 17 00:00:00 2001 From: mandeep Date: Tue, 10 Jan 2017 07:58:22 -0600 Subject: [PATCH 054/153] Added name to AUTHORS and change to CHANGELOG --- AUTHORS | 1 + CHANGELOG.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/AUTHORS b/AUTHORS index 1d7cb65ab..30132759b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -87,6 +87,7 @@ Lukas Bednar Luke Murphy Maciek Fijalkowski Maho +Mandeep Bhutani Manuel Krebber Marc Schlaich Marcin Bachry diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ad5a3c9f9..41d316dd5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,9 @@ New Features Changes ------- +* Old-style classes have been changed to new-style classes in order to improve + compatability with Python 2. Thanks to `@mandeep`_ for the PR (`#2147`_). + * It is now possible to skip test classes from being collected by setting a ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks to `@syre`_ for the report and `@lwm`_ for the PR. @@ -44,6 +47,7 @@ Changes .. _@wheerd: https://github.com/wheerd .. _@fogo: https://github.com/fogo .. _@lesteve: https://github.com/lesteve +.. _@mandeep: https://github.com/mandeep .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 @@ -52,6 +56,7 @@ Changes .. _#2013: https://github.com/pytest-dev/pytest/issues/2013 .. _#2101: https://github.com/pytest-dev/pytest/pull/2101 .. _#2166: https://github.com/pytest-dev/pytest/pull/2166 +.. _#2147: https://github.com/pytest-dev/pytest/issues/2147 3.0.6.dev0 (unreleased) ======================= From 123289a88ee01cfc3083fee22e0161cf4c9e4087 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 19 Jan 2017 11:38:15 +0100 Subject: [PATCH 055/153] fixes #2208 by introducing a iteration limit --- CHANGELOG.rst | 5 +++++ _pytest/compat.py | 12 ++++++++++-- testing/test_compat.py | 33 +++++++++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 41d316dd5..3d78d3273 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -41,6 +41,10 @@ Changes * fix `#2013`_: turn RecordedWarning into namedtupe, to give it a comprehensible repr while preventing unwarranted modification +* fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func. + Thanks `@RonnyPfannschmidt`_ for the Report and PR + + .. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck @@ -57,6 +61,7 @@ Changes .. _#2101: https://github.com/pytest-dev/pytest/pull/2101 .. _#2166: https://github.com/pytest-dev/pytest/pull/2166 .. _#2147: https://github.com/pytest-dev/pytest/issues/2147 +.. _#2208: https://github.com/pytest-dev/pytest/issues/2208 3.0.6.dev0 (unreleased) ======================= diff --git a/_pytest/compat.py b/_pytest/compat.py index 2f5e75cf1..09f681c86 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -180,8 +180,16 @@ def get_real_func(obj): """ gets the real function object of the (possibly) wrapped object by functools.wraps or functools.partial. """ - while hasattr(obj, "__wrapped__"): - obj = obj.__wrapped__ + start_obj = obj + for i in range(100): + new_obj = getattr(obj, '__wrapped__', None) + if new_obj is None: + break + obj = new_obj + else: + raise ValueError( + ("could not find real function of {start}" + "\nstopped at {current}").format(start=start_obj, current=obj)) if isinstance(obj, functools.partial): obj = obj.func return obj diff --git a/testing/test_compat.py b/testing/test_compat.py index 1fdd07e29..1736e8e2a 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,7 +1,7 @@ import sys import pytest -from _pytest.compat import is_generator +from _pytest.compat import is_generator, get_real_func def test_is_generator(): @@ -15,7 +15,30 @@ def test_is_generator(): assert not is_generator(foo) -@pytest.mark.skipif(sys.version_info < (3, 4), reason='asyncio available in Python 3.4+') +def test_real_func_loop_limit(): + + class Evil(object): + def __init__(self): + self.left = 1000 + + def __repr__(self): + return "".format(left=self.left) + + def __getattr__(self, attr): + if not self.left: + raise RuntimeError('its over') + self.left -= 1 + return self + + evil = Evil() + + with pytest.raises(ValueError): + res = get_real_func(evil) + print(res) + + +@pytest.mark.skipif(sys.version_info < (3, 4), + reason='asyncio available in Python 3.4+') def test_is_generator_asyncio(testdir): testdir.makepyfile(""" from _pytest.compat import is_generator @@ -27,12 +50,14 @@ def test_is_generator_asyncio(testdir): def test_is_generator_asyncio(): assert not is_generator(baz) """) - # avoid importing asyncio into pytest's own process, which in turn imports logging (#8) + # avoid importing asyncio into pytest's own process, + # which in turn imports logging (#8) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(['*1 passed*']) -@pytest.mark.skipif(sys.version_info < (3, 5), reason='async syntax available in Python 3.5+') +@pytest.mark.skipif(sys.version_info < (3, 5), + reason='async syntax available in Python 3.5+') def test_is_generator_async_syntax(testdir): testdir.makepyfile(""" from _pytest.compat import is_generator From 250597d468149121fd6d8ae8f867fbe8faf843a2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 19 Jan 2017 13:05:58 +0100 Subject: [PATCH 056/153] get_real_func: use saferepr when formatting the error message --- _pytest/compat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 09f681c86..e097dee51 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -189,7 +189,9 @@ def get_real_func(obj): else: raise ValueError( ("could not find real function of {start}" - "\nstopped at {current}").format(start=start_obj, current=obj)) + "\nstopped at {current}").format( + start=py.io.saferepr(start_obj), + current=py.io.saferepr(obj))) if isinstance(obj, functools.partial): obj = obj.func return obj From c848d0a771c41b25a3bd8ff8e29cc5493b116a25 Mon Sep 17 00:00:00 2001 From: Ravi Chandra Date: Mon, 16 Jan 2017 16:09:46 +1300 Subject: [PATCH 057/153] Pass parameter name to `make_parametrize_id` hook function --- AUTHORS | 1 + CHANGELOG.rst | 5 +++++ _pytest/hookspec.py | 3 ++- _pytest/python.py | 5 +++-- testing/python/metafunc.py | 23 +++++++++++++++++++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 30132759b..34189d61a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -120,6 +120,7 @@ Quentin Pradet Ralf Schmitt Raphael Pierzina Raquel Alegre +Ravi Chandra Roberto Polli Romain Dorgueil Roman Bolshakov diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3d78d3273..e4c5f2a79 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -79,10 +79,15 @@ Changes subdirectories with ini configuration files now uses the correct ini file (`#2148`_). Thanks `@pelme`_. +* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an + additional parameter. + Thanks `@unsignedint`_ for the PR. + * .. _@malinoff: https://github.com/malinoff .. _@pelme: https://github.com/pelme +.. _@unsignedint: https://github.com/unsignedint .. _#2129: https://github.com/pytest-dev/pytest/issues/2129 .. _#2148: https://github.com/pytest-dev/pytest/issues/2148 diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index b5f51eccf..989cb0c0b 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -157,9 +157,10 @@ def pytest_generate_tests(metafunc): """ generate (multiple) parametrized calls to a test function.""" @hookspec(firstresult=True) -def pytest_make_parametrize_id(config, val): +def pytest_make_parametrize_id(config, val, argname=None): """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``. + The parameter name is available as ``argname``, if required. """ # ------------------------------------------------------------------------- diff --git a/_pytest/python.py b/_pytest/python.py index fee68251d..18da7ac23 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -197,7 +197,7 @@ def pytest_pycollect_makeitem(collector, name, obj): res = list(collector._genfunctions(name, obj)) outcome.force_result(res) -def pytest_make_parametrize_id(config, val): +def pytest_make_parametrize_id(config, val, argname=None): return None @@ -941,7 +941,8 @@ def _idval(val, argname, idx, idfn, config=None): return _escape_strings(s) if config: - hook_id = config.hook.pytest_make_parametrize_id(config=config, val=val) + hook_id = config.hook.pytest_make_parametrize_id( + config=config, val=val, argname=argname) if hook_id: return hook_id diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index e88107d0b..9eea699bf 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1455,3 +1455,26 @@ class TestMarkersWithParametrization: "*test_func*0*PASS*", "*test_func*2*PASS*", ]) + + def test_pytest_make_parametrize_id_with_argname(self, testdir): + testdir.makeconftest(""" + def pytest_make_parametrize_id(config, val, argname): + return str(val * 2 if argname == 'x' else val * 10) + """) + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize("x", range(2)) + def test_func(x): + pass + + @pytest.mark.parametrize("y", [1]) + def test_func2(y): + pass + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_func*0*PASS*", + "*test_func*2*PASS*", + "*test_func2*10*PASS*", + ]) From 0e58c3fa80465a009c9c4d5b4500e73dd32ca80f Mon Sep 17 00:00:00 2001 From: Ravi Chandra Date: Sat, 21 Jan 2017 16:40:42 +1300 Subject: [PATCH 058/153] updates for PR review #2198 --- CHANGELOG.rst | 10 +++++----- _pytest/hookspec.py | 2 +- testing/python/metafunc.py | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e4c5f2a79..fc2a2de23 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -44,6 +44,10 @@ Changes * fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func. Thanks `@RonnyPfannschmidt`_ for the Report and PR +* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an + additional parameter. + Thanks `@unsignedint`_ for the PR. + .. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi @@ -52,6 +56,7 @@ Changes .. _@fogo: https://github.com/fogo .. _@lesteve: https://github.com/lesteve .. _@mandeep: https://github.com/mandeep +.. _@unsignedint: https://github.com/unsignedint .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 @@ -79,15 +84,10 @@ Changes subdirectories with ini configuration files now uses the correct ini file (`#2148`_). Thanks `@pelme`_. -* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an - additional parameter. - Thanks `@unsignedint`_ for the PR. - * .. _@malinoff: https://github.com/malinoff .. _@pelme: https://github.com/pelme -.. _@unsignedint: https://github.com/unsignedint .. _#2129: https://github.com/pytest-dev/pytest/issues/2129 .. _#2148: https://github.com/pytest-dev/pytest/issues/2148 diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index 989cb0c0b..bb382a597 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -157,7 +157,7 @@ def pytest_generate_tests(metafunc): """ generate (multiple) parametrized calls to a test function.""" @hookspec(firstresult=True) -def pytest_make_parametrize_id(config, val, argname=None): +def pytest_make_parametrize_id(config, val, argname): """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``. The parameter name is available as ``argname``, if required. diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 9eea699bf..372794a65 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1465,16 +1465,16 @@ class TestMarkersWithParametrization: import pytest @pytest.mark.parametrize("x", range(2)) - def test_func(x): + def test_func_a(x): pass @pytest.mark.parametrize("y", [1]) - def test_func2(y): + def test_func_b(y): pass """) result = testdir.runpytest("-v") result.stdout.fnmatch_lines([ - "*test_func*0*PASS*", - "*test_func*2*PASS*", - "*test_func2*10*PASS*", + "*test_func_a*0*PASS*", + "*test_func_a*2*PASS*", + "*test_func_b*10*PASS*", ]) From 3f30c228944f1a7ee93c80162954ca7fdc3dd896 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 26 Jan 2017 13:03:29 +0100 Subject: [PATCH 059/153] fix changelog merge mistake --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5e5af8a2c..c8582f40a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -46,7 +46,6 @@ Changes .. _@mattduck: https://github.com/mattduck .. _@wheerd: https://github.com/wheerd .. _@fogo: https://github.com/fogo -.. _@lesteve: https://github.com/lesteve .. _@mandeep: https://github.com/mandeep .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 From 43662ce789cdc87c478ff3c1a8314c7b51efa3bc Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Wed, 1 Feb 2017 13:37:13 +0100 Subject: [PATCH 060/153] allow error message matching in pytest.raises --- CHANGELOG.rst | 4 ++++ _pytest/python.py | 24 ++++++++++++++++++++---- testing/python/raises.py | 15 +++++++++++++++ testing/test_recwarn.py | 3 +-- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fbbefc1e6..816d3eb5b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,9 @@ New Features * ``pytest.warns`` now checks for subclass relationship rather than class equality. Thanks `@lesteve`_ for the PR (`#2166`_) +* ``pytest.raises`` now asserts that the error message matches a text or regex + with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR. + Changes ------- @@ -56,6 +59,7 @@ Changes .. _@fogo: https://github.com/fogo .. _@mandeep: https://github.com/mandeep .. _@unsignedint: https://github.com/unsignedint +.. _@Kriechi: https://github.com/Kriechi .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 diff --git a/_pytest/python.py b/_pytest/python.py index 18da7ac23..ff4edff5b 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1134,7 +1134,7 @@ def raises(expected_exception, *args, **kwargs): >>> with raises(ValueError) as exc_info: ... if value > 10: ... raise ValueError("value must be <= 10") - ... assert str(exc_info.value) == "value must be <= 10" # this will not execute + ... assert exc_info.type == ValueError # this will not execute Instead, the following approach must be taken (note the difference in scope):: @@ -1143,7 +1143,16 @@ def raises(expected_exception, *args, **kwargs): ... if value > 10: ... raise ValueError("value must be <= 10") ... - >>> assert str(exc_info.value) == "value must be <= 10" + >>> assert exc_info.type == ValueError + + Or you can use the keyword argument ``match`` to assert that the + exception matches a text or regex:: + + >>> with raises(ValueError, match='must be 0 or None'): + ... raise ValueError("value must be 0 or None") + + >>> with raises(ValueError, match=r'must be \d+$'): + ... raise ValueError("value must be 42") Or you can specify a callable by passing a to-be-called lambda:: @@ -1194,11 +1203,15 @@ def raises(expected_exception, *args, **kwargs): raise TypeError(msg % type(expected_exception)) message = "DID NOT RAISE {0}".format(expected_exception) + match_expr = None if not args: if "message" in kwargs: message = kwargs.pop("message") - return RaisesContext(expected_exception, message) + if "match" in kwargs: + match_expr = kwargs.pop("match") + message += " matching '{0}'".format(match_expr) + return RaisesContext(expected_exception, message, match_expr) elif isinstance(args[0], str): code, = args assert isinstance(code, str) @@ -1222,9 +1235,10 @@ def raises(expected_exception, *args, **kwargs): pytest.fail(message) class RaisesContext(object): - def __init__(self, expected_exception, message): + def __init__(self, expected_exception, message, match_expr): self.expected_exception = expected_exception self.message = message + self.match_expr = match_expr self.excinfo = None def __enter__(self): @@ -1246,6 +1260,8 @@ class RaisesContext(object): suppress_exception = issubclass(self.excinfo.type, self.expected_exception) if sys.version_info[0] == 2 and suppress_exception: sys.exc_clear() + if self.match_expr: + self.excinfo.match(self.match_expr) return suppress_exception diff --git a/testing/python/raises.py b/testing/python/raises.py index 08edd5c6a..58d6db244 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -118,3 +118,18 @@ class TestRaises: for o in gc.get_objects(): assert type(o) is not T + + def test_raises_match(self): + msg = r"with base \d+" + with pytest.raises(ValueError, match=msg): + int('asdf') + + msg = "with base 10" + with pytest.raises(ValueError, match=msg): + int('asdf') + + msg = "with base 16" + expr = r"Pattern '{}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(msg) + with pytest.raises(AssertionError, match=expr): + with pytest.raises(ValueError, match=msg): + int('asdf', base=10) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 40d11cd04..231ef028e 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -112,10 +112,9 @@ class TestDeprecatedCall(object): pytest.deprecated_call(self.dep_explicit, 0) def test_deprecated_call_as_context_manager_no_warning(self): - with pytest.raises(pytest.fail.Exception) as ex: + with pytest.raises(pytest.fail.Exception, matches='^DID NOT WARN'): with pytest.deprecated_call(): self.dep(1) - assert str(ex.value).startswith("DID NOT WARN") def test_deprecated_call_as_context_manager(self): with pytest.deprecated_call(): From abbff681babdfc788871b77cc7126bf0a63e9f6b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 2 Feb 2017 18:01:44 -0200 Subject: [PATCH 061/153] Fix '{0}' format for py26 --- testing/python/raises.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/python/raises.py b/testing/python/raises.py index 58d6db244..8170ec793 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -129,7 +129,7 @@ class TestRaises: int('asdf') msg = "with base 16" - expr = r"Pattern '{}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(msg) + expr = r"Pattern '{0}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(msg) with pytest.raises(AssertionError, match=expr): with pytest.raises(ValueError, match=msg): int('asdf', base=10) From 91c6bef77a674d5cd79af464e5c19745427b5510 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 15 Feb 2017 14:55:12 +0100 Subject: [PATCH 062/153] Add venv to the default norecursedirs venv (without a dot) is commonly used as a name for a virtualenv directory, and we don't want to collect that. --- CHANGELOG.rst | 3 +++ _pytest/main.py | 2 +- doc/en/customize.rst | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 816d3eb5b..4abc183d0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -51,6 +51,9 @@ Changes additional parameter. Thanks `@unsignedint`_ for the PR. +* Add ``venv`` to the default ``norecursedirs`` setting. + Thanks `@The-Compiler`_ for the PR. + .. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi diff --git a/_pytest/main.py b/_pytest/main.py index 41a48cda2..73858e099 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -27,7 +27,7 @@ EXIT_NOTESTSCOLLECTED = 5 def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", - type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg']) + type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv']) parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.", type="args", default=[]) #parser.addini("dirpatterns", diff --git a/doc/en/customize.rst b/doc/en/customize.rst index d12a49037..c6d3eb473 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -158,7 +158,7 @@ Builtin configuration file options [seq] matches any character in seq [!seq] matches any char not in seq - Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'``. + Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'``. Setting a ``norecursedirs`` replaces the default. Here is an example of how to avoid certain directories: From fb0b90646ec86ced32d146ee42e25f4cd72d2229 Mon Sep 17 00:00:00 2001 From: Michal Wajszczuk Date: Thu, 16 Feb 2017 19:41:51 +0100 Subject: [PATCH 063/153] New-style classes implemented for python 2.7 - #2147 --- AUTHORS | 1 + CHANGELOG.rst | 3 +- _pytest/vendored_packages/pluggy.py | 12 +-- doc/en/assert.rst | 2 +- doc/en/example/assertion/failure_demo.py | 12 +-- .../assertion/test_setup_flow_example.py | 2 +- doc/en/example/attic.rst | 4 +- doc/en/example/costlysetup/conftest.py | 2 +- doc/en/example/markers.rst | 10 +- doc/en/example/multipython.py | 2 +- doc/en/example/parametrize.rst | 8 +- doc/en/example/pythoncollection.py | 2 +- doc/en/example/pythoncollection.rst | 2 +- doc/en/example/reportingdemo.rst | 6 +- doc/en/example/simple.rst | 4 +- doc/en/example/special.rst | 4 +- doc/en/fixture.rst | 10 +- doc/en/funcarg_compare.rst | 2 +- doc/en/genapi.py | 2 +- doc/en/getting-started.rst | 2 +- doc/en/skipping.rst | 2 +- doc/en/test/attic.rst | 2 +- doc/en/unittest.rst | 2 +- doc/en/usage.rst | 4 +- testing/acceptance_test.py | 10 +- testing/code/test_code.py | 6 +- testing/code/test_excinfo.py | 8 +- testing/code/test_source.py | 22 ++--- testing/deprecated_test.py | 2 +- testing/python/approx.py | 2 +- testing/python/collect.py | 48 ++++----- testing/python/fixture.py | 98 +++++++++---------- testing/python/integration.py | 18 ++-- testing/python/metafunc.py | 32 +++--- testing/python/raises.py | 4 +- testing/test_argcomplete.py | 2 +- testing/test_assertion.py | 16 +-- testing/test_assertrewrite.py | 10 +- testing/test_cache.py | 4 +- testing/test_capture.py | 16 +-- testing/test_collection.py | 38 +++---- testing/test_config.py | 32 +++--- testing/test_conftest.py | 8 +- testing/test_doctest.py | 18 ++-- testing/test_junitxml.py | 12 +-- testing/test_mark.py | 30 +++--- testing/test_monkeypatch.py | 8 +- testing/test_nose.py | 10 +- testing/test_parseopt.py | 4 +- testing/test_pastebin.py | 6 +- testing/test_pdb.py | 6 +- testing/test_pluginmanager.py | 20 ++-- testing/test_pytester.py | 6 +- testing/test_resultlog.py | 2 +- testing/test_runner.py | 18 ++-- testing/test_runner_xunit.py | 16 +-- testing/test_session.py | 12 +-- testing/test_skipping.py | 20 ++-- testing/test_terminal.py | 26 ++--- testing/test_tmpdir.py | 4 +- testing/test_unittest.py | 4 +- 61 files changed, 351 insertions(+), 349 deletions(-) diff --git a/AUTHORS b/AUTHORS index 43c7d3156..e9e7f7c82 100644 --- a/AUTHORS +++ b/AUTHORS @@ -108,6 +108,7 @@ Michael Aquilina Michael Birtwell Michael Droettboom Michael Seifert +Michal Wajszczuk Mike Lundy Ned Batchelder Neven Mundar diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4abc183d0..23b87b18c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,7 +21,7 @@ Changes ------- * Old-style classes have been changed to new-style classes in order to improve - compatability with Python 2. Thanks to `@mandeep`_ for the PR (`#2147`_). + compatibility with Python 2. Thanks to `@MichalTHEDUDE`_ and `@mandeep`_ for the PR (`#2147`_). * It is now possible to skip test classes from being collected by setting a ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks @@ -61,6 +61,7 @@ Changes .. _@wheerd: https://github.com/wheerd .. _@fogo: https://github.com/fogo .. _@mandeep: https://github.com/mandeep +.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE .. _@unsignedint: https://github.com/unsignedint .. _@Kriechi: https://github.com/Kriechi diff --git a/_pytest/vendored_packages/pluggy.py b/_pytest/vendored_packages/pluggy.py index a300053a1..11658c3b1 100644 --- a/_pytest/vendored_packages/pluggy.py +++ b/_pytest/vendored_packages/pluggy.py @@ -167,7 +167,7 @@ def normalize_hookimpl_opts(opts): opts.setdefault("optionalhook", False) -class _TagTracer: +class _TagTracer(object): def __init__(self): self._tag2proc = {} self.writer = None @@ -214,7 +214,7 @@ class _TagTracer: self._tag2proc[tags] = processor -class _TagTracerSub: +class _TagTracerSub(object): def __init__(self, root, tags): self.root = root self.tags = tags @@ -254,7 +254,7 @@ def _wrapped_call(wrap_controller, func): return call_outcome.get_result() -class _CallOutcome: +class _CallOutcome(object): """ Outcome of a function call, either an exception or a proper result. Calling the ``get_result`` method will return the result or reraise the exception raised when the function was called. """ @@ -286,7 +286,7 @@ def _reraise(cls, val, tb): """) -class _TracedHookExecution: +class _TracedHookExecution(object): def __init__(self, pluginmanager, before, after): self.pluginmanager = pluginmanager self.before = before @@ -580,7 +580,7 @@ class PluginManager(object): return orig -class _MultiCall: +class _MultiCall(object): """ execute a call into multiple python functions/methods. """ # XXX note that the __multicall__ argument is supported only @@ -673,7 +673,7 @@ def varnames(func, startindex=None): return x -class _HookRelay: +class _HookRelay(object): """ hook holder object for performing 1:N hook calls where N is the number of registered plugins. diff --git a/doc/en/assert.rst b/doc/en/assert.rst index d93da178d..5ec07a78e 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -223,7 +223,7 @@ provides an alternative explanation for ``Foo`` objects:: now, given this test module:: # content of test_foocompare.py - class Foo: + class Foo(object): def __init__(self, val): self.val = val diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index a4ff758b1..d31fba2ad 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -128,7 +128,7 @@ def test_attribute_multiple(): def globf(x): return x+1 -class TestRaises: +class TestRaises(object): def test_raises(self): s = 'qwe' raises(TypeError, "int(s)") @@ -167,7 +167,7 @@ def test_dynamic_compile_shows_nicely(): -class TestMoreErrors: +class TestMoreErrors(object): def test_complex_error(self): def f(): return 44 @@ -213,23 +213,23 @@ class TestMoreErrors: x = 0 -class TestCustomAssertMsg: +class TestCustomAssertMsg(object): def test_single_line(self): - class A: + class A(object): a = 1 b = 2 assert A.a == b, "A.a appears not to be b" def test_multiline(self): - class A: + class A(object): a = 1 b = 2 assert A.a == b, "A.a appears not to be b\n" \ "or does not appear to be b\none of those" def test_custom_repr(self): - class JSON: + class JSON(object): a = 1 def __repr__(self): return "This is JSON\n{\n 'foo': 'bar'\n}" diff --git a/doc/en/example/assertion/test_setup_flow_example.py b/doc/en/example/assertion/test_setup_flow_example.py index 512330cb4..100effa49 100644 --- a/doc/en/example/assertion/test_setup_flow_example.py +++ b/doc/en/example/assertion/test_setup_flow_example.py @@ -1,7 +1,7 @@ def setup_module(module): module.TestStateFullThing.classcount = 0 -class TestStateFullThing: +class TestStateFullThing(object): def setup_class(cls): cls.classcount += 1 diff --git a/doc/en/example/attic.rst b/doc/en/example/attic.rst index 1bc32b283..6004ebb8f 100644 --- a/doc/en/example/attic.rst +++ b/doc/en/example/attic.rst @@ -15,7 +15,7 @@ example: specifying and selecting acceptance tests def pytest_funcarg__accept(request): return AcceptFixture(request) - class AcceptFixture: + class AcceptFixture(object): def __init__(self, request): if not request.config.option.acceptance: pytest.skip("specify -A to run acceptance tests") @@ -61,7 +61,7 @@ extend the `accept example`_ by putting this in our test module: arg.tmpdir.mkdir("special") return arg - class TestSpecialAcceptance: + class TestSpecialAcceptance(object): def test_sometest(self, accept): assert accept.tmpdir.join("special").check() diff --git a/doc/en/example/costlysetup/conftest.py b/doc/en/example/costlysetup/conftest.py index c8b9a257e..ea3c1cffb 100644 --- a/doc/en/example/costlysetup/conftest.py +++ b/doc/en/example/costlysetup/conftest.py @@ -7,7 +7,7 @@ def setup(request): yield setup setup.finalize() -class CostlySetup: +class CostlySetup(object): def __init__(self): import time print ("performing costly setup") diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 7dd5c8359..ab5c03f17 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -21,7 +21,7 @@ You can "mark" a test function with custom metadata like this:: pass def test_another(): pass - class TestClass: + class TestClass(object): def test_method(self): pass @@ -242,7 +242,7 @@ its test methods:: # content of test_mark_classlevel.py import pytest @pytest.mark.webtest - class TestClass: + class TestClass(object): def test_startup(self): pass def test_startup_and_more(self): @@ -256,14 +256,14 @@ To remain backward-compatible with Python 2.4 you can also set a import pytest - class TestClass: + class TestClass(object): pytestmark = pytest.mark.webtest or if you need to use multiple markers you can use a list:: import pytest - class TestClass: + class TestClass(object): pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] You can also set a module level marker:: @@ -407,7 +407,7 @@ code you can read over all such settings. Example:: pytestmark = pytest.mark.glob("module", x=1) @pytest.mark.glob("class", x=2) - class TestClass: + class TestClass(object): @pytest.mark.glob("function", x=3) def test_something(self): pass diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py index 1f5e976ef..586f44184 100644 --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -16,7 +16,7 @@ def python1(request, tmpdir): def python2(request, python1): return Python(request.param, python1.picklefile) -class Python: +class Python(object): def __init__(self, version, picklefile): self.pythonpath = py.path.local.sysfind(version) if not self.pythonpath: diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 609158590..f0572e9a1 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -168,7 +168,7 @@ only have to work a bit to construct the correct arguments for pytest's scenario1 = ('basic', {'attribute': 'value'}) scenario2 = ('advanced', {'attribute': 'value2'}) - class TestSampleWithScenarios: + class TestSampleWithScenarios(object): scenarios = [scenario1, scenario2] def test_demo1(self, attribute): @@ -241,9 +241,9 @@ creates a database object for the actual test invocations:: if 'db' in metafunc.fixturenames: metafunc.parametrize("db", ['d1', 'd2'], indirect=True) - class DB1: + class DB1(object): "one database object" - class DB2: + class DB2(object): "alternative database object" @pytest.fixture @@ -350,7 +350,7 @@ parametrizer`_ but in a lot less code:: metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist]) - class TestClass: + class TestClass(object): # a map specifying multiple argument sets for a test method params = { 'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ], diff --git a/doc/en/example/pythoncollection.py b/doc/en/example/pythoncollection.py index 0b9e35df4..9c4bd31ce 100644 --- a/doc/en/example/pythoncollection.py +++ b/doc/en/example/pythoncollection.py @@ -4,7 +4,7 @@ def test_function(): pass -class TestClass: +class TestClass(object): def test_method(self): pass def test_anothermethod(self): diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 0d53b0593..e3ab5f0cd 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -107,7 +107,7 @@ This would make ``pytest`` look for tests in files that match the ``check_* that match ``*_check``. For example, if we have:: # content of check_myapp.py - class CheckMyApp: + class CheckMyApp(object): def simple_check(self): pass def complex_check(self): diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 141208321..44d2f15a4 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -550,7 +550,7 @@ get on the terminal - we are working on that):: self = def test_single_line(self): - class A: + class A(object): a = 1 b = 2 > assert A.a == b, "A.a appears not to be b" @@ -564,7 +564,7 @@ get on the terminal - we are working on that):: self = def test_multiline(self): - class A: + class A(object): a = 1 b = 2 > assert A.a == b, "A.a appears not to be b\n" \ @@ -581,7 +581,7 @@ get on the terminal - we are working on that):: self = def test_custom_repr(self): - class JSON: + class JSON(object): a = 1 def __repr__(self): return "This is JSON\n{\n 'foo': 'bar'\n}" diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index d4b3fba43..c4935e2c5 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -425,7 +425,7 @@ tests in a class. Here is a test module example: import pytest @pytest.mark.incremental - class TestUserHandling: + class TestUserHandling(object): def test_login(self): pass def test_modification(self): @@ -483,7 +483,7 @@ Here is an example for making a ``db`` fixture available in a directory: # content of a/conftest.py import pytest - class DB: + class DB(object): pass @pytest.fixture(scope="session") diff --git a/doc/en/example/special.rst b/doc/en/example/special.rst index fdffef089..1fc32f6c8 100644 --- a/doc/en/example/special.rst +++ b/doc/en/example/special.rst @@ -28,7 +28,7 @@ will be called ahead of running any tests:: # content of test_module.py - class TestHello: + class TestHello(object): @classmethod def callme(cls): print ("callme called!") @@ -39,7 +39,7 @@ will be called ahead of running any tests:: def test_method2(self): print ("test_method1 called") - class TestOther: + class TestOther(object): @classmethod def callme(cls): print ("callme other called") diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 0a9000611..6f88aad73 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -557,7 +557,7 @@ and instantiate an object ``app`` where we stick the already defined import pytest - class App: + class App(object): def __init__(self, smtp): self.smtp = smtp @@ -728,7 +728,7 @@ and declare its use in a test module via a ``usefixtures`` marker:: import pytest @pytest.mark.usefixtures("cleandir") - class TestDirectoryInit: + class TestDirectoryInit(object): def test_cwd_starts_empty(self): assert os.listdir(os.getcwd()) == [] with open("myfile", "w") as f: @@ -791,7 +791,7 @@ self-contained implementation of this idea:: import pytest - class DB: + class DB(object): def __init__(self): self.intransaction = [] def begin(self, name): @@ -803,7 +803,7 @@ self-contained implementation of this idea:: def db(): return DB() - class TestClass: + class TestClass(object): @pytest.fixture(autouse=True) def transact(self, request, db): db.begin(request.function.__name__) @@ -861,7 +861,7 @@ into a conftest.py file **without** using ``autouse``:: and then e.g. have a TestClass using it by declaring the need:: @pytest.mark.usefixtures("transact") - class TestClass: + class TestClass(object): def test_method1(self): ... diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 3d121b944..b857a014d 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -24,7 +24,7 @@ resources. Here is a basic example how we could implement a per-session Database object:: # content of conftest.py - class Database: + class Database(object): def __init__(self): print ("database instance created") def destroy(self): diff --git a/doc/en/genapi.py b/doc/en/genapi.py index 89ddc8731..0ede44fa2 100644 --- a/doc/en/genapi.py +++ b/doc/en/genapi.py @@ -1,7 +1,7 @@ import textwrap import inspect -class Writer: +class Writer(object): def __init__(self, clsname): self.clsname = clsname diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 557e8245d..488ac301f 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -111,7 +111,7 @@ to group tests logically, in classes and modules. Let's write a class containing two tests:: # content of test_class.py - class TestClass: + class TestClass(object): def test_one(self): x = "this" assert 'h' in x diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 0a2e18208..cdcb56c2c 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -98,7 +98,7 @@ You can use the ``skipif`` decorator (and any other marker) on classes:: @pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows") - class TestPosixCalls: + class TestPosixCalls(object): def test_function(self): "will not be setup or run under 'win32' platform" diff --git a/doc/en/test/attic.rst b/doc/en/test/attic.rst index 11140db2c..06944661c 100644 --- a/doc/en/test/attic.rst +++ b/doc/en/test/attic.rst @@ -110,7 +110,7 @@ If you want to disable a complete test class you can set the class-level attribute ``disabled``. For example, in order to avoid running some tests on Win32:: - class TestPosixOnly: + class TestPosixOnly(object): disabled = sys.platform == 'win32' def test_xxx(self): diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 1bee0e146..78aac427a 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -71,7 +71,7 @@ it from a unittest-style test:: @pytest.fixture(scope="class") def db_class(request): - class DummyDB: + class DummyDB(object): pass # set a class attribute on the invoking test context request.cls.db = DummyDB() diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 15b3d71b0..d5d35993e 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -226,7 +226,7 @@ to all testcases you can use ``LogXML.add_global_properties`` def start_and_prepare_env(): pass - class TestMe: + class TestMe(object): def test_foo(self): assert True @@ -314,7 +314,7 @@ You can specify additional plugins to ``pytest.main``:: # content of myinvoke.py import pytest - class MyPlugin: + class MyPlugin(object): def pytest_sessionfinish(self): print("*** test run reporting finishing") diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 88e3fa449..0d3fc1016 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -8,7 +8,7 @@ import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR -class TestGeneralUsage: +class TestGeneralUsage(object): def test_config_error(self, testdir): testdir.makeconftest(""" def pytest_configure(config): @@ -410,7 +410,7 @@ class TestGeneralUsage: ]) -class TestInvocationVariants: +class TestInvocationVariants(object): def test_earlyinit(self, testdir): p = testdir.makepyfile(""" import pytest @@ -502,7 +502,7 @@ class TestInvocationVariants: out, err = capsys.readouterr() def test_invoke_plugin_api(self, testdir, capsys): - class MyPlugin: + class MyPlugin(object): def pytest_addoption(self, parser): parser.addoption("--myopt") @@ -670,7 +670,7 @@ class TestInvocationVariants: assert request.config.pluginmanager.hasplugin('python') -class TestDurations: +class TestDurations(object): source = """ import time frag = 0.002 @@ -741,7 +741,7 @@ class TestDurations: assert result.ret == 0 -class TestDurationWithFixture: +class TestDurationWithFixture(object): source = """ import time frag = 0.001 diff --git a/testing/code/test_code.py b/testing/code/test_code.py index ad9db6d2e..a1f31b4a9 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -20,7 +20,7 @@ def test_code_gives_back_name_for_not_existing_file(): assert code.fullsource is None def test_code_with_class(): - class A: + class A(object): pass pytest.raises(TypeError, "_pytest._code.Code(A)") @@ -136,7 +136,7 @@ def test_frame_getargs(): ('z', {'c': 'd'})] -class TestExceptionInfo: +class TestExceptionInfo(object): def test_bad_getsource(self): try: @@ -147,7 +147,7 @@ class TestExceptionInfo: assert exci.getrepr() -class TestTracebackEntry: +class TestTracebackEntry(object): def test_getsource(self): try: diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 23b0a985e..e2354ff5d 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -25,7 +25,7 @@ else: import pytest pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3])) -class TWMock: +class TWMock(object): WRITE = object() def __init__(self): @@ -89,7 +89,7 @@ def h(): g() # -class TestTraceback_f_g_h: +class TestTraceback_f_g_h(object): def setup_method(self, method): try: h() @@ -386,7 +386,7 @@ def test_match_raises_error(testdir): "*AssertionError*Pattern*[123]*not found*", ]) -class TestFormattedExcinfo: +class TestFormattedExcinfo(object): @pytest.fixture def importasmod(self, request): @@ -472,7 +472,7 @@ raise ValueError() pr = FormattedExcinfo() class FakeCode(object): - class raw: + class raw(object): co_filename = '?' path = '?' diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 13bfccd54..c161f75f8 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -49,7 +49,7 @@ def test_source_from_function(): assert str(source).startswith('def test_source_str_function():') def test_source_from_method(): - class TestClass: + class TestClass(object): def test_method(self): pass source = _pytest._code.Source(TestClass().test_method) @@ -119,7 +119,7 @@ def test_isparseable(): assert not Source(" \nif 1:\npass").isparseable() assert not Source(chr(0)).isparseable() -class TestAccesses: +class TestAccesses(object): source = Source("""\ def f(x): pass @@ -143,7 +143,7 @@ class TestAccesses: l = [x for x in self.source] assert len(l) == 4 -class TestSourceParsingAndCompiling: +class TestSourceParsingAndCompiling(object): source = Source("""\ def f(x): assert (x == @@ -307,7 +307,7 @@ class TestSourceParsingAndCompiling: pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval') def test_getstartingblock_singleline(): - class A: + class A(object): def __init__(self, *args): frame = sys._getframe(1) self.source = _pytest._code.Frame(frame).statement @@ -318,7 +318,7 @@ def test_getstartingblock_singleline(): assert len(l) == 1 def test_getstartingblock_multiline(): - class A: + class A(object): def __init__(self, *args): frame = sys._getframe(1) self.source = _pytest._code.Frame(frame).statement @@ -461,16 +461,16 @@ def test_getfslineno(): assert lineno == A_lineno assert getfslineno(3) == ("", -1) - class B: + class B(object): pass B.__name__ = "B2" assert getfslineno(B)[1] == -1 def test_code_of_object_instance_with_call(): - class A: + class A(object): pass pytest.raises(TypeError, lambda: _pytest._code.Source(A())) - class WithCall: + class WithCall(object): def __call__(self): pass @@ -559,7 +559,7 @@ x = 3 """) assert str(source) == "raise ValueError(\n 23\n)" -class TestTry: +class TestTry(object): pytestmark = astonly source = """\ try: @@ -586,7 +586,7 @@ else: source = getstatement(5, self.source) assert str(source) == " raise KeyError()" -class TestTryFinally: +class TestTryFinally(object): source = """\ try: raise ValueError @@ -604,7 +604,7 @@ finally: -class TestIf: +class TestIf(object): pytestmark = astonly source = """\ if 1: diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index e610458e0..6473989e6 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -48,7 +48,7 @@ def test_str_args_deprecated(tmpdir, testdir): from _pytest.main import EXIT_NOTESTSCOLLECTED warnings = [] - class Collect: + class Collect(object): def pytest_logwarning(self, message): warnings.append(message) diff --git a/testing/python/approx.py b/testing/python/approx.py index fc1cbf9ab..d7063e215 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -20,7 +20,7 @@ class MyDocTestRunner(doctest.DocTestRunner): example.source.strip(), got.strip(), example.want.strip())) -class TestApprox: +class TestApprox(object): def test_repr_string(self): # for some reason in Python 2.6 it is not displaying the tolerance representation correctly diff --git a/testing/python/collect.py b/testing/python/collect.py index 1e69f2da9..5511b3d75 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -12,7 +12,7 @@ from _pytest.main import ( ) -class TestModule: +class TestModule(object): def test_failing_import(self, testdir): modcol = testdir.getmodulecol("import alksdjalskdjalkjals") pytest.raises(Collector.CollectError, modcol.collect) @@ -105,10 +105,10 @@ class TestModule: assert name not in stdout -class TestClass: +class TestClass(object): def test_class_with_init_warning(self, testdir): testdir.makepyfile(""" - class TestClass1: + class TestClass1(object): def __init__(self): pass """) @@ -129,7 +129,7 @@ class TestClass: def test_setup_teardown_class_as_classmethod(self, testdir): testdir.makepyfile(test_mod1=""" - class TestClassMethod: + class TestClassMethod(object): @classmethod def setup_class(cls): pass @@ -167,7 +167,7 @@ class TestClass: ) -class TestGenerator: +class TestGenerator(object): def test_generative_functions(self, testdir): modcol = testdir.getmodulecol(""" def func1(arg, arg2): @@ -192,7 +192,7 @@ class TestGenerator: modcol = testdir.getmodulecol(""" def func1(arg, arg2): assert arg == arg2 - class TestGenMethods: + class TestGenMethods(object): def test_gen(self): yield func1, 17, 3*5 yield func1, 42, 6*7 @@ -246,7 +246,7 @@ class TestGenerator: modcol = testdir.getmodulecol(""" def func1(arg, arg2): assert arg == arg2 - class TestGenMethods: + class TestGenMethods(object): def test_gen(self): yield "m1", func1, 17, 3*5 yield "m2", func1, 42, 6*7 @@ -326,7 +326,7 @@ class TestGenerator: # has been used during collection. o = testdir.makepyfile(""" setuplist = [] - class TestClass: + class TestClass(object): def setup_method(self, func): #print "setup_method", self, func setuplist.append(self) @@ -360,7 +360,7 @@ class TestGenerator: assert not skipped and not failed -class TestFunction: +class TestFunction(object): def test_getmodulecollector(self, testdir): item = testdir.getitem("def test_func(): pass") modcol = item.getparent(pytest.Module) @@ -369,7 +369,7 @@ class TestFunction: def test_function_as_object_instance_ignored(self, testdir): testdir.makepyfile(""" - class A: + class A(object): def __call__(self, tmpdir): 0/0 @@ -420,7 +420,7 @@ class TestFunction: def test_issue213_parametrize_value_no_equal(self, testdir): testdir.makepyfile(""" import pytest - class A: + class A(object): def __eq__(self, other): raise ValueError("not possible") @pytest.mark.parametrize('arg', [A()]) @@ -551,11 +551,11 @@ class TestFunction: item = testdir.getitem("def test_func(): raise ValueError") config = item.config - class MyPlugin1: + class MyPlugin1(object): def pytest_pyfunc_call(self, pyfuncitem): raise ValueError - class MyPlugin2: + class MyPlugin2(object): def pytest_pyfunc_call(self, pyfuncitem): return True @@ -683,7 +683,7 @@ class TestFunction: assert [x.originalname for x in items] == ['test_func', 'test_func'] -class TestSorting: +class TestSorting(object): def test_check_equality(self, testdir): modcol = testdir.getmodulecol(""" def test_pass(): pass @@ -733,7 +733,7 @@ class TestSorting: assert [item.name for item in colitems] == ['test_b', 'test_a'] -class TestConftestCustomization: +class TestConftestCustomization(object): def test_pytest_pycollect_module(self, testdir): testdir.makeconftest(""" import pytest @@ -847,7 +847,7 @@ def test_modulecol_roundtrip(testdir): assert modcol.name == newcol.name -class TestTracebackCutting: +class TestTracebackCutting(object): def test_skip_simple(self): excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")') assert excinfo.traceback[-1].frame.code.name == "skip" @@ -973,7 +973,7 @@ class TestTracebackCutting: assert filter_traceback(tb[-1]) -class TestReportInfo: +class TestReportInfo(object): def test_itemreport_reportinfo(self, testdir, linecomp): testdir.makeconftest(""" import pytest @@ -998,7 +998,7 @@ class TestReportInfo: def test_class_reportinfo(self, testdir): modcol = testdir.getmodulecol(""" # lineno 0 - class TestClass: + class TestClass(object): def test_hello(self): pass """) classcol = testdir.collect_by_name(modcol, "TestClass") @@ -1033,7 +1033,7 @@ class TestReportInfo: def check(x): pass yield check, 3 - class TestClass: + class TestClass(object): def test_method(self): pass """ @@ -1042,7 +1042,7 @@ class TestReportInfo: # https://github.com/pytest-dev/pytest/issues/1204 modcol = testdir.getmodulecol(""" # lineno 0 - class TestClass: + class TestClass(object): def __getattr__(self, name): return "this is not an int" @@ -1064,7 +1064,7 @@ def test_customized_python_discovery(testdir): p = testdir.makepyfile(""" def check_simple(): pass - class CheckMyApp: + class CheckMyApp(object): def check_meth(self): pass """) @@ -1139,7 +1139,7 @@ def test_customize_through_attributes(testdir): return MyClass(name, parent=collector) """) testdir.makepyfile(""" - class MyTestClass: + class MyTestClass(object): def test_hello(self): pass """) @@ -1153,11 +1153,11 @@ def test_customize_through_attributes(testdir): def test_unorderable_types(testdir): testdir.makepyfile(""" - class TestJoinEmpty: + class TestJoinEmpty(object): pass def make_test(): - class Test: + class Test(object): pass Test.__name__ = "TestFoo" return Test diff --git a/testing/python/fixture.py b/testing/python/fixture.py index be99ed833..269d2c68a 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -20,7 +20,7 @@ def test_getfuncargnames(): def h(arg1, arg2, arg3="hello"): pass assert fixtures.getfuncargnames(h) == ('arg1', 'arg2') - class A: + class A(object): def f(self, arg1, arg2="hello"): pass @@ -28,7 +28,7 @@ def test_getfuncargnames(): if sys.version_info < (3,0): assert fixtures.getfuncargnames(A.f) == ('arg1',) -class TestFillFixtures: +class TestFillFixtures(object): def test_fillfuncargs_exposed(self): # used by oejskit, kept for compatibility assert pytest._fillfuncargs == fixtures.fillfixtures @@ -79,7 +79,7 @@ class TestFillFixtures: def something(request): return request.function.__name__ - class TestClass: + class TestClass(object): def test_method(self, something): assert something == "test_method" def test_func(something): @@ -91,7 +91,7 @@ class TestFillFixtures: def test_funcarg_lookup_classlevel(self, testdir): p = testdir.makepyfile(""" import pytest - class TestClass: + class TestClass(object): @pytest.fixture def something(self, request): @@ -134,7 +134,7 @@ class TestFillFixtures: def spam(): return 'spam' - class TestSpam: + class TestSpam(object): @pytest.fixture def spam(self, spam): @@ -463,7 +463,7 @@ class TestFillFixtures: assert result.ret == 0 -class TestRequestBasic: +class TestRequestBasic(object): def test_request_attributes(self, testdir): item = testdir.getitem(""" import pytest @@ -484,7 +484,7 @@ class TestRequestBasic: def test_request_attributes_method(self, testdir): item, = testdir.getitems(""" import pytest - class TestB: + class TestB(object): @pytest.fixture def something(self, request): @@ -502,7 +502,7 @@ class TestRequestBasic: @pytest.fixture def something(request): pass - class TestClass: + class TestClass(object): def test_method(self, something): pass """) @@ -704,7 +704,7 @@ class TestRequestBasic: def test_func(): pass - class TestClass: + class TestClass(object): @pytest.fixture(scope="class", autouse=True) def setup_class(self): l.append("class") @@ -771,7 +771,7 @@ class TestRequestBasic: reprec = testdir.inline_run() reprec.assertoutcome(passed=2) -class TestRequestMarking: +class TestRequestMarking(object): def test_applymarker(self, testdir): item1,item2 = testdir.getitems(""" import pytest @@ -779,7 +779,7 @@ class TestRequestMarking: @pytest.fixture def something(request): pass - class TestClass: + class TestClass(object): def test_func1(self, something): pass def test_func2(self, something): @@ -831,7 +831,7 @@ class TestRequestMarking: reprec = testdir.inline_run() reprec.assertoutcome(passed=2) -class TestRequestCachedSetup: +class TestRequestCachedSetup(object): def test_request_cachedsetup_defaultmodule(self, testdir): reprec = testdir.inline_runsource(""" mysetup = ["hello",].pop @@ -844,7 +844,7 @@ class TestRequestCachedSetup: def test_func1(something): assert something == "hello" - class TestClass: + class TestClass(object): def test_func1a(self, something): assert something == "hello" """) @@ -862,7 +862,7 @@ class TestRequestCachedSetup: assert something == "hello3" def test_func2(something): assert something == "hello2" - class TestClass: + class TestClass(object): def test_func1a(self, something): assert something == "hello" def test_func2b(self, something): @@ -996,7 +996,7 @@ class TestRequestCachedSetup: "*ZeroDivisionError*", ]) -class TestFixtureUsages: +class TestFixtureUsages(object): def test_noargfixturedec(self, testdir): testdir.makepyfile(""" import pytest @@ -1138,7 +1138,7 @@ class TestFixtureUsages: def test_factory_setup_as_classes_fails(self, testdir): testdir.makepyfile(""" import pytest - class arg1: + class arg1(object): def __init__(self, request): self.x = 1 arg1 = pytest.fixture()(arg1) @@ -1172,7 +1172,7 @@ class TestFixtureUsages: request.cls.hello = "world" l.append(1) - class TestClass: + class TestClass(object): def test_one(self): assert self.hello == "world" assert len(l) == 1 @@ -1198,7 +1198,7 @@ class TestFixtureUsages: """) testdir.makepyfile(""" - class TestClass: + class TestClass(object): def test_one(self): assert self.hello == "world" def test_two(self): @@ -1217,7 +1217,7 @@ class TestFixtureUsages: testdir.makepyfile(""" import pytest - class TestClass: + class TestClass(object): @pytest.fixture def setup1(self, request): assert self == request.instance @@ -1256,7 +1256,7 @@ class TestFixtureUsages: assert l == [1,2, 10,20] -class TestFixtureManagerParseFactories: +class TestFixtureManagerParseFactories(object): @pytest.fixture def testdir(self, request): @@ -1280,7 +1280,7 @@ class TestFixtureManagerParseFactories: def test_parsefactories_evil_objects_issue214(self, testdir): testdir.makepyfile(""" - class A: + class A(object): def __call__(self): pass def __getattr__(self, name): @@ -1311,7 +1311,7 @@ class TestFixtureManagerParseFactories: @pytest.fixture def hello(request): return "module" - class TestClass: + class TestClass(object): @pytest.fixture def hello(self, request): return "class" @@ -1360,7 +1360,7 @@ class TestFixtureManagerParseFactories: reprec.assertoutcome(passed=2) -class TestAutouseDiscovery: +class TestAutouseDiscovery(object): @pytest.fixture def testdir(self, testdir): @@ -1402,14 +1402,14 @@ class TestAutouseDiscovery: def test_two_classes_separated_autouse(self, testdir): testdir.makepyfile(""" import pytest - class TestA: + class TestA(object): l = [] @pytest.fixture(autouse=True) def setup1(self): self.l.append(1) def test_setup1(self): assert self.l == [1] - class TestB: + class TestB(object): l = [] @pytest.fixture(autouse=True) def setup2(self): @@ -1423,7 +1423,7 @@ class TestAutouseDiscovery: def test_setup_at_classlevel(self, testdir): testdir.makepyfile(""" import pytest - class TestClass: + class TestClass(object): @pytest.fixture(autouse=True) def permethod(self, request): request.instance.funcname = request.function.__name__ @@ -1505,13 +1505,13 @@ class TestAutouseDiscovery: def test_x(): assert l == ["module"] - class TestA: + class TestA(object): @pytest.fixture(autouse=True) def append2(self): l.append("A") def test_hello(self): assert l == ["module", "module", "A"], l - class TestA2: + class TestA2(object): def test_world(self): assert l == ["module", "module", "A", "module"], l """) @@ -1519,7 +1519,7 @@ class TestAutouseDiscovery: reprec.assertoutcome(passed=3) -class TestAutouseManagement: +class TestAutouseManagement(object): def test_autouse_conftest_mid_directory(self, testdir): pkgdir = testdir.mkpydir("xyz123") pkgdir.join("conftest.py").write(_pytest._code.Source(""" @@ -1654,10 +1654,10 @@ class TestAutouseManagement: testdir.makepyfile(""" import pytest - class TestClass: + class TestClass(object): def test_1(self): pass - class TestClass2: + class TestClass2(object): def test_2(self): pass """) @@ -1682,7 +1682,7 @@ class TestAutouseManagement: def mappend(): l.append(1) - class TestHallo: + class TestHallo(object): def test_method(self): assert l == [1,3,2] """) @@ -1696,7 +1696,7 @@ class TestAutouseManagement: def pytest_generate_tests(metafunc): if metafunc.cls is not None: metafunc.parametrize("item", [1,2], scope="class") - class TestClass: + class TestClass(object): @pytest.fixture(scope="class", autouse=True) def addteardown(self, item, request): l.append("setup-%d" % item) @@ -1756,7 +1756,7 @@ class TestAutouseManagement: reprec.assertoutcome(passed=2) -class TestFixtureMarker: +class TestFixtureMarker(object): def test_parametrize(self, testdir): testdir.makepyfile(""" import pytest @@ -1822,7 +1822,7 @@ class TestFixtureMarker: def test_2(arg): assert arg == 1 assert len(l) == 1 - class TestClass: + class TestClass(object): def test3(self, arg): assert arg == 1 assert len(l) == 1 @@ -1916,7 +1916,7 @@ class TestFixtureMarker: def test_2(arg): assert arg == 1 assert len(l) == 1 - class TestClass: + class TestClass(object): def test3(self, arg): assert arg == 1 assert len(l) == 1 @@ -2135,12 +2135,12 @@ class TestFixtureMarker: testdir.makepyfile(""" import pytest - class TestClass2: + class TestClass2(object): def test_1(self): pass def test_2(self): pass - class TestClass: + class TestClass(object): def test_3(self): pass """) @@ -2213,7 +2213,7 @@ class TestFixtureMarker: l = [] - class TestClass: + class TestClass(object): @classmethod @pytest.fixture(scope="class", autouse=True) def setup1(self, request, param1): @@ -2273,7 +2273,7 @@ class TestFixtureMarker: testpath = testdir.makepyfile(""" import pytest - class Box: + class Box(object): value = 0 @pytest.fixture(scope='class') @@ -2284,11 +2284,11 @@ class TestFixtureMarker: def test_a(a): assert a == 1 - class Test1: + class Test1(object): def test_b(self, a): assert a == 2 - class Test2: + class Test2(object): def test_c(self, a): assert a == 3""") reprec = testdir.inline_run(testpath) @@ -2402,11 +2402,11 @@ class TestFixtureMarker: request.addfinalizer(lambda: l.append("fin %s" % request.param)) return request.param - class TestGreetings: + class TestGreetings(object): def test_hello(self, human): l.append("test_hello") - class TestMetrics: + class TestMetrics(object): def test_name(self, human): l.append("test_name") @@ -2499,7 +2499,7 @@ class TestFixtureMarker: '*test_foo*beta*']) -class TestRequestScopeAccess: +class TestRequestScopeAccess(object): pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[ ["session", "", "fspath class function module"], ["module", "module fspath", "cls function"], @@ -2543,7 +2543,7 @@ class TestRequestScopeAccess: reprec = testdir.inline_run() reprec.assertoutcome(passed=1) -class TestErrors: +class TestErrors(object): def test_subfactory_missing_funcarg(self, testdir): testdir.makepyfile(""" import pytest @@ -2607,7 +2607,7 @@ class TestErrors: "*1 error*", ]) -class TestShowFixtures: +class TestShowFixtures(object): def test_funcarg_compat(self, testdir): config = testdir.parseconfigure("--funcargs") assert config.option.showfixtures @@ -2770,7 +2770,7 @@ class TestShowFixtures: @pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture']) -class TestContextManagerFixtureFuncs: +class TestContextManagerFixtureFuncs(object): def test_simple(self, testdir, flavor): testdir.makepyfile(""" @@ -2877,7 +2877,7 @@ class TestContextManagerFixtureFuncs: result = testdir.runpytest("-s") result.stdout.fnmatch_lines("*mew*") -class TestParameterizedSubRequest: +class TestParameterizedSubRequest(object): def test_call_from_fixture(self, testdir): testfile = testdir.makepyfile(""" import pytest diff --git a/testing/python/integration.py b/testing/python/integration.py index 6697342ea..4f888276b 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -3,7 +3,7 @@ from _pytest import python from _pytest import runner -class TestOEJSKITSpecials: +class TestOEJSKITSpecials(object): def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage testdir.makeconftest(""" import pytest @@ -19,7 +19,7 @@ class TestOEJSKITSpecials: @pytest.fixture def arg1(request): return 42 - class MyClass: + class MyClass(object): pass """) # this hook finds funcarg factories @@ -48,7 +48,7 @@ class TestOEJSKITSpecials: @pytest.fixture def arg1(request): return 42 - class MyClass: + class MyClass(object): pass """) # this hook finds funcarg factories @@ -76,7 +76,7 @@ def test_wrapped_getfslineno(): fs2, lineno2 = python.getfslineno(wrap) assert lineno > lineno2, "getfslineno does not unwrap correctly" -class TestMockDecoration: +class TestMockDecoration(object): def test_wrapped_getfuncargnames(self): from _pytest.compat import getfuncargnames @@ -207,7 +207,7 @@ class TestMockDecoration: @patch('os.getcwd') @patch('os.path') @mark.slow - class TestSimple: + class TestSimple(object): def test_simple_thing(self, mock_path, mock_getcwd): pass """) @@ -215,7 +215,7 @@ class TestMockDecoration: reprec.assertoutcome(passed=1) -class TestReRunTests: +class TestReRunTests(object): def test_rerun(self, testdir): testdir.makeconftest(""" from _pytest.runner import runtestprotocol @@ -251,7 +251,7 @@ def test_pytestconfig_is_session_scoped(): assert pytestconfig._pytestfixturefunction.scope == "session" -class TestNoselikeTestAttribute: +class TestNoselikeTestAttribute(object): def test_module_with_global_test(self, testdir): testdir.makepyfile(""" __test__ = False @@ -270,7 +270,7 @@ class TestNoselikeTestAttribute: pass test_func.__test__ = False - class TestSome: + class TestSome(object): __test__ = False def test_method(self): pass @@ -328,7 +328,7 @@ class TestNoselikeTestAttribute: @pytest.mark.issue351 -class TestParameterize: +class TestParameterize(object): def test_idfn_marker(self, testdir): testdir.makepyfile(""" diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 372794a65..949b265a3 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -13,12 +13,12 @@ from hypothesis import strategies PY3 = sys.version_info >= (3, 0) -class TestMetafunc: +class TestMetafunc(object): def Metafunc(self, func): # the unit tests of this class check if things work correctly # on the funcarg level, so we don't need a full blown # initiliazation - class FixtureInfo: + class FixtureInfo(object): name2fixturedefs = None def __init__(self, names): @@ -68,7 +68,7 @@ class TestMetafunc: def func(arg1): pass metafunc = self.Metafunc(func) - class obj: pass + class obj(object): pass metafunc.addcall(param=obj) metafunc.addcall(param=obj) @@ -83,7 +83,7 @@ class TestMetafunc: metafunc = self.Metafunc(func) - class obj: pass + class obj(object): pass metafunc.addcall(funcargs={"x": 2}) metafunc.addcall(funcargs={"x": 3}) @@ -150,7 +150,7 @@ class TestMetafunc: def func(x, y): pass metafunc = self.Metafunc(func) - class A: + class A(object): pass metafunc.parametrize("x", [A(), A()]) @@ -601,7 +601,7 @@ class TestMetafunc: pytestmark = pytest.mark.parametrize("x", [1,2]) def test_func(x): assert 0, x - class TestClass: + class TestClass(object): pytestmark = pytest.mark.parametrize("y", [3,4]) def test_meth(self, x, y): assert 0, x @@ -672,7 +672,7 @@ class TestMetafunc: assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)" -class TestMetafuncFunctional: +class TestMetafuncFunctional(object): def test_attributes(self, testdir): p = testdir.makepyfile(""" # assumes that generate/provide runs in the same process @@ -691,7 +691,7 @@ class TestMetafuncFunctional: assert metafunc.function == test_function assert metafunc.cls is None - class TestClass: + class TestClass(object): def test_method(self, metafunc, pytestconfig): assert metafunc.config == pytestconfig assert metafunc.module.__name__ == __name__ @@ -716,7 +716,7 @@ class TestMetafuncFunctional: def pytest_generate_tests(metafunc): metafunc.addcall(funcargs=dict(arg1=1, arg2=1)) - class TestClass: + class TestClass(object): def test_myfunc(self, arg1, arg2): assert arg1 == arg2 """) @@ -756,7 +756,7 @@ class TestMetafuncFunctional: def pytest_generate_tests(metafunc): assert 'xyz' not in metafunc.fixturenames - class TestHello: + class TestHello(object): def test_hello(xyz): pass """) @@ -782,7 +782,7 @@ class TestMetafuncFunctional: def arg2(request): return request.param[1] - class TestClass: + class TestClass(object): def test_myfunc(self, arg1, arg2): assert arg1 == arg2 """) @@ -795,7 +795,7 @@ class TestMetafuncFunctional: def test_generate_tests_in_class(self, testdir): p = testdir.makepyfile(""" - class TestClass: + class TestClass(object): def pytest_generate_tests(self, metafunc): metafunc.addcall(funcargs={'hello': 'world'}, id="hello") @@ -814,7 +814,7 @@ class TestMetafuncFunctional: metafunc.addcall({'arg1': 10}) metafunc.addcall({'arg1': 20}) - class TestClass: + class TestClass(object): def test_func(self, arg1): assert not hasattr(self, 'x') self.x = 1 @@ -831,7 +831,7 @@ class TestMetafuncFunctional: def pytest_generate_tests(metafunc): metafunc.addcall({'arg1': 1}) - class TestClass: + class TestClass(object): def test_method(self, arg1): assert arg1 == self.val def setup_method(self, func): @@ -1117,7 +1117,7 @@ class TestMetafuncFunctional: assert expectederror in failures[0].longrepr.reprcrash.message -class TestMetafuncFunctionalAuto: +class TestMetafuncFunctionalAuto(object): """ Tests related to automatically find out the correct scope for parametrized tests (#1832). """ @@ -1236,7 +1236,7 @@ class TestMetafuncFunctionalAuto: assert output.count('preparing foo-3') == 1 -class TestMarkersWithParametrization: +class TestMarkersWithParametrization(object): pytestmark = pytest.mark.issue308 def test_simple_mark(self, testdir): s = """ diff --git a/testing/python/raises.py b/testing/python/raises.py index 8170ec793..21a6f808c 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -2,7 +2,7 @@ import pytest import sys -class TestRaises: +class TestRaises(object): def test_raises(self): source = "int('qwe')" excinfo = pytest.raises(ValueError, source) @@ -20,7 +20,7 @@ class TestRaises: pytest.raises(ValueError, int, 'hello') def test_raises_callable_no_exception(self): - class A: + class A(object): def __call__(self): pass try: diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index ace7d8ceb..ed6db4c78 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -69,7 +69,7 @@ class FilesCompleter(object): completion += [f + '/' for f in anticomp] return completion -class TestArgComplete: +class TestArgComplete(object): @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") def test_compare_with_compgen(self): from _pytest._argcomplete import FastFilesCompleter diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 8cfd0ad85..fc2819f69 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -25,7 +25,7 @@ def mock_config(): return Config() -class TestImportHookInstallation: +class TestImportHookInstallation(object): @pytest.mark.parametrize('initial_conftest', [True, False]) @pytest.mark.parametrize('mode', ['plain', 'rewrite']) @@ -159,7 +159,7 @@ class TestImportHookInstallation: plugin_state = "{plugin_state}" - class DummyDistInfo: + class DummyDistInfo(object): project_name = 'spam' version = '1.0' @@ -174,7 +174,7 @@ class TestImportHookInstallation: 'hampkg/__init__.py'] return [] - class DummyEntryPoint: + class DummyEntryPoint(object): name = 'spam' module_name = 'spam.py' attrs = () @@ -257,7 +257,7 @@ class TestImportHookInstallation: 'pytest_tests_internal_non_existing2') -class TestBinReprIntegration: +class TestBinReprIntegration(object): def test_pytest_assertrepr_compare_called(self, testdir): testdir.makeconftest(""" @@ -288,7 +288,7 @@ def callequal(left, right, verbose=False): return plugin.pytest_assertrepr_compare(config, '==', left, right) -class TestAssert_reprcompare: +class TestAssert_reprcompare(object): def test_different_types(self): assert callequal([0, 1], 'foo') is None @@ -442,7 +442,7 @@ class TestAssert_reprcompare: assert len(expl) > 1 def test_list_bad_repr(self): - class A: + class A(object): def __repr__(self): raise ValueError(42) expl = callequal([], [A()]) @@ -501,7 +501,7 @@ class TestAssert_reprcompare: assert msg -class TestFormatExplanation: +class TestFormatExplanation(object): def test_special_chars_full(self, testdir): # Issue 453, for the bug this would raise IndexError @@ -593,7 +593,7 @@ class TestFormatExplanation: assert util.format_explanation(expl) == res -class TestTruncateExplanation: +class TestTruncateExplanation(object): """ Confirm assertion output is truncated as expected """ diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index fdf674f25..0ec528e82 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -57,7 +57,7 @@ def getmsg(f, extra_ns=None, must_pass=False): pytest.fail("function didn't raise at all") -class TestAssertionRewrite: +class TestAssertionRewrite(object): def test_place_initial_imports(self): s = """'Doc string'\nother = stuff""" @@ -333,7 +333,7 @@ class TestAssertionRewrite: @pytest.mark.skipif("sys.version_info < (3,5)") def test_at_operator_issue1290(self, testdir): testdir.makepyfile(""" - class Matrix: + class Matrix(object): def __init__(self, num): self.num = num def __matmul__(self, other): @@ -515,7 +515,7 @@ class TestAssertionRewrite: assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0] -class TestRewriteOnImport: +class TestRewriteOnImport(object): def test_pycache_is_a_file(self, testdir): testdir.tmpdir.join("__pycache__").write("Hello") @@ -884,7 +884,7 @@ class TestAssertionRewriteHookDetails(object): """ path = testdir.mkpydir("foo") path.join("test_foo.py").write(_pytest._code.Source(""" - class Test: + class Test(object): def test_foo(self): import pkgutil data = pkgutil.get_data('foo.test_foo', 'data.txt') @@ -912,7 +912,7 @@ def test_issue731(testdir): assert 'unbalanced braces' not in result.stdout.str() -class TestIssue925(): +class TestIssue925(object): def test_simple_case(self, testdir): testdir.makepyfile(""" def test_ternary_display(): diff --git a/testing/test_cache.py b/testing/test_cache.py index 98053f869..c1b98489e 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -7,7 +7,7 @@ import shutil pytest_plugins = "pytester", -class TestNewAPI: +class TestNewAPI(object): def test_config_cache_makedir(self, testdir): testdir.makeini("[pytest]") config = testdir.parseconfigure() @@ -129,7 +129,7 @@ def test_cache_show(testdir): ]) -class TestLastFailed: +class TestLastFailed(object): def test_lastfailed_usecase(self, testdir, monkeypatch): monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1) diff --git a/testing/test_capture.py b/testing/test_capture.py index cbb5fc81b..365329f59 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -55,7 +55,7 @@ def StdCapture(out=True, err=True, in_=True): return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture) -class TestCaptureManager: +class TestCaptureManager(object): def test_getmethod_default_no_fd(self, monkeypatch): from _pytest.capture import pytest_addoption from _pytest.config import Parser @@ -154,7 +154,7 @@ def test_collect_capturing(testdir): ]) -class TestPerTestCapturing: +class TestPerTestCapturing(object): def test_capture_and_fixtures(self, testdir): p = testdir.makepyfile(""" def setup_module(mod): @@ -275,7 +275,7 @@ class TestPerTestCapturing: ]) -class TestLoggingInteraction: +class TestLoggingInteraction(object): def test_logging_stream_ownership(self, testdir): p = testdir.makepyfile(""" def test_logging(): @@ -395,7 +395,7 @@ class TestLoggingInteraction: assert 'operation on closed file' not in result.stderr.str() -class TestCaptureFixture: +class TestCaptureFixture(object): @pytest.mark.parametrize("opt", [[], ["-s"]]) def test_std_functional(self, testdir, opt): reprec = testdir.inline_runsource(""" @@ -622,7 +622,7 @@ def test_error_during_readouterr(testdir): ]) -class TestTextIO: +class TestTextIO(object): def test_text(self): f = capture.TextIO() f.write("hello") @@ -737,7 +737,7 @@ def lsof_check(): assert len2 < len1 + 3, out2 -class TestFDCapture: +class TestFDCapture(object): pytestmark = needsosdup def test_simple(self, tmpfile): @@ -832,7 +832,7 @@ def saved_fd(fd): os.close(new_fd) -class TestStdCapture: +class TestStdCapture(object): captureclass = staticmethod(StdCapture) @contextlib.contextmanager @@ -990,7 +990,7 @@ class TestStdCaptureFD(TestStdCapture): cap.stop_capturing() -class TestStdCaptureFDinvalidFD: +class TestStdCaptureFDinvalidFD(object): pytestmark = needsosdup def test_stdcapture_fd_invalid_fd(self, testdir): diff --git a/testing/test_collection.py b/testing/test_collection.py index 4e44874e1..8575aa63a 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -2,7 +2,7 @@ import pytest, py from _pytest.main import Session, EXIT_NOTESTSCOLLECTED -class TestCollector: +class TestCollector(object): def test_collect_versus_item(self): from pytest import Collector, Item assert not issubclass(Collector, Item) @@ -50,7 +50,7 @@ class TestCollector: def test_getparent(self, testdir): modcol = testdir.getmodulecol(""" - class TestClass: + class TestClass(object): def test_foo(): pass """) @@ -88,7 +88,7 @@ class TestCollector: def test_can_skip_class_with_test_attr(self, testdir): """Assure test class is skipped when using `__test__=False` (See #2007).""" testdir.makepyfile(""" - class TestFoo(): + class TestFoo(object): __test__ = False def __init__(self): pass @@ -101,7 +101,7 @@ class TestCollector: '*no tests ran in*', ]) -class TestCollectFS: +class TestCollectFS(object): def test_ignored_certain_directories(self, testdir): tmpdir = testdir.tmpdir tmpdir.ensure("build", 'test_notfound.py') @@ -163,11 +163,11 @@ class TestCollectFS: assert [x.name for x in items] == ['test_%s' % dirname] -class TestCollectPluginHookRelay: +class TestCollectPluginHookRelay(object): def test_pytest_collect_file(self, testdir): wascalled = [] - class Plugin: + class Plugin(object): def pytest_collect_file(self, path, parent): if not path.basename.startswith("."): # Ignore hidden files, e.g. .testmondata. @@ -181,7 +181,7 @@ class TestCollectPluginHookRelay: def test_pytest_collect_directory(self, testdir): wascalled = [] - class Plugin: + class Plugin(object): def pytest_collect_directory(self, path, parent): wascalled.append(path.basename) @@ -192,7 +192,7 @@ class TestCollectPluginHookRelay: assert "world" in wascalled -class TestPrunetraceback: +class TestPrunetraceback(object): def test_custom_repr_failure(self, testdir): p = testdir.makepyfile(""" @@ -238,7 +238,7 @@ class TestPrunetraceback: ]) -class TestCustomConftests: +class TestCustomConftests(object): def test_ignore_collect_path(self, testdir): testdir.makeconftest(""" def pytest_ignore_collect(path, config): @@ -333,7 +333,7 @@ class TestCustomConftests: "*test_x*" ]) -class TestSession: +class TestSession(object): def test_parsearg(self, testdir): p = testdir.makepyfile("def test_func(): pass") subdir = testdir.mkdir("sub") @@ -391,7 +391,7 @@ class TestSession: def test_collect_protocol_method(self, testdir): p = testdir.makepyfile(""" - class TestClass: + class TestClass(object): def test_method(self): pass """) @@ -490,7 +490,7 @@ class TestSession: def test_find_byid_without_instance_parents(self, testdir): p = testdir.makepyfile(""" - class TestClass: + class TestClass(object): def test_method(self): pass """) @@ -500,7 +500,7 @@ class TestSession: item, = items assert item.nodeid.endswith("TestClass::()::test_method") -class Test_getinitialnodes: +class Test_getinitialnodes(object): def test_global_file(self, testdir, tmpdir): x = tmpdir.ensure("x.py") with tmpdir.as_cwd(): @@ -527,7 +527,7 @@ class Test_getinitialnodes: for col in col.listchain(): assert col.config is config -class Test_genitems: +class Test_genitems(object): def test_check_collect_hashes(self, testdir): p = testdir.makepyfile(""" def test_1(): @@ -550,7 +550,7 @@ class Test_genitems: def testone(): pass - class TestX: + class TestX(object): def testmethod_one(self): pass @@ -583,11 +583,11 @@ class Test_genitems: python_functions = *_test test """) p = testdir.makepyfile(''' - class MyTestSuite: + class MyTestSuite(object): def x_test(self): pass - class TestCase: + class TestCase(object): def test_y(self): pass ''') @@ -602,7 +602,7 @@ def test_matchnodes_two_collections_same_file(testdir): def pytest_configure(config): config.pluginmanager.register(Plugin2()) - class Plugin2: + class Plugin2(object): def pytest_collect_file(self, path, parent): if path.ext == ".abc": return MyFile2(path, parent) @@ -634,7 +634,7 @@ def test_matchnodes_two_collections_same_file(testdir): ]) -class TestNodekeywords: +class TestNodekeywords(object): def test_no_under(self, testdir): modcol = testdir.getmodulecol(""" def test_pass(): pass diff --git a/testing/test_config.py b/testing/test_config.py index e6aa423e8..7876063bd 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -4,7 +4,7 @@ import _pytest._code from _pytest.config import getcfg, get_common_ancestor, determine_setup from _pytest.main import EXIT_NOTESTSCOLLECTED -class TestParseIni: +class TestParseIni(object): @pytest.mark.parametrize('section, filename', [('pytest', 'pytest.ini'), ('tool:pytest', 'setup.cfg')]) @@ -84,7 +84,7 @@ class TestParseIni: result = testdir.inline_run("--confcutdir=.") assert result.ret == 0 -class TestConfigCmdlineParsing: +class TestConfigCmdlineParsing(object): def test_parsing_again_fails(self, testdir): config = testdir.parseconfig() pytest.raises(AssertionError, lambda: config.parse([])) @@ -115,7 +115,7 @@ class TestConfigCmdlineParsing: ret = pytest.main("-c " + temp_cfg_file) assert ret == _pytest.main.EXIT_OK -class TestConfigAPI: +class TestConfigAPI(object): def test_config_trace(self, testdir): config = testdir.parseconfig() l = [] @@ -304,7 +304,7 @@ class TestConfigAPI: assert config.getoption('confcutdir') == str(testdir.tmpdir.join('dir')) -class TestConfigFromdictargs: +class TestConfigFromdictargs(object): def test_basic_behavior(self): from _pytest.config import Config option_dict = { @@ -389,19 +389,19 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch): def my_iter(name): assert name == "pytest11" - class Dist: + class Dist(object): project_name = 'spam' version = '1.0' def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] - class EntryPoint: + class EntryPoint(object): name = "mytestplugin" dist = Dist() def load(self): - class PseudoPlugin: + class PseudoPlugin(object): x = 42 return PseudoPlugin() @@ -423,14 +423,14 @@ def test_setuptools_importerror_issue1479(testdir, monkeypatch): def my_iter(name): assert name == "pytest11" - class Dist: + class Dist(object): project_name = 'spam' version = '1.0' def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] - class EntryPoint: + class EntryPoint(object): name = "mytestplugin" dist = Dist() @@ -450,14 +450,14 @@ def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch): def my_iter(name): assert name == "pytest11" - class Dist: + class Dist(object): project_name = 'spam' version = '1.0' def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] - class EntryPoint: + class EntryPoint(object): name = "mytestplugin" dist = Dist() @@ -557,7 +557,7 @@ def test_notify_exception(testdir, capfd): out, err = capfd.readouterr() assert "ValueError" in err - class A: + class A(object): def pytest_internalerror(self, excrepr): return True @@ -571,7 +571,7 @@ def test_load_initial_conftest_last_ordering(testdir): from _pytest.config import get_config pm = get_config().pluginmanager - class My: + class My(object): def pytest_load_initial_conftests(self): pass @@ -602,7 +602,7 @@ def test_get_plugin_specs_as_list(): assert _get_plugin_specs_as_list(('foo', 'bar')) == ['foo', 'bar'] -class TestWarning: +class TestWarning(object): def test_warn_config(self, testdir): testdir.makeconftest(""" l = [] @@ -641,7 +641,7 @@ class TestWarning: *WT1*test_warn_on_test_item*:7 hello* """) -class TestRootdir: +class TestRootdir(object): def test_simple_noini(self, tmpdir): assert get_common_ancestor([tmpdir]) == tmpdir a = tmpdir.mkdir("a") @@ -699,7 +699,7 @@ class TestRootdir: assert rootdir == tmpdir -class TestOverrideIniArgs: +class TestOverrideIniArgs(object): @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) def test_override_ini_names(self, testdir, name): testdir.tmpdir.join(name).write(py.std.textwrap.dedent(""" diff --git a/testing/test_conftest.py b/testing/test_conftest.py index c0fa74701..e9fd1927a 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -24,14 +24,14 @@ def ConftestWithSetinitial(path): return conftest def conftest_setinitial(conftest, args, confcutdir=None): - class Namespace: + class Namespace(object): def __init__(self): self.file_or_dir = args self.confcutdir = str(confcutdir) self.noconftest = False conftest._set_initial_conftests(Namespace()) -class TestConftestValueAccessGlobal: +class TestConftestValueAccessGlobal(object): def test_basic_init(self, basedir): conftest = PytestPluginManager() p = basedir.join("adir") @@ -265,7 +265,7 @@ def test_conftest_found_with_double_dash(testdir): """) -class TestConftestVisibility: +class TestConftestVisibility(object): def _setup_tree(self, testdir): # for issue616 # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html @@ -398,7 +398,7 @@ def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error): def test_issue1073_conftest_special_objects(testdir): testdir.makeconftest(""" - class DontTouchMe: + class DontTouchMe(object): def __getattr__(self, x): raise Exception('cant touch me') diff --git a/testing/test_doctest.py b/testing/test_doctest.py index d4d903708..429bb8de9 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -6,7 +6,7 @@ from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile import pytest -class TestDoctests: +class TestDoctests(object): def test_collect_testtextfile(self, testdir): w = testdir.maketxtfile(whatever="") @@ -378,7 +378,7 @@ class TestDoctests: def test_doctestmodule_two_tests_one_fail(self, testdir): p = testdir.makepyfile(""" - class MyClass: + class MyClass(object): def bad_meth(self): ''' >>> magic = 42 @@ -401,7 +401,7 @@ class TestDoctests: doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE """) p = testdir.makepyfile(""" - class MyClass: + class MyClass(object): ''' >>> a = "foo " >>> print(a) @@ -418,7 +418,7 @@ class TestDoctests: doctest_optionflags = ELLIPSIS """) p = testdir.makepyfile(""" - class MyClass: + class MyClass(object): ''' >>> a = "foo " >>> print(a) @@ -505,7 +505,7 @@ class TestDoctests: reprec.assertoutcome(failed=1) -class TestLiterals: +class TestLiterals(object): @pytest.mark.parametrize('config_mode', ['ini', 'comment']) def test_allow_unicode(self, testdir, config_mode): @@ -592,7 +592,7 @@ class TestLiterals: reprec.assertoutcome(passed=passed, failed=int(not passed)) -class TestDoctestSkips: +class TestDoctestSkips(object): """ If all examples in a doctest are skipped due to the SKIP option, then the tests should be SKIPPED rather than PASSED. (#957) @@ -646,7 +646,7 @@ class TestDoctestSkips: reprec.assertoutcome(passed=0, skipped=0) -class TestDoctestAutoUseFixtures: +class TestDoctestAutoUseFixtures(object): SCOPES = ['module', 'session', 'class', 'function'] @@ -765,7 +765,7 @@ class TestDoctestAutoUseFixtures: result.stdout.fnmatch_lines(['*=== 1 passed in *']) -class TestDoctestNamespaceFixture: +class TestDoctestNamespaceFixture(object): SCOPES = ['module', 'session', 'class', 'function'] @@ -815,7 +815,7 @@ class TestDoctestNamespaceFixture: reprec.assertoutcome(passed=1) -class TestDoctestReportingOption: +class TestDoctestReportingOption(object): def _run_doctest_report(self, testdir, format): testdir.makepyfile(""" def foo(): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 83256d7c6..70c02332c 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -79,7 +79,7 @@ class DomNode(object): return type(self)(self.__node.nextSibling) -class TestPython: +class TestPython(object): def test_summing_simple(self, testdir): testdir.makepyfile(""" import pytest @@ -263,7 +263,7 @@ class TestPython: def test_classname_instance(self, testdir): testdir.makepyfile(""" - class TestClass: + class TestClass(object): def test_method(self): assert 0 """) @@ -376,7 +376,7 @@ class TestPython: testdir.makepyfile(""" def test_func(): assert 0 - class TestHello: + class TestHello(object): def test_hello(self): pass """) @@ -569,7 +569,7 @@ def test_mangle_test_address(): def test_dont_configure_on_slaves(tmpdir): gotten = [] - class FakeConfig: + class FakeConfig(object): def __init__(self): self.pluginmanager = self self.option = self @@ -588,7 +588,7 @@ def test_dont_configure_on_slaves(tmpdir): assert len(gotten) == 1 -class TestNonPython: +class TestNonPython(object): def test_summing_simple(self, testdir): testdir.makeconftest(""" import pytest @@ -750,7 +750,7 @@ def test_double_colon_split_function_issue469(testdir): def test_double_colon_split_method_issue469(testdir): testdir.makepyfile(""" import pytest - class TestClass: + class TestClass(object): @pytest.mark.parametrize('param', ["double::colon"]) def test_func(self, param): pass diff --git a/testing/test_mark.py b/testing/test_mark.py index 5b52e55db..9474d1033 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -3,7 +3,7 @@ import os import py, pytest from _pytest.mark import MarkGenerator as Mark -class TestMark: +class TestMark(object): def test_markinfo_repr(self): from _pytest.mark import MarkInfo, Mark m = MarkInfo(Mark("hello", (1,2), {})) @@ -301,7 +301,7 @@ def test_parametrized_collected_from_command_line(testdir): rec.assertoutcome(passed=3) -class TestFunctional: +class TestFunctional(object): def test_mark_per_function(self, testdir): p = testdir.makepyfile(""" @@ -326,7 +326,7 @@ class TestFunctional: def test_marklist_per_class(self, testdir): item = testdir.getitem(""" import pytest - class TestClass: + class TestClass(object): pytestmark = [pytest.mark.hello, pytest.mark.world] def test_func(self): assert TestClass.test_func.hello @@ -339,7 +339,7 @@ class TestFunctional: item = testdir.getitem(""" import pytest pytestmark = [pytest.mark.hello, pytest.mark.world] - class TestClass: + class TestClass(object): def test_func(self): assert TestClass.test_func.hello assert TestClass.test_func.world @@ -352,7 +352,7 @@ class TestFunctional: item = testdir.getitem(""" import pytest @pytest.mark.hello - class TestClass: + class TestClass(object): def test_func(self): assert TestClass.test_func.hello """) @@ -363,7 +363,7 @@ class TestFunctional: item = testdir.getitem(""" import pytest @pytest.mark.hello - class TestClass: + class TestClass(object): pytestmark = pytest.mark.world def test_func(self): assert TestClass.test_func.hello @@ -377,7 +377,7 @@ class TestFunctional: p = testdir.makepyfile(""" import pytest pytestmark = pytest.mark.hello("pos1", x=1, y=2) - class TestClass: + class TestClass(object): # classlevel overrides module level pytestmark = pytest.mark.hello(x=3) @pytest.mark.hello("pos0", z=4) @@ -403,11 +403,11 @@ class TestFunctional: # issue 199 - propagate markers into nested classes p = testdir.makepyfile(""" import pytest - class TestA: + class TestA(object): pytestmark = pytest.mark.a def test_b(self): assert True - class TestC: + class TestC(object): # this one didnt get marked def test_d(self): assert True @@ -422,7 +422,7 @@ class TestFunctional: import pytest @pytest.mark.a - class Base: pass + class Base(object): pass @pytest.mark.b class Test1(Base): @@ -441,7 +441,7 @@ class TestFunctional: p = testdir.makepyfile(""" import pytest - class TestBase: + class TestBase(object): def test_foo(self): pass @@ -465,7 +465,7 @@ class TestFunctional: import pytest @pytest.mark.a - class Base: pass + class Base(object): pass @pytest.mark.b class Base2(Base): pass @@ -485,7 +485,7 @@ class TestFunctional: def test_mark_with_wrong_marker(self, testdir): reprec = testdir.inline_runsource(""" import pytest - class pytestmark: + class pytestmark(object): pass def test_func(): pass @@ -630,7 +630,7 @@ class TestFunctional: reprec.assertoutcome(skipped=1) -class TestKeywordSelection: +class TestKeywordSelection(object): def test_select_simple(self, testdir): file_test = testdir.makepyfile(""" @@ -659,7 +659,7 @@ class TestKeywordSelection: p = testdir.makepyfile(test_select=""" def test_1(): pass - class TestClass: + class TestClass(object): def test_2(self): pass """) diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 9d02e2cc0..04a53d93a 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -16,7 +16,7 @@ def mp(): def test_setattr(): - class A: + class A(object): x = 1 monkeypatch = MonkeyPatch() @@ -39,7 +39,7 @@ def test_setattr(): assert A.x == 5 -class TestSetattrWithImportPath: +class TestSetattrWithImportPath(object): def test_string_expression(self, monkeypatch): monkeypatch.setattr("os.path.abspath", lambda x: "hello2") assert os.path.abspath("123") == "hello2" @@ -79,7 +79,7 @@ class TestSetattrWithImportPath: def test_delattr(): - class A: + class A(object): x = 1 monkeypatch = MonkeyPatch() @@ -294,7 +294,7 @@ class SampleNewInherit(SampleNew): pass -class SampleOld: +class SampleOld(object): # oldstyle on python2 @staticmethod def hello(): diff --git a/testing/test_nose.py b/testing/test_nose.py index f54246111..11fb83176 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -26,7 +26,7 @@ def test_setup_func_with_setup_decorator(): from _pytest.nose import call_optional l = [] - class A: + class A(object): @pytest.fixture(autouse=True) def f(self): l.append(1) @@ -38,7 +38,7 @@ def test_setup_func_with_setup_decorator(): def test_setup_func_not_callable(): from _pytest.nose import call_optional - class A: + class A(object): f = 1 call_optional(A(), "f") @@ -270,7 +270,7 @@ def test_nose_setup_ordering(testdir): def setup_module(mod): mod.visited = True - class TestClass: + class TestClass(object): def setup(self): assert visited def test_first(self): @@ -377,7 +377,7 @@ def test_istest_class_decorator(testdir): p = testdir.makepyfile(""" import nose.tools @nose.tools.istest - class NotTestPrefix: + class NotTestPrefix(object): def test_method(self): pass """) @@ -388,7 +388,7 @@ def test_nottest_class_decorator(testdir): testdir.makepyfile(""" import nose.tools @nose.tools.nottest - class TestPrefix: + class TestPrefix(object): def test_method(self): pass """) diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index e933dbb8d..03d71de43 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -8,7 +8,7 @@ from _pytest import config as parseopt def parser(): return parseopt.Parser() -class TestParser: +class TestParser(object): def test_no_help_by_default(self, capsys): parser = parseopt.Parser(usage="xyz") pytest.raises(SystemExit, lambda: parser.parse(["-h"])) @@ -139,7 +139,7 @@ class TestParser: parser.addoption("--hello", dest="hello", action="store") parser.addoption("--world", dest="world", default=42) - class A: + class A(object): pass option = A() diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 8123424ca..f0d2a9ba2 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -2,7 +2,7 @@ import sys import pytest -class TestPasteCapture: +class TestPasteCapture(object): @pytest.fixture def pastebinlist(self, monkeypatch, request): @@ -71,7 +71,7 @@ class TestPasteCapture: ]) -class TestPaste: +class TestPaste(object): @pytest.fixture def pastebin(self, request): @@ -88,7 +88,7 @@ class TestPaste: def mocked(url, data): calls.append((url, data)) - class DummyFile: + class DummyFile(object): def read(self): # part of html of a normal response return b'View raw.' diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 63a0c2f00..cf6d6f7fb 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -18,7 +18,7 @@ def custom_pdb_calls(): called = [] # install dummy debugger class and track which methods were called on it - class _CustomPdb: + class _CustomPdb(object): def __init__(self, *args, **kwargs): called.append("init") @@ -33,7 +33,7 @@ def custom_pdb_calls(): -class TestPDB: +class TestPDB(object): @pytest.fixture def pdblist(self, request): @@ -374,7 +374,7 @@ class TestPDB: def test_pdb_custom_cls_with_settrace(self, testdir, monkeypatch): testdir.makepyfile(custom_pdb=""" - class CustomPdb: + class CustomPdb(object): def set_trace(*args, **kwargs): print 'custom set_trace>' """) diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 45ad321a3..3214d868b 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -11,7 +11,7 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED, Session def pytestpm(): return PytestPluginManager() -class TestPytestPluginInteractions: +class TestPytestPluginInteractions(object): def test_addhooks_conftestplugin(self, testdir): testdir.makepyfile(newhooks=""" def pytest_myhook(xyz): @@ -85,7 +85,7 @@ class TestPytestPluginInteractions: config = testdir.parseconfig() l = [] - class A: + class A(object): def pytest_configure(self, config): l.append(self) @@ -105,11 +105,11 @@ class TestPytestPluginInteractions: pytestpm = get_config().pluginmanager # fully initialized with plugins saveindent = [] - class api1: + class api1(object): def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) - class api2: + class api2(object): def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) raise ValueError() @@ -156,11 +156,11 @@ class TestPytestPluginInteractions: def test_warn_on_deprecated_multicall(self, pytestpm): warnings = [] - class get_warnings: + class get_warnings(object): def pytest_logwarning(self, message): warnings.append(message) - class Plugin: + class Plugin(object): def pytest_configure(self, __multicall__): pass @@ -173,11 +173,11 @@ class TestPytestPluginInteractions: def test_warn_on_deprecated_addhooks(self, pytestpm): warnings = [] - class get_warnings: + class get_warnings(object): def pytest_logwarning(self, code, fslocation, message, nodeid): warnings.append(message) - class Plugin: + class Plugin(object): def pytest_testhook(): pass @@ -221,7 +221,7 @@ def test_importplugin_error_message(testdir, pytestpm): assert py.std.re.match(expected, str(excinfo.value)) -class TestPytestPluginManager: +class TestPytestPluginManager(object): def test_register_imported_modules(self): pm = PytestPluginManager() mod = py.std.types.ModuleType("x.y.pytest_hello") @@ -348,7 +348,7 @@ class TestPytestPluginManager: pytestpm.consider_conftest(mod) -class TestPytestPluginManagerBootstrapming: +class TestPytestPluginManagerBootstrapming(object): def test_preparse_args(self, pytestpm): pytest.raises(ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 49cf43a3e..9c9b56371 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -12,7 +12,7 @@ def test_make_hook_recorder(testdir): pytest.xfail("internal reportrecorder tests need refactoring") - class rep: + class rep(object): excinfo = None passed = False failed = True @@ -25,7 +25,7 @@ def test_make_hook_recorder(testdir): failures = recorder.getfailures() assert failures == [rep] - class rep: + class rep(object): excinfo = None passed = False failed = False @@ -74,7 +74,7 @@ def test_testdir_runs_with_plugin(testdir): def make_holder(): - class apiclass: + class apiclass(object): def pytest_xyz(self, arg): "x" def pytest_xyz_noarg(self): diff --git a/testing/test_resultlog.py b/testing/test_resultlog.py index e2d4fc263..c2f4a801c 100644 --- a/testing/test_resultlog.py +++ b/testing/test_resultlog.py @@ -70,7 +70,7 @@ def test_write_log_entry(): assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()] -class TestWithFunctionIntegration: +class TestWithFunctionIntegration(object): # XXX (hpk) i think that the resultlog plugin should # provide a Parser object so that one can remain # ignorant regarding formatting details. diff --git a/testing/test_runner.py b/testing/test_runner.py index 727defa92..265e99d54 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -8,7 +8,7 @@ import pytest import sys from _pytest import runner, main -class TestSetupState: +class TestSetupState(object): def test_setup(self, testdir): ss = runner.SetupState() item = testdir.getitem("def test_func(): pass") @@ -71,7 +71,7 @@ class TestSetupState: assert err.value.args == ('oops2',) -class BaseFunctionalTests: +class BaseFunctionalTests(object): def test_passfunction(self, testdir): reports = testdir.runitem(""" def test_func(): @@ -200,7 +200,7 @@ class BaseFunctionalTests: rec = testdir.inline_runsource(""" import pytest - class TestClass: + class TestClass(object): def test_method(self): pass def teardown_class(cls): @@ -239,7 +239,7 @@ class BaseFunctionalTests: rec = testdir.inline_runsource(""" import pytest - class TestClass: + class TestClass(object): def teardown_method(self, x, y, z): pass @@ -351,12 +351,12 @@ class TestExecutionForked(BaseFunctionalTests): assert rep.failed assert rep.when == "???" -class TestSessionReports: +class TestSessionReports(object): def test_collect_result(self, testdir): col = testdir.getmodulecol(""" def test_func1(): pass - class TestClass: + class TestClass(object): pass """) rep = runner.collect_one_node(col) @@ -409,7 +409,7 @@ def test_runtest_in_module_ordering(testdir): import pytest def pytest_runtest_setup(item): # runs after class-level! item.function.mylist.append("module") - class TestClass: + class TestClass(object): def pytest_runtest_setup(self, item): assert not hasattr(item.function, 'mylist') item.function.mylist = ['class'] @@ -680,7 +680,7 @@ def test_store_except_info_on_eror(): sys.last_traceback and friends. """ # Simulate item that raises a specific exception - class ItemThatRaises: + class ItemThatRaises(object): def runtest(self): raise IndexError('TEST') try: @@ -693,7 +693,7 @@ def test_store_except_info_on_eror(): assert sys.last_traceback -class TestReportContents: +class TestReportContents(object): """ Test user-level API of ``TestReport`` objects. """ diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index e1f0924c6..9bf15bd66 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -24,7 +24,7 @@ def test_module_and_function_setup(testdir): assert modlevel[0] == 42 assert test_modlevel.answer == 17 - class TestFromClass: + class TestFromClass(object): def test_module(self): assert modlevel[0] == 42 assert not hasattr(test_modlevel, 'answer') @@ -69,7 +69,7 @@ def test_setup_function_failure_no_teardown(testdir): def test_class_setup(testdir): reprec = testdir.inline_runsource(""" - class TestSimpleClassSetup: + class TestSimpleClassSetup(object): clslevel = [] def setup_class(cls): cls.clslevel.append(23) @@ -92,7 +92,7 @@ def test_class_setup(testdir): def test_class_setup_failure_no_teardown(testdir): reprec = testdir.inline_runsource(""" - class TestSimpleClassSetup: + class TestSimpleClassSetup(object): clslevel = [] def setup_class(cls): 0/0 @@ -110,7 +110,7 @@ def test_class_setup_failure_no_teardown(testdir): def test_method_setup(testdir): reprec = testdir.inline_runsource(""" - class TestSetupMethod: + class TestSetupMethod(object): def setup_method(self, meth): self.methsetup = meth def teardown_method(self, meth): @@ -126,7 +126,7 @@ def test_method_setup(testdir): def test_method_setup_failure_no_teardown(testdir): reprec = testdir.inline_runsource(""" - class TestMethodSetup: + class TestMethodSetup(object): clslevel = [] def setup_method(self, method): self.clslevel.append(1) @@ -145,7 +145,7 @@ def test_method_setup_failure_no_teardown(testdir): def test_method_generator_setup(testdir): reprec = testdir.inline_runsource(""" - class TestSetupTeardownOnInstance: + class TestSetupTeardownOnInstance(object): def setup_class(cls): cls.classsetup = True @@ -195,7 +195,7 @@ def test_func_generator_setup(testdir): def test_method_setup_uses_fresh_instances(testdir): reprec = testdir.inline_runsource(""" - class TestSelfState1: + class TestSelfState1(object): memory = [] def test_hello(self): self.memory.append(self) @@ -276,7 +276,7 @@ def test_setup_teardown_function_level_with_optional_argument(testdir, monkeypat def test_function_1(): pass def test_function_2(): pass - class Test: + class Test(object): def setup_method(self, {arg}): trace('setup_method') def teardown_method(self, {arg}): trace('teardown_method') diff --git a/testing/test_session.py b/testing/test_session.py index a7dcb27a4..b6e7d2b6c 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -2,7 +2,7 @@ import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED -class SessionTests: +class SessionTests(object): def test_basic_testitem_events(self, testdir): tfile = testdir.makepyfile(""" def test_one(): @@ -11,7 +11,7 @@ class SessionTests: assert 0 def test_other(): raise ValueError(23) - class TestClass: + class TestClass(object): def test_two(self, someargs): pass """) @@ -97,12 +97,12 @@ class SessionTests: def test_broken_repr(self, testdir): p = testdir.makepyfile(""" import pytest - class BrokenRepr1: + class BrokenRepr1(object): foo=0 def __repr__(self): raise Exception("Ha Ha fooled you, I'm a broken repr().") - class TestBrokenClass: + class TestBrokenClass(object): def test_explicit_bad_repr(self): t = BrokenRepr1() pytest.raises(Exception, 'repr(t)') @@ -145,7 +145,7 @@ class TestNewSession(SessionTests): l.append(2) def test_3(): assert l == [1,2] - class Testmygroup: + class Testmygroup(object): reslist = l def test_1(self): self.reslist.append(1) @@ -167,7 +167,7 @@ class TestNewSession(SessionTests): def test_one(): raise ValueError() - class TestX: + class TestX(object): def test_method_one(self): pass diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 2e7868d3a..6a8d147ad 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -5,7 +5,7 @@ from _pytest.skipping import MarkEvaluator, folded_skips, pytest_runtest_setup from _pytest.runner import runtestprotocol -class TestEvaluator: +class TestEvaluator(object): def test_no_marker(self, testdir): item = testdir.getitem("def test_func(): pass") evalskipif = MarkEvaluator(item, 'skipif') @@ -114,7 +114,7 @@ class TestEvaluator: def test_skipif_class(self, testdir): item, = testdir.getitems(""" import pytest - class TestClass: + class TestClass(object): pytestmark = pytest.mark.skipif("config._hackxyz") def test_func(self): pass @@ -126,7 +126,7 @@ class TestEvaluator: assert expl == "condition: config._hackxyz" -class TestXFail: +class TestXFail(object): @pytest.mark.parametrize('strict', [True, False]) def test_xfail_simple(self, testdir, strict): @@ -452,7 +452,7 @@ class TestXFail: assert result.ret == (1 if strict else 0) -class TestXFailwithSetupTeardown: +class TestXFailwithSetupTeardown(object): def test_failing_setup_issue9(self, testdir): testdir.makepyfile(""" import pytest @@ -484,7 +484,7 @@ class TestXFailwithSetupTeardown: ]) -class TestSkip: +class TestSkip(object): def test_skip_class(self, testdir): testdir.makepyfile(""" import pytest @@ -581,7 +581,7 @@ class TestSkip: "*1 skipped*", ]) -class TestSkipif: +class TestSkipif(object): def test_skipif_conditional(self, testdir): item = testdir.getitem(""" import pytest @@ -648,7 +648,7 @@ def test_skipif_class(testdir): p = testdir.makepyfile(""" import pytest - class TestClass: + class TestClass(object): pytestmark = pytest.mark.skipif("True") def test_that(self): assert 0 @@ -667,7 +667,7 @@ def test_skip_reasons_folding(): message = "justso" longrepr = (path, lineno, message) - class X: + class X(object): pass ev1 = X() ev1.when = "execute" @@ -694,7 +694,7 @@ def test_skipped_reasons_functional(testdir): doskip() def test_func(): pass - class TestClass: + class TestClass(object): def test_method(self): doskip() """, @@ -892,7 +892,7 @@ def test_imperativeskip_on_xfail_test(testdir): *2 skipped* """) -class TestBooleanCondition: +class TestBooleanCondition(object): def test_skipif(self, testdir): testdir.makepyfile(""" import pytest diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3efd7b1f9..77fb3b154 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -16,7 +16,7 @@ from _pytest.terminal import build_summary_stats_line, _plugin_nameversions DistInfo = collections.namedtuple('DistInfo', ['project_name', 'version']) -class Option: +class Option(object): def __init__(self, verbose=False, fulltrace=False): self.verbose = verbose self.fulltrace = fulltrace @@ -56,7 +56,7 @@ def test_plugin_nameversion(input, expected): assert result == expected -class TestTerminal: +class TestTerminal(object): def test_pass_skip_fail(self, testdir, option): testdir.makepyfile(""" import pytest @@ -127,7 +127,7 @@ class TestTerminal: def test_itemreport_subclasses_show_subclassed_file(self, testdir): testdir.makepyfile(test_p1=""" - class BaseTests: + class BaseTests(object): def test_p1(self): pass class TestClass(BaseTests): @@ -151,7 +151,7 @@ class TestTerminal: def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir): a = testdir.mkpydir("a123") a.join("test_hello123.py").write(_pytest._code.Source(""" - class TestClass: + class TestClass(object): def test_method(self): pass """)) @@ -204,7 +204,7 @@ class TestTerminal: result.stdout.fnmatch_lines(['*KeyboardInterrupt*']) -class TestCollectonly: +class TestCollectonly(object): def test_collectonly_basic(self, testdir): testdir.makepyfile(""" def test_func(): @@ -249,7 +249,7 @@ class TestCollectonly: p = testdir.makepyfile(""" def test_func1(): pass - class TestClass: + class TestClass(object): def test_method(self): pass """) @@ -310,7 +310,7 @@ def test_repr_python_version(monkeypatch): finally: monkeypatch.undo() # do this early as pytest can get confused -class TestFixtureReporting: +class TestFixtureReporting(object): def test_setup_fixture_error(self, testdir): testdir.makepyfile(""" def setup_function(function): @@ -395,7 +395,7 @@ class TestFixtureReporting: "*1 failed*", ]) -class TestTerminalFunctional: +class TestTerminalFunctional(object): def test_deselected(self, testdir): testpath = testdir.makepyfile(""" def test_one(): @@ -431,7 +431,7 @@ class TestTerminalFunctional: p1 = testdir.makepyfile(""" def test_passes(): pass - class TestClass: + class TestClass(object): def test_method(self): pass """) @@ -487,7 +487,7 @@ class TestTerminalFunctional: raise ValueError() def test_pass(): pass - class TestClass: + class TestClass(object): def test_skip(self): pytest.skip("hello") def test_gen(): @@ -612,8 +612,8 @@ def test_color_yes_collection_on_non_atty(testdir, verbose): def test_getreportopt(): - class config: - class option: + class config(object): + class option(object): reportchars = "" disablepytestwarnings = True @@ -683,7 +683,7 @@ def test_traceconfig(testdir, monkeypatch): assert result.ret == EXIT_NOTESTSCOLLECTED -class TestGenericReporting: +class TestGenericReporting(object): """ this test class can be subclassed with a different option provider to run e.g. distributed tests. """ diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 232acb6d2..32ac76331 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -34,7 +34,7 @@ def test_ensuretemp(recwarn): assert d1 == d2 assert d1.check(dir=1) -class TestTempdirHandler: +class TestTempdirHandler(object): def test_mktemp(self, testdir): from _pytest.tmpdir import TempdirFactory config = testdir.parseconfig() @@ -48,7 +48,7 @@ class TestTempdirHandler: assert tmp2.relto(t.getbasetemp()).startswith("this") assert tmp2 != tmp -class TestConfigTmpdir: +class TestConfigTmpdir(object): def test_getbasetemp_custom_removes_old(self, testdir): mytemp = testdir.tmpdir.join("xyz") p = testdir.makepyfile(""" diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 9625ae0f8..832ea08cc 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -385,7 +385,7 @@ def test_trial_testfunction_todo_property(testdir): reprec.assertoutcome(skipped=1) -class TestTrialUnittest: +class TestTrialUnittest(object): def setup_class(cls): cls.ut = pytest.importorskip("twisted.trial.unittest") @@ -704,7 +704,7 @@ def test_unittest_setup_interaction(testdir, fix_type, stmt): def test_non_unittest_no_setupclass_support(testdir): testpath = testdir.makepyfile(""" - class TestFoo: + class TestFoo(object): x = 0 @classmethod From f1900bbea6d7402fb511d84d2b1e58e17b59a03d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 18 Feb 2017 10:34:41 -0200 Subject: [PATCH 064/153] Revert subclassing explicitly from object introduced by accident in #2260 --- _pytest/vendored_packages/pluggy.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/_pytest/vendored_packages/pluggy.py b/_pytest/vendored_packages/pluggy.py index 11658c3b1..9c13932b3 100644 --- a/_pytest/vendored_packages/pluggy.py +++ b/_pytest/vendored_packages/pluggy.py @@ -75,7 +75,7 @@ __all__ = ["PluginManager", "PluginValidationError", "HookCallError", _py3 = sys.version_info > (3, 0) -class HookspecMarker(object): +class HookspecMarker: """ Decorator helper class for marking functions as hook specifications. You can instantiate it with a project_name to get a decorator. @@ -113,7 +113,7 @@ class HookspecMarker(object): return setattr_hookspec_opts -class HookimplMarker(object): +class HookimplMarker: """ Decorator helper class for marking functions as hook implementations. You can instantiate with a project_name to get a decorator. @@ -167,7 +167,7 @@ def normalize_hookimpl_opts(opts): opts.setdefault("optionalhook", False) -class _TagTracer(object): +class _TagTracer: def __init__(self): self._tag2proc = {} self.writer = None @@ -214,7 +214,7 @@ class _TagTracer(object): self._tag2proc[tags] = processor -class _TagTracerSub(object): +class _TagTracerSub: def __init__(self, root, tags): self.root = root self.tags = tags @@ -254,7 +254,7 @@ def _wrapped_call(wrap_controller, func): return call_outcome.get_result() -class _CallOutcome(object): +class _CallOutcome: """ Outcome of a function call, either an exception or a proper result. Calling the ``get_result`` method will return the result or reraise the exception raised when the function was called. """ @@ -286,7 +286,7 @@ def _reraise(cls, val, tb): """) -class _TracedHookExecution(object): +class _TracedHookExecution: def __init__(self, pluginmanager, before, after): self.pluginmanager = pluginmanager self.before = before @@ -580,7 +580,7 @@ class PluginManager(object): return orig -class _MultiCall(object): +class _MultiCall: """ execute a call into multiple python functions/methods. """ # XXX note that the __multicall__ argument is supported only @@ -673,7 +673,7 @@ def varnames(func, startindex=None): return x -class _HookRelay(object): +class _HookRelay: """ hook holder object for performing 1:N hook calls where N is the number of registered plugins. @@ -770,7 +770,7 @@ class _HookCaller(object): proc(res[0]) -class HookImpl(object): +class HookImpl: def __init__(self, plugin, plugin_name, function, hook_impl_opts): self.function = function self.argnames = varnames(self.function) From 82785fcd4025c6827f504ce082d99116bff54e11 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 18 Feb 2017 12:57:26 -0200 Subject: [PATCH 065/153] Use warnings.catch_warnings instead of WarningsRecorder This has the benefical side-effect of not calling the original warnings.showwarnings function, which in its original form only writes the formatted warning to sys.stdout. Calling the original warnings.showwarnings has the effect that nested WarningsRecorder all catch the warnings: with WarningsRecorder() as rec1: with WarningsRecorder() as rec2: warnings.warn(UserWarning, 'some warning') (both rec1 and rec2 sees the warning) When running tests with `testdir`, the main pytest session would then see the warnings created by the internal code being tested (if any), and the main pytest session would end up with warnings as well. --- _pytest/warnings.py | 28 +++++----------------------- testing/test_warnings.py | 6 +++--- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 84f64ea92..d654901d0 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,5 +1,3 @@ -from _pytest.recwarn import RecordedWarning, WarningsRecorder -import inspect import os import pytest import warnings @@ -42,35 +40,19 @@ def pytest_addoption(parser): @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(item): - wrec = WarningsRecorder() - - def showwarning(message, category, filename, lineno, file=None, line=None): - frame = inspect.currentframe() - if '/_pytest/recwarn' in frame.f_back.f_code.co_filename: - # we are in test recorder, so this warning is already handled - return - wrec._list.append(RecordedWarning( - message, category, filename, lineno, file, line)) - # still perform old showwarning functionality - wrec._showwarning( - message, category, filename, lineno, file=file, line=line) - args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") - with wrec: - _showwarning = wrec._showwarning - warnings.showwarning = showwarning - wrec._module.simplefilter('once') + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter('once') for arg in args: - wrec._module._setoption(arg) + warnings._setoption(arg) for arg in inifilters: - _setoption(wrec._module, arg) + _setoption(warnings, arg) yield - wrec._showwarning = _showwarning - for warning in wrec.list: + for warning in log: msg = warnings.formatwarning( warning.message, warning.category, os.path.relpath(warning.filename), warning.lineno, warning.line) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 34badaee8..738ef347b 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -12,7 +12,7 @@ def pyfile_with_warnings(testdir): def test_normal_flow(testdir, pyfile_with_warnings): - result = testdir.runpytest_subprocess() + result = testdir.runpytest() result.stdout.fnmatch_lines([ '*== pytest-warning summary ==*', @@ -35,7 +35,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method): [pytest] filterwarnings= error ''') - result = testdir.runpytest_subprocess(*args) + result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ 'E PendingDeprecationWarning: functionality is pending deprecation', 'test_as_errors.py:3: PendingDeprecationWarning', @@ -52,7 +52,7 @@ def test_ignore(testdir, pyfile_with_warnings, method): filterwarnings= ignore ''') - result = testdir.runpytest_subprocess(*args) + result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ '* 1 passed in *', ]) From 6ba3475448d3ab78b4bf7de9aa81baa47357a70d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 19 Feb 2017 09:16:32 -0800 Subject: [PATCH 066/153] Make capsys more like stdio streams in python3. Resolves #1407. --- AUTHORS | 1 + CHANGELOG.rst | 9 +++++++++ _pytest/capture.py | 4 ++-- _pytest/compat.py | 16 ++++++++++++++++ testing/test_capture.py | 24 ++++++++++++++++++------ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index e9e7f7c82..d17963e73 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Andrzej Ostrowski Andy Freeland Anthon van der Neut Antony Lee +Anthony Sottile Armin Rigo Aron Curzon Aviv Palivoda diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 23b87b18c..ee3eae097 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -55,6 +55,14 @@ Changes Thanks `@The-Compiler`_ for the PR. +Bug Fixes +--------- + +* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer`` + while using ``capsys`` fixture in python 3. (`#1407`_). + Thanks to `@asottile`_. + + .. _@davidszotten: https://github.com/davidszotten .. _@fushi: https://github.com/fushi .. _@mattduck: https://github.com/mattduck @@ -65,6 +73,7 @@ Changes .. _@unsignedint: https://github.com/unsignedint .. _@Kriechi: https://github.com/Kriechi +.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1952: https://github.com/pytest-dev/pytest/pull/1952 diff --git a/_pytest/capture.py b/_pytest/capture.py index 3fe1816d8..6a1cae41d 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -12,8 +12,8 @@ from tempfile import TemporaryFile import py import pytest +from _pytest.compat import CaptureIO -from py.io import TextIO unicode = py.builtin.text patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'} @@ -403,7 +403,7 @@ class SysCapture(object): if name == "stdin": tmpfile = DontReadFromInput() else: - tmpfile = TextIO() + tmpfile = CaptureIO() self.tmpfile = tmpfile def start(self): diff --git a/_pytest/compat.py b/_pytest/compat.py index e097dee51..09df385d1 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -251,3 +251,19 @@ else: except UnicodeError: errors = 'replace' return v.encode('ascii', errors) + + +if _PY2: + from py.io import TextIO as CaptureIO +else: + import io + + class CaptureIO(io.TextIOWrapper): + def __init__(self): + super(CaptureIO, self).__init__( + io.BytesIO(), + encoding='UTF-8', newline='', write_through=True, + ) + + def getvalue(self): + return self.buffer.getvalue().decode('UTF-8') diff --git a/testing/test_capture.py b/testing/test_capture.py index 365329f59..364281fe6 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -281,7 +281,7 @@ class TestLoggingInteraction(object): def test_logging(): import logging import pytest - stream = capture.TextIO() + stream = capture.CaptureIO() logging.basicConfig(stream=stream) stream.close() # to free memory/release resources """) @@ -622,16 +622,16 @@ def test_error_during_readouterr(testdir): ]) -class TestTextIO(object): +class TestCaptureIO(object): def test_text(self): - f = capture.TextIO() + f = capture.CaptureIO() f.write("hello") s = f.getvalue() assert s == "hello" f.close() def test_unicode_and_str_mixture(self): - f = capture.TextIO() + f = capture.CaptureIO() if sys.version_info >= (3, 0): f.write("\u00f6") pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))") @@ -642,6 +642,18 @@ class TestTextIO(object): f.close() assert isinstance(s, unicode) + @pytest.mark.skipif( + sys.version_info[0] == 2, + reason='python 3 only behaviour', + ) + def test_write_bytes_to_buffer(self): + """In python3, stdout / stderr are text io wrappers (exposing a buffer + property of the underlying bytestream). See issue #1407 + """ + f = capture.CaptureIO() + f.buffer.write(b'foo\r\n') + assert f.getvalue() == 'foo\r\n' + def test_bytes_io(): f = py.io.BytesIO() @@ -900,8 +912,8 @@ class TestStdCapture(object): with self.getcapture() as cap: sys.stdout.write("hello") sys.stderr.write("world") - sys.stdout = capture.TextIO() - sys.stderr = capture.TextIO() + sys.stdout = capture.CaptureIO() + sys.stderr = capture.CaptureIO() print ("not seen") sys.stderr.write("not seen\n") out, err = cap.readouterr() From 8b598f00e9f06a366eef36826070e42e7673fa24 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 19 Feb 2017 10:24:41 -0800 Subject: [PATCH 067/153] Make pytester use pytest's capture implementation --- _pytest/pytester.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 723f8bbb4..17cba5700 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -14,6 +14,7 @@ from weakref import WeakKeyDictionary from py.builtin import print_ +from _pytest.capture import MultiCapture, SysCapture from _pytest._code import Source import py import pytest @@ -737,7 +738,8 @@ class Testdir(object): if kwargs.get("syspathinsert"): self.syspathinsert() now = time.time() - capture = py.io.StdCapture() + capture = MultiCapture(Capture=SysCapture) + capture.start_capturing() try: try: reprec = self.inline_run(*args, **kwargs) @@ -752,7 +754,8 @@ class Testdir(object): class reprec(object): ret = 3 finally: - out, err = capture.reset() + out, err = capture.readouterr() + capture.stop_capturing() sys.stdout.write(out) sys.stderr.write(err) From 26e50f116233e64d56aef40d491679e76e8d38a1 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Fri, 3 Feb 2017 10:30:28 +0100 Subject: [PATCH 068/153] junitxml: adjust junitxml output file to comply with JUnit xsd Change XML file structure in the manner that failures in call and errors in teardown in one test will appear under separate testcase elements in the XML report. --- CHANGELOG.rst | 9 ++++++++- _pytest/junitxml.py | 38 ++++++++++++++++++++++++++++++++++++-- testing/test_junitxml.py | 23 +++++++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ee3eae097..44ac7baf3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -87,7 +87,10 @@ Bug Fixes 3.0.7 (unreleased) ======================= -* +* Change junitxml.py to produce reports that comply with Junitxml schema. + If the same test fails with failure in call and then errors in teardown + we split testcase element into two, one containing the error and the other + the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR. * @@ -95,6 +98,10 @@ Bug Fixes * +.. _@kkoukiou: https://github.com/KKoukiou + +.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 + 3.0.6 (2017-01-22) ================== diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index f486ea10c..4f7792aec 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -273,6 +273,9 @@ class LogXML(object): self.node_reporters = {} # nodeid -> _NodeReporter self.node_reporters_ordered = [] self.global_properties = [] + # List of reports that failed on call but teardown is pending. + self.open_reports = [] + self.cnt_double_fail_tests = 0 def finalize(self, report): nodeid = getattr(report, 'nodeid', report) @@ -332,14 +335,33 @@ class LogXML(object): -> teardown node2 -> teardown node1 """ + close_report = None if report.passed: if report.when == "call": # ignore setup/teardown reporter = self._opentestcase(report) reporter.append_pass(report) elif report.failed: + if report.when == "teardown": + # The following vars are needed when xdist plugin is used + report_wid = getattr(report, "worker_id", None) + report_ii = getattr(report, "item_index", None) + close_report = next( + (rep for rep in self.open_reports + if (rep.nodeid == report.nodeid and + getattr(rep, "item_index", None) == report_ii and + getattr(rep, "worker_id", None) == report_wid + ) + ), None) + if close_report: + # We need to open new testcase in case we have failure in + # call and error in teardown in order to follow junit + # schema + self.finalize(close_report) + self.cnt_double_fail_tests += 1 reporter = self._opentestcase(report) if report.when == "call": reporter.append_failure(report) + self.open_reports.append(report) else: reporter.append_error(report) elif report.skipped: @@ -348,6 +370,17 @@ class LogXML(object): self.update_testcase_duration(report) if report.when == "teardown": self.finalize(report) + report_wid = getattr(report, "worker_id", None) + report_ii = getattr(report, "item_index", None) + close_report = next( + (rep for rep in self.open_reports + if (rep.nodeid == report.nodeid and + getattr(rep, "item_index", None) == report_ii and + getattr(rep, "worker_id", None) == report_wid + ) + ), None) + if close_report: + self.open_reports.remove(close_report) def update_testcase_duration(self, report): """accumulates total duration for nodeid from given report and updates @@ -380,8 +413,9 @@ class LogXML(object): suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time - numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error'] - + numtests = (self.stats['passed'] + self.stats['failure'] + + self.stats['skipped'] + self.stats['error'] - + self.cnt_double_fail_tests) logfile.write('') logfile.write(Junit.testsuite( diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 70c02332c..b4e4c5b14 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -189,6 +189,29 @@ class TestPython(object): fnode.assert_attr(message="test teardown failure") assert "ValueError" in fnode.toxml() + def test_call_failure_teardown_error(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.fixture + def arg(): + yield + raise Exception("Teardown Exception") + def test_function(arg): + raise Exception("Call Exception") + """) + result, dom = runandparse(testdir) + assert result.ret + node = dom.find_first_by_tag("testsuite") + node.assert_attr(errors=1, failures=1, tests=1) + first, second = dom.find_by_tag("testcase") + if not first or not second or first == second: + assert 0 + fnode = first.find_first_by_tag("failure") + fnode.assert_attr(message="Exception: Call Exception") + snode = second.find_first_by_tag("error") + snode.assert_attr(message="test teardown failure") + def test_skip_contains_name_reason(self, testdir): testdir.makepyfile(""" import pytest From e24081bf76568cb4fc7b96b981c752647231fc5b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Feb 2017 12:02:48 -0300 Subject: [PATCH 069/153] Change warning output --- _pytest/terminal.py | 22 ++++++++++++---------- _pytest/warnings.py | 10 ++-------- testing/test_warnings.py | 14 +++++++------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index f509283cb..f8d3ec2ce 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -24,9 +24,9 @@ def pytest_addoption(parser): help="show extra test summary info as specified by chars (f)ailed, " "(E)error, (s)skipped, (x)failed, (X)passed, " "(p)passed, (P)passed with output, (a)all except pP. " - "The pytest warnings are displayed at all times except when " - "--disable-pytest-warnings is set") - group._addoption('--disable-pytest-warnings', default=False, + "Warnings are displayed at all times except when " + "--disable-warnings is set") + group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, dest='disablepytestwarnings', action='store_true', help='disable warnings summary, overrides -r w flag') group._addoption('-l', '--showlocals', @@ -441,10 +441,14 @@ class TerminalReporter(object): warnings = self.stats.get("warnings") if not warnings: return - self.write_sep("=", "pytest-warning summary") + self.write_sep("=", "warnings summary") for w in warnings: - self._tw.line("W%s %s %s" % (w.code, - w.fslocation, w.message)) + msg = '' + if w.fslocation: + msg += str(w.fslocation) + ' ' + msg += w.message + self._tw.line(msg) + self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') def summary_passes(self): if self.config.option.tbstyle != "no": @@ -546,8 +550,7 @@ def flatten(l): def build_summary_stats_line(stats): keys = ("failed passed skipped deselected " - "xfailed xpassed warnings error").split() - key_translation = {'warnings': 'pytest-warnings'} + "xfailed xpassed warnings error").split() unknown_key_seen = False for key in stats.keys(): if key not in keys: @@ -558,8 +561,7 @@ def build_summary_stats_line(stats): for key in keys: val = stats.get(key, None) if val: - key_name = key_translation.get(key, key) - parts.append("%d %s" % (len(val), key_name)) + parts.append("%d %s" % (len(val), key)) if parts: line = ", ".join(parts) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index d654901d0..b27c20b9d 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -55,11 +55,5 @@ def pytest_runtest_call(item): for warning in log: msg = warnings.formatwarning( warning.message, warning.category, - os.path.relpath(warning.filename), warning.lineno, warning.line) - fslocation = getattr(item, "location", None) - if fslocation is None: - fslocation = getattr(item, "fspath", None) - else: - fslocation = "%s:%s" % fslocation[:2] - fslocation = "in %s the following warning was recorded:\n" % fslocation - item.config.warn("W0", msg, fslocation=fslocation) + warning.filename, warning.lineno, warning.line) + item.config.warn("W0", msg, fslocation=None) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 738ef347b..943c8244c 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,6 +1,8 @@ import pytest +WARNINGS_SUMMARY_HEADER = 'warnings summary' + @pytest.fixture def pyfile_with_warnings(testdir): testdir.makepyfile(''' @@ -14,16 +16,14 @@ def pyfile_with_warnings(testdir): def test_normal_flow(testdir, pyfile_with_warnings): result = testdir.runpytest() result.stdout.fnmatch_lines([ - '*== pytest-warning summary ==*', + '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', - ' test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', + '*test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', - 'WW0 in *test_normal_flow.py:1 the following warning was recorded:', - ' test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', + '*test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', ' warnings.warn(DeprecationWarning("functionality is deprecated"))', - '* 1 passed, 2 pytest-warnings*', + '* 1 passed, 2 warnings*', ]) @@ -56,5 +56,5 @@ def test_ignore(testdir, pyfile_with_warnings, method): result.stdout.fnmatch_lines([ '* 1 passed in *', ]) - assert 'pytest-warning summary' not in result.stdout.str() + assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() From de09023e450462f78a562ea802b9d327af19661d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Mar 2017 16:07:37 -0300 Subject: [PATCH 070/153] Also capture warnings during setup/teardown --- _pytest/warnings.py | 32 +++++++++++++++++++++++++++++--- testing/test_warnings.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index b27c20b9d..1458c25c3 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,4 +1,6 @@ import os +from contextlib import contextmanager + import pytest import warnings @@ -38,8 +40,13 @@ def pytest_addoption(parser): "to warnings.filterwarnings. Process after -W and --pythonwarnings.") -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_call(item): +@contextmanager +def catch_warnings_for_item(item): + """ + catches the warnings generated during setup/call/teardown execution + of the given item and after it is done posts them as warnings to this + item. + """ args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") with warnings.catch_warnings(record=True) as log: @@ -56,4 +63,23 @@ def pytest_runtest_call(item): msg = warnings.formatwarning( warning.message, warning.category, warning.filename, warning.lineno, warning.line) - item.config.warn("W0", msg, fslocation=None) + item.config.warn("unused", msg, fslocation=None) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item): + with catch_warnings_for_item(item): + yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_setup(item): + with catch_warnings_for_item(item): + yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_teardown(item): + with catch_warnings_for_item(item): + yield + diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 943c8244c..77eefb7e3 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -27,6 +27,34 @@ def test_normal_flow(testdir, pyfile_with_warnings): ]) +def test_setup_teardown_warnings(testdir, pyfile_with_warnings): + testdir.makepyfile(''' + import warnings + import pytest + + @pytest.fixture + def fix(): + warnings.warn(UserWarning("warning during setup")) + yield + warnings.warn(UserWarning("warning during teardown")) + + def test_func(fix): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*== %s ==*' % WARNINGS_SUMMARY_HEADER, + + '*test_setup_teardown_warnings.py:6: UserWarning: warning during setup', + ' warnings.warn(UserWarning("warning during setup"))', + + '*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown', + ' warnings.warn(UserWarning("warning during teardown"))', + '* 1 passed, 2 warnings*', + ]) + + + @pytest.mark.parametrize('method', ['cmdline', 'ini']) def test_as_errors(testdir, pyfile_with_warnings, method): args = ('-W', 'error') if method == 'cmdline' else () From bddb922f7bd39c378fb97d42ed04d49108c27362 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Mar 2017 16:32:10 -0300 Subject: [PATCH 071/153] Rename internal option to disable_warnings --- _pytest/terminal.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index f8d3ec2ce..d0bb4f4b5 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -27,7 +27,7 @@ def pytest_addoption(parser): "Warnings are displayed at all times except when " "--disable-warnings is set") group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, - dest='disablepytestwarnings', action='store_true', + dest='disable_warnings', action='store_true', help='disable warnings summary, overrides -r w flag') group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, @@ -57,9 +57,9 @@ def pytest_configure(config): def getreportopt(config): reportopts = "" reportchars = config.option.reportchars - if not config.option.disablepytestwarnings and 'w' not in reportchars: + if not config.option.disable_warnings and 'w' not in reportchars: reportchars += 'w' - elif config.option.disablepytestwarnings and 'w' in reportchars: + elif config.option.disable_warnings and 'w' in reportchars: reportchars = reportchars.replace('w', '') if reportchars: for char in reportchars: From 272afa9422abff0070b47973ab69ccaca6fadeb1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 4 Mar 2017 20:53:42 -0300 Subject: [PATCH 072/153] Display node ids and the warnings generated by it The rationale of using node ids is that users can copy/paste it to run a chosen test --- _pytest/terminal.py | 22 +++++++++++---------- _pytest/warnings.py | 2 +- testing/test_warnings.py | 41 ++++++++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index d0bb4f4b5..fbfb03232 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -2,6 +2,9 @@ This is a good source for looking at the various reporting hooks. """ +import operator +import itertools + from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED import pytest @@ -28,7 +31,7 @@ def pytest_addoption(parser): "--disable-warnings is set") group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False, dest='disable_warnings', action='store_true', - help='disable warnings summary, overrides -r w flag') + help='disable warnings summary') group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default).") @@ -438,16 +441,15 @@ class TerminalReporter(object): def summary_warnings(self): if self.hasopt("w"): - warnings = self.stats.get("warnings") - if not warnings: + all_warnings = self.stats.get("warnings") + if not all_warnings: return - self.write_sep("=", "warnings summary") - for w in warnings: - msg = '' - if w.fslocation: - msg += str(w.fslocation) + ' ' - msg += w.message - self._tw.line(msg) + self.write_sep("=", "warnings summary", yellow=True, bold=False) + grouped = itertools.groupby(all_warnings, key=operator.attrgetter('nodeid')) + for nodeid, warnings in grouped: + self._tw.line(str(nodeid)) + for w in warnings: + self._tw.line(w.message) self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') def summary_passes(self): diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 1458c25c3..9a91d181c 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -63,7 +63,7 @@ def catch_warnings_for_item(item): msg = warnings.formatwarning( warning.message, warning.category, warning.filename, warning.lineno, warning.line) - item.config.warn("unused", msg, fslocation=None) + item.warn("unused", msg) @pytest.hookimpl(hookwrapper=True) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 77eefb7e3..3b9908ef7 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -4,27 +4,48 @@ import pytest WARNINGS_SUMMARY_HEADER = 'warnings summary' @pytest.fixture -def pyfile_with_warnings(testdir): - testdir.makepyfile(''' - import warnings - def test_func(): - warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) - warnings.warn(DeprecationWarning("functionality is deprecated")) - ''') +def pyfile_with_warnings(testdir, request): + """ + Create a test file which calls a function in a module which generates warnings. + """ + testdir.syspathinsert() + test_name = request.function.__name__ + module_name = test_name.lstrip('test_') + '_module' + testdir.makepyfile(**{ + module_name: ''' + import warnings + def foo(): + warnings.warn(PendingDeprecationWarning("functionality is pending deprecation")) + warnings.warn(DeprecationWarning("functionality is deprecated")) + return 1 + ''', + test_name: ''' + import {module_name} + def test_func(): + assert {module_name}.foo() == 1 + '''.format(module_name=module_name) + }) def test_normal_flow(testdir, pyfile_with_warnings): + """ + Check that the warnings section is displayed, containing test node ids followed by + all warnings generated by that test node. + """ result = testdir.runpytest() result.stdout.fnmatch_lines([ '*== %s ==*' % WARNINGS_SUMMARY_HEADER, - '*test_normal_flow.py:3: PendingDeprecationWarning: functionality is pending deprecation', + '*test_normal_flow.py::test_func', + + '*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation', ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', - '*test_normal_flow.py:4: DeprecationWarning: functionality is deprecated', + '*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated', ' warnings.warn(DeprecationWarning("functionality is deprecated"))', '* 1 passed, 2 warnings*', ]) + assert result.stdout.str().count('test_normal_flow.py::test_func') == 1 def test_setup_teardown_warnings(testdir, pyfile_with_warnings): @@ -66,7 +87,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method): result = testdir.runpytest(*args) result.stdout.fnmatch_lines([ 'E PendingDeprecationWarning: functionality is pending deprecation', - 'test_as_errors.py:3: PendingDeprecationWarning', + 'as_errors_module.py:3: PendingDeprecationWarning', '* 1 failed in *', ]) From 22864b75ee080c8c600813b4f8985d2a70870dcb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 9 Mar 2017 23:08:50 -0300 Subject: [PATCH 073/153] Refactor recwarn to use warnings.catch_warnings instead of custom code Since we dropped 2.5, we can now use warnings.catch_warnings to do the "catch warnings" magic for us, simplifying the code a bit. --- _pytest/recwarn.py | 42 +++++++++-------------------------------- testing/test_recwarn.py | 4 ---- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 9031bdbda..43f68ed12 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -6,11 +6,10 @@ import py import sys import warnings import pytest -from collections import namedtuple @pytest.yield_fixture -def recwarn(request): +def recwarn(): """Return a WarningsRecorder instance that provides these methods: * ``pop(category=None)``: return last warning matching the category. @@ -115,19 +114,14 @@ def warns(expected_warning, *args, **kwargs): return func(*args[1:], **kwargs) -RecordedWarning = namedtuple('RecordedWarning', ( - 'message', 'category', 'filename', 'lineno', 'file', 'line', -)) - - -class WarningsRecorder(object): +class WarningsRecorder(warnings.catch_warnings): """A context manager to record raised warnings. Adapted from `warnings.catch_warnings`. """ - def __init__(self, module=None): - self._module = sys.modules['warnings'] if module is None else module + def __init__(self): + super(WarningsRecorder, self).__init__(record=True) self._entered = False self._list = [] @@ -164,38 +158,20 @@ class WarningsRecorder(object): if self._entered: __tracebackhide__ = True raise RuntimeError("Cannot enter %r twice" % self) - self._entered = True - self._filters = self._module.filters - self._module.filters = self._filters[:] - self._showwarning = self._module.showwarning - - def showwarning(message, category, filename, lineno, - file=None, line=None): - self._list.append(RecordedWarning( - message, category, filename, lineno, file, line)) - - # still perform old showwarning functionality - self._showwarning( - message, category, filename, lineno, file=file, line=line) - - self._module.showwarning = showwarning - - # allow the same warning to be raised more than once - - self._module.simplefilter('always') + self._list = super(WarningsRecorder, self).__enter__() + warnings.simplefilter('always') return self def __exit__(self, *exc_info): if not self._entered: __tracebackhide__ = True raise RuntimeError("Cannot exit %r without entering first" % self) - self._module.filters = self._filters - self._module.showwarning = self._showwarning + super(WarningsRecorder, self).__exit__(*exc_info) class WarningsChecker(WarningsRecorder): - def __init__(self, expected_warning=None, module=None): - super(WarningsChecker, self).__init__(module=module) + def __init__(self, expected_warning=None): + super(WarningsChecker, self).__init__() msg = ("exceptions must be old-style classes or " "derived from Warning, not %s") diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 231ef028e..c5244411c 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -217,7 +217,6 @@ class TestWarns(object): excinfo.match(re.escape(message_template.format(warning_classes, [each.message for each in warninfo]))) - def test_record(self): with pytest.warns(UserWarning) as record: warnings.warn("user", UserWarning) @@ -225,9 +224,6 @@ class TestWarns(object): assert len(record) == 1 assert str(record[0].message) == "user" - print(repr(record[0])) - assert str(record[0].message) in repr(record[0]) - def test_record_only(self): with pytest.warns(None) as record: warnings.warn("user", UserWarning) From 3c07072bfd3580bcd1b7334f77d2352cb8a9814d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 13 Mar 2017 19:52:35 -0300 Subject: [PATCH 074/153] Fix test_recwarn in Python 3.6 No longer test for implementation details of recwarn since warnings.catch_warnings has changed significantly in 3.6. --- testing/test_recwarn.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index c5244411c..1269af431 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -8,25 +8,19 @@ from _pytest.recwarn import WarningsRecorder def test_recwarn_functional(testdir): reprec = testdir.inline_runsource(""" import warnings - oldwarn = warnings.showwarning def test_method(recwarn): - assert warnings.showwarning != oldwarn warnings.warn("hello") warn = recwarn.pop() assert isinstance(warn.message, UserWarning) - def test_finalized(): - assert warnings.showwarning == oldwarn """) res = reprec.countoutcomes() - assert tuple(res) == (2, 0, 0), res + assert tuple(res) == (1, 0, 0), res class TestWarningsRecorderChecker(object): - def test_recording(self, recwarn): - showwarning = py.std.warnings.showwarning + def test_recording(self): rec = WarningsRecorder() with rec: - assert py.std.warnings.showwarning != showwarning assert not rec.list py.std.warnings.warn_explicit("hello", UserWarning, "xyz", 13) assert len(rec.list) == 1 @@ -40,8 +34,6 @@ class TestWarningsRecorderChecker(object): assert l is rec.list pytest.raises(AssertionError, "rec.pop()") - assert showwarning == py.std.warnings.showwarning - def test_typechecking(self): from _pytest.recwarn import WarningsChecker with pytest.raises(TypeError): From 337f891d7808b081161238bbb2391f7e8e2d66ec Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Mar 2017 20:47:43 -0300 Subject: [PATCH 075/153] Fixed tests --- testing/deprecated_test.py | 2 +- testing/test_assertion.py | 2 +- testing/test_config.py | 7 ++++--- testing/test_junitxml.py | 7 +++++-- testing/test_pluginmanager.py | 4 ++-- testing/test_terminal.py | 16 ++++++++-------- testing/test_warnings.py | 1 - 7 files changed, 21 insertions(+), 18 deletions(-) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 6473989e6..bad844281 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -26,7 +26,7 @@ def test_funcarg_prefix_deprecation(testdir): """) result = testdir.runpytest('-ra') result.stdout.fnmatch_lines([ - ('WC1 None pytest_funcarg__value: ' + ('pytest_funcarg__value: ' 'declaring fixtures using "pytest_funcarg__" prefix is deprecated ' 'and scheduled to be removed in pytest 4.0. ' 'Please remove the prefix and use the @pytest.fixture decorator instead.'), diff --git a/testing/test_assertion.py b/testing/test_assertion.py index fc2819f69..7ab62e6f3 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -975,7 +975,7 @@ def test_assert_tuple_warning(testdir): assert(False, 'you shall not pass') """) result = testdir.runpytest('-rw') - result.stdout.fnmatch_lines('WR1*:2 assertion is always true*') + result.stdout.fnmatch_lines('*test_assert_tuple_warning.py:2 assertion is always true*') def test_assert_indirect_tuple_no_warning(testdir): testdir.makepyfile(""" diff --git a/testing/test_config.py b/testing/test_config.py index 7876063bd..4b2006a1d 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -632,13 +632,14 @@ class TestWarning(object): pass """) result = testdir.runpytest("--disable-pytest-warnings") - assert result.parseoutcomes()["pytest-warnings"] > 0 + assert result.parseoutcomes()["warnings"] > 0 assert "hello" not in result.stdout.str() result = testdir.runpytest() result.stdout.fnmatch_lines(""" - ===*pytest-warning summary*=== - *WT1*test_warn_on_test_item*:7 hello* + ===*warnings summary*=== + *test_warn_on_test_item_from_request::test_hello* + *hello* """) class TestRootdir(object): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 70c02332c..1dd9302f9 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -810,7 +810,10 @@ def test_record_property(testdir): pnodes = psnode.find_by_tag('property') pnodes[0].assert_attr(name="bar", value="1") pnodes[1].assert_attr(name="foo", value="<1") - result.stdout.fnmatch_lines('*C3*test_record_property.py*experimental*') + result.stdout.fnmatch_lines([ + 'test_record_property.py::test_record', + 'record_xml_property*experimental*', + ]) def test_record_property_same_name(testdir): @@ -986,4 +989,4 @@ def test_url_property(testdir): test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0] - assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" \ No newline at end of file + assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 3214d868b..3f4ce9aad 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -284,8 +284,8 @@ class TestPytestPluginManager(object): result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True) assert result.ret == EXIT_NOTESTSCOLLECTED result.stdout.fnmatch_lines([ - "WI1*skipped plugin*skipping1*hello*", - "WI1*skipped plugin*skipping2*hello*", + "*skipped plugin*skipping1*hello*", + "*skipped plugin*skipping2*hello*", ]) def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm): diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 77fb3b154..838ecf019 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -615,7 +615,7 @@ def test_getreportopt(): class config(object): class option(object): reportchars = "" - disablepytestwarnings = True + disable_warnings = True config.option.reportchars = "sf" assert getreportopt(config) == "sf" @@ -624,11 +624,11 @@ def test_getreportopt(): assert getreportopt(config) == "sfx" config.option.reportchars = "sfx" - config.option.disablepytestwarnings = False + config.option.disable_warnings = False assert getreportopt(config) == "sfxw" config.option.reportchars = "sfxw" - config.option.disablepytestwarnings = False + config.option.disable_warnings = False assert getreportopt(config) == "sfxw" @@ -837,8 +837,8 @@ def test_terminal_summary_warnings_are_displayed(testdir): """) result = testdir.runpytest('-rw') result.stdout.fnmatch_lines([ - '*C1*internal warning', - '*== 1 pytest-warnings in *', + '*internal warning', + '*== 1 warnings in *', ]) @@ -858,9 +858,9 @@ def test_terminal_summary_warnings_are_displayed(testdir): ("yellow", "1 weird", {"weird": (1,)}), ("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}), - ("yellow", "1 pytest-warnings", {"warnings": (1,)}), - ("yellow", "1 passed, 1 pytest-warnings", {"warnings": (1,), - "passed": (1,)}), + ("yellow", "1 warnings", {"warnings": (1,)}), + ("yellow", "1 passed, 1 warnings", {"warnings": (1,), + "passed": (1,)}), ("green", "5 passed", {"passed": (1,2,3,4,5)}), diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 3b9908ef7..ad80325f5 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -75,7 +75,6 @@ def test_setup_teardown_warnings(testdir, pyfile_with_warnings): ]) - @pytest.mark.parametrize('method', ['cmdline', 'ini']) def test_as_errors(testdir, pyfile_with_warnings, method): args = ('-W', 'error') if method == 'cmdline' else () From be5db6fa228e0d310194ecd64ad7e6795263c39f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 21:54:41 -0300 Subject: [PATCH 076/153] Capture warnings around the entire runtestprotocol This is necessary for the warnings plugin to play nice with the recwarn fixture --- _pytest/recwarn.py | 2 -- _pytest/warnings.py | 28 +++++++--------------------- testing/test_recwarn.py | 4 +++- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 43f68ed12..319bcb836 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -54,14 +54,12 @@ def deprecated_call(func=None, *args, **kwargs): def warn_explicit(message, category, *args, **kwargs): categories.append(category) - old_warn_explicit(message, category, *args, **kwargs) def warn(message, category=None, *args, **kwargs): if isinstance(message, Warning): categories.append(message.__class__) else: categories.append(category) - old_warn(message, category, *args, **kwargs) old_warn = warnings.warn old_warn_explicit = warnings.warn_explicit diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 9a91d181c..9b18185d2 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,4 +1,3 @@ -import os from contextlib import contextmanager import pytest @@ -50,7 +49,7 @@ def catch_warnings_for_item(item): args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") with warnings.catch_warnings(record=True) as log: - warnings.simplefilter('once') + warnings.simplefilter('always') for arg in args: warnings._setoption(arg) @@ -59,27 +58,14 @@ def catch_warnings_for_item(item): yield - for warning in log: - msg = warnings.formatwarning( - warning.message, warning.category, - warning.filename, warning.lineno, warning.line) - item.warn("unused", msg) + for warning in log: + msg = warnings.formatwarning( + warning.message, warning.category, + warning.filename, warning.lineno, warning.line) + item.warn("unused", msg) @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_call(item): +def pytest_runtest_protocol(item): with catch_warnings_for_item(item): yield - - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_setup(item): - with catch_warnings_for_item(item): - yield - - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_teardown(item): - with catch_warnings_for_item(item): - yield - diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 1269af431..5d5a68c89 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -145,7 +145,9 @@ class TestDeprecatedCall(object): pytest.deprecated_call(deprecated_function) """) result = testdir.runpytest() - result.stdout.fnmatch_lines('*=== 2 passed in *===') + # the 2 tests must pass, but the call to test_one() will generate a warning + # in pytest's summary + result.stdout.fnmatch_lines('*=== 2 passed, 1 warnings in *===') class TestWarns(object): From 78194093afe6bbb82aa2e636b67046ce96b7c238 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 21:55:03 -0300 Subject: [PATCH 077/153] Improve warning representation in terminal plugin and fix tests --- _pytest/config.py | 4 ++-- _pytest/fixtures.py | 2 +- _pytest/terminal.py | 43 +++++++++++++++++++++++++++++++------- testing/deprecated_test.py | 2 +- testing/python/collect.py | 10 ++++----- testing/test_assertion.py | 5 ++++- testing/test_config.py | 2 +- testing/test_junitxml.py | 2 +- testing/test_warnings.py | 8 +++---- 9 files changed, 55 insertions(+), 23 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 861775b87..0874c4599 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -910,11 +910,11 @@ class Config(object): fin = self._cleanup.pop() fin() - def warn(self, code, message, fslocation=None): + def warn(self, code, message, fslocation=None, nodeid=None): """ generate a warning for this test session. """ self.hook.pytest_logwarning.call_historic(kwargs=dict( code=code, message=message, - fslocation=fslocation, nodeid=None)) + fslocation=fslocation, nodeid=nodeid)) def get_terminal_writer(self): return self.pluginmanager.get_plugin("terminalreporter")._tw diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index c4d21635f..cd5c673ca 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -1080,7 +1080,7 @@ class FixtureManager(object): continue marker = defaultfuncargprefixmarker from _pytest import deprecated - self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name)) + self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid) name = name[len(self._argprefix):] elif not isinstance(marker, FixtureFunctionMarker): # magic globals with __getattr__ might have got us a wrong diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 741a0b600..dd92ddfa3 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -2,7 +2,6 @@ This is a good source for looking at the various reporting hooks. """ -import operator import itertools from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ @@ -83,13 +82,40 @@ def pytest_report_teststatus(report): letter = "f" return report.outcome, letter, report.outcome.upper() + class WarningReport(object): + """ + Simple structure to hold warnings information captured by ``pytest_logwarning``. + """ def __init__(self, code, message, nodeid=None, fslocation=None): + """ + :param code: unused + :param str message: user friendly message about the warning + :param str|None nodeid: node id that generated the warning (see ``get_location``). + :param tuple|py.path.local fslocation: + file system location of the source of the warning (see ``get_location``). + """ self.code = code self.message = message self.nodeid = nodeid self.fslocation = fslocation + def get_location(self, config): + """ + Returns the more user-friendly information about the location + of a warning, or None. + """ + if self.nodeid: + return self.nodeid + if self.fslocation: + if isinstance(self.fslocation, tuple) and len(self.fslocation) == 2: + filename, linenum = self.fslocation + relpath = py.path.local(filename).relto(config.invocation_dir) + return '%s:%d' % (relpath, linenum) + else: + return str(self.fslocation) + return None + class TerminalReporter(object): def __init__(self, config, file=None): @@ -169,8 +195,6 @@ class TerminalReporter(object): def pytest_logwarning(self, code, fslocation, message, nodeid): warnings = self.stats.setdefault("warnings", []) - if isinstance(fslocation, tuple): - fslocation = "%s:%d" % fslocation warning = WarningReport(code=code, fslocation=fslocation, message=message, nodeid=nodeid) warnings.append(warning) @@ -444,12 +468,17 @@ class TerminalReporter(object): all_warnings = self.stats.get("warnings") if not all_warnings: return + + grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config)) + self.write_sep("=", "warnings summary", yellow=True, bold=False) - grouped = itertools.groupby(all_warnings, key=operator.attrgetter('nodeid')) - for nodeid, warnings in grouped: - self._tw.line(str(nodeid)) + for location, warnings in grouped: + self._tw.line(str(location) or '') for w in warnings: - self._tw.line(w.message) + lines = w.message.splitlines() + indented = '\n'.join(' ' + x for x in lines) + self._tw.line(indented) + self._tw.line() self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html') def summary_passes(self): diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index bad844281..ac1abda9c 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -26,7 +26,7 @@ def test_funcarg_prefix_deprecation(testdir): """) result = testdir.runpytest('-ra') result.stdout.fnmatch_lines([ - ('pytest_funcarg__value: ' + ('*pytest_funcarg__value: ' 'declaring fixtures using "pytest_funcarg__" prefix is deprecated ' 'and scheduled to be removed in pytest 4.0. ' 'Please remove the prefix and use the @pytest.fixture decorator instead.'), diff --git a/testing/python/collect.py b/testing/python/collect.py index e4069983a..57dcaf54f 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -113,9 +113,9 @@ class TestClass(object): pass """) result = testdir.runpytest("-rw") - result.stdout.fnmatch_lines_random(""" - WC1*test_class_with_init_warning.py*__init__* - """) + result.stdout.fnmatch_lines([ + "*cannot collect test class 'TestClass1' because it has a __init__ constructor", + ]) def test_class_subclassobject(self, testdir): testdir.getmodulecol(""" @@ -1241,8 +1241,8 @@ def test_dont_collect_non_function_callable(testdir): result = testdir.runpytest('-rw') result.stdout.fnmatch_lines([ '*collected 1 item*', - 'WC2 *', - '*1 passed, 1 pytest-warnings in *', + "*cannot collect 'test_a' because it is not a function*", + '*1 passed, 1 warnings in *', ]) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 8bfe65abd..2c04df63e 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -975,7 +975,10 @@ def test_assert_tuple_warning(testdir): assert(False, 'you shall not pass') """) result = testdir.runpytest('-rw') - result.stdout.fnmatch_lines('*test_assert_tuple_warning.py:2 assertion is always true*') + result.stdout.fnmatch_lines([ + '*test_assert_tuple_warning.py:2', + '*assertion is always true*', + ]) def test_assert_indirect_tuple_no_warning(testdir): testdir.makepyfile(""" diff --git a/testing/test_config.py b/testing/test_config.py index 21142c8df..40b944adc 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -638,7 +638,7 @@ class TestWarning(object): result = testdir.runpytest() result.stdout.fnmatch_lines(""" ===*warnings summary*=== - *test_warn_on_test_item_from_request::test_hello* + *test_warn_on_test_item_from_request.py::test_hello* *hello* """) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index d4e4d4c09..a87745350 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -854,7 +854,7 @@ def test_record_property(testdir): pnodes[1].assert_attr(name="foo", value="<1") result.stdout.fnmatch_lines([ 'test_record_property.py::test_record', - 'record_xml_property*experimental*', + '*record_xml_property*experimental*', ]) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index ad80325f5..e0baed8d1 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -39,10 +39,10 @@ def test_normal_flow(testdir, pyfile_with_warnings): '*test_normal_flow.py::test_func', '*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation', - ' warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', + '* warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))', '*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated', - ' warnings.warn(DeprecationWarning("functionality is deprecated"))', + '* warnings.warn(DeprecationWarning("functionality is deprecated"))', '* 1 passed, 2 warnings*', ]) assert result.stdout.str().count('test_normal_flow.py::test_func') == 1 @@ -67,10 +67,10 @@ def test_setup_teardown_warnings(testdir, pyfile_with_warnings): '*== %s ==*' % WARNINGS_SUMMARY_HEADER, '*test_setup_teardown_warnings.py:6: UserWarning: warning during setup', - ' warnings.warn(UserWarning("warning during setup"))', + '*warnings.warn(UserWarning("warning during setup"))', '*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown', - ' warnings.warn(UserWarning("warning during teardown"))', + '*warnings.warn(UserWarning("warning during teardown"))', '* 1 passed, 2 warnings*', ]) From 7684b3af7b33fc02887d608029b1000b1ac286af Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 22:20:38 -0300 Subject: [PATCH 078/153] Recommend using py36 for testing on CONTRIBUTING --- CONTRIBUTING.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 71dc04d91..edf71dad7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -206,12 +206,12 @@ but here is a simple overview: #. Run all the tests - You need to have Python 2.7 and 3.5 available in your system. Now + You need to have Python 2.7 and 3.6 available in your system. Now running tests is as simple as issuing this command:: - $ tox -e linting,py27,py35 + $ tox -e linting,py27,py36 - This command will run tests via the "tox" tool against Python 2.7 and 3.5 + This command will run tests via the "tox" tool against Python 2.7 and 3.6 and also perform "lint" coding-style checks. #. You can now edit your local working copy. @@ -223,9 +223,9 @@ but here is a simple overview: $ tox -e py27 -- --pdb - Or to only run tests in a particular test module on Python 3.5:: + Or to only run tests in a particular test module on Python 3.6:: - $ tox -e py35 -- testing/test_config.py + $ tox -e py36 -- testing/test_config.py #. Commit and push once your tests pass and you are happy with your change(s):: From 42a5d6bdfa620fc13809c861a05077ef6d6f2a6a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 22:21:30 -0300 Subject: [PATCH 079/153] Add __future__ imports to all pytest modules This prevents silly errors from creeping in Python 2 when testing in Python 3 --- _pytest/_argcomplete.py | 2 +- _pytest/_code/__init__.py | 1 + _pytest/_code/_py2traceback.py | 1 + _pytest/_code/code.py | 1 + _pytest/_code/source.py | 2 +- _pytest/_pluggy.py | 2 +- _pytest/assertion/__init__.py | 1 + _pytest/assertion/rewrite.py | 2 +- _pytest/assertion/truncate.py | 2 +- _pytest/assertion/util.py | 1 + _pytest/cacheprovider.py | 2 +- _pytest/capture.py | 2 +- _pytest/compat.py | 1 + _pytest/config.py | 1 + _pytest/debugging.py | 2 +- _pytest/deprecated.py | 2 +- _pytest/doctest.py | 2 +- _pytest/fixtures.py | 1 + _pytest/freeze_support.py | 4 +++- _pytest/helpconfig.py | 2 ++ _pytest/junitxml.py | 6 ++++-- _pytest/main.py | 2 ++ _pytest/mark.py | 2 ++ _pytest/monkeypatch.py | 1 + _pytest/nose.py | 1 + _pytest/pastebin.py | 2 ++ _pytest/pytester.py | 2 ++ _pytest/python.py | 1 + _pytest/recwarn.py | 2 ++ _pytest/resultlog.py | 1 + _pytest/runner.py | 2 ++ _pytest/setuponly.py | 2 ++ _pytest/setupplan.py | 2 ++ _pytest/skipping.py | 2 ++ _pytest/terminal.py | 2 ++ _pytest/tmpdir.py | 2 ++ _pytest/unittest.py | 2 +- testing/acceptance_test.py | 1 + testing/code/test_code.py | 1 + testing/code/test_excinfo.py | 1 + testing/code/test_source.py | 1 + testing/deprecated_test.py | 1 + testing/test_argcomplete.py | 2 +- testing/test_assertion.py | 1 + testing/test_assertrewrite.py | 1 + testing/test_cache.py | 1 + testing/test_capture.py | 1 + testing/test_collection.py | 1 + testing/test_compat.py | 1 + testing/test_config.py | 1 + testing/test_conftest.py | 1 + testing/test_doctest.py | 1 + testing/test_entry_points.py | 1 + testing/test_helpconfig.py | 1 + testing/test_junitxml.py | 4 ++-- testing/test_mark.py | 1 + testing/test_monkeypatch.py | 1 + testing/test_nose.py | 1 + testing/test_parseopt.py | 2 +- testing/test_pastebin.py | 1 + testing/test_pdb.py | 1 + testing/test_pluginmanager.py | 1 + testing/test_pytester.py | 1 + testing/test_recwarn.py | 1 + testing/test_resultlog.py | 1 + testing/test_runner.py | 2 +- testing/test_runner_xunit.py | 8 +++++--- testing/test_session.py | 1 + testing/test_skipping.py | 1 + testing/test_terminal.py | 1 + testing/test_tmpdir.py | 1 + testing/test_unittest.py | 1 + 72 files changed, 94 insertions(+), 22 deletions(-) diff --git a/_pytest/_argcomplete.py b/_pytest/_argcomplete.py index 8fbbf2660..12040b53a 100644 --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -57,7 +57,7 @@ If things do not work right away: which should throw a KeyError: 'COMPLINE' (which is properly set by the global argcomplete script). """ - +from __future__ import absolute_import, division, print_function import sys import os from glob import glob diff --git a/_pytest/_code/__init__.py b/_pytest/_code/__init__.py index 3463c11ea..815c13b42 100644 --- a/_pytest/_code/__init__.py +++ b/_pytest/_code/__init__.py @@ -1,4 +1,5 @@ """ python inspection/code generation API """ +from __future__ import absolute_import, division, print_function from .code import Code # noqa from .code import ExceptionInfo # noqa from .code import Frame # noqa diff --git a/_pytest/_code/_py2traceback.py b/_pytest/_code/_py2traceback.py index a830d9899..d45ee01fa 100644 --- a/_pytest/_code/_py2traceback.py +++ b/_pytest/_code/_py2traceback.py @@ -2,6 +2,7 @@ # CHANGES: # - some_str is replaced, trying to create unicode strings # +from __future__ import absolute_import, division, print_function import types def format_exception_only(etype, value): diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 6eceb0c7f..2f1ac7fb0 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys from inspect import CO_VARARGS, CO_VARKEYWORDS import re diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index fcec0f5ca..8e6148410 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -1,4 +1,4 @@ -from __future__ import generators +from __future__ import absolute_import, division, generators, print_function from bisect import bisect_right import sys diff --git a/_pytest/_pluggy.py b/_pytest/_pluggy.py index 87d32cf8d..6cc1d3d54 100644 --- a/_pytest/_pluggy.py +++ b/_pytest/_pluggy.py @@ -2,7 +2,7 @@ imports symbols from vendored "pluggy" if available, otherwise falls back to importing "pluggy" from the default namespace. """ - +from __future__ import absolute_import, division, print_function try: from _pytest.vendored_packages.pluggy import * # noqa from _pytest.vendored_packages.pluggy import __version__ # noqa diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index fe0653bb7..edda06345 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -1,6 +1,7 @@ """ support for presenting detailed information in failing assertions. """ +from __future__ import absolute_import, division, print_function import py import sys diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 7408c4746..484d5bd8b 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -1,5 +1,5 @@ """Rewrite assertion AST to produce nice error messages""" - +from __future__ import absolute_import, division, print_function import ast import _ast import errno diff --git a/_pytest/assertion/truncate.py b/_pytest/assertion/truncate.py index 3c031b11f..1e1306356 100644 --- a/_pytest/assertion/truncate.py +++ b/_pytest/assertion/truncate.py @@ -4,7 +4,7 @@ Utilities for truncating assertion output. Current default behaviour is to truncate assertion explanations at ~8 terminal lines, unless running in "-vv" mode or running on CI. """ - +from __future__ import absolute_import, division, print_function import os import py diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py index 7b4edb6fd..06eda8d91 100644 --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -1,4 +1,5 @@ """Utilities for assertion debugging""" +from __future__ import absolute_import, division, print_function import pprint import _pytest._code diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 782fdadd9..0b8e71a71 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -4,7 +4,7 @@ merged implementation of the cache provider the name cache was not chosen to ensure pluggy automatically ignores the external pytest-cache """ - +from __future__ import absolute_import, division, print_function import py import pytest import json diff --git a/_pytest/capture.py b/_pytest/capture.py index 6a1cae41d..c6fc80c0b 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -2,7 +2,7 @@ per-test stdout/stderr capturing mechanism. """ -from __future__ import with_statement +from __future__ import absolute_import, division, print_function import contextlib import sys diff --git a/_pytest/compat.py b/_pytest/compat.py index 09df385d1..b688ae509 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -1,6 +1,7 @@ """ python version compatibility code """ +from __future__ import absolute_import, division, print_function import sys import inspect import types diff --git a/_pytest/config.py b/_pytest/config.py index 6a607c7e3..92f9005b4 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -1,4 +1,5 @@ """ command line options, ini-file and conftest.py processing. """ +from __future__ import absolute_import, division, print_function import argparse import shlex import traceback diff --git a/_pytest/debugging.py b/_pytest/debugging.py index c21e0977d..1e7dcad5d 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -1,5 +1,5 @@ """ interactive debugging with PDB, the Python Debugger. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function import pdb import sys diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index 6edc475f6..e75ff099e 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -5,7 +5,7 @@ that is planned to be removed in the next pytest release. Keeping it in a central location makes it easy to track what is deprecated and should be removed when the time comes. """ - +from __future__ import absolute_import, division, print_function MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \ 'pass a list of arguments instead.' diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 4ee21b12d..f9299be72 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -1,5 +1,5 @@ """ discover and run doctests in modules and test files.""" -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function import traceback diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index c4d21635f..a151d615d 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys from py._code.code import FormattedExcinfo diff --git a/_pytest/freeze_support.py b/_pytest/freeze_support.py index f78ccd298..6c2120559 100644 --- a/_pytest/freeze_support.py +++ b/_pytest/freeze_support.py @@ -2,6 +2,8 @@ Provides a function to report all internal modules for using freezing tools pytest """ +from __future__ import absolute_import, division, print_function + def pytest_namespace(): return {'freeze_includes': freeze_includes} @@ -42,4 +44,4 @@ def _iter_all_modules(package, prefix=''): for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'): yield prefix + m else: - yield prefix + name \ No newline at end of file + yield prefix + name diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index 6e66b11c4..abc792f7e 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -1,4 +1,6 @@ """ version info, help messages, tracing configuration. """ +from __future__ import absolute_import, division, print_function + import py import pytest import os, sys diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index e5f20b6f6..4bd334a16 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -4,9 +4,11 @@ Based on initial code from Ross Lawley. + +Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/ +src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd """ -# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/ -# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd +from __future__ import absolute_import, division, print_function import functools import py diff --git a/_pytest/main.py b/_pytest/main.py index f100b7974..3d7b456d2 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -1,4 +1,6 @@ """ core implementation of testing process: init, session, runtest loop. """ +from __future__ import absolute_import, division, print_function + import functools import os import sys diff --git a/_pytest/mark.py b/_pytest/mark.py index 582eb1277..643f43ce0 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -1,4 +1,6 @@ """ generic mechanism for marking and selecting python functions. """ +from __future__ import absolute_import, division, print_function + import inspect from collections import namedtuple from operator import attrgetter diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 30618cc57..9151db3ad 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -1,4 +1,5 @@ """ monkeypatching and mocking functionality. """ +from __future__ import absolute_import, division, print_function import os, sys import re diff --git a/_pytest/nose.py b/_pytest/nose.py index 038746868..828a919f9 100644 --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -1,4 +1,5 @@ """ run test suites written for nose. """ +from __future__ import absolute_import, division, print_function import sys diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py index 9f1cf9063..6f3ce8fed 100644 --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -1,4 +1,6 @@ """ submit failure or test session information to a pastebin service. """ +from __future__ import absolute_import, division, print_function + import pytest import sys import tempfile diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 97fc62312..48d9b0956 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -1,4 +1,6 @@ """ (disabled by default) support for testing pytest and pytest plugins. """ +from __future__ import absolute_import, division, print_function + import codecs import gc import os diff --git a/_pytest/python.py b/_pytest/python.py index 471a9563f..7d94ed61a 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1,4 +1,5 @@ """ Python test discovery, setup and run of test functions. """ +from __future__ import absolute_import, division, print_function import fnmatch import inspect diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 43f68ed12..91cc85fdf 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -1,4 +1,6 @@ """ recording warnings during test function execution. """ +from __future__ import absolute_import, division, print_function + import inspect import _pytest._code diff --git a/_pytest/resultlog.py b/_pytest/resultlog.py index fc0025983..fbf06d630 100644 --- a/_pytest/resultlog.py +++ b/_pytest/resultlog.py @@ -1,6 +1,7 @@ """ log machine-parseable test session result information in a plain text file. """ +from __future__ import absolute_import, division, print_function import py import os diff --git a/_pytest/runner.py b/_pytest/runner.py index f17155dae..4277f8ee3 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -1,4 +1,6 @@ """ basic collect and runtest protocol implementations """ +from __future__ import absolute_import, division, print_function + import bdb import sys from time import time diff --git a/_pytest/setuponly.py b/_pytest/setuponly.py index 1752c575f..15e195ad5 100644 --- a/_pytest/setuponly.py +++ b/_pytest/setuponly.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, division, print_function + import pytest import sys diff --git a/_pytest/setupplan.py b/_pytest/setupplan.py index f0853dee5..e11bd4069 100644 --- a/_pytest/setupplan.py +++ b/_pytest/setupplan.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, division, print_function + import pytest diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 86176acaf..228c52935 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -1,4 +1,6 @@ """ support for skip/xfail functions and markers. """ +from __future__ import absolute_import, division, print_function + import os import sys import traceback diff --git a/_pytest/terminal.py b/_pytest/terminal.py index b07945426..4607b752e 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -2,6 +2,8 @@ This is a good source for looking at the various reporting hooks. """ +from __future__ import absolute_import, division, print_function + from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED import pytest diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 67f999e5a..7c5fac17c 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -1,4 +1,6 @@ """ support for providing temporary directories to test functions. """ +from __future__ import absolute_import, division, print_function + import re import pytest diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 276b9ba16..5a8cb9d66 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -1,5 +1,5 @@ """ discovery and running of std-library "unittest" style tests. """ -from __future__ import absolute_import +from __future__ import absolute_import, division, print_function import sys import traceback diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 0d3fc1016..debda79ca 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function import os import sys diff --git a/testing/code/test_code.py b/testing/code/test_code.py index a1f31b4a9..479a2e7cc 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys import _pytest._code diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index e2354ff5d..06b61c831 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function import operator import _pytest diff --git a/testing/code/test_source.py b/testing/code/test_source.py index c161f75f8..bdbc00d19 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -1,6 +1,7 @@ # flake8: noqa # disable flake check on this file because some constructs are strange # or redundant on purpose and can't be disable on a line-by-line basis +from __future__ import absolute_import, division, print_function import sys import _pytest._code diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 6473989e6..ad2f2021d 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index ed6db4c78..6887c419c 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -1,4 +1,4 @@ -from __future__ import with_statement +from __future__ import absolute_import, division, print_function import py, pytest # test for _argcomplete but not specific for any application diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e5c18b43a..789352a7f 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function import sys import textwrap diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 047a2ac6e..f4fc0c464 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import glob import os import py_compile diff --git a/testing/test_cache.py b/testing/test_cache.py index c1b98489e..47072e07e 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys import _pytest diff --git a/testing/test_capture.py b/testing/test_capture.py index 28326fa73..6296abe78 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function # note: py.io capture tests where copied from # pylib 1.4.20.dev2 (rev 13d9af95547e) from __future__ import with_statement diff --git a/testing/test_collection.py b/testing/test_collection.py index 8575aa63a..c19fc0e72 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest, py from _pytest.main import Session, EXIT_NOTESTSCOLLECTED diff --git a/testing/test_compat.py b/testing/test_compat.py index 1736e8e2a..7b2251ef6 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys import pytest diff --git a/testing/test_config.py b/testing/test_config.py index 171e9486e..94ad2b130 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import py, pytest import _pytest._code diff --git a/testing/test_conftest.py b/testing/test_conftest.py index e9fd1927a..db67a0cc8 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function from textwrap import dedent import _pytest._code diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 429bb8de9..82597b477 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,4 +1,5 @@ # encoding: utf-8 +from __future__ import absolute_import, division, print_function import sys import _pytest._code from _pytest.compat import MODULE_NOT_FOUND_ERROR diff --git a/testing/test_entry_points.py b/testing/test_entry_points.py index 370b93129..6ca68b481 100644 --- a/testing/test_entry_points.py +++ b/testing/test_entry_points.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pkg_resources import pytest diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index fc3c8fdf6..6eecbfd37 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function from _pytest.main import EXIT_NOTESTSCOLLECTED import pytest diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index a0f51fc71..3d07e54e5 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- - +from __future__ import absolute_import, division, print_function from xml.dom import minidom import py import sys @@ -1028,4 +1028,4 @@ def test_url_property(testdir): test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0] - assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" \ No newline at end of file + assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" diff --git a/testing/test_mark.py b/testing/test_mark.py index 9474d1033..79e485438 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import os import py, pytest diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 04a53d93a..1efcf7f95 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import os import sys import textwrap diff --git a/testing/test_nose.py b/testing/test_nose.py index 11fb83176..798badc1c 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest def setup_module(mod): diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 03d71de43..f990e8b04 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,4 +1,4 @@ -from __future__ import with_statement +from __future__ import absolute_import, division, print_function import sys import os import py, pytest diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index f0d2a9ba2..3fe66e972 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -1,4 +1,5 @@ # encoding: utf-8 +from __future__ import absolute_import, division, print_function import sys import pytest diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 161b4f5f7..ec5862082 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys import platform diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 3214d868b..ec8389746 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,4 +1,5 @@ # encoding: UTF-8 +from __future__ import absolute_import, division, print_function import pytest import py import os diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 9c9b56371..932427ad3 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest import os from _pytest.pytester import HookRecorder diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 1269af431..890d2ce0a 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import warnings import re import py diff --git a/testing/test_resultlog.py b/testing/test_resultlog.py index c2f4a801c..cb083225c 100644 --- a/testing/test_resultlog.py +++ b/testing/test_resultlog.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import os import _pytest._code diff --git a/testing/test_runner.py b/testing/test_runner.py index 265e99d54..51d430fc8 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement +from __future__ import absolute_import, division, print_function import _pytest._code import os diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index 9bf15bd66..92ba97202 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -1,6 +1,8 @@ -# -# test correct setup/teardowns at -# module, class, and instance level +""" + test correct setup/teardowns at + module, class, and instance level +""" +from __future__ import absolute_import, division, print_function import pytest diff --git a/testing/test_session.py b/testing/test_session.py index 66a0f5978..d08f7b3e2 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest from _pytest.main import EXIT_NOTESTSCOLLECTED diff --git a/testing/test_skipping.py b/testing/test_skipping.py index f621a010f..5f25c3e6e 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import pytest import sys diff --git a/testing/test_terminal.py b/testing/test_terminal.py index d72f6a12b..9d7a2d7d2 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,6 +1,7 @@ """ terminal reporting of the full testing process. """ +from __future__ import absolute_import, division, print_function import collections import sys diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 32ac76331..ccd70ed8b 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function import sys import py import pytest diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 832ea08cc..af9851997 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function from _pytest.main import EXIT_NOTESTSCOLLECTED import pytest import gc From e5021dc9dc59c73e03e3beadd14de771a4c6b1bd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 22:27:28 -0300 Subject: [PATCH 080/153] Replace py.builtin.print_() calls by builtin print() function --- _pytest/pytester.py | 16 +++++++--------- _pytest/resultlog.py | 4 ++-- testing/python/collect.py | 10 ++++++---- testing/test_capture.py | 6 +++--- testing/test_mark.py | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 48d9b0956..ee0e5bbe7 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -14,8 +14,6 @@ from fnmatch import fnmatch from weakref import WeakKeyDictionary -from py.builtin import print_ - from _pytest.capture import MultiCapture, SysCapture from _pytest._code import Source import py @@ -231,15 +229,15 @@ class HookRecorder(object): name, check = entries.pop(0) for ind, call in enumerate(self.calls[i:]): if call._name == name: - print_("NAMEMATCH", name, call) + print("NAMEMATCH", name, call) if eval(check, backlocals, call.__dict__): - print_("CHECKERMATCH", repr(check), "->", call) + print("CHECKERMATCH", repr(check), "->", call) else: - print_("NOCHECKERMATCH", repr(check), "-", call) + print("NOCHECKERMATCH", repr(check), "-", call) continue i += ind + 1 break - print_("NONAMEMATCH", name, "with", call) + print("NONAMEMATCH", name, "with", call) else: pytest.fail("could not find %r check %r" % (name, check)) @@ -926,8 +924,8 @@ class Testdir(object): cmdargs = [str(x) for x in cmdargs] p1 = self.tmpdir.join("stdout") p2 = self.tmpdir.join("stderr") - print_("running:", ' '.join(cmdargs)) - print_(" in:", str(py.path.local())) + print("running:", ' '.join(cmdargs)) + print(" in:", str(py.path.local())) f1 = codecs.open(str(p1), "w", encoding="utf8") f2 = codecs.open(str(p2), "w", encoding="utf8") try: @@ -953,7 +951,7 @@ class Testdir(object): def _dump_lines(self, lines, fp): try: for line in lines: - py.builtin.print_(line, file=fp) + print(line, file=fp) except UnicodeEncodeError: print("couldn't print to %s because of encoding" % (fp,)) diff --git a/_pytest/resultlog.py b/_pytest/resultlog.py index fbf06d630..3e4b00cf9 100644 --- a/_pytest/resultlog.py +++ b/_pytest/resultlog.py @@ -62,9 +62,9 @@ class ResultLog(object): self.logfile = logfile # preferably line buffered def write_log_entry(self, testpath, lettercode, longrepr): - py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile) + print("%s %s" % (lettercode, testpath), file=self.logfile) for line in longrepr.splitlines(): - py.builtin.print_(" %s" % line, file=self.logfile) + print(" %s" % line, file=self.logfile) def log_outcome(self, report, lettercode, longrepr): testpath = getattr(report, 'nodeid', None) diff --git a/testing/python/collect.py b/testing/python/collect.py index e4069983a..e67b6bc84 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -274,6 +274,7 @@ class TestGenerator(object): def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): o = testdir.makepyfile(""" + from __future__ import print_function def test_generative_order_of_execution(): import py, pytest test_list = [] @@ -283,8 +284,8 @@ class TestGenerator(object): test_list.append(item) def assert_order_of_execution(): - py.builtin.print_('expected order', expected_list) - py.builtin.print_('but got ', test_list) + print('expected order', expected_list) + print('but got ', test_list) assert test_list == expected_list for i in expected_list: @@ -298,6 +299,7 @@ class TestGenerator(object): def test_order_of_execution_generator_different_codeline(self, testdir): o = testdir.makepyfile(""" + from __future__ import print_function def test_generative_tests_different_codeline(): import py, pytest test_list = [] @@ -313,8 +315,8 @@ class TestGenerator(object): test_list.append(0) def assert_order_of_execution(): - py.builtin.print_('expected order', expected_list) - py.builtin.print_('but got ', test_list) + print('expected order', expected_list) + print('but got ', test_list) assert test_list == expected_list yield list_append_0 diff --git a/testing/test_capture.py b/testing/test_capture.py index 6296abe78..aa2a3bae5 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -14,7 +14,7 @@ import contextlib from _pytest import capture from _pytest.capture import CaptureManager from _pytest.main import EXIT_NOTESTSCOLLECTED -from py.builtin import print_ + needsosdup = pytest.mark.xfail("not hasattr(os, 'dup')") @@ -712,7 +712,7 @@ def test_dupfile(tmpfile): assert nf != tmpfile assert nf.fileno() != tmpfile.fileno() assert nf not in flist - print_(i, end="", file=nf) + print(i, end="", file=nf) flist.append(nf) for i in range(5): f = flist[i] @@ -786,7 +786,7 @@ class TestFDCapture(object): def test_stderr(self): cap = capture.FDCapture(2) cap.start() - print_("hello", file=sys.stderr) + print("hello", file=sys.stderr) s = cap.snap() cap.done() assert s == "hello\n" diff --git a/testing/test_mark.py b/testing/test_mark.py index 79e485438..3bec1d0d0 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -674,7 +674,7 @@ class TestKeywordSelection(object): item.extra_keyword_matches.add("xxx") """) reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword) - py.builtin.print_("keyword", repr(keyword)) + print("keyword", repr(keyword)) passed, skipped, failed = reprec.listoutcomes() assert len(passed) == 1 assert passed[0].nodeid.endswith("test_2") From 4d947077bbed4862d4e0a055ce967ba490be96af Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 16 Mar 2017 23:07:03 -0300 Subject: [PATCH 081/153] Fix test in py26 that expected a floor division error message --- testing/code/test_excinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 06b61c831..b7dafdb46 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -370,7 +370,7 @@ def test_codepath_Queue_example(): def test_match_succeeds(): with pytest.raises(ZeroDivisionError) as excinfo: - 0 / 0 + 0 // 0 excinfo.match(r'.*zero.*') def test_match_raises_error(testdir): From e368fb4b29912fe618d523b2ca9197016738d7d6 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 7 Sep 2016 11:00:27 +0200 Subject: [PATCH 082/153] implement pytest.param this allows a clear addition of parameterization parameters that carry along marks instead of nesting multiple mark objects and destroying the possibility of creating function valued parameters, it just folders everything together into one object carrfying parameters, and the marks. --- _pytest/mark.py | 77 +++++++++++++++++++++++------ _pytest/python.py | 71 +++++++++++++-------------- doc/en/parametrize.rst | 51 ++++++++++++++------ testing/python/metafunc.py | 99 ++++++++++++++++++++++++++------------ testing/test_mark.py | 28 ++++++++--- 5 files changed, 224 insertions(+), 102 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index 643f43ce0..a06b02b14 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -6,17 +6,72 @@ from collections import namedtuple from operator import attrgetter from .compat import imap + def alias(name): return property(attrgetter(name), doc='alias for ' + name) +class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): + @classmethod + def param(cls, *values, **kw): + marks = kw.pop('marks', ()) + if isinstance(marks, MarkDecorator): + marks = marks, + else: + assert isinstance(marks, (tuple, list, set)) + + def param_extract_id(id=None): + return id + + id = param_extract_id(**kw) + return cls(values, marks, id) + + @classmethod + def extract_from(cls, parameterset, legacy_force_tuple=False): + """ + :param parameterset: + a legacy style parameterset that may or may not be a tuple, + and may or may not be wrapped into a mess of mark objects + + :param legacy_force_tuple: + enforce tuple wrapping so single argument tuple values + don't get decomposed and break tests + + """ + + if isinstance(parameterset, cls): + return parameterset + if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple: + return cls.param(parameterset) + + newmarks = [] + argval = parameterset + while isinstance(argval, MarkDecorator): + newmarks.append(MarkDecorator(Mark( + argval.markname, argval.args[:-1], argval.kwargs))) + argval = argval.args[-1] + assert not isinstance(argval, ParameterSet) + if legacy_force_tuple: + argval = argval, + + return cls(argval, marks=newmarks, id=None) + + @property + def deprecated_arg_dict(self): + return dict((mark.name, mark) for mark in self.marks) + + class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" + def pytest_namespace(): - return {'mark': MarkGenerator()} + return { + 'mark': MarkGenerator(), + 'param': ParameterSet.param, + } def pytest_addoption(parser): @@ -212,6 +267,7 @@ def istestfunc(func): return hasattr(func, "__call__") and \ getattr(func, "__name__", "") != "" + class MarkDecorator(object): """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be @@ -257,8 +313,11 @@ class MarkDecorator(object): def markname(self): return self.name # for backward-compat (2.4.1 had this attr) + def __eq__(self, other): + return self.mark == other.mark + def __repr__(self): - return "" % self.mark + return "" % (self.mark,) def __call__(self, *args, **kwargs): """ if passed a single callable argument: decorate it with mark info. @@ -291,19 +350,7 @@ class MarkDecorator(object): return self.__class__(self.mark.combined_with(mark)) -def extract_argvalue(maybe_marked_args): - # TODO: incorrect mark data, the old code wanst able to collect lists - # individual parametrized argument sets can be wrapped in a series - # of markers in which case we unwrap the values and apply the mark - # at Function init - newmarks = {} - argval = maybe_marked_args - while isinstance(argval, MarkDecorator): - newmark = MarkDecorator(Mark( - argval.markname, argval.args[:-1], argval.kwargs)) - newmarks[newmark.name] = newmark - argval = argval.args[-1] - return argval, newmarks + class Mark(namedtuple('Mark', 'name, args, kwargs')): diff --git a/_pytest/python.py b/_pytest/python.py index 7d94ed61a..81eed00f0 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -788,36 +788,35 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import extract_argvalue + from _pytest.mark import ParameterSet from py.io import saferepr - unwrapped_argvalues = [] - newkeywords = [] - for maybe_marked_args in argvalues: - argval, newmarks = extract_argvalue(maybe_marked_args) - unwrapped_argvalues.append(argval) - newkeywords.append(newmarks) - argvalues = unwrapped_argvalues - if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] - if len(argnames) == 1: - argvalues = [(val,) for val in argvalues] - if not argvalues: - argvalues = [(NOTSET,) * len(argnames)] - # we passed a empty list to parameterize, skip that test - # + force_tuple = len(argnames) == 1 + else: + force_tuple = False + parameters = [ + ParameterSet.extract_from(x, legacy_force_tuple=force_tuple) + for x in argvalues] + del argvalues + + + if not parameters: fs, lineno = getfslineno(self.function) - newmark = pytest.mark.skip( - reason="got empty parameter set %r, function %s at %s:%d" % ( - argnames, self.function.__name__, fs, lineno)) - newkeywords = [{newmark.markname: newmark}] + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, self.function.__name__, fs, lineno) + mark = pytest.mark.skip(reason=reason) + parameters.append(ParameterSet( + values=(NOTSET,) * len(argnames), + marks=[mark], + id=None, + )) if scope is None: scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) - scopenum = scope2index( - scope, descr='call to {0}'.format(self.parametrize)) + scopenum = scope2index(scope, descr='call to {0}'.format(self.parametrize)) valtypes = {} for arg in argnames: if arg not in self.fixturenames: @@ -845,22 +844,22 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): idfn = ids ids = None if ids: - if len(ids) != len(argvalues): - raise ValueError('%d tests specified with %d ids' %( - len(argvalues), len(ids))) + if len(ids) != len(parameters): + raise ValueError('%d tests specified with %d ids' % ( + len(parameters), len(ids))) for id_value in ids: if id_value is not None and not isinstance(id_value, py.builtin._basestring): msg = 'ids must be list of strings, found: %s (type: %s)' raise ValueError(msg % (saferepr(id_value), type(id_value).__name__)) - ids = idmaker(argnames, argvalues, idfn, ids, self.config) + ids = idmaker(argnames, parameters, idfn, ids, self.config) newcalls = [] for callspec in self._calls or [CallSpec2(self)]: - elements = zip(ids, argvalues, newkeywords, count()) - for a_id, valset, keywords, param_index in elements: - assert len(valset) == len(argnames) + elements = zip(ids, parameters, count()) + for a_id, param, param_index in elements: + assert len(param.values) == len(argnames) newcallspec = callspec.copy(self) - newcallspec.setmulti(valtypes, argnames, valset, a_id, - keywords, scopenum, param_index) + newcallspec.setmulti(valtypes, argnames, param.values, a_id, + param.deprecated_arg_dict, scopenum, param_index) newcalls.append(newcallspec) self._calls = newcalls @@ -959,17 +958,19 @@ def _idval(val, argname, idx, idfn, config=None): return val.__name__ return str(argname)+str(idx) -def _idvalset(idx, valset, argnames, idfn, ids, config=None): +def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): + if parameterset.id is not None: + return parameterset.id if ids is None or (idx >= len(ids) or ids[idx] is None): this_id = [_idval(val, argname, idx, idfn, config) - for val, argname in zip(valset, argnames)] + for val, argname in zip(parameterset.values, argnames)] return "-".join(this_id) else: return _escape_strings(ids[idx]) -def idmaker(argnames, argvalues, idfn=None, ids=None, config=None): - ids = [_idvalset(valindex, valset, argnames, idfn, ids, config) - for valindex, valset in enumerate(argvalues)] +def idmaker(argnames, parametersets, idfn=None, ids=None, config=None): + ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config) + for valindex, parameterset in enumerate(parametersets)] if len(set(ids)) != len(ids): # The ids are not unique duplicates = [testid for testid in ids if ids.count(testid) > 1] diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index b93099e7d..10de065c7 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -55,17 +55,17 @@ them in turn:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.5.2, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items - + test_expectation.py ..F - + ======= FAILURES ======== _______ test_eval[6*9-42] ________ - + test_input = '6*9', expected = 42 - + @pytest.mark.parametrize("test_input,expected", [ ("3+5", 8), ("2+4", 6), @@ -73,9 +73,9 @@ them in turn:: ]) def test_eval(test_input, expected): > assert eval(test_input) == expected - E AssertionError: assert 54 == 42 + E assert 54 == 42 E + where 54 = eval('6*9') - + test_expectation.py:8: AssertionError ======= 1 failed, 2 passed in 0.12 seconds ======== @@ -94,21 +94,42 @@ for example with the builtin ``mark.xfail``:: @pytest.mark.parametrize("test_input,expected", [ ("3+5", 8), ("2+4", 6), - pytest.mark.xfail(("6*9", 42)), + pytest.param("6*9", 42, + marks=pytest.mark.xfail), ]) def test_eval(test_input, expected): assert eval(test_input) == expected +.. note:: + + prior to version 3.1 the supported mechanism for marking values + used the syntax:: + + import pytest + @pytest.mark.parametrize("test_input,expected", [ + ("3+5", 8), + ("2+4", 6), + pytest.mark.xfail(("6*9", 42),), + ]) + def test_eval(test_input, expected): + assert eval(test_input) == expected + + + This was an initial hack to support the feature but soon was demonstrated to be incomplete, + broken for passing functions or applying multiple marks with the same name but different parameters. + The old syntax will be removed in pytest-4.0. + + Let's run this:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.5.2, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items - + test_expectation.py ..x - + ======= 2 passed, 1 xfailed in 0.12 seconds ======== The one parameter set which caused a failure previously now @@ -181,15 +202,15 @@ Let's also run with a stringinput that will lead to a failing test:: F ======= FAILURES ======== _______ test_valid_string[!] ________ - + stringinput = '!' - + def test_valid_string(stringinput): > assert stringinput.isalpha() - E AssertionError: assert False + E assert False E + where False = () E + where = '!'.isalpha - + test_strings.py:3: AssertionError 1 failed in 0.12 seconds diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 949b265a3..c347dc9e2 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -207,37 +207,40 @@ class TestMetafunc(object): @pytest.mark.issue250 def test_idmaker_autoname(self): from _pytest.python import idmaker - result = idmaker(("a", "b"), [("string", 1.0), - ("st-ring", 2.0)]) + result = idmaker(("a", "b"), [pytest.param("string", 1.0), + pytest.param("st-ring", 2.0)]) assert result == ["string-1.0", "st-ring-2.0"] - result = idmaker(("a", "b"), [(object(), 1.0), - (object(), object())]) + result = idmaker(("a", "b"), [pytest.param(object(), 1.0), + pytest.param(object(), object())]) assert result == ["a0-1.0", "a1-b1"] # unicode mixing, issue250 - result = idmaker((py.builtin._totext("a"), "b"), [({}, b'\xc3\xb4')]) + result = idmaker( + (py.builtin._totext("a"), "b"), + [pytest.param({}, b'\xc3\xb4')]) assert result == ['a0-\\xc3\\xb4'] def test_idmaker_with_bytes_regex(self): from _pytest.python import idmaker - result = idmaker(("a"), [(re.compile(b'foo'), 1.0)]) + result = idmaker(("a"), [pytest.param(re.compile(b'foo'), 1.0)]) assert result == ["foo"] def test_idmaker_native_strings(self): from _pytest.python import idmaker totext = py.builtin._totext - result = idmaker(("a", "b"), [(1.0, -1.1), - (2, -202), - ("three", "three hundred"), - (True, False), - (None, None), - (re.compile('foo'), re.compile('bar')), - (str, int), - (list("six"), [66, 66]), - (set([7]), set("seven")), - (tuple("eight"), (8, -8, 8)), - (b'\xc3\xb4', b"name"), - (b'\xc3\xb4', totext("other")), + result = idmaker(("a", "b"), [ + pytest.param(1.0, -1.1), + pytest.param(2, -202), + pytest.param("three", "three hundred"), + pytest.param(True, False), + pytest.param(None, None), + pytest.param(re.compile('foo'), re.compile('bar')), + pytest.param(str, int), + pytest.param(list("six"), [66, 66]), + pytest.param(set([7]), set("seven")), + pytest.param(tuple("eight"), (8, -8, 8)), + pytest.param(b'\xc3\xb4', b"name"), + pytest.param(b'\xc3\xb4', totext("other")), ]) assert result == ["1.0--1.1", "2--202", @@ -257,7 +260,7 @@ class TestMetafunc(object): from _pytest.python import idmaker enum = pytest.importorskip("enum") e = enum.Enum("Foo", "one, two") - result = idmaker(("a", "b"), [(e.one, e.two)]) + result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) assert result == ["Foo.one-Foo.two"] @pytest.mark.issue351 @@ -268,9 +271,10 @@ class TestMetafunc(object): if isinstance(val, Exception): return repr(val) - result = idmaker(("a", "b"), [(10.0, IndexError()), - (20, KeyError()), - ("three", [1, 2, 3]), + result = idmaker(("a", "b"), [ + pytest.param(10.0, IndexError()), + pytest.param(20, KeyError()), + pytest.param("three", [1, 2, 3]), ], idfn=ids) assert result == ["10.0-IndexError()", "20-KeyError()", @@ -284,9 +288,9 @@ class TestMetafunc(object): def ids(val): return 'a' - result = idmaker(("a", "b"), [(10.0, IndexError()), - (20, KeyError()), - ("three", [1, 2, 3]), + result = idmaker(("a", "b"), [pytest.param(10.0, IndexError()), + pytest.param(20, KeyError()), + pytest.param("three", [1, 2, 3]), ], idfn=ids) assert result == ["a-a0", "a-a1", @@ -306,9 +310,10 @@ class TestMetafunc(object): rec = WarningsRecorder() with rec: - idmaker(("a", "b"), [(10.0, IndexError()), - (20, KeyError()), - ("three", [1, 2, 3]), + idmaker(("a", "b"), [ + pytest.param(10.0, IndexError()), + pytest.param(20, KeyError()), + pytest.param("three", [1, 2, 3]), ], idfn=ids) assert [str(i.message) for i in rec.list] == [ @@ -351,14 +356,21 @@ class TestMetafunc(object): def test_idmaker_with_ids(self): from _pytest.python import idmaker - result = idmaker(("a", "b"), [(1, 2), - (3, 4)], + result = idmaker(("a", "b"), [pytest.param(1, 2), + pytest.param(3, 4)], ids=["a", None]) assert result == ["a", "3-4"] + def test_idmaker_with_paramset_id(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [pytest.param(1, 2, id="me"), + pytest.param(3, 4, id="you")], + ids=["a", None]) + assert result == ["me", "you"] + def test_idmaker_with_ids_unique_names(self): from _pytest.python import idmaker - result = idmaker(("a"), [1,2,3,4,5], + result = idmaker(("a"), map(pytest.param, [1,2,3,4,5]), ids=["a", "a", "b", "c", "b"]) assert result == ["a0", "a1", "b0", "c", "b1"] @@ -1438,6 +1450,31 @@ class TestMarkersWithParametrization(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + + @pytest.mark.parametrize('strict', [True, False]) + def test_parametrize_marked_value(self, testdir, strict): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + pytest.param( + 2,3, + marks=pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict}), + ), + pytest.param( + 2,3, + marks=[pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})], + ), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """.format(strict=strict) + testdir.makepyfile(s) + reprec = testdir.inline_run() + passed, failed = (0, 2) if strict else (2, 0) + reprec.assertoutcome(passed=passed, failed=failed) + + def test_pytest_make_parametrize_id(self, testdir): testdir.makeconftest(""" def pytest_make_parametrize_id(config, val): diff --git a/testing/test_mark.py b/testing/test_mark.py index 3bec1d0d0..9a8c35bf3 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1,8 +1,9 @@ from __future__ import absolute_import, division, print_function import os +import sys -import py, pytest -from _pytest.mark import MarkGenerator as Mark +import pytest +from _pytest.mark import MarkGenerator as Mark, ParameterSet class TestMark(object): def test_markinfo_repr(self): @@ -10,9 +11,11 @@ class TestMark(object): m = MarkInfo(Mark("hello", (1,2), {})) repr(m) - def test_pytest_exists_in_namespace_all(self): - assert 'mark' in py.test.__all__ - assert 'mark' in pytest.__all__ + @pytest.mark.parametrize('attr', ['mark', 'param']) + @pytest.mark.parametrize('modulename', ['py.test', 'pytest']) + def test_pytest_exists_in_namespace_all(self, attr, modulename): + module = sys.modules[modulename] + assert attr in module.__all__ def test_pytest_mark_notcallable(self): mark = Mark() @@ -415,7 +418,7 @@ class TestFunctional(object): """) items, rec = testdir.inline_genitems(p) for item in items: - print (item, item.keywords) + print(item, item.keywords) assert 'a' in item.keywords def test_mark_decorator_subclass_does_not_propagate_to_base(self, testdir): @@ -739,3 +742,16 @@ class TestKeywordSelection(object): assert_test_is_not_selected("__") assert_test_is_not_selected("()") + + +@pytest.mark.parametrize('argval, expected', [ + (pytest.mark.skip()((1, 2)), + ParameterSet(values=(1, 2), marks=[pytest.mark.skip], id=None)), + (pytest.mark.xfail(pytest.mark.skip()((1, 2))), + ParameterSet(values=(1, 2), + marks=[pytest.mark.xfail, pytest.mark.skip], id=None)), + +]) +def test_parameterset_extractfrom(argval, expected): + extracted = ParameterSet.extract_from(argval) + assert extracted == expected From e8a1b36c82aa987364452b1a40bcc1b82000fda0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 20 Mar 2017 10:21:47 +0100 Subject: [PATCH 083/153] add changelog --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e91059b54..37570ed43 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,9 @@ New Features * ``pytest.raises`` now asserts that the error message matches a text or regex with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR. +* ``pytest.param`` can be used to declare test parameter sets with marks and test ids. + Thanks `@RonnyPfannschmidt`_ for the PR. + Changes ------- From 3373e02eaeaec826d64de6dc6dff36d6072fe767 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 20:06:01 -0300 Subject: [PATCH 084/153] Add __future__ imports to warnings module --- _pytest/warnings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 9b18185d2..7d29460b3 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -1,7 +1,9 @@ +from __future__ import absolute_import, division, print_function + +import warnings from contextlib import contextmanager import pytest -import warnings def _setoption(wmod, arg): From d027f760c0aad45d038617971846c20f71b9b590 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 20:40:53 -0300 Subject: [PATCH 085/153] Avoid displaying the same warning multiple times for an item --- _pytest/warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 7d29460b3..bfa2b0087 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -51,7 +51,7 @@ def catch_warnings_for_item(item): args = item.config.getoption('pythonwarnings') or [] inifilters = item.config.getini("filterwarnings") with warnings.catch_warnings(record=True) as log: - warnings.simplefilter('always') + warnings.simplefilter('once') for arg in args: warnings._setoption(arg) From fa56114115d8948c5960895034f6481700ffe6f2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 22:01:22 -0300 Subject: [PATCH 086/153] Clean up warnings generated by pytest's own suite --- _pytest/python.py | 2 +- testing/acceptance_test.py | 12 ++++++++--- testing/python/fixture.py | 41 ++++++++++++++++++++++++-------------- testing/python/metafunc.py | 3 ++- testing/test_config.py | 2 +- testing/test_parseopt.py | 15 +++++++------- tox.ini | 6 +++++- 7 files changed, 52 insertions(+), 29 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 81eed00f0..e763aa888 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -936,7 +936,7 @@ def _idval(val, argname, idx, idfn, config=None): import warnings msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx) msg += '\nUpdate your code as this will raise an error in pytest-4.0.' - warnings.warn(msg) + warnings.warn(msg, DeprecationWarning) if s: return _escape_strings(s) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index debda79ca..f0047574e 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -339,10 +339,16 @@ class TestGeneralUsage(object): "*ERROR*test_b.py::b*", ]) + @pytest.mark.usefixtures('recwarn') def test_namespace_import_doesnt_confuse_import_hook(self, testdir): - # Ref #383. Python 3.3's namespace package messed with our import hooks - # Importing a module that didn't exist, even if the ImportError was - # gracefully handled, would make our test crash. + """ + Ref #383. Python 3.3's namespace package messed with our import hooks + Importing a module that didn't exist, even if the ImportError was + gracefully handled, would make our test crash. + + Use recwarn here to silence this warning in Python 2.6 and 2.7: + ImportWarning: Not importing directory '...\not_a_package': missing __init__.py + """ testdir.mkdir('not_a_package') p = testdir.makepyfile(""" try: diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 269d2c68a..4c9ad7a91 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -545,22 +545,33 @@ class TestRequestBasic(object): return l.pop() def test_func(something): pass """) + import contextlib + if getfixmethod == 'getfuncargvalue': + warning_expectation = pytest.warns(DeprecationWarning) + else: + # see #1830 for a cleaner way to accomplish this + @contextlib.contextmanager + def expecting_no_warning(): yield + + warning_expectation = expecting_no_warning() + req = item._request - fixture_fetcher = getattr(req, getfixmethod) - pytest.raises(FixtureLookupError, fixture_fetcher, "notexists") - val = fixture_fetcher("something") - assert val == 1 - val = fixture_fetcher("something") - assert val == 1 - val2 = fixture_fetcher("other") - assert val2 == 2 - val2 = fixture_fetcher("other") # see about caching - assert val2 == 2 - pytest._fillfuncargs(item) - assert item.funcargs["something"] == 1 - assert len(get_public_names(item.funcargs)) == 2 - assert "request" in item.funcargs - #assert item.funcargs == {'something': 1, "other": 2} + with warning_expectation: + fixture_fetcher = getattr(req, getfixmethod) + with pytest.raises(FixtureLookupError): + fixture_fetcher("notexists") + val = fixture_fetcher("something") + assert val == 1 + val = fixture_fetcher("something") + assert val == 1 + val2 = fixture_fetcher("other") + assert val2 == 2 + val2 = fixture_fetcher("other") # see about caching + assert val2 == 2 + pytest._fillfuncargs(item) + assert item.funcargs["something"] == 1 + assert len(get_public_names(item.funcargs)) == 2 + assert "request" in item.funcargs def test_request_addfinalizer(self, testdir): item = testdir.getitem(""" diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index c347dc9e2..380dbf0e6 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -347,7 +347,8 @@ class TestMetafunc(object): def test_foo(arg): pass """) - result = testdir.runpytest("--collect-only") + with pytest.warns(DeprecationWarning): + result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines([ "", " ", diff --git a/testing/test_config.py b/testing/test_config.py index fed781ce0..0d8e6abfc 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -141,7 +141,7 @@ class TestConfigAPI(object): from __future__ import unicode_literals def pytest_addoption(parser): - parser.addoption('--hello', type='string') + parser.addoption('--hello', type=str) """) config = testdir.parseconfig('--hello=this') assert config.getoption('hello') == 'this' diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index f990e8b04..38542783a 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -34,15 +34,16 @@ class TestParser(object): ) def test_argument_type(self): - argument = parseopt.Argument('-t', dest='abc', type='int') + argument = parseopt.Argument('-t', dest='abc', type=int) assert argument.type is int - argument = parseopt.Argument('-t', dest='abc', type='string') + argument = parseopt.Argument('-t', dest='abc', type=str) assert argument.type is str argument = parseopt.Argument('-t', dest='abc', type=float) assert argument.type is float - with pytest.raises(KeyError): - argument = parseopt.Argument('-t', dest='abc', type='choice') - argument = parseopt.Argument('-t', dest='abc', type='choice', + with pytest.warns(DeprecationWarning): + with pytest.raises(KeyError): + argument = parseopt.Argument('-t', dest='abc', type='choice') + argument = parseopt.Argument('-t', dest='abc', type=str, choices=['red', 'blue']) assert argument.type is str @@ -176,8 +177,8 @@ class TestParser(object): elif option.type is str: option.default = "world" parser = parseopt.Parser(processopt=defaultget) - parser.addoption("--this", dest="this", type="int", action="store") - parser.addoption("--hello", dest="hello", type="string", action="store") + parser.addoption("--this", dest="this", type=int, action="store") + parser.addoption("--hello", dest="hello", type=str, action="store") parser.addoption("--no", dest="no", action="store_true") option = parser.parse([]) assert option.hello == "world" diff --git a/tox.ini b/tox.ini index 1b9fb9f5a..f8ea5fabd 100644 --- a/tox.ini +++ b/tox.ini @@ -176,7 +176,11 @@ python_files=test_*.py *_test.py testing/*/*.py python_classes=Test Acceptance python_functions=test norecursedirs = .tox ja .hg cx_freeze_source - +filterwarnings= error + # produced by path.local + ignore:bad escape.*:DeprecationWarning:re + # produced by path.readlines + ignore:.*U.*mode is deprecated:DeprecationWarning [flake8] ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402 From eabe3eed6b853b68b54cffe295adaf73be468eef Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 23:35:01 -0300 Subject: [PATCH 087/153] Add docs for the warnings functionality --- doc/en/contents.rst | 2 +- doc/en/customize.rst | 20 ++++ doc/en/recwarn.rst | 143 +--------------------------- doc/en/warnings.rst | 218 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 142 deletions(-) create mode 100644 doc/en/warnings.rst diff --git a/doc/en/contents.rst b/doc/en/contents.rst index d7f900810..f6a561839 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -18,7 +18,7 @@ Full pytest documentation monkeypatch tmpdir capture - recwarn + warnings doctest mark skipping diff --git a/doc/en/customize.rst b/doc/en/customize.rst index c6d3eb473..4421889db 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -240,3 +240,23 @@ Builtin configuration file options By default, pytest will stop searching for ``conftest.py`` files upwards from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any, or up to the file-system root. + + +.. confval:: filterwarnings + + .. versionadded:: 3.1 + + Sets a list of filters and actions that should be taken for matched + warnings. By default all warnings emitted during the test session + will be displayed in a summary at the end of the test session. + + .. code-block:: ini + + # content of pytest.ini + [pytest] + filterwarnings = + error + ignore::DeprecationWarning + + This tells pytest to ignore deprecation warnings and turn all other warnings + into errors. For more information please refer to :ref:`warnings`. diff --git a/doc/en/recwarn.rst b/doc/en/recwarn.rst index 823ba945b..513af0d45 100644 --- a/doc/en/recwarn.rst +++ b/doc/en/recwarn.rst @@ -1,142 +1,3 @@ -.. _`asserting warnings`: +:orphan: -.. _assertwarnings: - -Asserting Warnings -===================================================== - -.. _`asserting warnings with the warns function`: - -.. _warns: - -Asserting warnings with the warns function ------------------------------------------------ - -.. versionadded:: 2.8 - -You can check that code raises a particular warning using ``pytest.warns``, -which works in a similar manner to :ref:`raises `:: - - import warnings - import pytest - - def test_warning(): - with pytest.warns(UserWarning): - warnings.warn("my warning", UserWarning) - -The test will fail if the warning in question is not raised. - -You can also call ``pytest.warns`` on a function or code string:: - - pytest.warns(expected_warning, func, *args, **kwargs) - pytest.warns(expected_warning, "func(*args, **kwargs)") - -The function also returns a list of all raised warnings (as -``warnings.WarningMessage`` objects), which you can query for -additional information:: - - with pytest.warns(RuntimeWarning) as record: - warnings.warn("another warning", RuntimeWarning) - - # check that only one warning was raised - assert len(record) == 1 - # check that the message matches - assert record[0].message.args[0] == "another warning" - -Alternatively, you can examine raised warnings in detail using the -:ref:`recwarn ` fixture (see below). - -.. note:: - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated - differently; see :ref:`ensuring_function_triggers`. - -.. _`recording warnings`: - -.. _recwarn: - -Recording warnings ------------------------- - -You can record raised warnings either using ``pytest.warns`` or with -the ``recwarn`` fixture. - -To record with ``pytest.warns`` without asserting anything about the warnings, -pass ``None`` as the expected warning type:: - - with pytest.warns(None) as record: - warnings.warn("user", UserWarning) - warnings.warn("runtime", RuntimeWarning) - - assert len(record) == 2 - assert str(record[0].message) == "user" - assert str(record[1].message) == "runtime" - -The ``recwarn`` fixture will record warnings for the whole function:: - - import warnings - - def test_hello(recwarn): - warnings.warn("hello", UserWarning) - assert len(recwarn) == 1 - w = recwarn.pop(UserWarning) - assert issubclass(w.category, UserWarning) - assert str(w.message) == "hello" - assert w.filename - assert w.lineno - -Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded -warnings: a WarningsRecorder instance. To view the recorded warnings, you can -iterate over this instance, call ``len`` on it to get the number of recorded -warnings, or index into it to get a particular recorded warning. It also -provides these methods: - -.. autoclass:: _pytest.recwarn.WarningsRecorder() - :members: - -Each recorded warning has the attributes ``message``, ``category``, -``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the -class of the warning. The ``message`` is the warning itself; calling -``str(message)`` will return the actual message of the warning. - -.. note:: - :class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1 - -.. note:: - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated - differently; see :ref:`ensuring_function_triggers`. - -.. _`ensuring a function triggers a deprecation warning`: - -.. _ensuring_function_triggers: - -Ensuring a function triggers a deprecation warning -------------------------------------------------------- - -You can also call a global helper for checking -that a certain function call triggers a ``DeprecationWarning`` or -``PendingDeprecationWarning``:: - - import pytest - - def test_global(): - pytest.deprecated_call(myfunction, 17) - -By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be -caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide -them. If you wish to record them in your own code, use the -command ``warnings.simplefilter('always')``:: - - import warnings - import pytest - - def test_deprecation(recwarn): - warnings.simplefilter('always') - warnings.warn("deprecated", DeprecationWarning) - assert len(recwarn) == 1 - assert recwarn.pop(DeprecationWarning) - -You can also use it as a contextmanager:: - - def test_global(): - with pytest.deprecated_call(): - myobject.deprecated_method() +This page has been moved, please see :ref:`assertwarnings`. diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst new file mode 100644 index 000000000..ae15e3129 --- /dev/null +++ b/doc/en/warnings.rst @@ -0,0 +1,218 @@ +.. _`warnings`: + +Warnings Capture +================ + +.. versionadded:: 3.1 + +Starting from version ``3.1``, pytest now automatically catches all warnings during test execution +and displays them at the end of the session:: + + # content of test_show_warnings.py + import warnings + + def deprecated_function(): + warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) + return 1 + + def test_one(): + assert deprecated_function() == 1 + +Running pytest now produces this output:: + + $ pytest test_show_warnings.py + . + ============================== warnings summary =============================== + test_show_warning.py::test_one + C:\pytest\.tmp\test_show_warning.py:4: DeprecationWarning: this function is deprecated, use another_function() + warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) + + -- Docs: http://doc.pytest.org/en/latest/warnings.html + 1 passed, 1 warnings in 0.01 seconds + +The ``-W`` flag can be passed to control which warnings will be displayed or even turn +them into errors:: + + $ pytest -q test_show_warning.py -W error::DeprecationWarning + F + ================================== FAILURES =================================== + __________________________________ test_one ___________________________________ + + def test_one(): + > assert deprecated_function() == 1 + + test_show_warning.py:8: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + def deprecated_function(): + > warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) + E DeprecationWarning: this function is deprecated, use another_function() + + test_show_warning.py:4: DeprecationWarning + 1 failed in 0.02 seconds + +The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option. +For example, the configuration below will ignore all deprecation warnings, but will transform +all other warnings into errors. + +.. code-block:: ini + + [pytest] + filterwarnings = + error + ignore::DeprecationWarning + + +When a warning matches more than one option in the list, the action for the last matching option +is performed. + +Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own +`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python +documentation for other examples and advanced usage. + +*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_ +*plugin.* + +.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W +.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter +.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings + +.. _`asserting warnings`: + +.. _assertwarnings: + +.. _`asserting warnings with the warns function`: + +.. _warns: + +Asserting warnings with the warns function +----------------------------------------------- + +.. versionadded:: 2.8 + +You can check that code raises a particular warning using ``pytest.warns``, +which works in a similar manner to :ref:`raises `:: + + import warnings + import pytest + + def test_warning(): + with pytest.warns(UserWarning): + warnings.warn("my warning", UserWarning) + +The test will fail if the warning in question is not raised. + +You can also call ``pytest.warns`` on a function or code string:: + + pytest.warns(expected_warning, func, *args, **kwargs) + pytest.warns(expected_warning, "func(*args, **kwargs)") + +The function also returns a list of all raised warnings (as +``warnings.WarningMessage`` objects), which you can query for +additional information:: + + with pytest.warns(RuntimeWarning) as record: + warnings.warn("another warning", RuntimeWarning) + + # check that only one warning was raised + assert len(record) == 1 + # check that the message matches + assert record[0].message.args[0] == "another warning" + +Alternatively, you can examine raised warnings in detail using the +:ref:`recwarn ` fixture (see below). + +.. note:: + ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated + differently; see :ref:`ensuring_function_triggers`. + +.. _`recording warnings`: + +.. _recwarn: + +Recording warnings +------------------------ + +You can record raised warnings either using ``pytest.warns`` or with +the ``recwarn`` fixture. + +To record with ``pytest.warns`` without asserting anything about the warnings, +pass ``None`` as the expected warning type:: + + with pytest.warns(None) as record: + warnings.warn("user", UserWarning) + warnings.warn("runtime", RuntimeWarning) + + assert len(record) == 2 + assert str(record[0].message) == "user" + assert str(record[1].message) == "runtime" + +The ``recwarn`` fixture will record warnings for the whole function:: + + import warnings + + def test_hello(recwarn): + warnings.warn("hello", UserWarning) + assert len(recwarn) == 1 + w = recwarn.pop(UserWarning) + assert issubclass(w.category, UserWarning) + assert str(w.message) == "hello" + assert w.filename + assert w.lineno + +Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded +warnings: a WarningsRecorder instance. To view the recorded warnings, you can +iterate over this instance, call ``len`` on it to get the number of recorded +warnings, or index into it to get a particular recorded warning. It also +provides these methods: + +.. autoclass:: _pytest.recwarn.WarningsRecorder() + :members: + +Each recorded warning has the attributes ``message``, ``category``, +``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the +class of the warning. The ``message`` is the warning itself; calling +``str(message)`` will return the actual message of the warning. + +.. note:: + :class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1 + +.. note:: + ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated + differently; see :ref:`ensuring_function_triggers`. + +.. _`ensuring a function triggers a deprecation warning`: + +.. _ensuring_function_triggers: + +Ensuring a function triggers a deprecation warning +------------------------------------------------------- + +You can also call a global helper for checking +that a certain function call triggers a ``DeprecationWarning`` or +``PendingDeprecationWarning``:: + + import pytest + + def test_global(): + pytest.deprecated_call(myfunction, 17) + +By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be +caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide +them. If you wish to record them in your own code, use the +command ``warnings.simplefilter('always')``:: + + import warnings + import pytest + + def test_deprecation(recwarn): + warnings.simplefilter('always') + warnings.warn("deprecated", DeprecationWarning) + assert len(recwarn) == 1 + assert recwarn.pop(DeprecationWarning) + +You can also use it as a contextmanager:: + + def test_global(): + with pytest.deprecated_call(): + myobject.deprecated_method() From 916d272c443bf22ff73759e797be29979ec70b7a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 20 Mar 2017 23:44:50 -0300 Subject: [PATCH 088/153] Fix test on linux --- testing/test_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_cache.py b/testing/test_cache.py index 47072e07e..f5904be39 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -55,7 +55,7 @@ class TestNewAPI(object): assert result.ret == 1 result.stdout.fnmatch_lines([ "*could not create cache path*", - "*1 pytest-warnings*", + "*1 warnings*", ]) def test_config_cache(self, testdir): From 2c730743f1b80b38b044ecc08c98609df60ba375 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Mar 2017 22:17:07 -0300 Subject: [PATCH 089/153] Fix errors related to warnings raised by xdist - pytester was creating a 'pexpect' directory to serve as temporary dir, but due to the fact that xdist adds the current directory to sys.path, that directory was being considered as candidate for import as a package. The directory is empty and a warning was being raised about it missing __init__ file, which is now turned into an error by our filterwarnings config in pytest.ini. - Decided to play it safe and ignore any warnings during `pytest.importorskip`. - pytest-xdist and execnet raise two warnings which should be fixed upstream: pytest-dev/pytest-xdist/issues/133 --- _pytest/pytester.py | 2 +- _pytest/runner.py | 17 ++++++++++++----- tox.ini | 4 ++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index ee0e5bbe7..6381595ee 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -1008,7 +1008,7 @@ class Testdir(object): The pexpect child is returned. """ - basetemp = self.tmpdir.mkdir("pexpect") + basetemp = self.tmpdir.mkdir("temp-pexpect") invoke = " ".join(map(str, self._getpytestargs())) cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string) return self.spawn(cmd, expect_timeout=expect_timeout) diff --git a/_pytest/runner.py b/_pytest/runner.py index 4277f8ee3..07f4be019 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -554,14 +554,21 @@ def importorskip(modname, minversion=None): __version__ attribute. If no minversion is specified the a skip is only triggered if the module can not be imported. """ + import warnings __tracebackhide__ = True compile(modname, '', 'eval') # to catch syntaxerrors should_skip = False - try: - __import__(modname) - except ImportError: - # Do not raise chained exception here(#1485) - should_skip = True + + with warnings.catch_warnings(): + # make sure to ignore ImportWarnings that might happen because + # of existing directories with the same name we're trying to + # import but without a __init__.py file + warnings.simplefilter('ignore') + try: + __import__(modname) + except ImportError: + # Do not raise chained exception here(#1485) + should_skip = True if should_skip: raise Skipped("could not import %r" %(modname,), allow_module_level=True) mod = sys.modules[modname] diff --git a/tox.ini b/tox.ini index f8ea5fabd..5c103f94c 100644 --- a/tox.ini +++ b/tox.ini @@ -181,6 +181,10 @@ filterwarnings= error ignore:bad escape.*:DeprecationWarning:re # produced by path.readlines ignore:.*U.*mode is deprecated:DeprecationWarning + # produced by pytest-xdist + ignore:.*type argument to addoption.*:DeprecationWarning + # produced by python >=3.5 on execnet (pytest-xdist) + ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning [flake8] ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402 From 74b54ac0ec9c64d8a6693afdb3e2a6da4eef92c6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 21 Mar 2017 22:32:41 -0300 Subject: [PATCH 090/153] Fix errors related to warnings raised on pypy test environment For some reason pypy raises this warning in the line that the catch_warnings block was added: ______________________________ ERROR collecting ______________________________ C:\ProgramData\chocolatey\lib\python.pypy\tools\pypy2-v5.4.1-win32\lib-python\2.7\pkgutil.py:476: in find_loader loader = importer.find_module(fullname) c:\pytest\.tox\pypy\site-packages\_pytest\assertion\rewrite.py:75: in find_module fd, fn, desc = imp.find_module(lastname, path) /?:3: in anonymous ??? E ImportWarning: Not importing directory 'c:\users\bruno\appdata\local\temp\pytest-of-Bruno\pytest-3192\testdir\test_cmdline_python_package0' missing __init__.py --- testing/acceptance_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index f0047574e..fe02d82f0 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -530,6 +530,7 @@ class TestInvocationVariants(object): ]) def test_cmdline_python_package(self, testdir, monkeypatch): + import warnings monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False) path = testdir.mkpydir("tpkg") path.join("test_hello.py").write("def test_hello(): pass") @@ -552,7 +553,11 @@ class TestInvocationVariants(object): return what empty_package = testdir.mkpydir("empty_package") monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package)) - result = testdir.runpytest("--pyargs", ".") + # the path which is not a package raises a warning on pypy; + # no idea why only pypy and not normal python warn about it here + with warnings.catch_warnings(): + warnings.simplefilter('ignore', ImportWarning) + result = testdir.runpytest("--pyargs", ".") assert result.ret == 0 result.stdout.fnmatch_lines([ "*2 passed*" From 0c1c2580d0d3c8d037ab3e4f5524b626068c33e5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 22 Mar 2017 12:40:31 -0300 Subject: [PATCH 091/153] Add CHANGELOG entry --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 37570ed43..05c868079 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,10 @@ New Features * ``pytest.param`` can be used to declare test parameter sets with marks and test ids. Thanks `@RonnyPfannschmidt`_ for the PR. +* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically + captures and displays warnings at the end of the test session. + Thanks `@nicoddemus`_ for the PR. + Changes ------- From 5818e65cf3407f672bfc9b50c54385e45a1ef98b Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:30:57 +0100 Subject: [PATCH 092/153] remove pytest_namespace from _pytest/assertion --- _pytest/assertion/__init__.py | 3 --- pytest.py | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index edda06345..cf9265330 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -25,9 +25,6 @@ def pytest_addoption(parser): expression information.""") -def pytest_namespace(): - return {'register_assert_rewrite': register_assert_rewrite} - def register_assert_rewrite(*names): """Register one or more module names to be rewritten on import. diff --git a/pytest.py b/pytest.py index e376e417e..26667f5e5 100644 --- a/pytest.py +++ b/pytest.py @@ -9,6 +9,7 @@ __all__ = [ 'hookspec', 'hookimpl', '__version__', + 'register_assert_rewrite' ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -22,6 +23,7 @@ from _pytest.config import ( main, UsageError, _preloadplugins, cmdline, hookspec, hookimpl ) +from _pytest.assertion import register_assert_rewrite from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works From fab9b993f80ce696e7be41bcd76a415a9fdce7c6 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:34:38 +0100 Subject: [PATCH 093/153] remove pytest_namespace from _pytest.freeze_support --- _pytest/config.py | 1 + _pytest/freeze_support.py | 3 --- pytest.py | 5 ++++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index db37280ca..751098b36 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -101,6 +101,7 @@ default_plugins = ( "junitxml resultlog doctest cacheprovider freeze_support " "setuponly setupplan warnings").split() + builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") diff --git a/_pytest/freeze_support.py b/_pytest/freeze_support.py index 6c2120559..52f86087f 100644 --- a/_pytest/freeze_support.py +++ b/_pytest/freeze_support.py @@ -5,9 +5,6 @@ pytest from __future__ import absolute_import, division, print_function -def pytest_namespace(): - return {'freeze_includes': freeze_includes} - def freeze_includes(): """ diff --git a/pytest.py b/pytest.py index 26667f5e5..741e2d7f6 100644 --- a/pytest.py +++ b/pytest.py @@ -9,7 +9,8 @@ __all__ = [ 'hookspec', 'hookimpl', '__version__', - 'register_assert_rewrite' + 'register_assert_rewrite', + 'freeze_includes', ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -24,7 +25,9 @@ from _pytest.config import ( hookspec, hookimpl ) from _pytest.assertion import register_assert_rewrite +from _pytest.freeze_support import freeze_includes from _pytest import __version__ + _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 794fd5658c53a22c304c94a8f703d3e05edaa7ac Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:40:38 +0100 Subject: [PATCH 094/153] remove pytest_namespace from _pytest/debugging.py --- _pytest/debugging.py | 16 ++++++++-------- pytest.py | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 1e7dcad5d..6c6bd6a02 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -16,8 +16,6 @@ def pytest_addoption(parser): help="start a custom interactive Python debugger on errors. " "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb") -def pytest_namespace(): - return {'set_trace': pytestPDB().set_trace} def pytest_configure(config): if config.getvalue("usepdb_cls"): @@ -43,25 +41,27 @@ def pytest_configure(config): pytestPDB._pdb_cls = pdb_cls config._cleanup.append(fin) + class pytestPDB(object): """ Pseudo PDB that defers to the real pdb. """ _pluginmanager = None _config = None _pdb_cls = pdb.Pdb - def set_trace(self): + @classmethod + def set_trace(cls): """ invoke PDB set_trace debugging, dropping any IO capturing. """ import _pytest.config frame = sys._getframe().f_back - if self._pluginmanager is not None: - capman = self._pluginmanager.getplugin("capturemanager") + if cls._pluginmanager is not None: + capman = cls._pluginmanager.getplugin("capturemanager") if capman: capman.suspendcapture(in_=True) - tw = _pytest.config.create_terminal_writer(self._config) + tw = _pytest.config.create_terminal_writer(cls._config) tw.line() tw.sep(">", "PDB set_trace (IO-capturing turned off)") - self._pluginmanager.hook.pytest_enter_pdb(config=self._config) - self._pdb_cls().set_trace(frame) + cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config) + cls._pdb_cls().set_trace(frame) class PdbInvoke(object): diff --git a/pytest.py b/pytest.py index 741e2d7f6..d9073f01c 100644 --- a/pytest.py +++ b/pytest.py @@ -11,6 +11,7 @@ __all__ = [ '__version__', 'register_assert_rewrite', 'freeze_includes', + 'set_trace', ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -27,7 +28,10 @@ from _pytest.config import ( from _pytest.assertion import register_assert_rewrite from _pytest.freeze_support import freeze_includes from _pytest import __version__ +from _pytest.debugging import pytestPDB as __pytestPDB + +set_trace = __pytestPDB.set_trace + _preloadplugins() # to populate pytest.* namespace so help(pytest) works - From c74103f395f5ff1d338c79fe8daa50ce472c5552 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 13:51:29 +0100 Subject: [PATCH 095/153] remove pytest_namespace from recwarn and fixture decorators --- _pytest/fixtures.py | 2 -- _pytest/recwarn.py | 5 ----- pytest.py | 7 ++++++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 9196e4fdd..0116890c2 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -52,8 +52,6 @@ def pytest_namespace(): 'function': pytest.Item, }) return { - 'fixture': fixture, - 'yield_fixture': yield_fixture, 'collect': {'_fillfuncargs': fillfixtures} } diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 579439fe4..43d360736 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -26,11 +26,6 @@ def recwarn(): yield wrec -def pytest_namespace(): - return {'deprecated_call': deprecated_call, - 'warns': warns} - - def deprecated_call(func=None, *args, **kwargs): """ assert that calling ``func(*args, **kwargs)`` triggers a ``DeprecationWarning`` or ``PendingDeprecationWarning``. diff --git a/pytest.py b/pytest.py index d9073f01c..aca96c143 100644 --- a/pytest.py +++ b/pytest.py @@ -12,6 +12,10 @@ __all__ = [ 'register_assert_rewrite', 'freeze_includes', 'set_trace', + 'warns', + 'deprecated_call', + 'fixture', + 'yield_fixture' ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -25,11 +29,12 @@ from _pytest.config import ( main, UsageError, _preloadplugins, cmdline, hookspec, hookimpl ) +from _pytest.fixtures import fixture, yield_fixture from _pytest.assertion import register_assert_rewrite from _pytest.freeze_support import freeze_includes from _pytest import __version__ from _pytest.debugging import pytestPDB as __pytestPDB - +from _pytest.recwarn import warns, deprecated_call set_trace = __pytestPDB.set_trace From 6a02cdbb35cdd37518ff6522deb38ba8e3b97b97 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 14:09:39 +0100 Subject: [PATCH 096/153] remove pytest_namespace from _pytest/runner.py --- _pytest/runner.py | 7 ------- pytest.py | 10 +++++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/_pytest/runner.py b/_pytest/runner.py index 07f4be019..ee1683042 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -10,13 +10,6 @@ import pytest from _pytest._code.code import TerminalRepr, ExceptionInfo -def pytest_namespace(): - return { - 'fail' : fail, - 'skip' : skip, - 'importorskip' : importorskip, - 'exit' : exit, - } # # pytest plugin hooks diff --git a/pytest.py b/pytest.py index aca96c143..518b65a38 100644 --- a/pytest.py +++ b/pytest.py @@ -15,7 +15,12 @@ __all__ = [ 'warns', 'deprecated_call', 'fixture', - 'yield_fixture' + 'yield_fixture', + 'fail', + 'skip', + 'importorskip', + 'exit', + ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -35,8 +40,7 @@ from _pytest.freeze_support import freeze_includes from _pytest import __version__ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call - - +from _pytest.runner import fail, skip, importorskip, exit set_trace = __pytestPDB.set_trace _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 90788defb24076663f8f0a89828885fd49e16076 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 16:58:29 +0100 Subject: [PATCH 097/153] remove pytest_namespace from _pytest.mark and fix latent pytest nesting bug --- _pytest/mark.py | 18 +++++++++++++----- pytest.py | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index a06b02b14..ccc90d6e8 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -66,10 +66,8 @@ class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" - def pytest_namespace(): return { - 'mark': MarkGenerator(), 'param': ParameterSet.param, } @@ -225,9 +223,13 @@ def matchkeyword(colitem, keywordexpr): def pytest_configure(config): - import pytest + config._old_mark_config = MARK_GEN._config if config.option.strict: - pytest.mark._config = config + MARK_GEN._config = config + + +def pytest_unconfigure(config): + MARK_GEN._config = config._old_mark_config class MarkGenerator(object): @@ -241,11 +243,13 @@ class MarkGenerator(object): will set a 'slowtest' :class:`MarkInfo` object on the ``test_function`` object. """ + _config = None + def __getattr__(self, name): if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") - if hasattr(self, '_config'): + if self._config is not None: self._check(name) return MarkDecorator(Mark(name, (), {})) @@ -263,6 +267,7 @@ class MarkGenerator(object): if name not in self._markers: raise AttributeError("%r not a registered marker" % (name,)) + def istestfunc(func): return hasattr(func, "__call__") and \ getattr(func, "__name__", "") != "" @@ -384,3 +389,6 @@ class MarkInfo(object): def __iter__(self): """ yield MarkInfo objects each relating to a marking-call. """ return imap(MarkInfo, self._marks) + + +MARK_GEN = MarkGenerator() diff --git a/pytest.py b/pytest.py index 518b65a38..95cb8ccc6 100644 --- a/pytest.py +++ b/pytest.py @@ -20,6 +20,7 @@ __all__ = [ 'skip', 'importorskip', 'exit', + 'mark', ] @@ -41,6 +42,7 @@ from _pytest import __version__ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit +from _pytest.mark import MARK_GEN as mark set_trace = __pytestPDB.set_trace _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 9b755f6ec6077c2e6d49b2d893f147cc7d35cce2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:03:23 +0100 Subject: [PATCH 098/153] remove pytest_namespace from _pytest.skipping --- _pytest/skipping.py | 4 ---- pytest.py | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 228c52935..f2ee41518 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -57,10 +57,6 @@ def pytest_configure(config): ) -def pytest_namespace(): - return dict(xfail=xfail) - - class XFailed(pytest.fail.Exception): """ raised from an explicit call to pytest.xfail() """ diff --git a/pytest.py b/pytest.py index 95cb8ccc6..c05ed77de 100644 --- a/pytest.py +++ b/pytest.py @@ -18,6 +18,7 @@ __all__ = [ 'yield_fixture', 'fail', 'skip', + 'xfail', 'importorskip', 'exit', 'mark', @@ -43,6 +44,7 @@ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit from _pytest.mark import MARK_GEN as mark +from _pytest.skipping import xfail set_trace = __pytestPDB.set_trace _preloadplugins() # to populate pytest.* namespace so help(pytest) works From 7d797b7dbfc07915c421c91a007e29ea7467bdc6 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:12:16 +0100 Subject: [PATCH 099/153] add a note about the deprecation of the pytest_namespace hook --- _pytest/hookspec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index 403f823a0..917632669 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -16,7 +16,9 @@ def pytest_addhooks(pluginmanager): @hookspec(historic=True) def pytest_namespace(): - """return dict of name->object to be made globally available in + """ + DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged + return dict of name->object to be made globally available in the pytest namespace. This hook is called at plugin registration time. """ From 839c9361539aec8bf90a56cdea89ddccc8c40754 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:30:52 +0100 Subject: [PATCH 100/153] _pytest.mark: fix unconfigure after bad configure, still potential bug --- _pytest/mark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index ccc90d6e8..23ec44c29 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -229,7 +229,7 @@ def pytest_configure(config): def pytest_unconfigure(config): - MARK_GEN._config = config._old_mark_config + MARK_GEN._config = getattr(config, '_old_mark_config', None) class MarkGenerator(object): From 9b58d6eaca17758551929f0ebb390ad900e03581 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:31:34 +0100 Subject: [PATCH 101/153] prepare a own pytest.collect fake module in oder to remove the nested builtin namespaces --- _pytest/compat.py | 23 +++++++++++++++++++++++ pytest.py | 3 +++ 2 files changed, 26 insertions(+) diff --git a/_pytest/compat.py b/_pytest/compat.py index b688ae509..5be7f7a2a 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -254,6 +254,29 @@ else: return v.encode('ascii', errors) +COLLECT_FAKEMODULE_ATTRIBUTES = ( + 'Collector', + 'Module', + 'Generator', + 'Function', + 'Instance', + 'Session', + 'Item', + 'Class', + 'File', + '_fillfuncargs', +) + + +def _setup_collect_fakemodule(): + from types import ModuleType + import pytest + pytest.collect = ModuleType('pytest.collect') + pytest.collect.__all__ = [] # used for setns + for attr in COLLECT_FAKEMODULE_ATTRIBUTES: + setattr(pytest.collect, attr, getattr(pytest, attr)) + + if _PY2: from py.io import TextIO as CaptureIO else: diff --git a/pytest.py b/pytest.py index c05ed77de..c6146e291 100644 --- a/pytest.py +++ b/pytest.py @@ -47,4 +47,7 @@ from _pytest.mark import MARK_GEN as mark from _pytest.skipping import xfail set_trace = __pytestPDB.set_trace + +from _pytest.compat import _setup_collect_fakemodule _preloadplugins() # to populate pytest.* namespace so help(pytest) works +_setup_collect_fakemodule() \ No newline at end of file From 61f418a26750bd89d26aff9ad3f3cb1eb534b838 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:33:54 +0100 Subject: [PATCH 102/153] hollow out pytest_namespace in _pytest.fixtures --- _pytest/fixtures.py | 4 +--- pytest.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 0116890c2..621e38057 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -51,9 +51,7 @@ def pytest_namespace(): 'module': pytest.Module, 'function': pytest.Item, }) - return { - 'collect': {'_fillfuncargs': fillfixtures} - } + return {} def get_scope_node(node, scope): diff --git a/pytest.py b/pytest.py index c6146e291..bac816b11 100644 --- a/pytest.py +++ b/pytest.py @@ -45,6 +45,7 @@ from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit from _pytest.mark import MARK_GEN as mark from _pytest.skipping import xfail +from _pytest.fixtures import fillfixtures as _fillfuncargs set_trace = __pytestPDB.set_trace From 99c8f2d4035a15543fefc6cef2c3abb883a97d23 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:41:20 +0100 Subject: [PATCH 103/153] remove pytest_namespace from _pytest.main --- _pytest/main.py | 5 ----- pytest.py | 11 +++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 3d7b456d2..da4ae8e41 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -77,11 +77,6 @@ def pytest_addoption(parser): help="base temporary directory for this test run.") -def pytest_namespace(): - collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) - return dict(collect=collect) - - def pytest_configure(config): pytest.config = config # compatibility diff --git a/pytest.py b/pytest.py index bac816b11..a072b49ae 100644 --- a/pytest.py +++ b/pytest.py @@ -23,6 +23,14 @@ __all__ = [ 'exit', 'mark', + '_fillfuncargs', + + 'Item', + 'File', + 'Collector', + 'Session', + + ] if __name__ == '__main__': # if run as a script or by 'python -m pytest' @@ -45,7 +53,10 @@ from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit from _pytest.mark import MARK_GEN as mark from _pytest.skipping import xfail +from _pytest.main import Item, Collector, File, Session from _pytest.fixtures import fillfixtures as _fillfuncargs + + set_trace = __pytestPDB.set_trace From ae234786eabeae89ece706f826dcac5dcdaca012 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:56:12 +0100 Subject: [PATCH 104/153] remove pytest_namespace from _pytest.python --- _pytest/python.py | 22 ++++++---------------- pytest.py | 4 ++++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index e763aa888..61000676e 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -22,6 +22,7 @@ from _pytest.compat import ( get_real_func, getfslineno, safe_getattr, getlocation, enum, ) +from _pytest.runner import fail cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) cutdir2 = py.path.local(_pytest.__file__).dirpath() @@ -126,21 +127,6 @@ def pytest_configure(config): "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures " ) -@pytest.hookimpl(trylast=True) -def pytest_namespace(): - raises.Exception = pytest.fail.Exception - return { - 'raises': raises, - 'approx': approx, - 'collect': { - 'Module': Module, - 'Class': Class, - 'Instance': Instance, - 'Function': Function, - 'Generator': Generator, - } - } - @pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): @@ -1234,7 +1220,11 @@ def raises(expected_exception, *args, **kwargs): func(*args[1:], **kwargs) except expected_exception: return _pytest._code.ExceptionInfo() - pytest.fail(message) + fail(message) + +raises.Exception = fail.Exception + + class RaisesContext(object): def __init__(self, expected_exception, message, match_expr): diff --git a/pytest.py b/pytest.py index a072b49ae..8185d6503 100644 --- a/pytest.py +++ b/pytest.py @@ -55,6 +55,10 @@ from _pytest.mark import MARK_GEN as mark from _pytest.skipping import xfail from _pytest.main import Item, Collector, File, Session from _pytest.fixtures import fillfixtures as _fillfuncargs +from _pytest.python import ( + raises, approx, + Module, Class, Instance, Function, Generator, +) set_trace = __pytestPDB.set_trace From 23bc9815c4aaf6f247df462dab665f9311e875ca Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Feb 2017 17:59:48 +0100 Subject: [PATCH 105/153] remove pytest_namespace from _pytest.fixtures --- _pytest/fixtures.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 621e38057..16481ca1c 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -19,6 +19,11 @@ from _pytest.compat import ( ) def pytest_sessionstart(session): + scopename2class.update({ + 'class': pytest.Class, + 'module': pytest.Module, + 'function': pytest.Item, + }) session._fixturemanager = FixtureManager(session) @@ -45,15 +50,6 @@ def scopeproperty(name=None, doc=None): return decoratescope -def pytest_namespace(): - scopename2class.update({ - 'class': pytest.Class, - 'module': pytest.Module, - 'function': pytest.Item, - }) - return {} - - def get_scope_node(node, scope): cls = scopename2class.get(scope) if cls is None: From 809c36e1f6d5cd837da798026f20aa62c6c7a4e2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 1 Mar 2017 19:57:47 +0100 Subject: [PATCH 106/153] add a changelog note for pytest_namespace --- CHANGELOG.rst | 4 ++++ doc/en/writing_plugins.rst | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 05c868079..91341bad5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,10 @@ New Features Changes ------- +* remove all internal uses of pytest_namespace hooks, + this is to prepare the removal of preloadconfig in pytest 4.0 + Thanks to `@RonnyPfannschmidt`_ for the PR. + * Old-style classes have been changed to new-style classes in order to improve compatibility with Python 2. Thanks to `@MichalTHEDUDE`_ and `@mandeep`_ for the PR (`#2147`_). diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index f6ed6e4e3..bb07ba0df 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -517,7 +517,6 @@ Initialization, command line and configuration hooks .. autofunction:: pytest_load_initial_conftests .. autofunction:: pytest_cmdline_preparse .. autofunction:: pytest_cmdline_parse -.. autofunction:: pytest_namespace .. autofunction:: pytest_addoption .. autofunction:: pytest_cmdline_main .. autofunction:: pytest_configure From 92f6ab188137a44479b0c6bcf209c3bf2ea31b41 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:00:59 +0100 Subject: [PATCH 107/153] fix all singular internal module imports and add a test for them --- _pytest/compat.py | 9 ++++++++ _pytest/debugging.py | 5 ++--- _pytest/fixtures.py | 39 +++++++++++++------------------- _pytest/main.py | 50 +++++++++++++++++++++++------------------- _pytest/monkeypatch.py | 8 +++---- _pytest/python.py | 21 +++++++++--------- _pytest/recwarn.py | 14 +++++++----- _pytest/runner.py | 3 +-- _pytest/skipping.py | 38 +++++++++++++++++--------------- 9 files changed, 98 insertions(+), 89 deletions(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 5be7f7a2a..c06e3f4ca 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -291,3 +291,12 @@ else: def getvalue(self): return self.buffer.getvalue().decode('UTF-8') + +class FuncargnamesCompatAttr(object): + """ helper class so that Metafunc, Function and FixtureRequest + don't need to each define the "funcargnames" compatibility attribute. + """ + @property + def funcargnames(self): + """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" + return self.fixturenames diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 6c6bd6a02..ad81fedc0 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -3,7 +3,6 @@ from __future__ import absolute_import, division, print_function import pdb import sys -import pytest def pytest_addoption(parser): @@ -35,7 +34,7 @@ def pytest_configure(config): pytestPDB._config = None pytestPDB._pdb_cls = pdb.Pdb - pdb.set_trace = pytest.set_trace + pdb.set_trace = pytestPDB.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config pytestPDB._pdb_cls = pdb_cls @@ -75,7 +74,7 @@ class PdbInvoke(object): def pytest_internalerror(self, excrepr, excinfo): for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" %line) + sys.stderr.write("INTERNALERROR> %s\n" % line) sys.stderr.flush() tb = _postmortem_traceback(excinfo) post_mortem(tb) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 16481ca1c..2710da792 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -4,7 +4,6 @@ import sys from py._code.code import FormattedExcinfo import py -import pytest import warnings import inspect @@ -17,12 +16,15 @@ from _pytest.compat import ( getlocation, getfuncargnames, safe_getattr, ) +from _pytest.runner import fail +from _pytest.compat import FuncargnamesCompatAttr +from _pytest import python def pytest_sessionstart(session): scopename2class.update({ - 'class': pytest.Class, - 'module': pytest.Module, - 'function': pytest.Item, + 'class': python.Class, + 'module': python.Module, + 'function': _pytest.main.Item, }) session._fixturemanager = FixtureManager(session) @@ -97,7 +99,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): if scope != "function": node = get_scope_node(collector, scope) if node is None: - assert scope == "class" and isinstance(collector, pytest.Module) + assert scope == "class" and isinstance(collector, _pytest.python.Module) # use module-level collector for class-scope (for now) node = collector if node and argname in node._name2pseudofixturedef: @@ -213,17 +215,6 @@ def slice_items(items, ignore, scoped_argkeys_cache): return items, None, None, None - -class FuncargnamesCompatAttr(object): - """ helper class so that Metafunc, Function and FixtureRequest - don't need to each define the "funcargnames" compatibility attribute. - """ - @property - def funcargnames(self): - """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" - return self.fixturenames - - def fillfixtures(function): """ fill missing funcargs for a test function. """ try: @@ -319,7 +310,7 @@ class FixtureRequest(FuncargnamesCompatAttr): @scopeproperty("class") def cls(self): """ class (can be None) where the test function was collected. """ - clscol = self._pyfuncitem.getparent(pytest.Class) + clscol = self._pyfuncitem.getparent(_pytest.python.Class) if clscol: return clscol.obj @@ -337,7 +328,7 @@ class FixtureRequest(FuncargnamesCompatAttr): @scopeproperty() def module(self): """ python module object where the test function was collected. """ - return self._pyfuncitem.getparent(pytest.Module).obj + return self._pyfuncitem.getparent(_pytest.python.Module).obj @scopeproperty() def fspath(self): @@ -500,7 +491,7 @@ class FixtureRequest(FuncargnamesCompatAttr): source_lineno, ) ) - pytest.fail(msg) + fail(msg) else: # indices might not be set if old-style metafunc.addcall() was used param_index = funcitem.callspec.indices.get(argname, 0) @@ -533,10 +524,10 @@ class FixtureRequest(FuncargnamesCompatAttr): if scopemismatch(invoking_scope, requested_scope): # try to report something helpful lines = self._factorytraceback() - pytest.fail("ScopeMismatch: You tried to access the %r scoped " - "fixture %r with a %r scoped request object, " - "involved factories\n%s" %( - (requested_scope, argname, invoking_scope, "\n".join(lines))), + fail("ScopeMismatch: You tried to access the %r scoped " + "fixture %r with a %r scoped request object, " + "involved factories\n%s" % ( + (requested_scope, argname, invoking_scope, "\n".join(lines))), pytrace=False) def _factorytraceback(self): @@ -546,7 +537,7 @@ class FixtureRequest(FuncargnamesCompatAttr): fs, lineno = getfslineno(factory) p = self._pyfuncitem.session.fspath.bestrelpath(fs) args = _format_args(factory) - lines.append("%s:%d: def %s%s" %( + lines.append("%s:%d: def %s%s" % ( p, lineno, factory.__name__, args)) return lines diff --git a/_pytest/main.py b/_pytest/main.py index da4ae8e41..623273655 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -8,14 +8,13 @@ import sys import _pytest import _pytest._code import py -import pytest try: from collections import MutableMapping as MappingMixin except ImportError: from UserDict import DictMixin as MappingMixin -from _pytest.config import directory_arg -from _pytest.runner import collect_one_node +from _pytest.config import directory_arg, UsageError, hookimpl +from _pytest.runner import collect_one_node, exit tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -27,6 +26,7 @@ EXIT_INTERNALERROR = 3 EXIT_USAGEERROR = 4 EXIT_NOTESTSCOLLECTED = 5 + def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv']) @@ -78,7 +78,7 @@ def pytest_addoption(parser): def pytest_configure(config): - pytest.config = config # compatibility + pytest.config = config # compatibiltiy def wrap_session(config, doit): @@ -93,12 +93,11 @@ def wrap_session(config, doit): config.hook.pytest_sessionstart(session=session) initstate = 2 session.exitstatus = doit(config, session) or 0 - except pytest.UsageError: + except UsageError: raise except KeyboardInterrupt: excinfo = _pytest._code.ExceptionInfo() - if initstate < 2 and isinstance( - excinfo.value, pytest.exit.Exception): + if initstate < 2 and isinstance(excinfo.value, exit.Exception): sys.stderr.write('{0}: {1}\n'.format( excinfo.typename, excinfo.value.msg)) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) @@ -120,9 +119,11 @@ def wrap_session(config, doit): config._ensure_unconfigure() return session.exitstatus + def pytest_cmdline_main(config): return wrap_session(config, _main) + def _main(config, session): """ default command line protocol for initialization, session, running tests and reporting. """ @@ -134,9 +135,11 @@ def _main(config, session): elif session.testscollected == 0: return EXIT_NOTESTSCOLLECTED + def pytest_collection(session): return session.perform_collect() + def pytest_runtestloop(session): if (session.testsfailed and not session.config.option.continue_on_collection_errors): @@ -153,6 +156,7 @@ def pytest_runtestloop(session): raise session.Interrupted(session.shouldstop) return True + def pytest_ignore_collect(path, config): p = path.dirpath() ignore_paths = config._getconftest_pathlist("collect_ignore", path=p) @@ -200,7 +204,7 @@ class _CompatProperty(object): # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( # name=self.name, owner=type(owner).__name__), # PendingDeprecationWarning, stacklevel=2) - return getattr(pytest, self.name) + return getattr(__import__('pytest'), self.name) @@ -284,7 +288,7 @@ class Node(object): def _getcustomclass(self, name): maybe_compatprop = getattr(type(self), name) if isinstance(maybe_compatprop, _CompatProperty): - return getattr(pytest, name) + return getattr(__import__('pytest'), name) else: cls = getattr(self, name) # TODO: reenable in the features branch @@ -367,9 +371,9 @@ class Node(object): ``marker`` can be a string or pytest.mark.* instance. """ - from _pytest.mark import MarkDecorator + from _pytest.mark import MarkDecorator, MARK_GEN if isinstance(marker, py.builtin._basestring): - marker = getattr(pytest.mark, marker) + marker = getattr(MARK_GEN, marker) elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker.name] = marker @@ -555,12 +559,12 @@ class Session(FSCollector): def _makeid(self): return "" - @pytest.hookimpl(tryfirst=True) + @hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl(tryfirst=True) + @hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self.testsfailed += 1 @@ -619,8 +623,8 @@ class Session(FSCollector): for arg, exc in self._notfound: line = "(no name %r in any of %r)" % (arg, exc.args[0]) errors.append("not found: %s\n%s" % (arg, line)) - #XXX: test this - raise pytest.UsageError(*errors) + # XXX: test this + raise UsageError(*errors) if not genitems: return rep.result else: @@ -648,7 +652,7 @@ class Session(FSCollector): names = self._parsearg(arg) path = names.pop(0) if path.check(dir=1): - assert not names, "invalid arg %r" %(arg,) + assert not names, "invalid arg %r" % (arg,) for path in path.visit(fil=lambda x: x.check(file=1), rec=self._recurse, bf=True, sort=True): for x in self._collectfile(path): @@ -707,9 +711,11 @@ class Session(FSCollector): path = self.config.invocation_dir.join(relpath, abs=True) if not path.check(): if self.config.option.pyargs: - raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)") + raise UsageError( + "file or package not found: " + arg + + " (missing __init__.py?)") else: - raise pytest.UsageError("file not found: " + arg) + raise UsageError("file not found: " + arg) parts[0] = path return parts @@ -732,11 +738,11 @@ class Session(FSCollector): nextnames = names[1:] resultnodes = [] for node in matching: - if isinstance(node, pytest.Item): + if isinstance(node, Item): if not names: resultnodes.append(node) continue - assert isinstance(node, pytest.Collector) + assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: has_matched = False @@ -754,11 +760,11 @@ class Session(FSCollector): def genitems(self, node): self.trace("genitems", node) - if isinstance(node, pytest.Item): + if isinstance(node, Item): node.ihook.pytest_itemcollected(item=node) yield node else: - assert isinstance(node, pytest.Collector) + assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: for subnode in rep.result: diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 9151db3ad..ec846f910 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -1,17 +1,17 @@ """ monkeypatching and mocking functionality. """ from __future__ import absolute_import, division, print_function -import os, sys +import os +import sys import re from py.builtin import _basestring - -import pytest +from _pytest.fixtures import fixture RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") -@pytest.fixture +@fixture def monkeypatch(): """The returned ``monkeypatch`` fixture provides these helper methods to modify objects, dictionaries or os.environ:: diff --git a/_pytest/python.py b/_pytest/python.py index 61000676e..985b476ca 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -9,13 +9,13 @@ import math from itertools import count import py -import pytest from _pytest.mark import MarkerError - +from _pytest.config import hookimpl import _pytest import _pytest._pluggy as pluggy from _pytest import fixtures +from _pytest import main from _pytest.compat import ( isclass, isfunction, is_generator, _escape_strings, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, @@ -50,7 +50,7 @@ def filter_traceback(entry): def pyobj_property(name): def get(self): - node = self.getparent(getattr(pytest, name)) + node = self.getparent(getattr(__import__('pytest'), name)) if node is not None: return node.obj doc = "python %s object this node was collected from (can be None)." % ( @@ -128,7 +128,7 @@ def pytest_configure(config): ) -@pytest.hookimpl(trylast=True) +@hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -141,6 +141,7 @@ def pytest_pyfunc_call(pyfuncitem): testfunction(**testargs) return True + def pytest_collect_file(path, parent): ext = path.ext if ext == ".py": @@ -156,7 +157,7 @@ def pytest_collect_file(path, parent): def pytest_pycollect_makemodule(path, parent): return Module(path, parent) -@pytest.hookimpl(hookwrapper=True) +@hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() @@ -252,7 +253,7 @@ class PyobjMixin(PyobjContext): assert isinstance(lineno, int) return fspath, lineno, modpath -class PyCollector(PyobjMixin, pytest.Collector): +class PyCollector(PyobjMixin, main.Collector): def funcnamefilter(self, name): return self._matches_prefix_or_glob_option('python_functions', name) @@ -576,7 +577,7 @@ class FunctionMixin(PyobjMixin): entry.set_repr_style('short') def _repr_failure_py(self, excinfo, style="long"): - if excinfo.errisinstance(pytest.fail.Exception): + if excinfo.errisinstance(fail.Exception): if not excinfo.value.pytrace: return py._builtin._totext(excinfo.value) return super(FunctionMixin, self)._repr_failure_py(excinfo, @@ -774,7 +775,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import ParameterSet + from _pytest.mark import extract_argvalue, MARK_GEN from py.io import saferepr if not isinstance(argnames, (tuple, list)): @@ -1240,7 +1241,7 @@ class RaisesContext(object): def __exit__(self, *tp): __tracebackhide__ = True if tp[0] is None: - pytest.fail(self.message) + fail(self.message) if sys.version_info < (2, 7): # py26: on __exit__() exc_value often does not contain the # exception value. @@ -1511,7 +1512,7 @@ class ApproxNonIterable(object): # the basic pytest Function item # -class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr): +class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr): """ a Function Item is responsible for setting up and executing a Python test function. """ diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 43d360736..73325cb8e 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -7,10 +7,11 @@ import _pytest._code import py import sys import warnings -import pytest + +from _pytest.fixtures import yield_fixture -@pytest.yield_fixture +@yield_fixture def recwarn(): """Return a WarningsRecorder instance that provides these methods: @@ -190,7 +191,8 @@ class WarningsChecker(WarningsRecorder): if not any(issubclass(r.category, self.expected_warning) for r in self): __tracebackhide__ = True - pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " - "The list of emitted warnings is: {1}.".format( - self.expected_warning, - [each.message for each in self])) + from _pytest.runner import fail + fail("DID NOT WARN. No warnings of type {0} was emitted. " + "The list of emitted warnings is: {1}.".format( + self.expected_warning, + [each.message for each in self])) diff --git a/_pytest/runner.py b/_pytest/runner.py index ee1683042..87dd240c8 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -6,7 +6,6 @@ import sys from time import time import py -import pytest from _pytest._code.code import TerminalRepr, ExceptionInfo @@ -257,7 +256,7 @@ def pytest_runtest_makereport(item, call): if not isinstance(excinfo, ExceptionInfo): outcome = "failed" longrepr = excinfo - elif excinfo.errisinstance(pytest.skip.Exception): + elif excinfo.errisinstance(skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() longrepr = (str(r.path), r.lineno, r.message) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index f2ee41518..c68cdea47 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -6,9 +6,9 @@ import sys import traceback import py -import pytest +from _pytest.config import hookimpl from _pytest.mark import MarkInfo, MarkDecorator - +from _pytest.runner import fail, skip def pytest_addoption(parser): group = parser.getgroup("general") @@ -25,6 +25,8 @@ def pytest_addoption(parser): def pytest_configure(config): if config.option.runxfail: + # yay a hack + import pytest old = pytest.xfail config._cleanup.append(lambda: setattr(pytest, "xfail", old)) @@ -57,7 +59,7 @@ def pytest_configure(config): ) -class XFailed(pytest.fail.Exception): +class XFailed(fail.Exception): """ raised from an explicit call to pytest.xfail() """ @@ -98,15 +100,15 @@ class MarkEvaluator(object): except Exception: self.exc = sys.exc_info() if isinstance(self.exc[1], SyntaxError): - msg = [" " * (self.exc[1].offset + 4) + "^",] + msg = [" " * (self.exc[1].offset + 4) + "^", ] msg.append("SyntaxError: invalid syntax") else: msg = traceback.format_exception_only(*self.exc[:2]) - pytest.fail("Error evaluating %r expression\n" - " %s\n" - "%s" - %(self.name, self.expr, "\n".join(msg)), - pytrace=False) + fail("Error evaluating %r expression\n" + " %s\n" + "%s" + % (self.name, self.expr, "\n".join(msg)), + pytrace=False) def _getglobals(self): d = {'os': os, 'sys': sys, 'config': self.item.config} @@ -138,7 +140,7 @@ class MarkEvaluator(object): # XXX better be checked at collection time msg = "you need to specify reason=STRING " \ "when using booleans as conditions." - pytest.fail(msg) + fail(msg) result = bool(expr) if result: self.result = True @@ -162,7 +164,7 @@ class MarkEvaluator(object): return expl -@pytest.hookimpl(tryfirst=True) +@hookimpl(tryfirst=True) def pytest_runtest_setup(item): # Check if skip or skipif are specified as pytest marks @@ -171,23 +173,23 @@ def pytest_runtest_setup(item): eval_skipif = MarkEvaluator(item, 'skipif') if eval_skipif.istrue(): item._evalskip = eval_skipif - pytest.skip(eval_skipif.getexplanation()) + skip(eval_skipif.getexplanation()) skip_info = item.keywords.get('skip') if isinstance(skip_info, (MarkInfo, MarkDecorator)): item._evalskip = True if 'reason' in skip_info.kwargs: - pytest.skip(skip_info.kwargs['reason']) + skip(skip_info.kwargs['reason']) elif skip_info.args: - pytest.skip(skip_info.args[0]) + skip(skip_info.args[0]) else: - pytest.skip("unconditional skip") + skip("unconditional skip") item._evalxfail = MarkEvaluator(item, 'xfail') check_xfail_no_run(item) -@pytest.mark.hookwrapper +@hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): check_xfail_no_run(pyfuncitem) outcome = yield @@ -217,7 +219,7 @@ def check_strict_xfail(pyfuncitem): pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False) -@pytest.hookimpl(hookwrapper=True) +@hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() @@ -237,7 +239,7 @@ def pytest_runtest_makereport(item, call): rep.wasxfail = rep.longrepr elif item.config.option.runxfail: pass # don't interefere - elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception): + elif call.excinfo and call.excinfo.errisinstance(xfail.Exception): rep.wasxfail = "reason: " + call.excinfo.value.msg rep.outcome = "skipped" elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \ From bb750a794566c2e2b10066d37ceba49cdfba7238 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:11:19 +0100 Subject: [PATCH 108/153] add missed file --- testing/test_modimport.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 testing/test_modimport.py diff --git a/testing/test_modimport.py b/testing/test_modimport.py new file mode 100644 index 000000000..9c7293bb8 --- /dev/null +++ b/testing/test_modimport.py @@ -0,0 +1,21 @@ +import py +import subprocess +import sys +import pytest +import _pytest + +MODSET = [ + x for x in py.path.local(_pytest.__file__).dirpath().visit('*.py') + if x.purebasename != '__init__' +] + + +@pytest.mark.parametrize('modfile', MODSET, ids=lambda x: x.purebasename) +def test_fileimport(modfile): + res = subprocess.call([ + sys.executable, + '-c', 'import sys, py; py.path.local(sys.argv[1]).pyimport()', + modfile.strpath, + ]) + if res: + pytest.fail("command result %s" % res) From 4d31ea83160331d8eb97de477bc3476d1a39653f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 15 Mar 2017 18:26:01 +0100 Subject: [PATCH 109/153] add a comment explaining the modimport tests --- testing/test_modimport.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testing/test_modimport.py b/testing/test_modimport.py index 9c7293bb8..2ab86bf7a 100644 --- a/testing/test_modimport.py +++ b/testing/test_modimport.py @@ -12,6 +12,10 @@ MODSET = [ @pytest.mark.parametrize('modfile', MODSET, ids=lambda x: x.purebasename) def test_fileimport(modfile): + # this test ensures all internal packages can import + # without needing the pytest namespace being set + # this is critical for the initialization of xdist + res = subprocess.call([ sys.executable, '-c', 'import sys, py; py.path.local(sys.argv[1]).pyimport()', From 7cdefce656119ec023bf371f14076c76f6782ceb Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 11:35:00 +0100 Subject: [PATCH 110/153] fix up oversights --- _pytest/fixtures.py | 5 +-- _pytest/python.py | 2 +- _pytest/recwarn.py | 1 - _pytest/skipping.py | 16 ++++++--- pytest.py | 84 +++++++++++++++++++++++++-------------------- 5 files changed, 61 insertions(+), 47 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 2710da792..714a8b406 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -682,8 +682,9 @@ def fail_fixturefunc(fixturefunc, msg): fs, lineno = getfslineno(fixturefunc) location = "%s:%s" % (fs, lineno+1) source = _pytest._code.Source(fixturefunc) - pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, - pytrace=False) + fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, + pytrace=False) + def call_fixture_func(fixturefunc, request, kwargs): yieldctx = is_generator(fixturefunc) diff --git a/_pytest/python.py b/_pytest/python.py index 985b476ca..4f33a356b 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1223,8 +1223,8 @@ def raises(expected_exception, *args, **kwargs): return _pytest._code.ExceptionInfo() fail(message) -raises.Exception = fail.Exception +raises.Exception = fail.Exception class RaisesContext(object): diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index 73325cb8e..7dce842f6 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -7,7 +7,6 @@ import _pytest._code import py import sys import warnings - from _pytest.fixtures import yield_fixture diff --git a/_pytest/skipping.py b/_pytest/skipping.py index c68cdea47..e7922da7d 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -204,7 +204,7 @@ def check_xfail_no_run(item): evalxfail = item._evalxfail if evalxfail.istrue(): if not evalxfail.get('run', True): - pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) + xfail("[NOTRUN] " + evalxfail.getexplanation()) def check_strict_xfail(pyfuncitem): @@ -216,7 +216,7 @@ def check_strict_xfail(pyfuncitem): if is_strict_xfail: del pyfuncitem._evalxfail explanation = evalxfail.getexplanation() - pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False) + fail('[XPASS(strict)] ' + explanation, pytrace=False) @hookimpl(hookwrapper=True) @@ -307,12 +307,14 @@ def pytest_terminal_summary(terminalreporter): for line in lines: tr._tw.line(line) + def show_simple(terminalreporter, lines, stat, format): failed = terminalreporter.stats.get(stat) if failed: for rep in failed: pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) - lines.append(format %(pos,)) + lines.append(format % (pos,)) + def show_xfailed(terminalreporter, lines): xfailed = terminalreporter.stats.get("xfailed") @@ -324,13 +326,15 @@ def show_xfailed(terminalreporter, lines): if reason: lines.append(" " + str(reason)) + def show_xpassed(terminalreporter, lines): xpassed = terminalreporter.stats.get("xpassed") if xpassed: for rep in xpassed: pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) reason = rep.wasxfail - lines.append("XPASS %s %s" %(pos, reason)) + lines.append("XPASS %s %s" % (pos, reason)) + def cached_eval(config, expr, d): if not hasattr(config, '_evalcache'): @@ -355,6 +359,7 @@ def folded_skips(skipped): l.append((len(events),) + key) return l + def show_skipped(terminalreporter, lines): tr = terminalreporter skipped = tr.stats.get('skipped', []) @@ -370,5 +375,6 @@ def show_skipped(terminalreporter, lines): for num, fspath, lineno, reason in fskips: if reason.startswith("Skipped: "): reason = reason[9:] - lines.append("SKIP [%d] %s:%d: %s" % + lines.append( + "SKIP [%d] %s:%d: %s" % (num, fspath, lineno, reason)) diff --git a/pytest.py b/pytest.py index 8185d6503..d720ee866 100644 --- a/pytest.py +++ b/pytest.py @@ -2,41 +2,7 @@ """ pytest: unit and functional testing with Python. """ -__all__ = [ - 'main', - 'UsageError', - 'cmdline', - 'hookspec', - 'hookimpl', - '__version__', - 'register_assert_rewrite', - 'freeze_includes', - 'set_trace', - 'warns', - 'deprecated_call', - 'fixture', - 'yield_fixture', - 'fail', - 'skip', - 'xfail', - 'importorskip', - 'exit', - 'mark', - '_fillfuncargs', - - 'Item', - 'File', - 'Collector', - 'Session', - - -] - -if __name__ == '__main__': # if run as a script or by 'python -m pytest' - # we trigger the below "else" condition by the following import - import pytest - raise SystemExit(pytest.main()) # else we are imported @@ -60,10 +26,52 @@ from _pytest.python import ( Module, Class, Instance, Function, Generator, ) - set_trace = __pytestPDB.set_trace +__all__ = [ + 'main', + 'UsageError', + 'cmdline', + 'hookspec', + 'hookimpl', + '__version__', + 'register_assert_rewrite', + 'freeze_includes', + 'set_trace', + 'warns', + 'deprecated_call', + 'fixture', + 'yield_fixture', + 'fail', + 'skip', + 'xfail', + 'importorskip', + 'exit', + 'mark', + 'approx', + '_fillfuncargs', -from _pytest.compat import _setup_collect_fakemodule -_preloadplugins() # to populate pytest.* namespace so help(pytest) works -_setup_collect_fakemodule() \ No newline at end of file + 'Item', + 'File', + 'Collector', + 'Session', + 'Module', + 'Class', + 'Instance', + 'Function', + 'Generator', + 'raises', + + +] + +if __name__ == '__main__': + # if run as a script or by 'python -m pytest' + # we trigger the below "else" condition by the following import + import pytest + raise SystemExit(pytest.main()) +else: + + from _pytest.compat import _setup_collect_fakemodule + _preloadplugins() # to populate pytest.* namespace so help(pytest) works + _setup_collect_fakemodule() From 147bb8aea5b4c50f0ce432bf6d0cdf547c24cf90 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 20:05:22 +0100 Subject: [PATCH 111/153] correct setting pytest.config --- _pytest/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/main.py b/_pytest/main.py index 623273655..f18466162 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -78,7 +78,7 @@ def pytest_addoption(parser): def pytest_configure(config): - pytest.config = config # compatibiltiy + __import__('pytest').config = config # compatibiltiy def wrap_session(config, doit): From c9ab4213986c5f074a69b7abd68f0dfca8db89ba Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 20:31:58 +0100 Subject: [PATCH 112/153] fix python2 only import loop failure --- _pytest/fixtures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 714a8b406..2c7c9e771 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -18,12 +18,12 @@ from _pytest.compat import ( ) from _pytest.runner import fail from _pytest.compat import FuncargnamesCompatAttr -from _pytest import python def pytest_sessionstart(session): + import _pytest.python scopename2class.update({ - 'class': python.Class, - 'module': python.Module, + 'class': _pytest.python.Class, + 'module': _pytest.python.Module, 'function': _pytest.main.Item, }) session._fixturemanager = FixtureManager(session) From efe03400d8a1a32666fdbc8b9f1c38d7c085322d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 16 Mar 2017 20:51:22 +0100 Subject: [PATCH 113/153] fixup nose/pytest plugins --- _pytest/nose.py | 18 +++++++++--------- _pytest/unittest.py | 46 ++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/_pytest/nose.py b/_pytest/nose.py index 828a919f9..9d4fc0b6e 100644 --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -4,8 +4,8 @@ from __future__ import absolute_import, division, print_function import sys import py -import pytest -from _pytest import unittest +from _pytest import unittest, runner, python +from _pytest.config import hookimpl def get_skip_exceptions(): @@ -20,19 +20,19 @@ def get_skip_exceptions(): def pytest_runtest_makereport(item, call): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): # let's substitute the excinfo with a pytest.skip one - call2 = call.__class__(lambda: - pytest.skip(str(call.excinfo.value)), call.when) + call2 = call.__class__( + lambda: runner.skip(str(call.excinfo.value)), call.when) call.excinfo = call2.excinfo -@pytest.hookimpl(trylast=True) +@hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): - if isinstance(item.parent, pytest.Generator): + if isinstance(item.parent, python.Generator): gen = item.parent if not hasattr(gen, '_nosegensetup'): call_optional(gen.obj, 'setup') - if isinstance(gen.parent, pytest.Instance): + if isinstance(gen.parent, python.Instance): call_optional(gen.parent.obj, 'setup') gen._nosegensetup = True if not call_optional(item.obj, 'setup'): @@ -51,14 +51,14 @@ def teardown_nose(item): def pytest_make_collect_report(collector): - if isinstance(collector, pytest.Generator): + if isinstance(collector, python.Generator): call_optional(collector.obj, 'setup') def is_potential_nosetest(item): # extra check needed since we do not do nose style setup/teardown # on direct unittest style classes - return isinstance(item, pytest.Function) and \ + return isinstance(item, python.Function) and \ not isinstance(item, unittest.TestCaseFunction) diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 5a8cb9d66..0cf0f1726 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -4,11 +4,12 @@ from __future__ import absolute_import, division, print_function import sys import traceback -import pytest # for transferring markers import _pytest._code -from _pytest.python import transfer_markers -from _pytest.skipping import MarkEvaluator +from _pytest.config import hookimpl +from _pytest.runner import fail, skip +from _pytest.python import transfer_markers, Class, Module, Function +from _pytest.skipping import MarkEvaluator, xfail def pytest_pycollect_makeitem(collector, name, obj): @@ -22,11 +23,11 @@ def pytest_pycollect_makeitem(collector, name, obj): return UnitTestCase(name, parent=collector) -class UnitTestCase(pytest.Class): +class UnitTestCase(Class): # marker for fixturemanger.getfixtureinfo() # to declare that our children do not support funcargs nofuncargs = True - + def setup(self): cls = self.obj if getattr(cls, '__unittest_skip__', False): @@ -46,7 +47,7 @@ class UnitTestCase(pytest.Class): return self.session._fixturemanager.parsefactories(self, unittest=True) loader = TestLoader() - module = self.getparent(pytest.Module).obj + module = self.getparent(Module).obj foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) @@ -65,7 +66,7 @@ class UnitTestCase(pytest.Class): yield TestCaseFunction('runTest', parent=self) -class TestCaseFunction(pytest.Function): +class TestCaseFunction(Function): _excinfo = None def setup(self): @@ -110,36 +111,37 @@ class TestCaseFunction(pytest.Function): try: l = traceback.format_exception(*rawexcinfo) l.insert(0, "NOTE: Incompatible Exception Representation, " - "displaying natively:\n\n") - pytest.fail("".join(l), pytrace=False) - except (pytest.fail.Exception, KeyboardInterrupt): + "displaying natively:\n\n") + fail("".join(l), pytrace=False) + except (fail.Exception, KeyboardInterrupt): raise except: - pytest.fail("ERROR: Unknown Incompatible Exception " - "representation:\n%r" %(rawexcinfo,), pytrace=False) + fail("ERROR: Unknown Incompatible Exception " + "representation:\n%r" % (rawexcinfo,), pytrace=False) except KeyboardInterrupt: raise - except pytest.fail.Exception: + except fail.Exception: excinfo = _pytest._code.ExceptionInfo() self.__dict__.setdefault('_excinfo', []).append(excinfo) def addError(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) + def addFailure(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) def addSkip(self, testcase, reason): try: - pytest.skip(reason) - except pytest.skip.Exception: + skip(reason) + except skip.Exception: self._evalskip = MarkEvaluator(self, 'SkipTest') self._evalskip.result = True self._addexcinfo(sys.exc_info()) def addExpectedFailure(self, testcase, rawexcinfo, reason=""): try: - pytest.xfail(str(reason)) - except pytest.xfail.Exception: + xfail(str(reason)) + except xfail.Exception: self._addexcinfo(sys.exc_info()) def addUnexpectedSuccess(self, testcase, reason=""): @@ -179,13 +181,14 @@ class TestCaseFunction(pytest.Function): self._testcase.debug() def _prunetraceback(self, excinfo): - pytest.Function._prunetraceback(self, excinfo) + Function._prunetraceback(self, excinfo) traceback = excinfo.traceback.filter( - lambda x:not x.frame.f_globals.get('__unittest')) + lambda x: not x.frame.f_globals.get('__unittest')) if traceback: excinfo.traceback = traceback -@pytest.hookimpl(tryfirst=True) + +@hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): if item._excinfo: @@ -197,7 +200,8 @@ def pytest_runtest_makereport(item, call): # twisted trial support -@pytest.hookimpl(hookwrapper=True) + +@hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): if isinstance(item, TestCaseFunction) and \ 'twisted.trial.unittest' in sys.modules: From 6165939b0d957497208a93e0737ec0d937cd791b Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 20 Mar 2017 15:47:25 +0100 Subject: [PATCH 114/153] fix rebase mistakes --- _pytest/python.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 4f33a356b..1f540c84f 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -390,7 +390,8 @@ def transfer_markers(funcobj, cls, mod): if not _marked(funcobj, pytestmark): pytestmark(funcobj) -class Module(pytest.File, PyCollector): + +class Module(main.File, PyCollector): """ Collector for test classes and functions. """ def _getobj(self): @@ -775,7 +776,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import extract_argvalue, MARK_GEN + from _pytest.mark import MARK_GEN, ParameterSet from py.io import saferepr if not isinstance(argnames, (tuple, list)): @@ -788,12 +789,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): for x in argvalues] del argvalues - if not parameters: fs, lineno = getfslineno(self.function) reason = "got empty parameter set %r, function %s at %s:%d" % ( argnames, self.function.__name__, fs, lineno) - mark = pytest.mark.skip(reason=reason) + mark = MARK_GEN.skip(reason=reason) parameters.append(ParameterSet( values=(NOTSET,) * len(argnames), marks=[mark], @@ -870,7 +870,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): if funcargs is not None: for name in funcargs: if name not in self.fixturenames: - pytest.fail("funcarg %r not used in this function." % name) + fail("funcarg %r not used in this function." % name) else: funcargs = {} if id is None: @@ -945,6 +945,7 @@ def _idval(val, argname, idx, idfn, config=None): return val.__name__ return str(argname)+str(idx) + def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): if parameterset.id is not None: return parameterset.id @@ -955,6 +956,7 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None): else: return _escape_strings(ids[idx]) + def idmaker(argnames, parametersets, idfn=None, ids=None, config=None): ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config) for valindex, parameterset in enumerate(parametersets)] @@ -1033,6 +1035,7 @@ def showfixtures(config): from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) + def _showfixtures_main(config, session): import _pytest.config session.perform_collect() From ebeba79be373a06b4a5fccce3fe1fadff60eddaa Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 20 Mar 2017 17:58:34 +0100 Subject: [PATCH 115/153] remove the namespace hook from mark after the param feature merge --- _pytest/mark.py | 6 ++---- pytest.py | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index 23ec44c29..c34df1239 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -66,10 +66,8 @@ class MarkerError(Exception): """Error in use of a pytest marker/attribute.""" -def pytest_namespace(): - return { - 'param': ParameterSet.param, - } +def param(*values, **kw): + return ParameterSet.param(*values, **kw) def pytest_addoption(parser): diff --git a/pytest.py b/pytest.py index d720ee866..4e4ccb32d 100644 --- a/pytest.py +++ b/pytest.py @@ -17,7 +17,7 @@ from _pytest import __version__ from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.recwarn import warns, deprecated_call from _pytest.runner import fail, skip, importorskip, exit -from _pytest.mark import MARK_GEN as mark +from _pytest.mark import MARK_GEN as mark, param from _pytest.skipping import xfail from _pytest.main import Item, Collector, File, Session from _pytest.fixtures import fillfixtures as _fillfuncargs @@ -48,6 +48,7 @@ __all__ = [ 'importorskip', 'exit', 'mark', + 'param', 'approx', '_fillfuncargs', From afb1778294bf4d4cd93d8695873d0101c3db18bf Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 28 Mar 2017 10:28:43 +0200 Subject: [PATCH 116/153] put in a singular namespace hook to work around the strange issue --- _pytest/main.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/_pytest/main.py b/_pytest/main.py index f18466162..ec6657ae9 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -77,6 +77,16 @@ def pytest_addoption(parser): help="base temporary directory for this test run.") + +def pytest_namespace(): + """keeping this one works around a deeper startup issue in pytest + + i tried to find it for a while but the amount of time turned unsustainable, + so i put a hack in to revisit later + """ + return {} + + def pytest_configure(config): __import__('pytest').config = config # compatibiltiy From 417b54abed921e4520b435cfcc87f342d376a278 Mon Sep 17 00:00:00 2001 From: reut Date: Wed, 5 Apr 2017 13:03:11 +0000 Subject: [PATCH 117/153] added option to unicode plugin name --- _pytest/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/config.py b/_pytest/config.py index db37280ca..306d9a123 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -415,7 +415,8 @@ class PytestPluginManager(PluginManager): # "terminal" or "capture". Those plugins are registered under their # basename for historic purposes but must be imported with the # _pytest prefix. - assert isinstance(modname, str), "module name as string required, got %r" % modname + assert isinstance(modname, (py.builtin.text, str)), "module name as text required, got %r" % modname + modname = str(modname) if self.get_plugin(modname) is not None: return if modname in builtin_plugins: From 333ce9849d55b9f8de16d1c1ca4138a27281b6cf Mon Sep 17 00:00:00 2001 From: reut Date: Thu, 6 Apr 2017 10:54:47 +0000 Subject: [PATCH 118/153] added acceptance test for unicode plugin names --- testing/acceptance_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index fe02d82f0..85dd83969 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -793,3 +793,17 @@ def test_zipimport_hook(testdir, tmpdir): assert result.ret == 0 result.stderr.fnmatch_lines(['*not found*foo*']) assert 'INTERNALERROR>' not in result.stdout.str() + + +def test_import_plugin_unicode_name(testdir): + testdir.makepyfile( + myplugin='', + ) + testdir.makepyfile(""" + def test(): pass + """) + testdir.makeconftest(""" + pytest_plugins = [u'myplugin'] + """) + r = testdir.runpytest() + assert r.ret == 0 From 24ac923938efb30469ede4457a0d06f9976dade2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 6 Apr 2017 12:42:17 -0300 Subject: [PATCH 119/153] Add CHANGELOG entry --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 05c868079..6eae9b1d1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -65,6 +65,9 @@ Changes * Add ``venv`` to the default ``norecursedirs`` setting. Thanks `@The-Compiler`_ for the PR. + +* ``PluginManager.import_plugin`` now accepts unicode plugin names in Python 2. + Thanks `@reutsharabani`_ for the PR. Bug Fixes @@ -82,6 +85,7 @@ Bug Fixes .. _@fogo: https://github.com/fogo .. _@mandeep: https://github.com/mandeep .. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE +.. _@reutsharabani: https://github.com/reutsharabani .. _@unsignedint: https://github.com/unsignedint .. _@Kriechi: https://github.com/Kriechi From 1b5f898dc56dca92dc8a9a2ca10daf1e7cf9291f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 6 Apr 2017 14:58:07 -0300 Subject: [PATCH 120/153] Ensure rewritten modules don't inherit __future__ flags from pytest In a recent refactoring we enabled all __future__ features in pytest modules, but that has the unwanted side effect of propagating those features to compile()'d modules inside assertion rewriting, unless we pass dont_inherit=False to compile(). --- _pytest/assertion/rewrite.py | 2 +- testing/test_assertrewrite.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 484d5bd8b..f7e255efd 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -337,7 +337,7 @@ def _rewrite_test(config, fn): return None, None rewrite_asserts(tree, fn, config) try: - co = compile(tree, fn.strpath, "exec") + co = compile(tree, fn.strpath, "exec", dont_inherit=True) except SyntaxError: # It's possible that this error is from some bug in the # assertion rewriting, but I don't know of a fast way to tell. diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index f4fc0c464..11b5ce051 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -712,6 +712,24 @@ def test_rewritten(): result.stdout.fnmatch_lines(['*= 1 passed in *=*']) assert 'pytest-warning summary' not in result.stdout.str() + @pytest.mark.skipif(sys.version_info[0] > 2, reason='python 2 only') + def test_rewrite_future_imports(self, testdir): + """Test that rewritten modules don't inherit the __future__ flags + from the assertrewrite module. + + assertion.rewrite imports __future__.division (and others), so + ensure rewritten modules don't inherit those flags. + + The test below will fail if __future__.division is enabled + """ + testdir.makepyfile(''' + def test(): + x = 1 / 2 + assert type(x) is int + ''') + result = testdir.runpytest() + assert result.ret == 0 + class TestAssertionRewriteHookDetails(object): def test_loader_is_package_false_for_module(self, testdir): From 0ab85e7a9cfc6f36a37a6e79dbb73b2b83c997bc Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Mon, 10 Apr 2017 17:42:19 +0900 Subject: [PATCH 121/153] Changed behavior if --lf and --ff are both used. When using both --last-failed/--lf and --failed-first/--ff pytest would run all tests with failed tests first (as if --lf was not provied). This patch changes it so that when using both flags, only the last failed tests are run. This makes it easier to set --ff as the default behavior via the config file and then selectively use --lf to only run the last failed tests. --- _pytest/cacheprovider.py | 6 +++--- testing/test_cache.py | 23 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 0b8e71a71..4cecc771d 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -139,11 +139,11 @@ class LFPlugin(object): # running a subset of all tests with recorded failures outside # of the set of tests currently executing pass - elif self.config.getvalue("failedfirst"): - items[:] = previously_failed + previously_passed - else: + elif self.config.getvalue("lf"): items[:] = previously_failed config.hook.pytest_deselected(items=previously_passed) + else: + items[:] = previously_failed + previously_passed def pytest_sessionfinish(self, session): config = self.config diff --git a/testing/test_cache.py b/testing/test_cache.py index f5904be39..672371596 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -192,13 +192,34 @@ class TestLastFailed(object): "test_a.py*", "test_b.py*", ]) - result = testdir.runpytest("--lf", "--ff") + result = testdir.runpytest("--ff") # Test order will be failing tests firs result.stdout.fnmatch_lines([ "test_b.py*", "test_a.py*", ]) + def test_lastfailed_failedfirst_order(self, testdir): + testdir.tmpdir.join('test_a.py').write(_pytest._code.Source(""" + def test_always_passes(): + assert 1 + """)) + testdir.tmpdir.join('test_b.py').write(_pytest._code.Source(""" + def test_always_fails(): + assert 0 + """)) + result = testdir.runpytest() + # Test order will be collection order; alphabetical + result.stdout.fnmatch_lines([ + "test_a.py*", + "test_b.py*", + ]) + result = testdir.runpytest("--lf", "--ff") + # Test order will be failing tests firs + result.stdout.fnmatch_lines([ + "test_b.py*", + ]) + def test_lastfailed_difference_invocations(self, testdir, monkeypatch): monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1) testdir.makepyfile(test_a=""" From 08d83a5c6a7c74cd00d21887731ad26a4412d7fd Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Mon, 10 Apr 2017 17:50:18 +0900 Subject: [PATCH 122/153] updated changelog and authors files --- AUTHORS | 1 + CHANGELOG.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/AUTHORS b/AUTHORS index 3d59906b2..3d7f10d5e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -78,6 +78,7 @@ Javier Romero Jeff Widman John Towler Jon Sonesen +Jonas Obrist Jordan Guymon Joshua Bronson Jurko Gospodnetić diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6eae9b1d1..ea758a317 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -69,6 +69,9 @@ Changes * ``PluginManager.import_plugin`` now accepts unicode plugin names in Python 2. Thanks `@reutsharabani`_ for the PR. +* fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run. + Thanks `@ojii`_ for the PR. + Bug Fixes --------- @@ -88,6 +91,7 @@ Bug Fixes .. _@reutsharabani: https://github.com/reutsharabani .. _@unsignedint: https://github.com/unsignedint .. _@Kriechi: https://github.com/Kriechi +.. _@ojii: https://github.com/ojii .. _#1407: https://github.com/pytest-dev/pytest/issues/1407 @@ -101,6 +105,7 @@ Bug Fixes .. _#2147: https://github.com/pytest-dev/pytest/issues/2147 .. _#2208: https://github.com/pytest-dev/pytest/issues/2208 .. _#2228: https://github.com/pytest-dev/pytest/issues/2228 +.. _#2308: https://github.com/pytest-dev/pytest/issues/2308 3.0.8 (unreleased) From 1125786e787b320dd26d8c720c6ce81c72a1c838 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 11 Apr 2017 17:55:55 -0300 Subject: [PATCH 123/153] Improve --lf/--ff test as commented during review --- testing/test_cache.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/testing/test_cache.py b/testing/test_cache.py index 672371596..600b5e6d9 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -200,14 +200,16 @@ class TestLastFailed(object): ]) def test_lastfailed_failedfirst_order(self, testdir): - testdir.tmpdir.join('test_a.py').write(_pytest._code.Source(""" - def test_always_passes(): - assert 1 - """)) - testdir.tmpdir.join('test_b.py').write(_pytest._code.Source(""" - def test_always_fails(): - assert 0 - """)) + testdir.makepyfile(**{ + 'test_a.py': """ + def test_always_passes(): + assert 1 + """, + 'test_b.py': """ + def test_always_fails(): + assert 0 + """, + }) result = testdir.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines([ @@ -219,6 +221,7 @@ class TestLastFailed(object): result.stdout.fnmatch_lines([ "test_b.py*", ]) + assert 'test_a.py' not in result.stdout.str() def test_lastfailed_difference_invocations(self, testdir, monkeypatch): monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1) From 83c508eea3dfc4f79f85c8bc95e3649a4a15617a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Mar 2017 21:02:52 -0300 Subject: [PATCH 124/153] Verify hooks after collection completes Fix #1821 --- CHANGELOG.rst | 12 +++++++++--- _pytest/config.py | 1 - _pytest/main.py | 1 + testing/acceptance_test.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ea758a317..acd7172f6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -53,11 +53,16 @@ Changes * Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError`` to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR. -* fix `#2013`_: turn RecordedWarning into namedtupe, - to give it a comprehensible repr while preventing unwarranted modification +* fix `#2013`_: turn RecordedWarning into ``namedtuple``, + to give it a comprehensible repr while preventing unwarranted modification. * fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func. - Thanks `@RonnyPfannschmidt`_ for the Report and PR + Thanks `@RonnyPfannschmidt`_ for the report and PR. + +* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This + makes it easy to write hooks for plugins which will be loaded during collection, for example using the + ``pytest_plugins`` special variable (`#1821`_). + Thanks `@nicoddemus`_ for the PR. * Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an additional parameter. @@ -96,6 +101,7 @@ Bug Fixes .. _#1407: https://github.com/pytest-dev/pytest/issues/1407 .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 +.. _#1821: https://github.com/pytest-dev/pytest/issues/1821 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1952: https://github.com/pytest-dev/pytest/pull/1952 .. _#2007: https://github.com/pytest-dev/pytest/issues/2007 diff --git a/_pytest/config.py b/_pytest/config.py index 306d9a123..140964aac 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -54,7 +54,6 @@ def main(args=None, plugins=None): return 4 else: try: - config.pluginmanager.check_pending() return config.hook.pytest_cmdline_main(config=config) finally: config._ensure_unconfigure() diff --git a/_pytest/main.py b/_pytest/main.py index 3d7b456d2..6f350616b 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -596,6 +596,7 @@ class Session(FSCollector): hook = self.config.hook try: items = self._perform_collect(args, genitems) + self.config.pluginmanager.check_pending() hook.pytest_collection_modifyitems(session=self, config=self.config, items=items) finally: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 85dd83969..00abfc38d 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -807,3 +807,31 @@ def test_import_plugin_unicode_name(testdir): """) r = testdir.runpytest() assert r.ret == 0 + + +def test_deferred_hook_checking(testdir): + """ + Check hooks as late as possible (#1821). + """ + testdir.syspathinsert() + testdir.makepyfile(**{ + 'plugin.py': """ + class Hooks: + def pytest_my_hook(self, config): + pass + + def pytest_configure(config): + config.pluginmanager.add_hookspecs(Hooks) + """, + 'conftest.py': """ + pytest_plugins = ['plugin'] + def pytest_my_hook(config): + return 40 + """, + 'test_foo.py': """ + def test(request): + assert request.config.hook.pytest_my_hook(config=request.config) == [40] + """ + }) + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 passed *']) From 0baf5e149988c55da937385aa0a4e40bd23663b5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Mar 2017 21:29:46 -0300 Subject: [PATCH 125/153] Fix test that expected "unknown hook" error on stderr --- testing/test_helpconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 6eecbfd37..41fa953ad 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -33,7 +33,7 @@ def test_hookvalidation_unknown(testdir): """) result = testdir.runpytest() assert result.ret != 0 - result.stderr.fnmatch_lines([ + result.stdout.fnmatch_lines([ '*unknown hook*pytest_hello*' ]) From 731776702d6f50b65b353709f206257bb2a73b78 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 11 Apr 2017 21:53:01 -0300 Subject: [PATCH 126/153] Fix hook name in LsofFdLeakChecker --- _pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 6381595ee..6dd6772b2 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -88,7 +88,7 @@ class LsofFdLeakChecker(object): return True @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_runtest_item(self, item): + def pytest_runtest_protocol(self, item): lines1 = self.get_open_files() yield if hasattr(sys, "pypy_version_info"): From d9a2e70155a85e769d32a1e1fbe7e655203e9575 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 13 Apr 2017 17:34:48 -0300 Subject: [PATCH 127/153] Change LsofFdLeakChecker to emit a warning instead of failing when detecting leaked FDs Related to #2366 --- _pytest/pytester.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 6dd6772b2..0ee0047e0 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -107,7 +107,8 @@ class LsofFdLeakChecker(object): error.extend([str(f) for f in lines2]) error.append(error[0]) error.append("*** function %s:%s: %s " % item.location) - pytest.fail("\n".join(error), pytrace=False) + error.append("See issue #2366") + item.warn('', "\n".join(error)) # XXX copied from execnet's conftest.py - needs to be merged From cac82e71d84a192a8d325618fa60bc25f9aa6158 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 13 Apr 2017 18:01:24 -0300 Subject: [PATCH 128/153] Improve item.warn handling of fslocation parameter Just pass fslocation forward and let the hook implementer decide what to do with the parameter --- _pytest/main.py | 3 --- _pytest/terminal.py | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 6f350616b..af8804137 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -309,9 +309,6 @@ class Node(object): fslocation = getattr(self, "location", None) if fslocation is None: fslocation = getattr(self, "fspath", None) - else: - fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1) - self.ihook.pytest_logwarning.call_historic(kwargs=dict( code=code, message=message, nodeid=self.nodeid, fslocation=fslocation)) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 528772764..8c3bacf2d 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -109,10 +109,10 @@ class WarningReport(object): if self.nodeid: return self.nodeid if self.fslocation: - if isinstance(self.fslocation, tuple) and len(self.fslocation) == 2: - filename, linenum = self.fslocation + if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2: + filename, linenum = self.fslocation[:2] relpath = py.path.local(filename).relto(config.invocation_dir) - return '%s:%d' % (relpath, linenum) + return '%s:%s' % (relpath, linenum) else: return str(self.fslocation) return None From c3aee4b1e64efa7893bcacbfde793e04f01a8dda Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 21 Aug 2016 18:13:32 +0200 Subject: [PATCH 129/153] second take at setuptools_scm since setuptools 18.6 fixes the issues with develop installs https://github.com/pypa/setuptools/blob/master/CHANGES.rst#186 https://github.com/pypa/setuptools/issues/439 --- .gitignore | 3 +++ HOWTORELEASE.rst | 8 ++------ _pytest/__init__.py | 7 +++++-- setup.py | 14 ++++---------- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 0e42b11ff..3b7ec9fac 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ include/ *~ .hypothesis/ +# autogenerated +_pytest/_version.py +# setuptools .eggs/ doc/*/_build diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index 372ecf7f1..523ed41cc 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -3,11 +3,9 @@ How to release pytest Note: this assumes you have already registered on pypi. -1. Bump version numbers in ``_pytest/__init__.py`` (``setup.py`` reads it). +1. Check and finalize ``CHANGELOG.rst``. -2. Check and finalize ``CHANGELOG.rst``. - -3. Write ``doc/en/announce/release-VERSION.txt`` and include +2. Write ``doc/en/announce/release-VERSION.txt`` and include it in ``doc/en/announce/index.txt``. Run this command to list names of authors involved:: git log $(git describe --abbrev=0 --tags)..HEAD --format='%aN' | sort -u @@ -81,5 +79,3 @@ Note: this assumes you have already registered on pypi. 9. Push ``master`` and ``features``. c. **major release (3.0.0)**: same steps as that of a **minor release** - - diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 0a37f23b2..a6f201091 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,5 @@ -# -__version__ = '3.1.0.dev0' +__all__ = ['__version__'] +try: + from ._version import version as __version__ +except ImportError: + __version__ = None # broken installation, we don't even try diff --git a/setup.py b/setup.py index 1d0630cd2..3cba379e4 100644 --- a/setup.py +++ b/setup.py @@ -18,15 +18,6 @@ classifiers = ['Development Status :: 6 - Mature', with open('README.rst') as fd: long_description = fd.read() -def get_version(): - p = os.path.join(os.path.dirname( - os.path.abspath(__file__)), "_pytest", "__init__.py") - with open(p) as f: - for line in f.readlines(): - if "__version__" in line: - return line.strip().split("=")[-1].strip(" '") - raise ValueError("could not read version") - def has_environment_marker_support(): """ @@ -63,7 +54,9 @@ def main(): name='pytest', description='pytest: simple powerful testing with Python', long_description=long_description, - version=get_version(), + use_scm_version={ + 'write_to': '_pytest/_version.py', + }, url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], @@ -74,6 +67,7 @@ def main(): keywords="test unittest", cmdclass={'test': PyTest}, # the following should be enabled for release + setup_requires=['setuptools-scm'], install_requires=install_requires, extras_require=extras_require, packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'], From 31e6fe8f52ef90adcb6a8bafcf08b26b8796b931 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 29 Aug 2016 15:53:41 +0200 Subject: [PATCH 130/153] HOWTORELEASE.tst: use restructuredtext autonumbering --- HOWTORELEASE.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index 523ed41cc..1cbe279b3 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -3,25 +3,25 @@ How to release pytest Note: this assumes you have already registered on pypi. -1. Check and finalize ``CHANGELOG.rst``. +#. Check and finalize ``CHANGELOG.rst``. -2. Write ``doc/en/announce/release-VERSION.txt`` and include +#. Write ``doc/en/announce/release-VERSION.txt`` and include it in ``doc/en/announce/index.txt``. Run this command to list names of authors involved:: git log $(git describe --abbrev=0 --tags)..HEAD --format='%aN' | sort -u -4. Regenerate the docs examples using tox:: +#. Regenerate the docs examples using tox:: tox -e regen -5. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions. +#. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions. -6. Use devpi for uploading a release tarball to a staging area:: +#. Use devpi for uploading a release tarball to a staging area:: devpi use https://devpi.net/USER/dev devpi upload --formats sdist,bdist_wheel -7. Run from multiple machines:: +#. Run from multiple machines:: devpi use https://devpi.net/USER/dev devpi test pytest==VERSION @@ -29,27 +29,27 @@ Note: this assumes you have already registered on pypi. Alternatively, you can use `devpi-cloud-tester `_ to test the package on AppVeyor and Travis (follow instructions on the ``README``). -8. Check that tests pass for relevant combinations with:: +#. Check that tests pass for relevant combinations with:: devpi list pytest or look at failures with "devpi list -f pytest". -9. Feeling confident? Publish to pypi:: +#. Feeling confident? Publish to pypi:: devpi push pytest==VERSION pypi:NAME where NAME is the name of pypi.python.org as configured in your ``~/.pypirc`` file `for devpi `_. -10. Tag the release:: +#. Tag the release:: git tag VERSION git push origin VERSION Make sure ```` is **exactly** the git hash at the time the package was created. -11. Send release announcement to mailing lists: +#. Send release announcement to mailing lists: - pytest-dev@python.org - python-announce-list@python.org @@ -57,7 +57,7 @@ Note: this assumes you have already registered on pypi. And announce the release on Twitter, making sure to add the hashtag ``#pytest``. -12. **After the release** +#. **After the release** a. **patch release (2.8.3)**: From c0a51f56624fef1b1977a285dcf1d5430e59a94e Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 19 Apr 2017 20:12:38 +0200 Subject: [PATCH 131/153] restore check-manifst functionality --- scripts/check-manifest.py | 1 - tox.ini | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/check-manifest.py b/scripts/check-manifest.py index 5911a84fe..909e7519b 100644 --- a/scripts/check-manifest.py +++ b/scripts/check-manifest.py @@ -18,4 +18,3 @@ if os.path.isdir('.git'): else: print('No .git directory found, skipping checking the manifest file') sys.exit(0) - diff --git a/tox.ini b/tox.ini index 5c103f94c..cbb685705 100644 --- a/tox.ini +++ b/tox.ini @@ -47,6 +47,8 @@ commands= [testenv:linting] basepython = python2.7 +setenv = # needed to keep check-manifest working + SETUPTOOLS_SCM_PRETEND_VERSION=0.0.1 deps = flake8 # pygments required by rst-lint From 2cf422733c0fadb43a707eb6c942425f2cc312a1 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 19 Apr 2017 20:25:53 +0200 Subject: [PATCH 132/153] restore linting, drop _pytest._version for check-manifest --- _pytest/__init__.py | 6 ++++-- setup.py | 4 +--- tox.ini | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/_pytest/__init__.py b/_pytest/__init__.py index a6f201091..2599bd05a 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,5 +1,7 @@ __all__ = ['__version__'] +import pkg_resources + try: - from ._version import version as __version__ -except ImportError: + __version__ = pkg_resources.get_distribution('pytest').version +except Exception: __version__ = None # broken installation, we don't even try diff --git a/setup.py b/setup.py index 3cba379e4..73d42b73d 100644 --- a/setup.py +++ b/setup.py @@ -54,9 +54,7 @@ def main(): name='pytest', description='pytest: simple powerful testing with Python', long_description=long_description, - use_scm_version={ - 'write_to': '_pytest/_version.py', - }, + use_scm_version=True, url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff --git a/tox.ini b/tox.ini index cbb685705..d46e468c8 100644 --- a/tox.ini +++ b/tox.ini @@ -47,8 +47,9 @@ commands= [testenv:linting] basepython = python2.7 -setenv = # needed to keep check-manifest working - SETUPTOOLS_SCM_PRETEND_VERSION=0.0.1 +# needed to keep check-manifest working +setenv = + SETUPTOOLS_SCM_PRETEND_VERSION=2.0.1 deps = flake8 # pygments required by rst-lint @@ -58,7 +59,7 @@ deps = commands = {envpython} scripts/check-manifest.py flake8 pytest.py _pytest testing - rst-lint CHANGELOG.rst HOWTORELEASE.rst README.rst + rst-lint CHANGELOG.rst HOWTORELEASE.rst README.rst --encoding utf-8 [testenv:py27-xdist] deps=pytest-xdist>=1.13 From 4242bf62627f90d895f32889da43dda62775a1fa Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 20 Apr 2017 21:46:58 +0200 Subject: [PATCH 133/153] use unknown to specify unknown versions --- _pytest/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 2599bd05a..df4c53b02 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,7 +1,10 @@ -__all__ = ['__version__'] import pkg_resources +__all__ = ['__version__'] + try: __version__ = pkg_resources.get_distribution('pytest').version except Exception: - __version__ = None # broken installation, we don't even try + # broken installation, we don't even try + # unknown only works because we do poor mans version compare + __version__ = 'unknown' From e02cb6d7ce804d242496e50cb7e892b740290dcd Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 23 Apr 2017 16:59:08 +0200 Subject: [PATCH 134/153] restore setuptools_scm write_to usage --- _pytest/__init__.py | 6 ++---- setup.cfg | 4 ++++ setup.py | 32 +++++++++++++++++++------------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/_pytest/__init__.py b/_pytest/__init__.py index df4c53b02..7c69a96f5 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,10 +1,8 @@ -import pkg_resources - __all__ = ['__version__'] try: - __version__ = pkg_resources.get_distribution('pytest').version -except Exception: + from ._version import __version__ +except ImportError: # broken installation, we don't even try # unknown only works because we do poor mans version compare __version__ = 'unknown' diff --git a/setup.cfg b/setup.cfg index f3299af5b..816539e2e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,6 +9,10 @@ upload-dir = doc/en/build/html [bdist_wheel] universal = 1 +[check-manifest] +ignore = + _pytest/_version.py + [metadata] license_file = LICENSE diff --git a/setup.py b/setup.py index 73d42b73d..a71692c25 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,23 @@ -import os, sys +import os +import sys import setuptools import pkg_resources from setuptools import setup, Command -classifiers = ['Development Status :: 6 - Mature', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Topic :: Software Development :: Testing', - 'Topic :: Software Development :: Libraries', - 'Topic :: Utilities'] + [ - ('Programming Language :: Python :: %s' % x) for x in - '2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split()] +classifiers = [ + 'Development Status :: 6 - Mature', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Topic :: Software Development :: Testing', + 'Topic :: Software Development :: Libraries', + 'Topic :: Utilities', +] + [ + ('Programming Language :: Python :: %s' % x) + for x in '2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split() +] with open('README.rst') as fd: long_description = fd.read() @@ -54,7 +58,9 @@ def main(): name='pytest', description='pytest: simple powerful testing with Python', long_description=long_description, - use_scm_version=True, + use_scm_version={ + 'write_to': '_pytest/_version.py', + }, url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], From a280e43949a489192dd7a80ee9c982843d9b153e Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2017 15:57:55 +0200 Subject: [PATCH 135/153] fix import error --- _pytest/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 7c69a96f5..6e41f0504 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,7 +1,7 @@ __all__ = ['__version__'] try: - from ._version import __version__ + from ._version import version as __version__ except ImportError: # broken installation, we don't even try # unknown only works because we do poor mans version compare From 8bcf88ec121215788b5edcd8fd6da8f8c15e6432 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 5 May 2017 11:16:05 +0200 Subject: [PATCH 136/153] try to consider all modules after registration as plugin --- _pytest/config.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 7c8fad79f..0a79cb5b1 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,7 +8,8 @@ import warnings import py # DON't import pytest here because it causes import cycle troubles -import sys, os +import sys +import os import _pytest._code import _pytest.hookspec # the extension point definitions import _pytest.assertion @@ -252,6 +253,9 @@ class PytestPluginManager(PluginManager): if ret: self.hook.pytest_plugin_registered.call_historic( kwargs=dict(plugin=plugin, manager=self)) + + if isinstance(plugin, types.ModuleType): + self.consider_module(plugin) return ret def getplugin(self, name): @@ -396,8 +400,7 @@ class PytestPluginManager(PluginManager): self.import_plugin(arg) def consider_conftest(self, conftestmodule): - if self.register(conftestmodule, name=conftestmodule.__file__): - self.consider_module(conftestmodule) + self.register(conftestmodule, name=conftestmodule.__file__) def consider_env(self): self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) @@ -441,7 +444,6 @@ class PytestPluginManager(PluginManager): else: mod = sys.modules[importspec] self.register(mod, modname) - self.consider_module(mod) def _get_plugin_specs_as_list(specs): From 66ba3c3aa42f3db02c3d00d6af5ba992bde4c818 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 9 May 2017 22:55:27 -0300 Subject: [PATCH 137/153] Introduce a task to generate the announcement file for releases --- HOWTORELEASE.rst | 12 +++++---- MANIFEST.in | 1 + tasks/__init__.py | 9 +++++++ tasks/generate.py | 56 +++++++++++++++++++++++++++++++++++++++++ tasks/release.minor.rst | 27 ++++++++++++++++++++ tasks/release.patch.rst | 17 +++++++++++++ 6 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 tasks/__init__.py create mode 100644 tasks/generate.py create mode 100644 tasks/release.minor.rst create mode 100644 tasks/release.patch.rst diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index 1cbe279b3..0d63cdbd4 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -1,18 +1,20 @@ How to release pytest -------------------------------------------- -Note: this assumes you have already registered on pypi. +Note: this assumes you have already registered on PyPI and you have +`invoke `_ installed. #. Check and finalize ``CHANGELOG.rst``. -#. Write ``doc/en/announce/release-VERSION.txt`` and include - it in ``doc/en/announce/index.txt``. Run this command to list names of authors involved:: +#. Generate a new release announcement:: - git log $(git describe --abbrev=0 --tags)..HEAD --format='%aN' | sort -u + invoke generate.announce VERSION + +Feel free to modify the generated files before committing. #. Regenerate the docs examples using tox:: - tox -e regen + tox -e regen #. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions. diff --git a/MANIFEST.in b/MANIFEST.in index c57cbd911..51041f0c9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -20,6 +20,7 @@ recursive-include extra *.py graft testing graft doc prune doc/en/_build +graft tasks exclude _pytest/impl diff --git a/tasks/__init__.py b/tasks/__init__.py new file mode 100644 index 000000000..9551ff059 --- /dev/null +++ b/tasks/__init__.py @@ -0,0 +1,9 @@ +""" +Invoke tasks to help with pytest development and release process. +""" + +import invoke + +from . import generate + +ns = invoke.Collection(generate) diff --git a/tasks/generate.py b/tasks/generate.py new file mode 100644 index 000000000..082cbe8cb --- /dev/null +++ b/tasks/generate.py @@ -0,0 +1,56 @@ +from pathlib import Path +from subprocess import check_output + +import invoke + + +@invoke.task(help={ + 'version': 'version being released', +}) +def announce(ctx, version): + """Generates a new release announcement entry in the docs.""" + print("[generate.announce] Generating Announce") + + # Get our list of authors + print("[generate.announce] Collecting author names") + + stdout = check_output(["git", "describe", "--abbrev=0", '--tags']) + stdout = stdout.decode('utf-8') + last_version = stdout.strip() + + stdout = check_output(["git", "log", "{}..HEAD".format(last_version), "--format=%aN"]) + stdout = stdout.decode('utf-8') + + contributors = set(stdout.splitlines()) + + template_name = 'release.minor.rst' if version.endswith('.0') else 'release.patch.rst' + template_text = Path(__file__).parent.joinpath(template_name).read_text(encoding='UTF-8') + + contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n' + text = template_text.format(version=version, contributors=contributors_text) + + target = Path(__file__).joinpath('../../doc/en/announce/release-{}.rst'.format(version)) + target.write_text(text, encoding='UTF-8') + print("[generate.announce] Generated {}".format(target.name)) + + # Update index with the new release entry + index_path = Path(__file__).joinpath('../../doc/en/announce/index.rst') + lines = index_path.read_text(encoding='UTF-8').splitlines() + indent = ' ' + for index, line in enumerate(lines): + if line.startswith('{}release-'.format(indent)): + new_line = indent + target.stem + if line != new_line: + lines.insert(index, new_line) + index_path.write_text('\n'.join(lines) + '\n', encoding='UTF-8') + print("[generate.announce] Updated {}".format(index_path.name)) + else: + print("[generate.announce] Skip {} (already contains release)".format(index_path.name)) + break + + print() + print('Please review the generated files and commit with:') + print(' git commit -a -m "Generate new release announcement for {}'.format(version)) + + + diff --git a/tasks/release.minor.rst b/tasks/release.minor.rst new file mode 100644 index 000000000..4bbce5a82 --- /dev/null +++ b/tasks/release.minor.rst @@ -0,0 +1,27 @@ +pytest-{version} +======================================= + +The pytest team is proud to announce the {version} release! + +pytest is a mature Python testing tool with more than a 1600 tests +against itself, passing on many different interpreters and platforms. + +This release contains a bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + +http://doc.pytest.org/en/latest/changelog.html + +For complete documentation, please visit: + + http://docs.pytest.org + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +{contributors} + +Happy testing, +The Pytest Development Team diff --git a/tasks/release.patch.rst b/tasks/release.patch.rst new file mode 100644 index 000000000..56764b913 --- /dev/null +++ b/tasks/release.patch.rst @@ -0,0 +1,17 @@ +pytest-{version} +======================================= + +pytest {version} has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +{contributors} + +Happy testing, +The pytest Development Team From a92e397011c02d0a6f336812934750fd95688058 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 12 May 2017 18:39:45 +0200 Subject: [PATCH 138/153] add changelog for fixing #2391 --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b71152cd8..3f4814d8e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -81,6 +81,8 @@ Changes * fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run. Thanks `@ojii`_ for the PR. +* fix `#2391`_: consider pytest_plugins on all plugin modules + Thansks `@RonnyPfannschmidt`_ for the PR. Bug Fixes --------- @@ -114,8 +116,9 @@ Bug Fixes .. _#2166: https://github.com/pytest-dev/pytest/pull/2166 .. _#2147: https://github.com/pytest-dev/pytest/issues/2147 .. _#2208: https://github.com/pytest-dev/pytest/issues/2208 -.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 +.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 .. _#2308: https://github.com/pytest-dev/pytest/issues/2308 +.. _#2391: https://github.com/pytest-dev/pytest/issues/2391 3.0.8 (unreleased) From f1c4e2c032795652cd0c7cf08a38f2e5221b2e57 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 12 May 2017 22:17:40 +0200 Subject: [PATCH 139/153] regendoc: reduce version noise by replacing minor/patch with placeholders --- CHANGELOG.rst | 4 +++ doc/en/Makefile | 5 +++- doc/en/assert.rst | 4 +-- doc/en/cache.rst | 6 ++-- doc/en/capture.rst | 2 +- doc/en/doctest.rst | 6 ++-- doc/en/example/markers.rst | 28 +++++++++---------- doc/en/example/nonpython.rst | 6 ++-- doc/en/example/parametrize.rst | 42 +++++++++++++++++++++------- doc/en/example/pythoncollection.rst | 40 ++++++++++++++++++--------- doc/en/example/reportingdemo.rst | 33 +++++++++++----------- doc/en/example/simple.rst | 24 ++++++++-------- doc/en/fixture.rst | 10 +++---- doc/en/getting-started.rst | 24 ++++++++-------- doc/en/index.rst | 2 +- doc/en/parametrize.rst | 28 +++++++++---------- doc/en/skipping.rst | 43 ++++++++++++++++------------- doc/en/tmpdir.rst | 2 +- doc/en/unittest.rst | 10 +++++-- doc/en/warnings.rst | 37 ++++++++++--------------- 20 files changed, 201 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b71152cd8..4a3c165cc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -81,6 +81,10 @@ Changes * fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run. Thanks `@ojii`_ for the PR. +* Replace minor/patch level version numbers in the documentation with placeholders. + This significantly reduces change-noise as different contributors regnerate + the documentation on different platforms. + Thanks `@RonnyPfannschmidt`_ for the PR. Bug Fixes --------- diff --git a/doc/en/Makefile b/doc/en/Makefile index 5499c405e..ffe344ffe 100644 --- a/doc/en/Makefile +++ b/doc/en/Makefile @@ -17,7 +17,10 @@ REGENDOC_ARGS := \ --normalize "/_{8,} (.*) _{8,}/_______ \1 ________/" \ --normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \ --normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \ - + --normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \ + --normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \ + --normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \ + --normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@" \ .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 26090247c..d3d06804e 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -26,7 +26,7 @@ you will see the return value of the function call:: $ pytest test_assert1.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -170,7 +170,7 @@ if you run this module:: $ pytest test_assert2.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items diff --git a/doc/en/cache.rst b/doc/en/cache.rst index b3b992507..688b6dd04 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -80,7 +80,7 @@ If you then run it with ``--lf``:: $ pytest --lf ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y run-last-failure: rerun last 2 failures rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items @@ -122,7 +122,7 @@ of ``FF`` and dots):: $ pytest --ff ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y run-last-failure: rerun last 2 failures first rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items @@ -227,7 +227,7 @@ You can always peek at the content of the cache using the $ py.test --cache-show ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: cachedir: $REGENDOC_TMPDIR/.cache ------------------------------- cache values ------------------------------- diff --git a/doc/en/capture.rst b/doc/en/capture.rst index 2f666b7bc..58ebdf840 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -64,7 +64,7 @@ of the failing function and hide the other one:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 348aed602..24c068a86 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -62,12 +62,12 @@ then you can just invoke ``pytest`` without command line options:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 1 items - + mymodule.py . - + ======= 1 passed in 0.12 seconds ======== It is possible to use fixtures using the ``getfixture`` helper:: diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index f57edf5c4..338f707a5 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:: $ pytest -v -m webtest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -45,7 +45,7 @@ Or the inverse, running all tests except the webtest ones:: $ pytest -v -m "not webtest" ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -66,7 +66,7 @@ tests based on their module, class, method, or function name:: $ pytest -v test_server.py::TestClass::test_method ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 5 items @@ -79,7 +79,7 @@ You can also select on the class:: $ pytest -v test_server.py::TestClass ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -92,7 +92,7 @@ Or select multiple nodes:: $ pytest -v test_server.py::TestClass test_server.py::test_send_http ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items @@ -130,7 +130,7 @@ select tests based on their names:: $ pytest -v -k http # running with the above defined example module ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -144,7 +144,7 @@ And you can also run all tests except the ones that match the keyword:: $ pytest -k "not send_http" -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -160,7 +160,7 @@ Or to select "http" and "quick" tests:: $ pytest -k "http or quick" -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -352,7 +352,7 @@ the test needs:: $ pytest -E stage2 ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -364,7 +364,7 @@ and here is one that specifies exactly the environment needed:: $ pytest -E stage1 ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -485,7 +485,7 @@ then you will see two tests skipped and two executed tests as expected:: $ pytest -rs # this option reports skip reasons ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -499,7 +499,7 @@ Note that if you specify a platform via the marker-command line option like this $ pytest -m linux ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -551,7 +551,7 @@ We can now use the ``-m option`` to select one set:: $ pytest -m interface --tb=short ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -573,7 +573,7 @@ or to select both "event" and "interface" tests:: $ pytest -m "interface or event" --tb=short ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index 38918dba4..5784f6ed6 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -27,7 +27,7 @@ now execute the test specification:: nonpython $ pytest test_simple.yml ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items @@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode:: nonpython $ pytest -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collecting ... collected 2 items @@ -81,7 +81,7 @@ interesting to just look at the collection tree:: nonpython $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 79f2ab31f..8d92517b9 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -130,7 +130,7 @@ objects, they are still using the default pytest representation:: $ pytest test_time.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 6 items @@ -181,7 +181,7 @@ this is a fully self-contained example which you can run with:: $ pytest test_scenarios.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -194,7 +194,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia $ pytest --collect-only test_scenarios.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -259,7 +259,7 @@ Let's first see how it looks like at collection time:: $ pytest test_backends.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -320,7 +320,7 @@ The result of this test will be successful:: $ pytest test_indirect_list.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -397,10 +397,32 @@ is to be run with different sets of arguments for its three arguments: Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):: . $ pytest -rs -q multipython.py - sssssssssssssss.........sss.........sss......... - ======= short test summary info ======== - SKIP [21] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.6' not found - 27 passed, 21 skipped in 0.12 seconds + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules + return self._path2confmods[path] + KeyError: local('$REGENDOC_TMPDIR/CWD/multipython.py') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules + return self._path2confmods[path] + KeyError: local('$REGENDOC_TMPDIR/CWD') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 357, in _importconftest + return self._conftestpath2mod[conftestpath] + KeyError: local('$REGENDOC_TMPDIR/CWD/conftest.py') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 363, in _importconftest + mod = conftestpath.pyimport() + File "$PYTHON_PREFIX/lib/python3.5/site-packages/py/_path/local.py", line 680, in pyimport + raise self.ImportMismatchError(modname, modfile, self) + py._path.local.LocalPath.ImportMismatchError: ('conftest', '$PWD/example/conftest.py', local('$REGENDOC_TMPDIR/CWD/conftest.py')) + ERROR: could not load $REGENDOC_TMPDIR/CWD/conftest.py + Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -447,7 +469,7 @@ If you run this with reporting for skips enabled:: $ pytest -rs test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index a262c1a6d..1668de8ae 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -117,7 +117,7 @@ then the test collection looks like this:: $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 2 items @@ -162,18 +162,32 @@ Finding out what is collected You can always peek at the collection tree without running tests like this:: . $ pytest --collect-only pythoncollection.py - ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 - rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - collected 3 items - - - - - - + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules + return self._path2confmods[path] + KeyError: local('$REGENDOC_TMPDIR/CWD/pythoncollection.py') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules + return self._path2confmods[path] + KeyError: local('$REGENDOC_TMPDIR/CWD') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 357, in _importconftest + return self._conftestpath2mod[conftestpath] + KeyError: local('$REGENDOC_TMPDIR/CWD/conftest.py') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 363, in _importconftest + mod = conftestpath.pyimport() + File "$PYTHON_PREFIX/lib/python3.5/site-packages/py/_path/local.py", line 680, in pyimport + raise self.ImportMismatchError(modname, modfile, self) + py._path.local.LocalPath.ImportMismatchError: ('conftest', '$PWD/example/conftest.py', local('$REGENDOC_TMPDIR/CWD/conftest.py')) + ERROR: could not load $REGENDOC_TMPDIR/CWD/conftest.py - ======= no tests ran in 0.12 seconds ======== customizing test collection to find all .py files --------------------------------------------------------- @@ -230,7 +244,7 @@ will be left out:: $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 80a3a242f..47c18851d 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -11,7 +11,7 @@ get on the terminal - we are working on that):: assertion $ pytest failure_demo.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/assertion, inifile: collected 42 items @@ -144,13 +144,9 @@ get on the terminal - we are working on that):: E 1 E 1 E 1 - E 1 - E - a2 - E + b2 - E 2 - E 2 - E 2 - E 2 + E 1... + E + E ...Full output truncated (7 lines hidden), use '-vv' to show failure_demo.py:59: AssertionError _______ TestSpecialisedExplanations.test_eq_list ________ @@ -184,14 +180,15 @@ get on the terminal - we are working on that):: def test_eq_dict(self): > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} - E Omitting 1 identical items, use -v to show + E Omitting 1 identical items, use -vv to show E Differing items: E {'b': 1} != {'b': 2} E Left contains more items: E {'c': 0} E Right contains more items: - E {'d': 0} - E Use -v to get the full diff + E {'d': 0}... + E + E ...Full output truncated (2 lines hidden), use '-vv' to show failure_demo.py:70: AssertionError _______ TestSpecialisedExplanations.test_eq_set ________ @@ -200,15 +197,16 @@ get on the terminal - we are working on that):: def test_eq_set(self): > assert set([0, 10, 11, 12]) == set([0, 20, 21]) - E assert {0, 10, 11, 12} == {0, 20, 21} + E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21} E Extra items in the left set: E 10 E 11 E 12 E Extra items in the right set: E 20 - E 21 - E Use -v to get the full diff + E 21... + E + E ...Full output truncated (2 lines hidden), use '-vv' to show failure_demo.py:73: AssertionError _______ TestSpecialisedExplanations.test_eq_longer_list ________ @@ -245,8 +243,9 @@ get on the terminal - we are working on that):: E which E includes foo E ? +++ - E and a - E tail + E and a... + E + E ...Full output truncated (2 lines hidden), use '-vv' to show failure_demo.py:83: AssertionError _______ TestSpecialisedExplanations.test_not_in_text_single ________ @@ -359,7 +358,7 @@ get on the terminal - we are working on that):: > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1207>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1219>:1: ValueError _______ TestRaises.test_raises_doesnt ________ self = diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 027356f50..80fbf3f74 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -113,7 +113,7 @@ directory with the above conftest.py:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -164,7 +164,7 @@ and when running it will see a skipped "slow" test:: $ pytest -rs # "-rs" means report details on the little 's' ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -178,7 +178,7 @@ Or run it including the ``slow`` marked test:: $ pytest --runslow ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -302,7 +302,7 @@ which will add the string to the test header accordingly:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y project deps: mylib-1.1 rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -327,7 +327,7 @@ which will add info only when run with "--v":: $ pytest -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache info1: did you know that ... did you? @@ -340,7 +340,7 @@ and nothing when run plainly:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -374,7 +374,7 @@ Now we can profile which test functions execute the slowest:: $ pytest --durations=3 ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -383,7 +383,7 @@ Now we can profile which test functions execute the slowest:: ======= slowest 3 test durations ======== 0.20s call test_some_are_slow.py::test_funcslow2 0.10s call test_some_are_slow.py::test_funcslow1 - 0.00s setup test_some_are_slow.py::test_funcfast + 0.00s setup test_some_are_slow.py::test_funcslow2 ======= 3 passed in 0.12 seconds ======== incremental testing - test steps @@ -440,7 +440,7 @@ If we run this:: $ pytest -rx ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -519,7 +519,7 @@ We can run this:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 7 items @@ -627,7 +627,7 @@ and run them:: $ pytest test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -721,7 +721,7 @@ and run it:: $ pytest -s test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 04b908522..f760c423e 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -70,7 +70,7 @@ marked ``smtp`` fixture function. Running the test looks like this:: $ pytest test_smtpsimple.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -188,7 +188,7 @@ inspect what is going on and can now run the tests:: $ pytest test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -523,7 +523,7 @@ Running the above tests results in the following test IDs being used:: $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 10 items @@ -574,7 +574,7 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 2 items @@ -643,7 +643,7 @@ Let's run the tests in verbose mode and with looking at the print-output:: $ pytest -v -s test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 153d9b2e1..789db3176 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -26,7 +26,7 @@ Installation:: To check your installation has installed the correct version:: $ pytest --version - This is pytest version 3.0.7, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py + This is pytest version 3.0.8.dev260+gb6125d9.d20170512, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py .. _`simpletest`: @@ -46,20 +46,20 @@ That's it. You can execute the test function now:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items - + test_sample.py F - + ======= FAILURES ======== _______ test_answer ________ - + def test_answer(): > assert func(3) == 5 E assert 4 == 5 E + where 4 = func(3) - + test_sample.py:5: AssertionError ======= 1 failed in 0.12 seconds ======== @@ -128,15 +128,15 @@ run the module by passing its filename:: .F ======= FAILURES ======== _______ TestClass.test_two ________ - + self = - + def test_two(self): x = "hello" > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') - + test_class.py:8: AssertionError 1 failed, 1 passed in 0.12 seconds @@ -165,14 +165,14 @@ before performing the test function call. Let's just run it:: F ======= FAILURES ======== _______ test_needsfiles ________ - + tmpdir = local('PYTEST_TMPDIR/test_needsfiles0') - + def test_needsfiles(tmpdir): print (tmpdir) > assert 0 E assert 0 - + test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- PYTEST_TMPDIR/test_needsfiles0 diff --git a/doc/en/index.rst b/doc/en/index.rst index 24ae67957..cb901b8d5 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -25,7 +25,7 @@ To execute it:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index 10de065c7..fc048fb08 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -55,17 +55,17 @@ them in turn:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items - + test_expectation.py ..F - + ======= FAILURES ======== _______ test_eval[6*9-42] ________ - + test_input = '6*9', expected = 42 - + @pytest.mark.parametrize("test_input,expected", [ ("3+5", 8), ("2+4", 6), @@ -73,9 +73,9 @@ them in turn:: ]) def test_eval(test_input, expected): > assert eval(test_input) == expected - E assert 54 == 42 + E AssertionError: assert 54 == 42 E + where 54 = eval('6*9') - + test_expectation.py:8: AssertionError ======= 1 failed, 2 passed in 0.12 seconds ======== @@ -124,12 +124,12 @@ Let's run this:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items - + test_expectation.py ..x - + ======= 2 passed, 1 xfailed in 0.12 seconds ======== The one parameter set which caused a failure previously now @@ -202,15 +202,15 @@ Let's also run with a stringinput that will lead to a failing test:: F ======= FAILURES ======== _______ test_valid_string[!] ________ - + stringinput = '!' - + def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert False + E AssertionError: assert False E + where False = () E + where = '!'.isalpha - + test_strings.py:3: AssertionError 1 failed in 0.12 seconds diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index c47228451..8e9ef3c7f 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -223,27 +223,32 @@ Here is a simple test file with the several usages: Running it with the report-on-xfail option gives this output:: example $ pytest -rx xfail_demo.py - ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 - rootdir: $REGENDOC_TMPDIR/example, inifile: - collected 7 items + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules + return self._path2confmods[path] + KeyError: local('$REGENDOC_TMPDIR/example/xfail_demo.py') - xfail_demo.py xxxxxxx - ======= short test summary info ======== - XFAIL xfail_demo.py::test_hello - XFAIL xfail_demo.py::test_hello2 - reason: [NOTRUN] - XFAIL xfail_demo.py::test_hello3 - condition: hasattr(os, 'sep') - XFAIL xfail_demo.py::test_hello4 - bug 110 - XFAIL xfail_demo.py::test_hello5 - condition: pytest.__version__[0] != "17" - XFAIL xfail_demo.py::test_hello6 - reason: reason - XFAIL xfail_demo.py::test_hello7 + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules + return self._path2confmods[path] + KeyError: local('$REGENDOC_TMPDIR/example') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 357, in _importconftest + return self._conftestpath2mod[conftestpath] + KeyError: local('$REGENDOC_TMPDIR/example/conftest.py') + + During handling of the above exception, another exception occurred: + Traceback (most recent call last): + File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 363, in _importconftest + mod = conftestpath.pyimport() + File "$PYTHON_PREFIX/lib/python3.5/site-packages/py/_path/local.py", line 680, in pyimport + raise self.ImportMismatchError(modname, modfile, self) + py._path.local.LocalPath.ImportMismatchError: ('conftest', '$PWD/example/conftest.py', local('$REGENDOC_TMPDIR/example/conftest.py')) + ERROR: could not load $REGENDOC_TMPDIR/example/conftest.py - ======= 7 xfailed in 0.12 seconds ======== xfail signature summary ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 637047195..642bb0814 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -29,7 +29,7 @@ Running this would result in a passed test except for the last $ pytest test_tmpdir.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 57e424511..6ee7d505e 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -108,7 +108,7 @@ the ``self.db`` values in the traceback:: $ pytest test_unittest_db.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -183,7 +183,13 @@ Running this test module ...:: $ pytest -q test_unittest_cleandir.py . - 1 passed in 0.12 seconds + ======= warnings summary ======== + test_unittest_cleandir.py::MyTest::test_method + $REGENDOC_TMPDIR/test_unittest_cleandir.py:11: ResourceWarning: unclosed file <_io.TextIOWrapper name='samplefile.ini' mode='r' encoding='UTF-8'> + s = open("samplefile.ini").read() + + -- Docs: http://doc.pytest.org/en/latest/warnings.html + 1 passed, 1 warnings in 0.12 seconds ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index ae15e3129..fb18c4a2f 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -21,35 +21,28 @@ and displays them at the end of the session:: Running pytest now produces this output:: $ pytest test_show_warnings.py - . - ============================== warnings summary =============================== - test_show_warning.py::test_one - C:\pytest\.tmp\test_show_warning.py:4: DeprecationWarning: this function is deprecated, use another_function() + ======= test session starts ======== + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: + collected 1 items + + test_show_warnings.py . + + ======= warnings summary ======== + test_show_warnings.py::test_one + $REGENDOC_TMPDIR/test_show_warnings.py:4: DeprecationWarning: this function is deprecated, use another_function() warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) - + -- Docs: http://doc.pytest.org/en/latest/warnings.html - 1 passed, 1 warnings in 0.01 seconds + ======= 1 passed, 1 warnings in 0.12 seconds ======== The ``-W`` flag can be passed to control which warnings will be displayed or even turn them into errors:: $ pytest -q test_show_warning.py -W error::DeprecationWarning - F - ================================== FAILURES =================================== - __________________________________ test_one ___________________________________ - - def test_one(): - > assert deprecated_function() == 1 - - test_show_warning.py:8: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - - def deprecated_function(): - > warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) - E DeprecationWarning: this function is deprecated, use another_function() - - test_show_warning.py:4: DeprecationWarning - 1 failed in 0.02 seconds + + no tests ran in 0.12 seconds + ERROR: file not found: test_show_warning.py The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option. For example, the configuration below will ignore all deprecation warnings, but will transform From c765fa6d042babcbb8ba9baaea4fcebe8c978c25 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 12 May 2017 22:38:50 +0200 Subject: [PATCH 140/153] add regendoc normaliz for pytest --version --- doc/en/Makefile | 3 ++- doc/en/getting-started.rst | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/en/Makefile b/doc/en/Makefile index ffe344ffe..7cfb6f1f8 100644 --- a/doc/en/Makefile +++ b/doc/en/Makefile @@ -18,9 +18,10 @@ REGENDOC_ARGS := \ --normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \ --normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \ --normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \ + --normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \ --normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \ --normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \ - --normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@" \ + --normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@" .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 789db3176..59abd4c79 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -26,7 +26,7 @@ Installation:: To check your installation has installed the correct version:: $ pytest --version - This is pytest version 3.0.8.dev260+gb6125d9.d20170512, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py + This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py .. _`simpletest`: From fe7d89f03370d087769c6093d7dac70f83160e0c Mon Sep 17 00:00:00 2001 From: Dmitri Pribysh Date: Wed, 22 Feb 2017 23:31:30 +0300 Subject: [PATCH 141/153] Add '--junit-suite-name' CLI option --- _pytest/junitxml.py | 13 ++++++++++--- testing/test_junitxml.py | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 4bd334a16..5240e0e28 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -226,13 +226,19 @@ def pytest_addoption(parser): metavar="str", default=None, help="prepend prefix to classnames in junit-xml output") + group.addoption( + '--junitsuitename', '--junit-suite-name', + action="store", + metavar="name", + default="pytest", + help="set the name attribute of root tag") def pytest_configure(config): xmlpath = config.option.xmlpath # prevent opening xmllog on slave nodes (xdist) if xmlpath and not hasattr(config, 'slaveinput'): - config._xml = LogXML(xmlpath, config.option.junitprefix) + config._xml = LogXML(xmlpath, config.option.junitprefix, config.option.junitsuitename) config.pluginmanager.register(config._xml) @@ -259,10 +265,11 @@ def mangle_test_address(address): class LogXML(object): - def __init__(self, logfile, prefix): + def __init__(self, logfile, prefix, suite_name="pytest"): logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile)) self.prefix = prefix + self.suite_name = suite_name self.stats = dict.fromkeys([ 'error', 'passed', @@ -422,7 +429,7 @@ class LogXML(object): logfile.write(Junit.testsuite( self._get_global_properties_node(), [x.to_xml() for x in self.node_reporters_ordered], - name="pytest", + name=self.suite_name, errors=self.stats['error'], failures=self.stats['failure'], skips=self.stats['skipped'], diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 7003d9f5f..417f8cca6 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -617,6 +617,7 @@ def test_dont_configure_on_slaves(tmpdir): self.option = self junitprefix = None + junitsuitename = "pytest" # XXX: shouldnt need tmpdir ? xmlpath = str(tmpdir.join('junix.xml')) register = gotten.append @@ -1032,3 +1033,29 @@ def test_url_property(testdir): test_case = minidom.parse(str(path)).getElementsByTagName('testcase')[0] assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" + + +def test_set_suite_name(testdir): + testdir.makepyfile(""" + import pytest + + def test_func(): + pass + """) + result, dom = runandparse(testdir, '--junit-suite-name', "my_suite") + assert result.ret == 0 + node = dom.find_first_by_tag("testsuite") + node.assert_attr(name="my_suite") + + +def test_set_suite_name_default(testdir): + testdir.makepyfile(""" + import pytest + + def test_func(): + pass + """) + result, dom = runandparse(testdir) + assert result.ret == 0 + node = dom.find_first_by_tag("testsuite") + node.assert_attr(name="pytest") From 204db4d1e20032f986730c6e9844d22f310c51c8 Mon Sep 17 00:00:00 2001 From: Dmitri Pribysh Date: Wed, 22 Feb 2017 23:31:37 +0300 Subject: [PATCH 142/153] Update Changelog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b71152cd8..d408ff551 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ New Features ------------ +* junitxml: Add `--junit-suite-name` option to specify root `` name for JUnit XML reports + * Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. Thanks `@wheerd`_ for the PR (`#2101`_). From bcfa6264f114f574bc7870c5fb50752db8da9349 Mon Sep 17 00:00:00 2001 From: Dmitri Pribysh Date: Wed, 22 Feb 2017 23:31:45 +0300 Subject: [PATCH 143/153] Update AUTHORS list --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index f3dafb999..90e639234 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,6 +48,7 @@ David Vierra Denis Kirisov Diego Russo Dmitry Dygalo +Dmitry Pribysh Duncan Betts Edison Gustavo Muenz Edoardo Batini From c9282f9e948a928dabfafa3b217ebb006a8ac223 Mon Sep 17 00:00:00 2001 From: Dmitri Pribysh Date: Fri, 12 May 2017 04:45:20 +0300 Subject: [PATCH 144/153] Transition to using ini option for suite name --- _pytest/junitxml.py | 9 ++------- testing/test_junitxml.py | 5 +++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 5240e0e28..301633706 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -226,19 +226,14 @@ def pytest_addoption(parser): metavar="str", default=None, help="prepend prefix to classnames in junit-xml output") - group.addoption( - '--junitsuitename', '--junit-suite-name', - action="store", - metavar="name", - default="pytest", - help="set the name attribute of root tag") + parser.addini("junit_suite_name", "Test suite name for JUnit report", default="pytest") def pytest_configure(config): xmlpath = config.option.xmlpath # prevent opening xmllog on slave nodes (xdist) if xmlpath and not hasattr(config, 'slaveinput'): - config._xml = LogXML(xmlpath, config.option.junitprefix, config.option.junitsuitename) + config._xml = LogXML(xmlpath, config.option.junitprefix, config.getini("junit_suite_name")) config.pluginmanager.register(config._xml) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 417f8cca6..9735f45ef 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -616,8 +616,9 @@ def test_dont_configure_on_slaves(tmpdir): self.pluginmanager = self self.option = self + getini = lambda self, name: "pytest" + junitprefix = None - junitsuitename = "pytest" # XXX: shouldnt need tmpdir ? xmlpath = str(tmpdir.join('junix.xml')) register = gotten.append @@ -1042,7 +1043,7 @@ def test_set_suite_name(testdir): def test_func(): pass """) - result, dom = runandparse(testdir, '--junit-suite-name', "my_suite") + result, dom = runandparse(testdir, '-o', "junit_suite_name=my_suite") assert result.ret == 0 node = dom.find_first_by_tag("testsuite") node.assert_attr(name="my_suite") From 2ab8d12fe3ebed9716786408798da7e25789b8af Mon Sep 17 00:00:00 2001 From: Dmitri Pribysh Date: Fri, 12 May 2017 04:54:15 +0300 Subject: [PATCH 145/153] Update changelog and add usage info --- CHANGELOG.rst | 3 ++- doc/en/usage.rst | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d408ff551..bdacbed89 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,7 @@ New Features ------------ -* junitxml: Add `--junit-suite-name` option to specify root `` name for JUnit XML reports +* fix `#533`_: Added ``junit_suite_name`` ini option to specify root `` name for JUnit XML reports * Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. Thanks `@wheerd`_ for the PR (`#2101`_). @@ -105,6 +105,7 @@ Bug Fixes .. _@ojii: https://github.com/ojii +.. _#533: https://github.com/pytest-dev/pytest/issues/533 .. _#1407: https://github.com/pytest-dev/pytest/issues/1407 .. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1821: https://github.com/pytest-dev/pytest/issues/1821 diff --git a/doc/en/usage.rst b/doc/en/usage.rst index f6c20a978..763328f5a 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -177,6 +177,15 @@ integration servers, use this invocation:: to create an XML file at ``path``. +.. versionadded:: 3.1 + +To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file: + +.. code-block:: ini + + [pytest] + junit_suite_name = my_suite + record_xml_property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From d1e44d16e7f2a510be0a3c885435ed4226abb777 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 12 May 2017 22:51:20 +0200 Subject: [PATCH 146/153] regenerate docs from the pytest env --- doc/en/Makefile | 1 + doc/en/assert.rst | 2 ++ doc/en/cache.rst | 3 ++ doc/en/capture.rst | 1 + doc/en/doctest.rst | 1 + doc/en/example/markers.rst | 34 +++++++++++++----- doc/en/example/nonpython.rst | 53 +++++++++++++++-------------- doc/en/example/parametrize.rst | 34 +++++------------- doc/en/example/pythoncollection.rst | 39 ++++++++------------- doc/en/example/reportingdemo.rst | 3 +- doc/en/example/simple.rst | 15 ++++++-- doc/en/fixture.rst | 9 +++-- doc/en/getting-started.rst | 5 ++- doc/en/index.rst | 1 + doc/en/parametrize.rst | 2 ++ doc/en/skipping.rst | 44 +++++++++++------------- doc/en/tmpdir.rst | 1 + doc/en/unittest.rst | 1 + doc/en/warnings.rst | 1 + 19 files changed, 136 insertions(+), 114 deletions(-) diff --git a/doc/en/Makefile b/doc/en/Makefile index 7cfb6f1f8..58c3b54e3 100644 --- a/doc/en/Makefile +++ b/doc/en/Makefile @@ -21,6 +21,7 @@ REGENDOC_ARGS := \ --normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \ --normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \ --normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \ + --normalize "@hypothesis-(\d+)\\.[.\d,]+@hypothesis-\1.x.y@" \ --normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@" .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest diff --git a/doc/en/assert.rst b/doc/en/assert.rst index d3d06804e..2d9f8bc8e 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -28,6 +28,7 @@ you will see the return value of the function call:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_assert1.py F @@ -172,6 +173,7 @@ if you run this module:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_assert2.py F diff --git a/doc/en/cache.rst b/doc/en/cache.rst index 688b6dd04..a273d292b 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -83,6 +83,7 @@ If you then run it with ``--lf``:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y run-last-failure: rerun last 2 failures rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 50 items test_50.py FF @@ -125,6 +126,7 @@ of ``FF`` and dots):: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y run-last-failure: rerun last 2 failures first rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 50 items test_50.py FF................................................ @@ -229,6 +231,7 @@ You can always peek at the content of the cache using the ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y cachedir: $REGENDOC_TMPDIR/.cache ------------------------------- cache values ------------------------------- cache/lastfailed contains: diff --git a/doc/en/capture.rst b/doc/en/capture.rst index 58ebdf840..a7e9c879d 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -66,6 +66,7 @@ of the failing function and hide the other one:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_module.py .F diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 24c068a86..35f4dc984 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -64,6 +64,7 @@ then you can just invoke ``pytest`` without command line options:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + plugins: hypothesis-3.x.y collected 1 items mymodule.py . diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 338f707a5..a28336c37 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -31,9 +31,10 @@ You can then restrict a test run to only run tests marked with ``webtest``:: $ pytest -v -m webtest ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -45,9 +46,10 @@ Or the inverse, running all tests except the webtest ones:: $ pytest -v -m "not webtest" ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -66,9 +68,10 @@ tests based on their module, class, method, or function name:: $ pytest -v test_server.py::TestClass::test_method ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -79,9 +82,10 @@ You can also select on the class:: $ pytest -v test_server.py::TestClass ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -92,9 +96,10 @@ Or select multiple nodes:: $ pytest -v test_server.py::TestClass test_server.py::test_send_http ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -130,9 +135,10 @@ select tests based on their names:: $ pytest -v -k http # running with the above defined example module ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -144,9 +150,10 @@ And you can also run all tests except the ones that match the keyword:: $ pytest -k "not send_http" -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -160,9 +167,10 @@ Or to select "http" and "quick" tests:: $ pytest -k "http or quick" -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -201,6 +209,8 @@ You can ask which markers exist for your test suite - the list includes our just $ pytest --markers @pytest.mark.webtest: mark a test as a webtest. + @pytest.mark.hypothesis: Tests which use hypothesis. + @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html @@ -354,6 +364,7 @@ the test needs:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_someenv.py s @@ -366,6 +377,7 @@ and here is one that specifies exactly the environment needed:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_someenv.py . @@ -377,6 +389,8 @@ The ``--markers`` option always gives you a list of available markers:: $ pytest --markers @pytest.mark.env(name): mark test to run only on named environment + @pytest.mark.hypothesis: Tests which use hypothesis. + @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html @@ -487,6 +501,7 @@ then you will see two tests skipped and two executed tests as expected:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items test_plat.py s.s. @@ -501,6 +516,7 @@ Note that if you specify a platform via the marker-command line option like this ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items test_plat.py . @@ -553,6 +569,7 @@ We can now use the ``-m option`` to select one set:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items test_module.py FF @@ -575,6 +592,7 @@ or to select both "event" and "interface" tests:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items test_module.py FFF diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index 5784f6ed6..fd3fc1f92 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -29,16 +29,16 @@ now execute the test specification:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: - collected 2 items + plugins: hypothesis-3.x.y + collected 0 items / 1 errors - test_simple.yml F. - - ======= FAILURES ======== - _______ usecase: hello ________ - usecase execution failed - spec failed: 'some': 'other' - no further details known at this point. - ======= 1 failed, 1 passed in 0.12 seconds ======== + ======= ERRORS ======== + _______ ERROR collecting test_simple.yml ________ + conftest.py:11: in collect + import yaml # we need a yaml parser, e.g. PyYAML + E ImportError: No module named 'yaml' + !!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!! + ======= 1 error in 0.12 seconds ======== .. regendoc:wipe @@ -59,20 +59,19 @@ consulted when reporting in ``verbose`` mode:: nonpython $ pytest -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR/nonpython, inifile: - collecting ... collected 2 items + plugins: hypothesis-3.x.y + collecting ... collected 0 items / 1 errors - test_simple.yml::hello FAILED - test_simple.yml::ok PASSED - - ======= FAILURES ======== - _______ usecase: hello ________ - usecase execution failed - spec failed: 'some': 'other' - no further details known at this point. - ======= 1 failed, 1 passed in 0.12 seconds ======== + ======= ERRORS ======== + _______ ERROR collecting test_simple.yml ________ + conftest.py:11: in collect + import yaml # we need a yaml parser, e.g. PyYAML + E ImportError: No module named 'yaml' + !!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!! + ======= 1 error in 0.12 seconds ======== .. regendoc:wipe @@ -83,9 +82,13 @@ interesting to just look at the collection tree:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: - collected 2 items - - - + plugins: hypothesis-3.x.y + collected 0 items / 1 errors - ======= no tests ran in 0.12 seconds ======== + ======= ERRORS ======== + _______ ERROR collecting test_simple.yml ________ + conftest.py:11: in collect + import yaml # we need a yaml parser, e.g. PyYAML + E ImportError: No module named 'yaml' + !!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!! + ======= 1 error in 0.12 seconds ======== diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 8d92517b9..2ab72da03 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -132,6 +132,7 @@ objects, they are still using the default pytest representation:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 6 items @@ -183,6 +184,7 @@ this is a fully self-contained example which you can run with:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items test_scenarios.py .... @@ -196,6 +198,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items @@ -261,6 +264,7 @@ Let's first see how it looks like at collection time:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items @@ -322,6 +326,7 @@ The result of this test will be successful:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items @@ -397,32 +402,8 @@ is to be run with different sets of arguments for its three arguments: Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):: . $ pytest -rs -q multipython.py - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules - return self._path2confmods[path] - KeyError: local('$REGENDOC_TMPDIR/CWD/multipython.py') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules - return self._path2confmods[path] - KeyError: local('$REGENDOC_TMPDIR/CWD') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 357, in _importconftest - return self._conftestpath2mod[conftestpath] - KeyError: local('$REGENDOC_TMPDIR/CWD/conftest.py') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 363, in _importconftest - mod = conftestpath.pyimport() - File "$PYTHON_PREFIX/lib/python3.5/site-packages/py/_path/local.py", line 680, in pyimport - raise self.ImportMismatchError(modname, modfile, self) - py._path.local.LocalPath.ImportMismatchError: ('conftest', '$PWD/example/conftest.py', local('$REGENDOC_TMPDIR/CWD/conftest.py')) - ERROR: could not load $REGENDOC_TMPDIR/CWD/conftest.py - + ................................................ + 48 passed in 0.12 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -471,6 +452,7 @@ If you run this with reporting for skips enabled:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_module.py .s diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 1668de8ae..86a96db5a 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -119,6 +119,7 @@ then the test collection looks like this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + plugins: hypothesis-3.x.y collected 2 items @@ -162,32 +163,19 @@ Finding out what is collected You can always peek at the collection tree without running tests like this:: . $ pytest --collect-only pythoncollection.py - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules - return self._path2confmods[path] - KeyError: local('$REGENDOC_TMPDIR/CWD/pythoncollection.py') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules - return self._path2confmods[path] - KeyError: local('$REGENDOC_TMPDIR/CWD') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 357, in _importconftest - return self._conftestpath2mod[conftestpath] - KeyError: local('$REGENDOC_TMPDIR/CWD/conftest.py') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 363, in _importconftest - mod = conftestpath.pyimport() - File "$PYTHON_PREFIX/lib/python3.5/site-packages/py/_path/local.py", line 680, in pyimport - raise self.ImportMismatchError(modname, modfile, self) - py._path.local.LocalPath.ImportMismatchError: ('conftest', '$PWD/example/conftest.py', local('$REGENDOC_TMPDIR/CWD/conftest.py')) - ERROR: could not load $REGENDOC_TMPDIR/CWD/conftest.py + ======= test session starts ======== + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + plugins: hypothesis-3.x.y + collected 3 items + + + + + + + ======= no tests ran in 0.12 seconds ======== customizing test collection to find all .py files --------------------------------------------------------- @@ -246,6 +234,7 @@ will be left out:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 47c18851d..3c3b6c01c 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -13,6 +13,7 @@ get on the terminal - we are working on that):: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/assertion, inifile: + plugins: hypothesis-3.x.y collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -358,7 +359,7 @@ get on the terminal - we are working on that):: > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1219>:1: ValueError + <0-codegen /home/rpfannsc/Projects/pytest-dev/pytest/_pytest/python.py:1219>:1: ValueError _______ TestRaises.test_raises_doesnt ________ self = diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 80fbf3f74..e93f1a0da 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -115,6 +115,7 @@ directory with the above conftest.py:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -166,6 +167,7 @@ and when running it will see a skipped "slow" test:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_module.py .s @@ -180,6 +182,7 @@ Or run it including the ``slow`` marked test:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_module.py .. @@ -305,6 +308,7 @@ which will add the string to the test header accordingly:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y project deps: mylib-1.1 rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -327,11 +331,12 @@ which will add info only when run with "--v":: $ pytest -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache info1: did you know that ... did you? rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -342,6 +347,7 @@ and nothing when run plainly:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -376,6 +382,7 @@ Now we can profile which test functions execute the slowest:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 3 items test_some_are_slow.py ... @@ -383,7 +390,7 @@ Now we can profile which test functions execute the slowest:: ======= slowest 3 test durations ======== 0.20s call test_some_are_slow.py::test_funcslow2 0.10s call test_some_are_slow.py::test_funcslow1 - 0.00s setup test_some_are_slow.py::test_funcslow2 + 0.00s teardown test_some_are_slow.py::test_funcslow2 ======= 3 passed in 0.12 seconds ======== incremental testing - test steps @@ -442,6 +449,7 @@ If we run this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 4 items test_step.py .Fx. @@ -521,6 +529,7 @@ We can run this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 7 items test_step.py .Fx. @@ -629,6 +638,7 @@ and run them:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_module.py FF @@ -723,6 +733,7 @@ and run it:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index f760c423e..13e0f3930 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -72,6 +72,7 @@ marked ``smtp`` fixture function. Running the test looks like this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_smtpsimple.py F @@ -190,6 +191,7 @@ inspect what is going on and can now run the tests:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_module.py FF @@ -525,6 +527,7 @@ Running the above tests results in the following test IDs being used:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 10 items @@ -574,9 +577,10 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 2 items test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED @@ -643,9 +647,10 @@ Let's run the tests in verbose mode and with looking at the print-output:: $ pytest -v -s test_module.py ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collecting ... collected 8 items test_module.py::test_0[1] SETUP otherarg 1 diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 59abd4c79..07e92aeaf 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -26,7 +26,9 @@ Installation:: To check your installation has installed the correct version:: $ pytest --version - This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py + This is pytest version 3.x.y, imported from /home/rpfannsc/Projects/pytest-dev/pytest/pytest.py + setuptools registered plugins: + hypothesis-3.x.y at $PYTHON_PREFIX/lib/python3.5/site-packages/hypothesis/extra/pytestplugin.py .. _`simpletest`: @@ -48,6 +50,7 @@ That's it. You can execute the test function now:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_sample.py F diff --git a/doc/en/index.rst b/doc/en/index.rst index cb901b8d5..c4f2ba28c 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -27,6 +27,7 @@ To execute it:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_sample.py F diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index fc048fb08..857006e9b 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -57,6 +57,7 @@ them in turn:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 3 items test_expectation.py ..F @@ -126,6 +127,7 @@ Let's run this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 3 items test_expectation.py ..x diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 8e9ef3c7f..1dd1ac2a4 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -223,32 +223,28 @@ Here is a simple test file with the several usages: Running it with the report-on-xfail option gives this output:: example $ pytest -rx xfail_demo.py - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules - return self._path2confmods[path] - KeyError: local('$REGENDOC_TMPDIR/example/xfail_demo.py') + ======= test session starts ======== + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR/example, inifile: + plugins: hypothesis-3.x.y + collected 7 items - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 326, in _getconftestmodules - return self._path2confmods[path] - KeyError: local('$REGENDOC_TMPDIR/example') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 357, in _importconftest - return self._conftestpath2mod[conftestpath] - KeyError: local('$REGENDOC_TMPDIR/example/conftest.py') - - During handling of the above exception, another exception occurred: - Traceback (most recent call last): - File "$PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/config.py", line 363, in _importconftest - mod = conftestpath.pyimport() - File "$PYTHON_PREFIX/lib/python3.5/site-packages/py/_path/local.py", line 680, in pyimport - raise self.ImportMismatchError(modname, modfile, self) - py._path.local.LocalPath.ImportMismatchError: ('conftest', '$PWD/example/conftest.py', local('$REGENDOC_TMPDIR/example/conftest.py')) - ERROR: could not load $REGENDOC_TMPDIR/example/conftest.py + xfail_demo.py xxxxxxx + ======= short test summary info ======== + XFAIL xfail_demo.py::test_hello + XFAIL xfail_demo.py::test_hello2 + reason: [NOTRUN] + XFAIL xfail_demo.py::test_hello3 + condition: hasattr(os, 'sep') + XFAIL xfail_demo.py::test_hello4 + bug 110 + XFAIL xfail_demo.py::test_hello5 + condition: pytest.__version__[0] != "17" + XFAIL xfail_demo.py::test_hello6 + reason: reason + XFAIL xfail_demo.py::test_hello7 + ======= 7 xfailed in 0.12 seconds ======== xfail signature summary ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 642bb0814..82b333877 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -31,6 +31,7 @@ Running this would result in a passed test except for the last ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_tmpdir.py F diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 6ee7d505e..8164b4243 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -110,6 +110,7 @@ the ``self.db`` values in the traceback:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 2 items test_unittest_db.py FF diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index fb18c4a2f..b9c680ead 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -24,6 +24,7 @@ Running pytest now produces this output:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: + plugins: hypothesis-3.x.y collected 1 items test_show_warnings.py . From f39f416c5d84530f6839b9ea7a3c5303a61329f8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 12 May 2017 17:52:50 -0300 Subject: [PATCH 147/153] Improve tests a bit Use a normal function instead of a lambda Parametrize test about suite name option --- testing/test_junitxml.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 9735f45ef..bc637b035 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -616,7 +616,8 @@ def test_dont_configure_on_slaves(tmpdir): self.pluginmanager = self self.option = self - getini = lambda self, name: "pytest" + def getini(self, name): + return "pytest" junitprefix = None # XXX: shouldnt need tmpdir ? @@ -1036,20 +1037,16 @@ def test_url_property(testdir): assert (test_case.getAttribute('url') == test_url), "The URL did not get written to the xml" -def test_set_suite_name(testdir): - testdir.makepyfile(""" - import pytest - - def test_func(): - pass - """) - result, dom = runandparse(testdir, '-o', "junit_suite_name=my_suite") - assert result.ret == 0 - node = dom.find_first_by_tag("testsuite") - node.assert_attr(name="my_suite") - - -def test_set_suite_name_default(testdir): +@pytest.mark.parametrize('suite_name', ['my_suite', '']) +def test_set_suite_name(testdir, suite_name): + if suite_name: + testdir.makeini(""" + [pytest] + junit_suite_name={0} + """.format(suite_name)) + expected = suite_name + else: + expected = 'pytest' testdir.makepyfile(""" import pytest @@ -1059,4 +1056,5 @@ def test_set_suite_name_default(testdir): result, dom = runandparse(testdir) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") - node.assert_attr(name="pytest") + node.assert_attr(name=expected) + From 03753ca201fcd9d0720a3e4ff2f8dd20dff1a4f0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 13 May 2017 13:25:52 +0200 Subject: [PATCH 148/153] intermediate state after attempt with the plain env, DONT MERGE --- doc/en/Makefile | 2 +- doc/en/assert.rst | 2 -- doc/en/cache.rst | 3 -- doc/en/capture.rst | 1 - doc/en/doctest.rst | 1 - doc/en/example/markers.rst | 34 +++++------------- doc/en/example/nonpython.rst | 53 ++++++++++++++--------------- doc/en/example/parametrize.rst | 6 ---- doc/en/example/pythoncollection.rst | 3 -- doc/en/example/reportingdemo.rst | 3 +- doc/en/example/simple.rst | 15 ++------ doc/en/fixture.rst | 9 ++--- doc/en/getting-started.rst | 5 +-- doc/en/index.rst | 1 - doc/en/parametrize.rst | 2 -- doc/en/skipping.rst | 1 - doc/en/tmpdir.rst | 1 - doc/en/unittest.rst | 1 - doc/en/warnings.rst | 1 - 19 files changed, 40 insertions(+), 104 deletions(-) diff --git a/doc/en/Makefile b/doc/en/Makefile index 58c3b54e3..5b534da09 100644 --- a/doc/en/Makefile +++ b/doc/en/Makefile @@ -41,7 +41,7 @@ clean: -rm -rf $(BUILDDIR)/* regen: - PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} + PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-p\ no:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 2d9f8bc8e..d3d06804e 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -28,7 +28,6 @@ you will see the return value of the function call:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_assert1.py F @@ -173,7 +172,6 @@ if you run this module:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_assert2.py F diff --git a/doc/en/cache.rst b/doc/en/cache.rst index a273d292b..688b6dd04 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -83,7 +83,6 @@ If you then run it with ``--lf``:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y run-last-failure: rerun last 2 failures rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 50 items test_50.py FF @@ -126,7 +125,6 @@ of ``FF`` and dots):: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y run-last-failure: rerun last 2 failures first rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 50 items test_50.py FF................................................ @@ -231,7 +229,6 @@ You can always peek at the content of the cache using the ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y cachedir: $REGENDOC_TMPDIR/.cache ------------------------------- cache values ------------------------------- cache/lastfailed contains: diff --git a/doc/en/capture.rst b/doc/en/capture.rst index a7e9c879d..58ebdf840 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -66,7 +66,6 @@ of the failing function and hide the other one:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_module.py .F diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 35f4dc984..24c068a86 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -64,7 +64,6 @@ then you can just invoke ``pytest`` without command line options:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - plugins: hypothesis-3.x.y collected 1 items mymodule.py . diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index a28336c37..338f707a5 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -31,10 +31,9 @@ You can then restrict a test run to only run tests marked with ``webtest``:: $ pytest -v -m webtest ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -46,10 +45,9 @@ Or the inverse, running all tests except the webtest ones:: $ pytest -v -m "not webtest" ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -68,10 +66,9 @@ tests based on their module, class, method, or function name:: $ pytest -v test_server.py::TestClass::test_method ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -82,10 +79,9 @@ You can also select on the class:: $ pytest -v test_server.py::TestClass ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -96,10 +92,9 @@ Or select multiple nodes:: $ pytest -v test_server.py::TestClass test_server.py::test_send_http ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -135,10 +130,9 @@ select tests based on their names:: $ pytest -v -k http # running with the above defined example module ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -150,10 +144,9 @@ And you can also run all tests except the ones that match the keyword:: $ pytest -k "not send_http" -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -167,10 +160,9 @@ Or to select "http" and "quick" tests:: $ pytest -k "http or quick" -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -209,8 +201,6 @@ You can ask which markers exist for your test suite - the list includes our just $ pytest --markers @pytest.mark.webtest: mark a test as a webtest. - @pytest.mark.hypothesis: Tests which use hypothesis. - @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html @@ -364,7 +354,6 @@ the test needs:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_someenv.py s @@ -377,7 +366,6 @@ and here is one that specifies exactly the environment needed:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_someenv.py . @@ -389,8 +377,6 @@ The ``--markers`` option always gives you a list of available markers:: $ pytest --markers @pytest.mark.env(name): mark test to run only on named environment - @pytest.mark.hypothesis: Tests which use hypothesis. - @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html @@ -501,7 +487,6 @@ then you will see two tests skipped and two executed tests as expected:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items test_plat.py s.s. @@ -516,7 +501,6 @@ Note that if you specify a platform via the marker-command line option like this ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items test_plat.py . @@ -569,7 +553,6 @@ We can now use the ``-m option`` to select one set:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items test_module.py FF @@ -592,7 +575,6 @@ or to select both "event" and "interface" tests:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items test_module.py FFF diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index fd3fc1f92..5784f6ed6 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -29,16 +29,16 @@ now execute the test specification:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: - plugins: hypothesis-3.x.y - collected 0 items / 1 errors + collected 2 items - ======= ERRORS ======== - _______ ERROR collecting test_simple.yml ________ - conftest.py:11: in collect - import yaml # we need a yaml parser, e.g. PyYAML - E ImportError: No module named 'yaml' - !!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!! - ======= 1 error in 0.12 seconds ======== + test_simple.yml F. + + ======= FAILURES ======== + _______ usecase: hello ________ + usecase execution failed + spec failed: 'some': 'other' + no further details known at this point. + ======= 1 failed, 1 passed in 0.12 seconds ======== .. regendoc:wipe @@ -59,19 +59,20 @@ consulted when reporting in ``verbose`` mode:: nonpython $ pytest -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR/nonpython, inifile: - plugins: hypothesis-3.x.y - collecting ... collected 0 items / 1 errors + collecting ... collected 2 items - ======= ERRORS ======== - _______ ERROR collecting test_simple.yml ________ - conftest.py:11: in collect - import yaml # we need a yaml parser, e.g. PyYAML - E ImportError: No module named 'yaml' - !!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!! - ======= 1 error in 0.12 seconds ======== + test_simple.yml::hello FAILED + test_simple.yml::ok PASSED + + ======= FAILURES ======== + _______ usecase: hello ________ + usecase execution failed + spec failed: 'some': 'other' + no further details known at this point. + ======= 1 failed, 1 passed in 0.12 seconds ======== .. regendoc:wipe @@ -82,13 +83,9 @@ interesting to just look at the collection tree:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/nonpython, inifile: - plugins: hypothesis-3.x.y - collected 0 items / 1 errors + collected 2 items + + + - ======= ERRORS ======== - _______ ERROR collecting test_simple.yml ________ - conftest.py:11: in collect - import yaml # we need a yaml parser, e.g. PyYAML - E ImportError: No module named 'yaml' - !!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!! - ======= 1 error in 0.12 seconds ======== + ======= no tests ran in 0.12 seconds ======== diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 2ab72da03..ff860d7ab 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -132,7 +132,6 @@ objects, they are still using the default pytest representation:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 6 items @@ -184,7 +183,6 @@ this is a fully self-contained example which you can run with:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items test_scenarios.py .... @@ -198,7 +196,6 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items @@ -264,7 +261,6 @@ Let's first see how it looks like at collection time:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items @@ -326,7 +322,6 @@ The result of this test will be successful:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items @@ -452,7 +447,6 @@ If you run this with reporting for skips enabled:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_module.py .s diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 86a96db5a..8d36c2e37 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -119,7 +119,6 @@ then the test collection looks like this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - plugins: hypothesis-3.x.y collected 2 items @@ -166,7 +165,6 @@ You can always peek at the collection tree without running tests like this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - plugins: hypothesis-3.x.y collected 3 items @@ -234,7 +232,6 @@ will be left out:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini - plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 3c3b6c01c..47c18851d 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -13,7 +13,6 @@ get on the terminal - we are working on that):: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/assertion, inifile: - plugins: hypothesis-3.x.y collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -359,7 +358,7 @@ get on the terminal - we are working on that):: > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen /home/rpfannsc/Projects/pytest-dev/pytest/_pytest/python.py:1219>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1219>:1: ValueError _______ TestRaises.test_raises_doesnt ________ self = diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index e93f1a0da..442f2f866 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -115,7 +115,6 @@ directory with the above conftest.py:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -167,7 +166,6 @@ and when running it will see a skipped "slow" test:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_module.py .s @@ -182,7 +180,6 @@ Or run it including the ``slow`` marked test:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_module.py .. @@ -308,7 +305,6 @@ which will add the string to the test header accordingly:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y project deps: mylib-1.1 rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -331,12 +327,11 @@ which will add info only when run with "--v":: $ pytest -v ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache info1: did you know that ... did you? rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -347,7 +342,6 @@ and nothing when run plainly:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -382,7 +376,6 @@ Now we can profile which test functions execute the slowest:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 3 items test_some_are_slow.py ... @@ -390,7 +383,7 @@ Now we can profile which test functions execute the slowest:: ======= slowest 3 test durations ======== 0.20s call test_some_are_slow.py::test_funcslow2 0.10s call test_some_are_slow.py::test_funcslow1 - 0.00s teardown test_some_are_slow.py::test_funcslow2 + 0.00s setup test_some_are_slow.py::test_funcfast ======= 3 passed in 0.12 seconds ======== incremental testing - test steps @@ -449,7 +442,6 @@ If we run this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 4 items test_step.py .Fx. @@ -529,7 +521,6 @@ We can run this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 7 items test_step.py .Fx. @@ -638,7 +629,6 @@ and run them:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_module.py FF @@ -733,7 +723,6 @@ and run it:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 13e0f3930..f760c423e 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -72,7 +72,6 @@ marked ``smtp`` fixture function. Running the test looks like this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_smtpsimple.py F @@ -191,7 +190,6 @@ inspect what is going on and can now run the tests:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_module.py FF @@ -527,7 +525,6 @@ Running the above tests results in the following test IDs being used:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 10 items @@ -577,10 +574,9 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 2 items test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED @@ -647,10 +643,9 @@ Let's run the tests in verbose mode and with looking at the print-output:: $ pytest -v -s test_module.py ======= test session starts ======== - platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3 + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collecting ... collected 8 items test_module.py::test_0[1] SETUP otherarg 1 diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 07e92aeaf..59abd4c79 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -26,9 +26,7 @@ Installation:: To check your installation has installed the correct version:: $ pytest --version - This is pytest version 3.x.y, imported from /home/rpfannsc/Projects/pytest-dev/pytest/pytest.py - setuptools registered plugins: - hypothesis-3.x.y at $PYTHON_PREFIX/lib/python3.5/site-packages/hypothesis/extra/pytestplugin.py + This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py .. _`simpletest`: @@ -50,7 +48,6 @@ That's it. You can execute the test function now:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_sample.py F diff --git a/doc/en/index.rst b/doc/en/index.rst index c4f2ba28c..cb901b8d5 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -27,7 +27,6 @@ To execute it:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_sample.py F diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index 857006e9b..fc048fb08 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -57,7 +57,6 @@ them in turn:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 3 items test_expectation.py ..F @@ -127,7 +126,6 @@ Let's run this:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 3 items test_expectation.py ..x diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 1dd1ac2a4..4ee8feb0c 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -226,7 +226,6 @@ Running it with the report-on-xfail option gives this output:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR/example, inifile: - plugins: hypothesis-3.x.y collected 7 items xfail_demo.py xxxxxxx diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 82b333877..642bb0814 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -31,7 +31,6 @@ Running this would result in a passed test except for the last ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_tmpdir.py F diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 8164b4243..6ee7d505e 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -110,7 +110,6 @@ the ``self.db`` values in the traceback:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 2 items test_unittest_db.py FF diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index b9c680ead..fb18c4a2f 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -24,7 +24,6 @@ Running pytest now produces this output:: ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: - plugins: hypothesis-3.x.y collected 1 items test_show_warnings.py . From 00e7ee532e1f692d508a2cb9241f4b78921f6f4c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 May 2017 20:25:53 -0300 Subject: [PATCH 149/153] Fix minor regendoc issues --- doc/en/Makefile | 2 +- doc/en/unittest.rst | 3 ++- doc/en/warnings.rst | 2 +- tox.ini | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/en/Makefile b/doc/en/Makefile index 5b534da09..286bbd8e7 100644 --- a/doc/en/Makefile +++ b/doc/en/Makefile @@ -41,7 +41,7 @@ clean: -rm -rf $(BUILDDIR)/* regen: - PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-p\ no:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} + PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 6ee7d505e..436e64e07 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -171,7 +171,8 @@ creation of a per-test temporary directory:: tmpdir.join("samplefile.ini").write("# testdata") def test_method(self): - s = open("samplefile.ini").read() + with open("samplefile.ini") as f: + s = f.read() assert "testdata" in s Due to the ``autouse`` flag the ``initdir`` fixture function will be diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index fb18c4a2f..64316cf6b 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -39,7 +39,7 @@ Running pytest now produces this output:: The ``-W`` flag can be passed to control which warnings will be displayed or even turn them into errors:: - $ pytest -q test_show_warning.py -W error::DeprecationWarning + $ pytest -q test_show_warnings.py -W error::DeprecationWarning no tests ran in 0.12 seconds ERROR: file not found: test_show_warning.py diff --git a/tox.ini b/tox.ini index 52bd471c4..a2d4af9ed 100644 --- a/tox.ini +++ b/tox.ini @@ -134,6 +134,7 @@ commands= [testenv:regen] changedir=doc/en +skipsdist=True basepython = python3.5 deps=sphinx PyYAML From feab3ba70ffcdca646d00080c1bdf32b4f1ed308 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 May 2017 20:23:04 -0300 Subject: [PATCH 150/153] Implement tasks to improve release automation --- HOWTORELEASE.rst | 27 ++++++++------- tasks/generate.py | 78 ++++++++++++++++++++++++++++++++++++------ tasks/requirements.txt | 3 ++ 3 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 tasks/requirements.txt diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index 0d63cdbd4..21834c672 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -1,27 +1,28 @@ How to release pytest -------------------------------------------- -Note: this assumes you have already registered on PyPI and you have -`invoke `_ installed. +.. important:: -#. Check and finalize ``CHANGELOG.rst``. + pytest releases must be prepared on **linux** because the docs and examples expect + to be executed in that platform. -#. Generate a new release announcement:: +#. Install development dependencies in a virtual environment with:: - invoke generate.announce VERSION + pip3 install -r tasks/requirements.txt -Feel free to modify the generated files before committing. +#. Create a branch ``release-X.Y.Z`` with the version for the release. Make sure it is up to date + with the latest ``master`` (for patch releases) and with the latest ``features`` merged with + the latest ``master`` (for minor releases). Ensure your are in a clean work tree. -#. Regenerate the docs examples using tox:: +#. Check and finalize ``CHANGELOG.rst`` (will be automated soon). - tox -e regen +#. Execute to automatically generate docs, announcements and upload a package to + your ``devpi`` staging server:: -#. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions. + invoke generate.pre_release --password -#. Use devpi for uploading a release tarball to a staging area:: - - devpi use https://devpi.net/USER/dev - devpi upload --formats sdist,bdist_wheel + If ``--password`` is not given, it is assumed the user is already logged in. If you don't have + an account, please ask for one! #. Run from multiple machines:: diff --git a/tasks/generate.py b/tasks/generate.py index 082cbe8cb..deb77ed4f 100644 --- a/tasks/generate.py +++ b/tasks/generate.py @@ -1,5 +1,6 @@ +import os from pathlib import Path -from subprocess import check_output +from subprocess import check_output, check_call import invoke @@ -9,11 +10,7 @@ import invoke }) def announce(ctx, version): """Generates a new release announcement entry in the docs.""" - print("[generate.announce] Generating Announce") - # Get our list of authors - print("[generate.announce] Collecting author names") - stdout = check_output(["git", "describe", "--abbrev=0", '--tags']) stdout = stdout.decode('utf-8') last_version = stdout.strip() @@ -29,12 +26,12 @@ def announce(ctx, version): contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n' text = template_text.format(version=version, contributors=contributors_text) - target = Path(__file__).joinpath('../../doc/en/announce/release-{}.rst'.format(version)) + target = Path(__file__).parent.joinpath('../doc/en/announce/release-{}.rst'.format(version)) target.write_text(text, encoding='UTF-8') print("[generate.announce] Generated {}".format(target.name)) # Update index with the new release entry - index_path = Path(__file__).joinpath('../../doc/en/announce/index.rst') + index_path = Path(__file__).parent.joinpath('../doc/en/announce/index.rst') lines = index_path.read_text(encoding='UTF-8').splitlines() indent = ' ' for index, line in enumerate(lines): @@ -48,9 +45,68 @@ def announce(ctx, version): print("[generate.announce] Skip {} (already contains release)".format(index_path.name)) break + check_call(['git', 'add', str(target)]) + + +@invoke.task() +def regen(ctx): + """Call regendoc tool to update examples and pytest output in the docs.""" + print("[generate.regen] Updating docs") + check_call(['tox', '-e', 'regen']) + + +@invoke.task() +def make_tag(ctx, version): + """Create a new (local) tag for the release, only if the repository is clean.""" + from git import Repo + + repo = Repo('.') + if repo.is_dirty(): + print('Current repository is dirty. Please commit any changes and try again.') + raise invoke.Exit(code=2) + + tag_names = [x.name for x in repo.tags] + if version in tag_names: + print("[generate.make_tag] Delete existing tag {}".format(version)) + repo.delete_tag(version) + + print("[generate.make_tag] Create tag {}".format(version)) + repo.create_tag(version) + + +@invoke.task() +def devpi_upload(ctx, version, user, password=None): + """Creates and uploads a package to devpi for testing.""" + if password: + print("[generate.devpi_upload] devpi login {}".format(user)) + check_call(['devpi', 'login', user, '--password', password]) + + check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)]) + + env = os.environ.copy() + env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version + check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env) + print("[generate.devpi_upload] package uploaded") + + +@invoke.task(help={ + 'version': 'version being released', + 'user': 'name of the user on devpi to stage the generated package', + 'password': 'user password on devpi to stage the generated package ' + '(if not given assumed logged in)', +}) +def pre_release(ctx, version, user, password=None): + """Generates new docs, release announcements and uploads a new release to devpi for testing.""" + announce(ctx, version) + regen(ctx) + + msg = 'Preparing release version {}'.format(version) + check_call(['git', 'commit', '-a', '-m', msg]) + + make_tag(ctx, version) + + devpi_upload(ctx, version=version, user=user, password=password) + print() - print('Please review the generated files and commit with:') - print(' git commit -a -m "Generate new release announcement for {}'.format(version)) - - + print('[generate.pre_release] Please push your branch and open a PR.') diff --git a/tasks/requirements.txt b/tasks/requirements.txt new file mode 100644 index 000000000..35afc29c8 --- /dev/null +++ b/tasks/requirements.txt @@ -0,0 +1,3 @@ +invoke +tox +gitpython From d86c89e19338f78e9f757a860eefbf9bb9994ab7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 17 May 2017 18:16:11 -0300 Subject: [PATCH 151/153] Revert refactor of old-style to new-style classes As discussed in the mailing list, unfortunately this might break APIs due to the subtle differences between new and old-style classes (see #2398). This reverts commit d4afa1554b225d11565d5fb81136edea51c54b0d from PR #2179. --- CHANGELOG.rst | 4 ---- _pytest/_argcomplete.py | 2 +- _pytest/assertion/__init__.py | 2 +- _pytest/cacheprovider.py | 2 +- _pytest/capture.py | 12 ++++++------ _pytest/config.py | 10 +++++----- _pytest/debugging.py | 5 ++--- _pytest/fixtures.py | 10 +++++----- _pytest/main.py | 2 +- _pytest/mark.py | 9 ++++----- _pytest/monkeypatch.py | 4 ++-- _pytest/pytester.py | 22 +++++++++++----------- _pytest/runner.py | 4 ++-- _pytest/skipping.py | 2 +- _pytest/terminal.py | 4 ++-- _pytest/tmpdir.py | 2 +- 16 files changed, 45 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b1852675d..2f790048b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -33,9 +33,6 @@ Changes this is to prepare the removal of preloadconfig in pytest 4.0 Thanks to `@RonnyPfannschmidt`_ for the PR. -* Old-style classes have been changed to new-style classes in order to improve - compatibility with Python 2. Thanks to `@MichalTHEDUDE`_ and `@mandeep`_ for the PR (`#2147`_). - * It is now possible to skip test classes from being collected by setting a ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks to `@syre`_ for the report and `@lwm`_ for the PR. @@ -123,7 +120,6 @@ Bug Fixes .. _#2013: https://github.com/pytest-dev/pytest/issues/2013 .. _#2101: https://github.com/pytest-dev/pytest/pull/2101 .. _#2166: https://github.com/pytest-dev/pytest/pull/2166 -.. _#2147: https://github.com/pytest-dev/pytest/issues/2147 .. _#2208: https://github.com/pytest-dev/pytest/issues/2208 .. _#2228: https://github.com/pytest-dev/pytest/issues/2228 .. _#2308: https://github.com/pytest-dev/pytest/issues/2308 diff --git a/_pytest/_argcomplete.py b/_pytest/_argcomplete.py index 12040b53a..8c93e4c92 100644 --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -62,7 +62,7 @@ import sys import os from glob import glob -class FastFilesCompleter(object): +class FastFilesCompleter: 'Fast file completer class' def __init__(self, directories=True): self.directories = directories diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index cf9265330..acb034d86 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -57,7 +57,7 @@ class DummyRewriteHook(object): pass -class AssertionState(object): +class AssertionState: """State for the assertion plugin.""" def __init__(self, config, mode): diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 4cecc771d..ab08362ee 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -87,7 +87,7 @@ class Cache(object): json.dump(value, f, indent=2, sort_keys=True) -class LFPlugin(object): +class LFPlugin: """ Plugin which implements the --lf (run last-failing) option """ def __init__(self, config): self.config = config diff --git a/_pytest/capture.py b/_pytest/capture.py index c6fc80c0b..6bc3fc1f0 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -57,7 +57,7 @@ def pytest_load_initial_conftests(early_config, parser, args): sys.stderr.write(err) -class CaptureManager(object): +class CaptureManager: def __init__(self, method): self._method = method @@ -182,7 +182,7 @@ def capfd(request): return c -class CaptureFixture(object): +class CaptureFixture: def __init__(self, captureclass, request): self.captureclass = captureclass self.request = request @@ -315,10 +315,10 @@ class MultiCapture(object): return (self.out.snap() if self.out is not None else "", self.err.snap() if self.err is not None else "") -class NoCapture(object): +class NoCapture: __init__ = start = done = suspend = resume = lambda *args: None -class FDCapture(object): +class FDCapture: """ Capture IO to/from a given os-level filedescriptor. """ def __init__(self, targetfd, tmpfile=None): @@ -394,7 +394,7 @@ class FDCapture(object): os.write(self.targetfd_save, data) -class SysCapture(object): +class SysCapture: def __init__(self, fd, tmpfile=None): name = patchsysdict[fd] self._old = getattr(sys, name) @@ -432,7 +432,7 @@ class SysCapture(object): self._old.flush() -class DontReadFromInput(object): +class DontReadFromInput: """Temporary stub class. Ideally when stdin is accessed, the capturing should be turned off, with possibly all data captured so far sent to the screen. This should be configurable, though, diff --git a/_pytest/config.py b/_pytest/config.py index 0a79cb5b1..0e65beec8 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -63,7 +63,7 @@ def main(args=None, plugins=None): sys.stderr.write("ERROR: %s\n" %(msg,)) return 4 -class cmdline(object): # compatibility namespace +class cmdline: # compatibility namespace main = staticmethod(main) @@ -464,7 +464,7 @@ def _get_plugin_specs_as_list(specs): return [] -class Parser(object): +class Parser: """ Parser for command line arguments and ini-file values. :ivar extra_info: dict of generic param -> value to display in case @@ -599,7 +599,7 @@ class ArgumentError(Exception): return self.msg -class Argument(object): +class Argument: """class that mimics the necessary behaviour of optparse.Option its currently a least effort implementation @@ -729,7 +729,7 @@ class Argument(object): return 'Argument({0})'.format(', '.join(args)) -class OptionGroup(object): +class OptionGroup: def __init__(self, name, description="", parser=None): self.name = name self.description = description @@ -855,7 +855,7 @@ class CmdOptions(object): def copy(self): return CmdOptions(self.__dict__) -class Notset(object): +class Notset: def __repr__(self): return "" diff --git a/_pytest/debugging.py b/_pytest/debugging.py index ad81fedc0..73a0a2ef5 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -40,8 +40,7 @@ def pytest_configure(config): pytestPDB._pdb_cls = pdb_cls config._cleanup.append(fin) - -class pytestPDB(object): +class pytestPDB: """ Pseudo PDB that defers to the real pdb. """ _pluginmanager = None _config = None @@ -63,7 +62,7 @@ class pytestPDB(object): cls._pdb_cls().set_trace(frame) -class PdbInvoke(object): +class PdbInvoke: def pytest_exception_interact(self, node, call, report): capman = node.config.pluginmanager.getplugin("capturemanager") if capman: diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 066d7e86a..1a6e245c7 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -241,7 +241,7 @@ def fillfixtures(function): def get_direct_param_fixture_func(request): return request.param -class FuncFixtureInfo(object): +class FuncFixtureInfo: def __init__(self, argnames, names_closure, name2fixturedefs): self.argnames = argnames self.names_closure = names_closure @@ -439,7 +439,7 @@ class FixtureRequest(FuncargnamesCompatAttr): fixturedef = self._getnextfixturedef(argname) except FixtureLookupError: if argname == "request": - class PseudoFixtureDef(object): + class PseudoFixtureDef: cached_result = (self, [0], None) scope = "function" return PseudoFixtureDef @@ -707,7 +707,7 @@ def call_fixture_func(fixturefunc, request, kwargs): return res -class FixtureDef(object): +class FixtureDef: """ A container for a factory definition. """ def __init__(self, fixturemanager, baseid, argname, func, scope, params, unittest=False, ids=None): @@ -806,7 +806,7 @@ def pytest_fixture_setup(fixturedef, request): return result -class FixtureFunctionMarker(object): +class FixtureFunctionMarker: def __init__(self, scope, params, autouse=False, ids=None, name=None): self.scope = scope self.params = params @@ -893,7 +893,7 @@ def pytestconfig(request): return request.config -class FixtureManager(object): +class FixtureManager: """ pytest fixtures definitions and information is stored and managed from this class. diff --git a/_pytest/main.py b/_pytest/main.py index ef5b4f378..480810cc8 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -190,7 +190,7 @@ def pytest_ignore_collect(path, config): return False -class FSHookProxy(object): +class FSHookProxy: def __init__(self, fspath, pm, remove_mods): self.fspath = fspath self.pm = pm diff --git a/_pytest/mark.py b/_pytest/mark.py index c34df1239..8b40a4f6e 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -152,7 +152,7 @@ def pytest_collection_modifyitems(items, config): items[:] = remaining -class MarkMapping(object): +class MarkMapping: """Provides a local mapping for markers where item access resolves to True if the marker is present. """ def __init__(self, keywords): @@ -166,7 +166,7 @@ class MarkMapping(object): return name in self._mymarks -class KeywordMapping(object): +class KeywordMapping: """Provides a local mapping for keywords. Given a list of names, map any substring of one of these names to True. """ @@ -230,7 +230,7 @@ def pytest_unconfigure(config): MARK_GEN._config = getattr(config, '_old_mark_config', None) -class MarkGenerator(object): +class MarkGenerator: """ Factory for :class:`MarkDecorator` objects - exposed as a ``pytest.mark`` singleton instance. Example:: @@ -270,8 +270,7 @@ def istestfunc(func): return hasattr(func, "__call__") and \ getattr(func, "__name__", "") != "" - -class MarkDecorator(object): +class MarkDecorator: """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be :ref:`retrieved by hooks as item keywords `. diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index ec846f910..a70b23dda 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -89,7 +89,7 @@ def derive_importpath(import_path, raising): return attr, target -class Notset(object): +class Notset: def __repr__(self): return "" @@ -97,7 +97,7 @@ class Notset(object): notset = Notset() -class MonkeyPatch(object): +class MonkeyPatch: """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes. """ diff --git a/_pytest/pytester.py b/_pytest/pytester.py index cc2e5a2ad..901caa340 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -167,7 +167,7 @@ def _pytest(request): """ return PytestArg(request) -class PytestArg(object): +class PytestArg: def __init__(self, request): self.request = request @@ -182,7 +182,7 @@ def get_public_names(l): return [x for x in l if x[0] != "_"] -class ParsedCall(object): +class ParsedCall: def __init__(self, name, kwargs): self.__dict__.update(kwargs) self._name = name @@ -193,7 +193,7 @@ class ParsedCall(object): return "" %(self._name, d) -class HookRecorder(object): +class HookRecorder: """Record all hooks called in a plugin manager. This wraps all the hook calls in the plugin manager, recording @@ -337,7 +337,7 @@ def testdir(request, tmpdir_factory): rex_outcome = re.compile(r"(\d+) ([\w-]+)") -class RunResult(object): +class RunResult: """The result of running a command. Attributes: @@ -383,7 +383,7 @@ class RunResult(object): -class Testdir(object): +class Testdir: """Temporary test directory with tools to test/run pytest itself. This is based on the ``tmpdir`` fixture but provides a number of @@ -711,7 +711,7 @@ class Testdir(object): rec = [] - class Collect(object): + class Collect: def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) @@ -722,7 +722,7 @@ class Testdir(object): if len(rec) == 1: reprec = rec.pop() else: - class reprec(object): + class reprec: pass reprec.ret = ret @@ -747,13 +747,13 @@ class Testdir(object): reprec = self.inline_run(*args, **kwargs) except SystemExit as e: - class reprec(object): + class reprec: ret = e.args[0] except Exception: traceback.print_exc() - class reprec(object): + class reprec: ret = 3 finally: out, err = capture.readouterr() @@ -1039,7 +1039,7 @@ def getdecoded(out): py.io.saferepr(out),) -class LineComp(object): +class LineComp: def __init__(self): self.stringio = py.io.TextIO() @@ -1055,7 +1055,7 @@ class LineComp(object): return LineMatcher(lines1).fnmatch_lines(lines2) -class LineMatcher(object): +class LineMatcher: """Flexible matching of text. This is a convenience class to test large texts like the output of diff --git a/_pytest/runner.py b/_pytest/runner.py index 87dd240c8..fd0b549a9 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -49,7 +49,7 @@ def pytest_sessionstart(session): def pytest_sessionfinish(session): session._setupstate.teardown_all() -class NodeInfo(object): +class NodeInfo: def __init__(self, location): self.location = location @@ -144,7 +144,7 @@ def call_runtest_hook(item, when, **kwds): ihook = getattr(item.ihook, hookname) return CallInfo(lambda: ihook(item=item, **kwds), when=when) -class CallInfo(object): +class CallInfo: """ Result/Exception info a function invocation. """ #: None or ExceptionInfo object. excinfo = None diff --git a/_pytest/skipping.py b/_pytest/skipping.py index e7922da7d..5af1ca404 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -72,7 +72,7 @@ def xfail(reason=""): xfail.Exception = XFailed -class MarkEvaluator(object): +class MarkEvaluator: def __init__(self, item, name): self.item = item self.name = name diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 8c3bacf2d..e226d607b 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -84,7 +84,7 @@ def pytest_report_teststatus(report): return report.outcome, letter, report.outcome.upper() -class WarningReport(object): +class WarningReport: """ Simple structure to hold warnings information captured by ``pytest_logwarning``. """ @@ -118,7 +118,7 @@ class WarningReport(object): return None -class TerminalReporter(object): +class TerminalReporter: def __init__(self, config, file=None): import _pytest.config self.config = config diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 7c5fac17c..596014059 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -8,7 +8,7 @@ import py from _pytest.monkeypatch import MonkeyPatch -class TempdirFactory(object): +class TempdirFactory: """Factory for temporary directories under the common base temp directory. The base directory can be configured using the ``--basetemp`` option. From 0ff7f5d0c6fe7c4cb206fa92bba8a418f1d2c50b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 19 May 2017 19:08:59 -0300 Subject: [PATCH 152/153] Prepare CHANGELOG for version 3.1.0 --- CHANGELOG.rst | 90 ++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f790048b..25853e136 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,17 +1,19 @@ -3.1.0.dev (unreleased) -====================== +3.1.0 (2017-05-20) +================== New Features ------------ -* fix `#533`_: Added ``junit_suite_name`` ini option to specify root `` name for JUnit XML reports +* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically + captures and displays warnings at the end of the test session. + Thanks `@nicoddemus`_ for the PR. + +* Added ``junit_suite_name`` ini option to specify root `` name for JUnit XML reports (`#533`_). * Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. Thanks `@wheerd`_ for the PR (`#2101`_). -* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR. - * ``pytest.warns`` now checks for subclass relationship rather than class equality. Thanks `@lesteve`_ for the PR (`#2166`_) @@ -21,10 +23,6 @@ New Features * ``pytest.param`` can be used to declare test parameter sets with marks and test ids. Thanks `@RonnyPfannschmidt`_ for the PR. -* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically - captures and displays warnings at the end of the test session. - Thanks `@nicoddemus`_ for the PR. - Changes ------- @@ -33,6 +31,8 @@ Changes this is to prepare the removal of preloadconfig in pytest 4.0 Thanks to `@RonnyPfannschmidt`_ for the PR. +* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR. + * It is now possible to skip test classes from being collected by setting a ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks to `@syre`_ for the report and `@lwm`_ for the PR. @@ -96,39 +96,6 @@ Bug Fixes while using ``capsys`` fixture in python 3. (`#1407`_). Thanks to `@asottile`_. - -.. _@davidszotten: https://github.com/davidszotten -.. _@fushi: https://github.com/fushi -.. _@mattduck: https://github.com/mattduck -.. _@wheerd: https://github.com/wheerd -.. _@fogo: https://github.com/fogo -.. _@mandeep: https://github.com/mandeep -.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE -.. _@reutsharabani: https://github.com/reutsharabani -.. _@unsignedint: https://github.com/unsignedint -.. _@Kriechi: https://github.com/Kriechi -.. _@ojii: https://github.com/ojii - - -.. _#533: https://github.com/pytest-dev/pytest/issues/533 -.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 -.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 -.. _#1821: https://github.com/pytest-dev/pytest/issues/1821 -.. _#1874: https://github.com/pytest-dev/pytest/pull/1874 -.. _#1952: https://github.com/pytest-dev/pytest/pull/1952 -.. _#2007: https://github.com/pytest-dev/pytest/issues/2007 -.. _#2013: https://github.com/pytest-dev/pytest/issues/2013 -.. _#2101: https://github.com/pytest-dev/pytest/pull/2101 -.. _#2166: https://github.com/pytest-dev/pytest/pull/2166 -.. _#2208: https://github.com/pytest-dev/pytest/issues/2208 -.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 -.. _#2308: https://github.com/pytest-dev/pytest/issues/2308 -.. _#2391: https://github.com/pytest-dev/pytest/issues/2391 - - -3.0.8 (unreleased) -================== - * Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather than ValueErrors in the ``fileno`` method (`#2276`_). Thanks `@metasyn`_ for the PR. @@ -136,7 +103,7 @@ Bug Fixes * Fix exception formatting while importing modules when the exception message contains non-ascii characters (`#2336`_). Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR. - + * Added documentation related to issue (`#1937`_) Thanks `@skylarjhdownes`_ for the PR. @@ -146,26 +113,45 @@ Bug Fixes * Show the correct error message when collect "parametrize" func with wrong args (`#2383`_). Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR. -* -* - -* - - - -.. _@skylarjhdownes: https://github.com/skylarjhdownes +.. _@davidszotten: https://github.com/davidszotten .. _@fabioz: https://github.com/fabioz -.. _@metasyn: https://github.com/metasyn +.. _@fogo: https://github.com/fogo +.. _@fushi: https://github.com/fushi .. _@Kodiologist: https://github.com/Kodiologist +.. _@Kriechi: https://github.com/Kriechi +.. _@mandeep: https://github.com/mandeep +.. _@mattduck: https://github.com/mattduck +.. _@metasyn: https://github.com/metasyn +.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE +.. _@ojii: https://github.com/ojii +.. _@reutsharabani: https://github.com/reutsharabani .. _@robin0371: https://github.com/robin0371 +.. _@skylarjhdownes: https://github.com/skylarjhdownes +.. _@unsignedint: https://github.com/unsignedint +.. _@wheerd: https://github.com/wheerd +.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 +.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 +.. _#1821: https://github.com/pytest-dev/pytest/issues/1821 +.. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1937: https://github.com/pytest-dev/pytest/issues/1937 +.. _#1952: https://github.com/pytest-dev/pytest/pull/1952 +.. _#2007: https://github.com/pytest-dev/pytest/issues/2007 +.. _#2013: https://github.com/pytest-dev/pytest/issues/2013 +.. _#2101: https://github.com/pytest-dev/pytest/pull/2101 +.. _#2166: https://github.com/pytest-dev/pytest/pull/2166 +.. _#2208: https://github.com/pytest-dev/pytest/issues/2208 +.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 .. _#2276: https://github.com/pytest-dev/pytest/issues/2276 +.. _#2308: https://github.com/pytest-dev/pytest/issues/2308 .. _#2336: https://github.com/pytest-dev/pytest/issues/2336 .. _#2369: https://github.com/pytest-dev/pytest/issues/2369 .. _#2383: https://github.com/pytest-dev/pytest/issues/2383 +.. _#2391: https://github.com/pytest-dev/pytest/issues/2391 +.. _#533: https://github.com/pytest-dev/pytest/issues/533 + 3.0.7 (2017-03-14) From 9b48613baa9ee1ab4e1e233bfe956de03e89b23e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 19 May 2017 18:12:59 -0400 Subject: [PATCH 153/153] Preparing release version 3.1.0 --- doc/en/announce/index.rst | 1 + doc/en/announce/release-3.1.0.rst | 61 +++++++++++++++++++++++++++++++ doc/en/cache.rst | 4 +- doc/en/example/parametrize.rst | 6 ++- doc/en/unittest.rst | 8 +--- doc/en/warnings.rst | 17 ++++++++- 6 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 doc/en/announce/release-3.1.0.rst diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 52dd90de0..5eadc9bf1 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.1.0 release-3.0.7 release-3.0.6 release-3.0.5 diff --git a/doc/en/announce/release-3.1.0.rst b/doc/en/announce/release-3.1.0.rst new file mode 100644 index 000000000..99cc6bdbe --- /dev/null +++ b/doc/en/announce/release-3.1.0.rst @@ -0,0 +1,61 @@ +pytest-3.1.0 +======================================= + +The pytest team is proud to announce the 3.1.0 release! + +pytest is a mature Python testing tool with more than a 1600 tests +against itself, passing on many different interpreters and platforms. + +This release contains a bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + +http://doc.pytest.org/en/latest/changelog.html + +For complete documentation, please visit: + + http://docs.pytest.org + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Ben Lloyd +* Bruno Oliveira +* David Giese +* David Szotten +* Dmitri Pribysh +* Florian Bruhin +* Florian Schulze +* Floris Bruynooghe +* John Towler +* Jonas Obrist +* Katerina Koukiou +* Kodi Arfer +* Krzysztof Szularz +* Lev Maximov +* Loïc Estève +* Luke Murphy +* Manuel Krebber +* Matthew Duck +* Matthias Bussonnier +* Michael Howitz +* Michal Wajszczuk +* Paweł Adamczak +* Rafael Bertoldi +* Ravi Chandra +* Ronny Pfannschmidt +* Skylar Downes +* Thomas Kriechbaumer +* Vitaly Lashmanov +* Vlad Dragos +* Wheerd +* Xander Johnson +* mandeep +* reut + + +Happy testing, +The Pytest Development Team diff --git a/doc/en/cache.rst b/doc/en/cache.rst index 688b6dd04..9672562af 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -231,10 +231,10 @@ You can always peek at the content of the cache using the rootdir: $REGENDOC_TMPDIR, inifile: cachedir: $REGENDOC_TMPDIR/.cache ------------------------------- cache values ------------------------------- - cache/lastfailed contains: - {'test_caching.py::test_function': True} example/value contains: 42 + cache/lastfailed contains: + {'test_caching.py::test_function': True} ======= no tests ran in 0.12 seconds ======== diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index ff860d7ab..7a9992ca7 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -397,8 +397,10 @@ is to be run with different sets of arguments for its three arguments: Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):: . $ pytest -rs -q multipython.py - ................................................ - 48 passed in 0.12 seconds + sssssssssssssss.........sss.........sss......... + ======= short test summary info ======== + SKIP [21] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.6' not found + 27 passed, 21 skipped in 0.12 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 436e64e07..06180f19d 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -184,13 +184,7 @@ Running this test module ...:: $ pytest -q test_unittest_cleandir.py . - ======= warnings summary ======== - test_unittest_cleandir.py::MyTest::test_method - $REGENDOC_TMPDIR/test_unittest_cleandir.py:11: ResourceWarning: unclosed file <_io.TextIOWrapper name='samplefile.ini' mode='r' encoding='UTF-8'> - s = open("samplefile.ini").read() - - -- Docs: http://doc.pytest.org/en/latest/warnings.html - 1 passed, 1 warnings in 0.12 seconds + 1 passed in 0.12 seconds ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 64316cf6b..1766f997c 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -40,9 +40,22 @@ The ``-W`` flag can be passed to control which warnings will be displayed or eve them into errors:: $ pytest -q test_show_warnings.py -W error::DeprecationWarning + F + ======= FAILURES ======== + _______ test_one ________ - no tests ran in 0.12 seconds - ERROR: file not found: test_show_warning.py + def test_one(): + > assert deprecated_function() == 1 + + test_show_warnings.py:8: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + def deprecated_function(): + > warnings.warn("this function is deprecated, use another_function()", DeprecationWarning) + E DeprecationWarning: this function is deprecated, use another_function() + + test_show_warnings.py:4: DeprecationWarning + 1 failed in 0.12 seconds The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option. For example, the configuration below will ignore all deprecation warnings, but will transform