From 0371a3023a870cb5b0ad22e9e655a7c95baccbcf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 5 Jul 2019 09:57:10 -0300 Subject: [PATCH 01/72] Fix comment in stepwise (follow up to #5555) [skip ci] --- src/_pytest/stepwise.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index a18a58573..6fa21cd1c 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -71,7 +71,6 @@ class StepwisePlugin: config.hook.pytest_deselected(items=already_passed) def pytest_runtest_logreport(self, report): - # Skip this hook if plugin is not active or the test is xfailed. if not self.active: return From f1b8431d9969cf93d1e0425344a7a67ce206e077 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 5 Jul 2019 19:04:55 -0300 Subject: [PATCH 02/72] Sort parametrize params to test_external_plugins_integrated This might cause problems during collection with pytest-xdist; we didn't see any so far mostly by luck I think. Shame on me for letting that slip in. --- testing/deprecated_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 5cbb694b1..39439ab44 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -70,7 +70,7 @@ def test_terminal_reporter_writer_attr(pytestconfig): assert terminal_reporter.writer is terminal_reporter._tw -@pytest.mark.parametrize("plugin", deprecated.DEPRECATED_EXTERNAL_PLUGINS) +@pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS)) @pytest.mark.filterwarnings("default") def test_external_plugins_integrated(testdir, plugin): testdir.syspathinsert() From 56a0dd765844051f01e8263cebd29dd936ac8397 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 7 Jul 2019 20:23:56 +0200 Subject: [PATCH 03/72] Update doc footer/copyright --- doc/en/_themes/flask/layout.html | 2 +- doc/en/conf.py | 3 +-- doc/en/license.rst | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/en/_themes/flask/layout.html b/doc/en/_themes/flask/layout.html index 19c43fbbe..f2fa8e6aa 100644 --- a/doc/en/_themes/flask/layout.html +++ b/doc/en/_themes/flask/layout.html @@ -16,7 +16,7 @@ {%- block footer %} {% if pagename == 'index' %} diff --git a/doc/en/conf.py b/doc/en/conf.py index 42dc18fd8..cbb8fdb4b 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -63,8 +63,7 @@ master_doc = "contents" # General information about the project. project = "pytest" -year = datetime.datetime.utcnow().year -copyright = "2015–2019 , holger krekel and pytest-dev team" +copyright = "2015–2019, holger krekel and pytest-dev team" # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/doc/en/license.rst b/doc/en/license.rst index 5ee55cf96..d94b34a99 100644 --- a/doc/en/license.rst +++ b/doc/en/license.rst @@ -9,7 +9,7 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc The MIT License (MIT) - Copyright (c) 2004-2017 Holger Krekel and others + Copyright (c) 2004-2019 Holger Krekel and others 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 From 71c8ca7d3dc0ef46a24814b05d42ec5bb38c1f2a Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 7 Jul 2019 18:45:46 +0200 Subject: [PATCH 04/72] Improve sidebar layout --- doc/en/_themes/flask/slim_searchbox.html | 15 +++++++++++++++ doc/en/_themes/flask/static/flasky.css_t | 6 +++--- doc/en/conf.py | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 doc/en/_themes/flask/slim_searchbox.html diff --git a/doc/en/_themes/flask/slim_searchbox.html b/doc/en/_themes/flask/slim_searchbox.html new file mode 100644 index 000000000..e98ad4ed9 --- /dev/null +++ b/doc/en/_themes/flask/slim_searchbox.html @@ -0,0 +1,15 @@ +{# + basic/searchbox.html with heading removed. +#} +{%- if pagename != "search" and builder != "singlehtml" %} + + +{%- endif %} diff --git a/doc/en/_themes/flask/static/flasky.css_t b/doc/en/_themes/flask/static/flasky.css_t index 6b593da29..d56dbc3d8 100644 --- a/doc/en/_themes/flask/static/flasky.css_t +++ b/doc/en/_themes/flask/static/flasky.css_t @@ -106,14 +106,14 @@ div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: {{ header_font }}; color: #444; - font-size: 24px; + font-size: 21px; font-weight: normal; - margin: 0 0 5px 0; + margin: 16px 0 0 0; padding: 0; } div.sphinxsidebar h4 { - font-size: 20px; + font-size: 18px; } div.sphinxsidebar h3 a { diff --git a/doc/en/conf.py b/doc/en/conf.py index 42dc18fd8..db78111bc 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -167,18 +167,18 @@ html_favicon = "img/pytest1favi.ico" html_sidebars = { "index": [ + "slim_searchbox.html", "sidebarintro.html", "globaltoc.html", "links.html", "sourcelink.html", - "searchbox.html", ], "**": [ + "slim_searchbox.html", "globaltoc.html", "relations.html", "links.html", "sourcelink.html", - "searchbox.html", ], } From 7aff51ee837679be9e0d1201a65ffe7d2df8c90c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 5 Jul 2019 20:16:12 -0300 Subject: [PATCH 05/72] Show warning about importing abcs from collections once This is being raised by `unittest2.compatibility`: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 9757b0983..95c49db04 100644 --- a/tox.ini +++ b/tox.ini @@ -131,6 +131,7 @@ filterwarnings = ignore:yield tests are deprecated, and scheduled to be removed in pytest 4.0:pytest.RemovedInPytest4Warning ignore:Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0:pytest.RemovedInPytest4Warning ignore::pytest.RemovedInPytest4Warning + default:Using or importing the ABCs:DeprecationWarning:unittest2.* ignore:Module already imported so cannot be rewritten:pytest.PytestWarning # produced by path.local ignore:bad escape.*:DeprecationWarning:re From 9b78a216a226435c3184a1cb8ea8fc170accd87f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 8 Jul 2019 19:08:09 -0300 Subject: [PATCH 06/72] Remove unused import --- doc/en/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index cbb8fdb4b..71692e00f 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -15,7 +15,6 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. -import datetime import os import sys From 89dfde95353651f12474621becd808e2330371da Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 8 Jul 2019 10:04:19 +0300 Subject: [PATCH 07/72] Add rudimentary mypy type checking Add a very lax mypy configuration, add it to tox -e linting, and fix/ignore the few errors that come up. The idea is to get it running before diving in too much. This enables: - Progressively adding type annotations and enabling more strict options, which will improve the codebase (IMO). - Annotating the public API in-line, and eventually exposing it to library users who use type checkers (with a py.typed file). Though, none of this is done yet. Refs https://github.com/pytest-dev/pytest/issues/3342. --- .gitignore | 1 + .pre-commit-config.yaml | 12 +++++++++ bench/bench.py | 2 +- setup.cfg | 8 ++++++ src/_pytest/_argcomplete.py | 3 ++- src/_pytest/_code/code.py | 13 +++++----- src/_pytest/_code/source.py | 3 ++- src/_pytest/assertion/rewrite.py | 25 +++++++++++-------- src/_pytest/capture.py | 12 ++++++--- src/_pytest/debugging.py | 2 +- src/_pytest/fixtures.py | 12 ++++++--- src/_pytest/mark/__init__.py | 3 ++- src/_pytest/mark/structures.py | 3 ++- src/_pytest/nodes.py | 3 ++- src/_pytest/outcomes.py | 12 ++++++--- src/_pytest/python_api.py | 15 +++++++---- src/_pytest/reports.py | 3 ++- src/_pytest/tmpdir.py | 5 +++- testing/code/test_excinfo.py | 3 ++- .../package_infinite_recursion/__init__.pyi | 0 .../config/collect_pytest_prefix/__init__.pyi | 0 .../conftest_usageerror/__init__.pyi | 0 .../fixtures/custom_item/__init__.pyi | 0 .../__init__.pyi | 0 .../__init__.pyi | 0 .../__init__.pyi | 0 .../marks_considered_keywords/__init__.pyi | 0 testing/python/raises.py | 3 ++- testing/test_compat.py | 3 ++- testing/test_pdb.py | 3 ++- 30 files changed, 104 insertions(+), 45 deletions(-) create mode 100644 testing/example_scripts/collect/package_infinite_recursion/__init__.pyi create mode 100644 testing/example_scripts/config/collect_pytest_prefix/__init__.pyi create mode 100644 testing/example_scripts/conftest_usageerror/__init__.pyi create mode 100644 testing/example_scripts/fixtures/custom_item/__init__.pyi create mode 100644 testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi create mode 100644 testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi create mode 100644 testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi create mode 100644 testing/example_scripts/marks/marks_considered_keywords/__init__.pyi diff --git a/.gitignore b/.gitignore index a008b4363..27bd93c7b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ env/ .tox .cache .pytest_cache +.mypy_cache .coverage .coverage.* coverage.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 12fa0d343..fce7978c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,7 @@ repos: hooks: - id: flake8 language_version: python3 + additional_dependencies: [flake8-typing-imports] - repo: https://github.com/asottile/reorder_python_imports rev: v1.4.0 hooks: @@ -42,6 +43,17 @@ repos: rev: v1.4.0 hooks: - id: rst-backticks +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.711 + hooks: + - id: mypy + name: mypy (src) + files: ^src/ + args: [] + - id: mypy + name: mypy (testing) + files: ^testing/ + args: [] - repo: local hooks: - id: rst diff --git a/bench/bench.py b/bench/bench.py index 31cc7ac13..c40fc8636 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -6,7 +6,7 @@ if __name__ == "__main__": import pstats script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"] - stats = cProfile.run("pytest.cmdline.main(%r)" % script, "prof") + cProfile.run("pytest.cmdline.main(%r)" % script, "prof") p = pstats.Stats("prof") p.strip_dirs() p.sort_stats("cumulative") diff --git a/setup.cfg b/setup.cfg index 2d6e5bee1..60e866562 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,3 +61,11 @@ ignore = [devpi:upload] formats = sdist.tgz,bdist_wheel + +[mypy] +ignore_missing_imports = True +no_implicit_optional = True +strict_equality = True +warn_redundant_casts = True +warn_return_any = True +warn_unused_configs = True diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index 1ebf7432c..688c9077d 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -56,6 +56,7 @@ If things do not work right away: import os import sys from glob import glob +from typing import Optional class FastFilesCompleter: @@ -91,7 +92,7 @@ if os.environ.get("_ARGCOMPLETE"): import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter = FastFilesCompleter() + filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter] def try_argcomplete(parser): argcomplete.autocomplete(parser, always_complete_options=False) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index aa4dcffce..d63c010e4 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -33,7 +33,8 @@ class Code: def __eq__(self, other): return self.raw == other.raw - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore def __ne__(self, other): return not self == other @@ -188,11 +189,11 @@ class TracebackEntry: """ path to the source code """ return self.frame.code.path - def getlocals(self): + @property + def locals(self): + """ locals of underlaying frame """ return self.frame.f_locals - locals = property(getlocals, None, None, "locals of underlaying frame") - def getfirstlinesource(self): return self.frame.code.firstlineno @@ -255,11 +256,11 @@ class TracebackEntry: line = "???" return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line) + @property def name(self): + """ co_name of underlaying code """ return self.frame.code.raw.co_name - name = property(name, None, None, "co_name of underlaying code") - class Traceback(list): """ Traceback objects encapsulate and offer higher level diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 70d5f8fcd..ea2fc5e3f 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -44,7 +44,8 @@ class Source: return str(self) == other return False - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore def __getitem__(self, key): if isinstance(key, int): diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 8b2c1e146..7bd46eeb6 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -12,6 +12,10 @@ import struct import sys import tokenize import types +from typing import Dict +from typing import List +from typing import Optional +from typing import Set import atomicwrites @@ -459,17 +463,18 @@ def set_location(node, lineno, col_offset): return node -def _get_assertion_exprs(src: bytes): # -> Dict[int, str] +def _get_assertion_exprs(src: bytes) -> Dict[int, str]: """Returns a mapping from {lineno: "assertion test expression"}""" - ret = {} + ret = {} # type: Dict[int, str] depth = 0 - lines = [] - assert_lineno = None - seen_lines = set() + lines = [] # type: List[str] + assert_lineno = None # type: Optional[int] + seen_lines = set() # type: Set[int] def _write_and_reset() -> None: nonlocal depth, lines, assert_lineno, seen_lines + assert assert_lineno is not None ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\") depth = 0 lines = [] @@ -477,21 +482,21 @@ def _get_assertion_exprs(src: bytes): # -> Dict[int, str] seen_lines = set() tokens = tokenize.tokenize(io.BytesIO(src).readline) - for tp, src, (lineno, offset), _, line in tokens: - if tp == tokenize.NAME and src == "assert": + for tp, source, (lineno, offset), _, line in tokens: + if tp == tokenize.NAME and source == "assert": assert_lineno = lineno elif assert_lineno is not None: # keep track of depth for the assert-message `,` lookup - if tp == tokenize.OP and src in "([{": + if tp == tokenize.OP and source in "([{": depth += 1 - elif tp == tokenize.OP and src in ")]}": + elif tp == tokenize.OP and source in ")]}": depth -= 1 if not lines: lines.append(line[offset:]) seen_lines.add(lineno) # a non-nested comma separates the expression from the message - elif depth == 0 and tp == tokenize.OP and src == ",": + elif depth == 0 and tp == tokenize.OP and source == ",": # one line assert with message if lineno in seen_lines and len(lines) == 1: offset_in_trimmed = offset + len(lines[-1]) - len(line) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 302979ef4..f89aaefba 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -547,6 +547,8 @@ class FDCaptureBinary: self.start = lambda: None self.done = lambda: None else: + self.start = self._start + self.done = self._done if targetfd == 0: assert not tmpfile, "cannot set tmpfile with stdin" tmpfile = open(os.devnull, "r") @@ -568,7 +570,7 @@ class FDCaptureBinary: self.targetfd, getattr(self, "targetfd_save", None), self._state ) - def start(self): + def _start(self): """ Start capturing on targetfd using memorized tmpfile. """ try: os.fstat(self.targetfd_save) @@ -585,7 +587,7 @@ class FDCaptureBinary: self.tmpfile.truncate() return res - def done(self): + def _done(self): """ stop capturing, restore streams, return original capture file, seeked to position zero. """ targetfd_save = self.__dict__.pop("targetfd_save") @@ -618,7 +620,8 @@ class FDCapture(FDCaptureBinary): snap() produces text """ - EMPTY_BUFFER = str() + # Ignore type because it doesn't match the type in the superclass (bytes). + EMPTY_BUFFER = str() # type: ignore def snap(self): res = super().snap() @@ -679,7 +682,8 @@ class SysCapture: class SysCaptureBinary(SysCapture): - EMPTY_BUFFER = b"" + # Ignore type because it doesn't match the type in the superclass (str). + EMPTY_BUFFER = b"" # type: ignore def snap(self): res = self.tmpfile.buffer.getvalue() diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 891630b43..2e3d49c37 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -74,7 +74,7 @@ class pytestPDB: _pluginmanager = None _config = None - _saved = [] + _saved = [] # type: list _recursive_debug = 0 _wrapped_pdb_cls = None diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 3262b65bb..965a2e6e9 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -6,6 +6,8 @@ import warnings from collections import defaultdict from collections import deque from collections import OrderedDict +from typing import Dict +from typing import Tuple import attr import py @@ -31,6 +33,9 @@ from _pytest.deprecated import FIXTURE_NAMED_REQUEST from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME +if False: # TYPE_CHECKING + from typing import Type + @attr.s(frozen=True) class PseudoFixtureDef: @@ -54,10 +59,10 @@ def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) -scopename2class = {} +scopename2class = {} # type: Dict[str, Type[nodes.Node]] -scope2props = dict(session=()) +scope2props = dict(session=()) # type: Dict[str, Tuple[str, ...]] scope2props["package"] = ("fspath",) scope2props["module"] = ("fspath", "module") scope2props["class"] = scope2props["module"] + ("cls",) @@ -960,7 +965,8 @@ class FixtureFunctionMarker: scope = attr.ib() params = attr.ib(converter=attr.converters.optional(tuple)) autouse = attr.ib(default=False) - ids = attr.ib(default=None, converter=_ensure_immutable_ids) + # Ignore type because of https://github.com/python/mypy/issues/6172. + ids = attr.ib(default=None, converter=_ensure_immutable_ids) # type: ignore name = attr.ib(default=None) def __call__(self, function): diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 30c6e0048..e76bb7857 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -91,7 +91,8 @@ def pytest_cmdline_main(config): return 0 -pytest_cmdline_main.tryfirst = True +# Ignore type because of https://github.com/python/mypy/issues/2087. +pytest_cmdline_main.tryfirst = True # type: ignore def deselect_by_keyword(items, config): diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 1af7a9b42..0887d6b9c 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -3,6 +3,7 @@ import warnings from collections import namedtuple from collections.abc import MutableMapping from operator import attrgetter +from typing import Set import attr @@ -298,7 +299,7 @@ class MarkGenerator: on the ``test_function`` object. """ _config = None - _markers = set() + _markers = set() # type: Set[str] def __getattr__(self, name): if name[0] == "_": diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 491cf9d2c..7e1c40bcb 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -280,7 +280,8 @@ class Node: truncate_locals=truncate_locals, ) - repr_failure = _repr_failure_py + def repr_failure(self, excinfo, style=None): + return self._repr_failure_py(excinfo, style) def get_fslocation_from_item(item): diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index fb4d471b5..c7e26f5cc 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -70,7 +70,8 @@ def exit(msg, returncode=None): raise Exit(msg, returncode) -exit.Exception = Exit +# Ignore type because of https://github.com/python/mypy/issues/2087. +exit.Exception = Exit # type: ignore def skip(msg="", *, allow_module_level=False): @@ -96,7 +97,8 @@ def skip(msg="", *, allow_module_level=False): raise Skipped(msg=msg, allow_module_level=allow_module_level) -skip.Exception = Skipped +# Ignore type because of https://github.com/python/mypy/issues/2087. +skip.Exception = Skipped # type: ignore def fail(msg="", pytrace=True): @@ -111,7 +113,8 @@ def fail(msg="", pytrace=True): raise Failed(msg=msg, pytrace=pytrace) -fail.Exception = Failed +# Ignore type because of https://github.com/python/mypy/issues/2087. +fail.Exception = Failed # type: ignore class XFailed(Failed): @@ -132,7 +135,8 @@ def xfail(reason=""): raise XFailed(reason) -xfail.Exception = XFailed +# Ignore type because of https://github.com/python/mypy/issues/2087. +xfail.Exception = XFailed # type: ignore def importorskip(modname, minversion=None, reason=None): diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 374fa598f..cbd833946 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -9,6 +9,7 @@ from collections.abc import Sized from decimal import Decimal from itertools import filterfalse from numbers import Number +from typing import Union from more_itertools.more import always_iterable @@ -58,7 +59,8 @@ class ApproxBase: a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) ) - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore def __ne__(self, actual): return not (actual == self) @@ -202,8 +204,10 @@ class ApproxScalar(ApproxBase): Perform approximate comparisons where the expected value is a single number. """ - DEFAULT_ABSOLUTE_TOLERANCE = 1e-12 - DEFAULT_RELATIVE_TOLERANCE = 1e-6 + # Using Real should be better than this Union, but not possible yet: + # https://github.com/python/typeshed/pull/3108 + DEFAULT_ABSOLUTE_TOLERANCE = 1e-12 # type: Union[float, Decimal] + DEFAULT_RELATIVE_TOLERANCE = 1e-6 # type: Union[float, Decimal] def __repr__(self): """ @@ -261,7 +265,8 @@ class ApproxScalar(ApproxBase): # Return true if the two numbers are within the tolerance. return abs(self.expected - actual) <= self.tolerance - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore @property def tolerance(self): @@ -691,7 +696,7 @@ def raises(expected_exception, *args, **kwargs): fail(message) -raises.Exception = fail.Exception +raises.Exception = fail.Exception # type: ignore class RaisesContext: diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index d2f1f33e2..4682d5b6e 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,4 +1,5 @@ from pprint import pprint +from typing import Optional import py @@ -28,7 +29,7 @@ def getslaveinfoline(node): class BaseReport: - when = None + when = None # type: Optional[str] location = None def __init__(self, **kw): diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index f2c4d905c..48680c07e 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -26,7 +26,10 @@ class TempPathFactory: # using os.path.abspath() to get absolute path instead of resolve() as it # does not work the same in all platforms (see #4427) # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012) - converter=attr.converters.optional(lambda p: Path(os.path.abspath(str(p)))) + # Ignore type because of https://github.com/python/mypy/issues/6172. + converter=attr.converters.optional( + lambda p: Path(os.path.abspath(str(p))) # type: ignore + ) ) _trace = attr.ib() _basetemp = attr.ib(default=None) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index f7787c282..d7771833a 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -589,7 +589,8 @@ raise ValueError() def test_repr_local_with_exception_in_class_property(self): class ExceptionWithBrokenClass(Exception): - @property + # Type ignored because it's bypassed intentionally. + @property # type: ignore def __class__(self): raise TypeError("boom!") diff --git a/testing/example_scripts/collect/package_infinite_recursion/__init__.pyi b/testing/example_scripts/collect/package_infinite_recursion/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/config/collect_pytest_prefix/__init__.pyi b/testing/example_scripts/config/collect_pytest_prefix/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/conftest_usageerror/__init__.pyi b/testing/example_scripts/conftest_usageerror/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/fixtures/custom_item/__init__.pyi b/testing/example_scripts/fixtures/custom_item/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi b/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi b/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/testing/python/raises.py b/testing/python/raises.py index ed3a5cd37..a67f25534 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -265,7 +265,8 @@ class TestRaises: """Test current behavior with regard to exceptions via __class__ (#4284).""" class CrappyClass(Exception): - @property + # Type ignored because it's bypassed intentionally. + @property # type: ignore def __class__(self): assert False, "via __class__" diff --git a/testing/test_compat.py b/testing/test_compat.py index 028d48bed..9e7d05c5d 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -141,7 +141,8 @@ def test_safe_isclass(): assert safe_isclass(type) is True class CrappyClass(Exception): - @property + # Type ignored because it's bypassed intentionally. + @property # type: ignore def __class__(self): assert False, "Should be ignored" diff --git a/testing/test_pdb.py b/testing/test_pdb.py index f3f7ca702..8d327cbb3 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -6,7 +6,8 @@ import pytest from _pytest.debugging import _validate_usepdb_cls try: - breakpoint + # Type ignored for Python <= 3.6. + breakpoint # type: ignore except NameError: SUPPORTS_BREAKPOINT_BUILTIN = False else: From 9db182370770b24a4434af48375a63fed9905a93 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 8 Jul 2019 20:33:43 -0300 Subject: [PATCH 08/72] Improve type-checking in OutcomeException Fix #5578 --- changelog/5578.bugfix.rst | 3 +++ src/_pytest/outcomes.py | 11 +++++++---- testing/test_runner.py | 16 ++++++++++++++++ testing/test_skipping.py | 3 ++- 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 changelog/5578.bugfix.rst diff --git a/changelog/5578.bugfix.rst b/changelog/5578.bugfix.rst new file mode 100644 index 000000000..5f6c39185 --- /dev/null +++ b/changelog/5578.bugfix.rst @@ -0,0 +1,3 @@ +Improve type checking for some exception-raising functions (``pytest.xfail``, ``pytest.skip``, etc) +so they provide better error messages when users meant to use marks (for example ``@pytest.xfail`` +instead of ``@pytest.mark.xfail``). diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index fb4d471b5..ca96a1b4f 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -13,16 +13,19 @@ class OutcomeException(BaseException): """ def __init__(self, msg=None, pytrace=True): + if msg is not None and not isinstance(msg, str): + error_msg = ( + "{} expected string as 'msg' parameter, got '{}' instead.\n" + "Perhaps you meant to use a mark?" + ) + raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__)) BaseException.__init__(self, msg) self.msg = msg self.pytrace = pytrace def __repr__(self): if self.msg: - val = self.msg - if isinstance(val, bytes): - val = val.decode("UTF-8", errors="replace") - return val + return self.msg return "<{} instance>".format(self.__class__.__name__) __str__ = __repr__ diff --git a/testing/test_runner.py b/testing/test_runner.py index 15180c071..82e413518 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -11,6 +11,7 @@ from _pytest import main from _pytest import outcomes from _pytest import reports from _pytest import runner +from _pytest.outcomes import OutcomeException class TestSetupState: @@ -990,3 +991,18 @@ class TestReportContents: rep = reports[1] assert rep.capstdout == "" assert rep.capstderr == "" + + +def test_outcome_exception_bad_msg(): + """Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)""" + + def func(): + pass + + expected = ( + "OutcomeException expected string as 'msg' parameter, got 'function' instead.\n" + "Perhaps you meant to use a mark?" + ) + with pytest.raises(TypeError) as excinfo: + OutcomeException(func) + assert str(excinfo.value) == expected diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 6bb5f7aff..8bba479f1 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1066,7 +1066,8 @@ def test_module_level_skip_error(testdir): testdir.makepyfile( """ import pytest - @pytest.skip + pytest.skip("skip_module_level") + def test_func(): assert True """ From c224c4f1d6aa90a20ff03aa75ed44328c843c644 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 10 Jul 2019 13:06:11 +0100 Subject: [PATCH 09/72] de-emphasize request.addfinalizer --- doc/en/fixture.rst | 74 +++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 852069731..f6179f1e0 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -400,6 +400,35 @@ The ``smtp_connection`` connection will be closed after the test finished execution because the ``smtp_connection`` object automatically closes when the ``with`` statement ends. +Using the contextlib.ExitStack context manager finalizers will always be called +regardless if the fixture *setup* code raises an exception. This is handy to properly +close all resources created by a fixture even if one of them fails to be created/acquired: + +.. code-block:: python + + # content of test_yield3.py + + import contextlib + + import pytest + + from .utils import connect + + + @pytest.fixture + def equipments(): + with contextlib.ExitStack() as stack: + r = [] + for port in ("C1", "C3", "C28"): + equip = connect(port) + stack.callback(equip.disconnect) + r.append(equip) + yield r + +In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still +be properly closed. Of course, if an exception happens before the finalize function is +registered then it will not be executed. + Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the *teardown* code (after the ``yield``) will not be called. @@ -428,28 +457,31 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean return smtp_connection # provide the fixture value +Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup: + +.. code-block:: python + + # content of test_yield3.py + + import contextlib + + import pytest + + from .utils import connect + + + @pytest.fixture + def equipments(request): + r = [] + for port in ("C1", "C3", "C28"): + equip = connect(port) + request.addfinalizer(equip.disconnect) + r.append(equip) + return r + + Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test -ends, but ``addfinalizer`` has two key differences over ``yield``: - -1. It is possible to register multiple finalizer functions. - -2. Finalizers will always be called regardless if the fixture *setup* code raises an exception. - This is handy to properly close all resources created by a fixture even if one of them - fails to be created/acquired:: - - @pytest.fixture - def equipments(request): - r = [] - for port in ('C1', 'C3', 'C28'): - equip = connect(port) - request.addfinalizer(equip.disconnect) - r.append(equip) - return r - - In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still - be properly closed. Of course, if an exception happens before the finalize function is - registered then it will not be executed. - +ends. .. _`request-context`: From d5f1d7aebed9bc9e437721d595af11721e934949 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 10 Jul 2019 20:14:43 +0200 Subject: [PATCH 10/72] doc: fix typos in OPENCOLLECTIVE.rst [ci skip] --- OPENCOLLECTIVE.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OPENCOLLECTIVE.rst b/OPENCOLLECTIVE.rst index 9b7ddcd06..8c1c90281 100644 --- a/OPENCOLLECTIVE.rst +++ b/OPENCOLLECTIVE.rst @@ -9,7 +9,7 @@ What is it ========== Open Collective is an online funding platform for open and transparent communities. -It provide tools to raise money and share your finances in full transparency. +It provides tools to raise money and share your finances in full transparency. It is the platform of choice for individuals and companies that want to make one-time or monthly donations directly to the project. @@ -19,7 +19,7 @@ Funds The OpenCollective funds donated to pytest will be used to fund overall maintenance, local sprints, merchandising (stickers to distribute in conferences for example), and future -gatherings of pytest developers (Sprints). +gatherings of pytest developers (sprints). `Core contributors`_ which are contributing on a continuous basis are free to submit invoices to bill maintenance hours using the platform. How much each contributor should request is still an From 37c37963c45b40cb7ef00e4b3ae50b1701604d81 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 10 Jul 2019 09:52:23 -0300 Subject: [PATCH 11/72] Fix rmtree to remove directories with read-only files Fix #5524 --- changelog/5524.bugfix.rst | 2 ++ src/_pytest/cacheprovider.py | 4 +-- src/_pytest/pathlib.py | 45 +++++++++++++++++++++------ testing/test_tmpdir.py | 60 ++++++++++++++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 changelog/5524.bugfix.rst diff --git a/changelog/5524.bugfix.rst b/changelog/5524.bugfix.rst new file mode 100644 index 000000000..96ebbd43e --- /dev/null +++ b/changelog/5524.bugfix.rst @@ -0,0 +1,2 @@ +Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only, +which could lead to pytest crashing when executed a second time with the ``--basetemp`` option. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 17463959f..496931e0f 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -14,7 +14,7 @@ import py import pytest from .pathlib import Path from .pathlib import resolve_from_str -from .pathlib import rmtree +from .pathlib import rm_rf README_CONTENT = """\ # pytest cache directory # @@ -44,7 +44,7 @@ class Cache: def for_config(cls, config): cachedir = cls.cache_dir_from_config(config) if config.getoption("cacheclear") and cachedir.exists(): - rmtree(cachedir, force=True) + rm_rf(cachedir) cachedir.mkdir() return cls(cachedir, config) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index ecc38eb0f..7094d8c69 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -7,12 +7,14 @@ import os import shutil import sys import uuid +import warnings from os.path import expanduser from os.path import expandvars from os.path import isabs from os.path import sep from posixpath import sep as posix_sep +from _pytest.warning_types import PytestWarning if sys.version_info[:2] >= (3, 6): from pathlib import Path, PurePath @@ -32,17 +34,42 @@ def ensure_reset_dir(path): ensures the given path is an empty directory """ if path.exists(): - rmtree(path, force=True) + rm_rf(path) path.mkdir() -def rmtree(path, force=False): - if force: - # NOTE: ignore_errors might leave dead folders around. - # Python needs a rm -rf as a followup. - shutil.rmtree(str(path), ignore_errors=True) - else: - shutil.rmtree(str(path)) +def rm_rf(path): + """Remove the path contents recursively, even if some elements + are read-only. + """ + + def chmod_w(p): + import stat + + mode = os.stat(str(p)).st_mode + os.chmod(str(p), mode | stat.S_IWRITE) + + def force_writable_and_retry(function, p, excinfo): + p = Path(p) + + # for files, we need to recursively go upwards + # in the directories to ensure they all are also + # writable + if p.is_file(): + for parent in p.parents: + chmod_w(parent) + # stop when we reach the original path passed to rm_rf + if parent == path: + break + + chmod_w(p) + try: + # retry the function that failed + function(str(p)) + except Exception as e: + warnings.warn(PytestWarning("(rm_rf) error removing {}: {}".format(p, e))) + + shutil.rmtree(str(path), onerror=force_writable_and_retry) def find_prefixed(root, prefix): @@ -168,7 +195,7 @@ def maybe_delete_a_numbered_dir(path): garbage = parent.joinpath("garbage-{}".format(uuid.uuid4())) path.rename(garbage) - rmtree(garbage, force=True) + rm_rf(garbage) except (OSError, EnvironmentError): # known races: # * other process did a cleanup at the same time diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index c4c7ebe25..01f9d4652 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,3 +1,5 @@ +import os +import stat import sys import attr @@ -311,11 +313,11 @@ class TestNumberedDir: ) def test_rmtree(self, tmp_path): - from _pytest.pathlib import rmtree + from _pytest.pathlib import rm_rf adir = tmp_path / "adir" adir.mkdir() - rmtree(adir) + rm_rf(adir) assert not adir.exists() @@ -323,9 +325,40 @@ class TestNumberedDir: afile = adir / "afile" afile.write_bytes(b"aa") - rmtree(adir, force=True) + rm_rf(adir) assert not adir.exists() + def test_rmtree_with_read_only_file(self, tmp_path): + """Ensure rm_rf can remove directories with read-only files in them (#5524)""" + from _pytest.pathlib import rm_rf + + fn = tmp_path / "dir/foo.txt" + fn.parent.mkdir() + + fn.touch() + + mode = os.stat(str(fn)).st_mode + os.chmod(str(fn), mode & ~stat.S_IWRITE) + + rm_rf(fn.parent) + + assert not fn.parent.is_dir() + + def test_rmtree_with_read_only_directory(self, tmp_path): + """Ensure rm_rf can remove read-only directories (#5524)""" + from _pytest.pathlib import rm_rf + + adir = tmp_path / "dir" + adir.mkdir() + + (adir / "foo.txt").touch() + mode = os.stat(str(adir)).st_mode + os.chmod(str(adir), mode & ~stat.S_IWRITE) + + rm_rf(adir) + + assert not adir.is_dir() + def test_cleanup_ignores_symlink(self, tmp_path): the_symlink = tmp_path / (self.PREFIX + "current") attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5")) @@ -349,3 +382,24 @@ def attempt_symlink_to(path, to_path): def test_tmpdir_equals_tmp_path(tmpdir, tmp_path): assert Path(tmpdir) == tmp_path + + +def test_basetemp_with_read_only_files(testdir): + """Integration test for #5524""" + testdir.makepyfile( + """ + import os + import stat + + def test(tmp_path): + fn = tmp_path / 'foo.txt' + fn.write_text('hello') + mode = os.stat(str(fn)).st_mode + os.chmod(str(fn), mode & ~stat.S_IREAD) + """ + ) + result = testdir.runpytest("--basetemp=tmp") + assert result.ret == 0 + # running a second time and ensure we don't crash + result = testdir.runpytest("--basetemp=tmp") + assert result.ret == 0 From c89e379f49f49bc5ed38b05a28cd9fd46de5762c Mon Sep 17 00:00:00 2001 From: Albert Tugushev Date: Fri, 12 Jul 2019 02:35:44 +0300 Subject: [PATCH 12/72] Fix typos --- src/_pytest/pathlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 7094d8c69..b5f2bf0fb 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -109,9 +109,9 @@ def _force_symlink(root, target, link_to): """helper to create the current symlink it's full of race conditions that are reasonably ok to ignore - for the context of best effort linking to the latest testrun + for the context of best effort linking to the latest test run - the presumption being thatin case of much parallelism + the presumption being that in case of much parallelism the inaccuracy is going to be acceptable """ current_symlink = root.joinpath(target) From d23fbab188f71c956d29f55ecae7ae1c0529fbe0 Mon Sep 17 00:00:00 2001 From: Wojtek Erbetowski Date: Sat, 13 Jul 2019 11:43:47 +0200 Subject: [PATCH 13/72] Add autouse fixture order information (#3404). A case with a fixture use both as an autouse and explititly was raised. This case sounds too narrow to add to documentation (and could be misleading for people learning pytest with explicitely using an autouse fixture). At the same time there was no documentation on the autouse vs regular fixture order, therefore this commit adds such an information. Code sample grew and it was extracted to the file. --- .../example/fixtures/test_fixtures_order.py | 38 +++++++++++++++++ doc/en/fixture.rst | 41 +++++-------------- 2 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 doc/en/example/fixtures/test_fixtures_order.py diff --git a/doc/en/example/fixtures/test_fixtures_order.py b/doc/en/example/fixtures/test_fixtures_order.py new file mode 100644 index 000000000..97b3e8005 --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order.py @@ -0,0 +1,38 @@ +import pytest + +# fixtures documentation order example +order = [] + + +@pytest.fixture(scope="session") +def s1(): + order.append("s1") + + +@pytest.fixture(scope="module") +def m1(): + order.append("m1") + + +@pytest.fixture +def f1(f3): + order.append("f1") + + +@pytest.fixture +def f3(): + order.append("f3") + + +@pytest.fixture(autouse=True) +def a1(): + order.append("a1") + + +@pytest.fixture +def f2(): + order.append("f2") + + +def test_order(f1, m1, f2, s1): + assert order == ["s1", "m1", "a1", "f3", "f1", "f2"] diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 852069731..78fef13e8 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -289,51 +289,30 @@ are finalized when the last test of a *package* finishes. Use this new feature sparingly and please make sure to report any issues you find. -Higher-scoped fixtures are instantiated first ---------------------------------------------- +Order: Higher-scoped fixtures are instantiated first +---------------------------------------------------- Within a function request for features, fixture of higher-scopes (such as ``session``) are instantiated first than lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows -the declared order in the test function and honours dependencies between fixtures. +the declared order in the test function and honours dependencies between fixtures. Autouse fixtures will be +instantiated before explicitly used fixtures. Consider the code below: -.. code-block:: python - - @pytest.fixture(scope="session") - def s1(): - pass - - - @pytest.fixture(scope="module") - def m1(): - pass - - - @pytest.fixture - def f1(tmpdir): - pass - - - @pytest.fixture - def f2(): - pass - - - def test_foo(f1, m1, f2, s1): - ... - +.. literalinclude:: example/fixtures/test_fixtures_order.py The fixtures requested by ``test_foo`` will be instantiated in the following order: 1. ``s1``: is the highest-scoped fixture (``session``). 2. ``m1``: is the second highest-scoped fixture (``module``). -3. ``tmpdir``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point because it is a dependency of ``f1``. -4. ``f1``: is the first ``function``-scoped fixture in ``test_foo`` parameter list. -5. ``f2``: is the last ``function``-scoped fixture in ``test_foo`` parameter list. +3. ``a1``: is a ``function``-scoped ``autouse`` fixture: it will be instantiated before other fixtures + within the same scope. +4. ``f3``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point +5. ``f1``: is the first ``function``-scoped fixture in ``test_foo`` parameter list. +6. ``f2``: is the last ``function``-scoped fixture in ``test_foo`` parameter list. .. _`finalization`: From da5add1294342c44b9dd43d21f4240cb9ed4fb0c Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 14 Jul 2019 17:16:56 +0200 Subject: [PATCH 14/72] Improve CSS layout --- AUTHORS | 1 + doc/en/_themes/flask/static/flasky.css_t | 71 ++++++++++++++---------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3f73b9272..8e2314283 100644 --- a/AUTHORS +++ b/AUTHORS @@ -238,6 +238,7 @@ Tareq Alayan Ted Xiao Thomas Grainger Thomas Hisch +Tim Hoffmann Tim Strazny Tom Dalton Tom Viner diff --git a/doc/en/_themes/flask/static/flasky.css_t b/doc/en/_themes/flask/static/flasky.css_t index d56dbc3d8..18bc9491e 100644 --- a/doc/en/_themes/flask/static/flasky.css_t +++ b/doc/en/_themes/flask/static/flasky.css_t @@ -8,11 +8,12 @@ {% set page_width = '1020px' %} {% set sidebar_width = '220px' %} -/* orange of logo is #d67c29 but we use black for links for now */ -{% set link_color = '#000' %} -{% set link_hover_color = '#000' %} +/* muted version of green logo color #C9D22A */ +{% set link_color = '#606413' %} +/* blue logo color */ +{% set link_hover_color = '#009de0' %} {% set base_font = 'sans-serif' %} -{% set header_font = 'serif' %} +{% set header_font = 'sans-serif' %} @import url("basic.css"); @@ -20,7 +21,7 @@ body { font-family: {{ base_font }}; - font-size: 17px; + font-size: 16px; background-color: white; color: #000; margin: 0; @@ -78,13 +79,13 @@ div.related { } div.sphinxsidebar a { - color: #444; text-decoration: none; - border-bottom: 1px dotted #999; + border-bottom: none; } div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; + color: {{ link_hover_color }}; + border-bottom: 1px solid {{ link_hover_color }}; } div.sphinxsidebar { @@ -205,10 +206,13 @@ div.body p, div.body dd, div.body li { line-height: 1.4em; } +ul.simple li { + margin-bottom: 0.5em; +} + div.admonition { background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; + padding: 10px 20px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } @@ -217,11 +221,6 @@ div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - div.admonition p.admonition-title { font-family: {{ header_font }}; font-weight: normal; @@ -231,7 +230,7 @@ div.admonition p.admonition-title { line-height: 1; } -div.admonition p.last { +div.admonition :last-child { margin-bottom: 0; } @@ -243,7 +242,7 @@ dt:target, .highlight { background: #FAF3E8; } -div.note { +div.note, div.warning { background-color: #eee; border: 1px solid #ccc; } @@ -257,6 +256,11 @@ div.topic { background-color: #eee; } +div.topic a { + text-decoration: none; + border-bottom: none; +} + p.admonition-title { display: inline; } @@ -358,21 +362,10 @@ ul, ol { pre { background: #eee; - padding: 7px 30px; - margin: 15px -30px; + padding: 7px 12px; line-height: 1.3em; } -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - tt { background-color: #ecf0f3; color: #222; @@ -393,6 +386,20 @@ a.reference:hover { border-bottom: 1px solid {{ link_hover_color }}; } +li.toctree-l1 a.reference, +li.toctree-l2 a.reference, +li.toctree-l3 a.reference, +li.toctree-l4 a.reference { + border-bottom: none; +} + +li.toctree-l1 a.reference:hover, +li.toctree-l2 a.reference:hover, +li.toctree-l3 a.reference:hover, +li.toctree-l4 a.reference:hover { + border-bottom: 1px solid {{ link_hover_color }}; +} + a.footnote-reference { text-decoration: none; font-size: 0.7em; @@ -408,6 +415,12 @@ a:hover tt { background: #EEE; } +#reference div.section h3 { + /* separate code elements in the reference section */ + border-top: 1px solid #ccc; + padding-top: 0.5em; +} + @media screen and (max-width: 870px) { From 01606315aaf6e7b1764f627bf1fb2d142b5fe3d5 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 15 Jul 2019 13:40:30 +0200 Subject: [PATCH 15/72] More CSS fine-tuning --- CONTRIBUTING.rst | 3 ++- doc/en/_themes/flask/static/flasky.css_t | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 128df6661..5ef418e0b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -5,8 +5,9 @@ Contribution getting started Contributions are highly welcomed and appreciated. Every little help counts, so do not hesitate! -.. contents:: Contribution links +.. contents:: :depth: 2 + :backlinks: none .. _submitfeedback: diff --git a/doc/en/_themes/flask/static/flasky.css_t b/doc/en/_themes/flask/static/flasky.css_t index 18bc9491e..ad23acf28 100644 --- a/doc/en/_themes/flask/static/flasky.css_t +++ b/doc/en/_themes/flask/static/flasky.css_t @@ -210,6 +210,15 @@ ul.simple li { margin-bottom: 0.5em; } +div.topic ul.simple li { + margin-bottom: 0; +} + +div.topic li > p:first-child { + margin-top: 0; + margin-bottom: 0; +} + div.admonition { background: #fafafa; padding: 10px 20px; From a96710dd8a143c588382cc5104acd6522e7426fe Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 15 Jul 2019 14:05:19 +0100 Subject: [PATCH 16/72] demonstrate ExitStack assuming connect returns a context manager --- doc/en/fixture.rst | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index f6179f1e0..53310ae8c 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -418,16 +418,10 @@ close all resources created by a fixture even if one of them fails to be created @pytest.fixture def equipments(): with contextlib.ExitStack() as stack: - r = [] - for port in ("C1", "C3", "C28"): - equip = connect(port) - stack.callback(equip.disconnect) - r.append(equip) - yield r + yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")] In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still -be properly closed. Of course, if an exception happens before the finalize function is -registered then it will not be executed. +be properly closed. Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the *teardown* code (after the ``yield``) will not be called. @@ -464,6 +458,7 @@ Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup: # content of test_yield3.py import contextlib + import functools import pytest @@ -474,14 +469,17 @@ Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup: def equipments(request): r = [] for port in ("C1", "C3", "C28"): - equip = connect(port) - request.addfinalizer(equip.disconnect) + cm = connect(port) + equip = cm.__enter__() + request.addfinalizer(functools.partial(cm.__exit__, None, None, None)) r.append(equip) return r Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test -ends. +ends. Of course, if an exception happens before the finalize function is registered then it +will not be executed. + .. _`request-context`: From 3ad315bceeb29a6eb7222c5392151791057c04e5 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 15 Jul 2019 16:15:59 +0200 Subject: [PATCH 17/72] Improve CSS layout of API reference --- doc/en/_themes/flask/static/flasky.css_t | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/en/_themes/flask/static/flasky.css_t b/doc/en/_themes/flask/static/flasky.css_t index ad23acf28..108c85401 100644 --- a/doc/en/_themes/flask/static/flasky.css_t +++ b/doc/en/_themes/flask/static/flasky.css_t @@ -424,12 +424,56 @@ a:hover tt { background: #EEE; } +#reference div.section h2 { + /* separate code elements in the reference section */ + border-top: 2px solid #ccc; + padding-top: 0.5em; +} + #reference div.section h3 { /* separate code elements in the reference section */ border-top: 1px solid #ccc; padding-top: 0.5em; } +dl.class, dl.function { + margin-top: 1em; + margin-bottom: 1em; +} + +dl.class > dd { + border-left: 3px solid #ccc; + margin-left: 0px; + padding-left: 30px; +} + +dl.field-list { + flex-direction: column; +} + +dl.field-list dd { + padding-left: 4em; + border-left: 3px solid #ccc; + margin-bottom: 0.5em; +} + +dl.field-list dd > ul { + list-style: none; + padding-left: 0px; +} + +dl.field-list dd > ul > li li :first-child { + text-indent: 0; +} + +dl.field-list dd > ul > li :first-child { + text-indent: -2em; + padding-left: 0px; +} + +dl.field-list dd > p:first-child { + text-indent: -2em; +} @media screen and (max-width: 870px) { From 6f1d358a0c9bd439b29049571c0594677f229a72 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 15 Jul 2019 16:16:44 +0200 Subject: [PATCH 18/72] Fix some ReST indentation issues in docstrings --- src/_pytest/mark/structures.py | 23 ++++++++++++----------- src/_pytest/python_api.py | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 0887d6b9c..c1a9e70f9 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -193,17 +193,18 @@ class MarkDecorator: pass When a MarkDecorator instance is called it does the following: - 1. If called with a single class as its only positional argument and no - additional keyword arguments, it attaches itself to the class so it - gets applied automatically to all test cases found in that class. - 2. If called with a single function as its only positional argument and - no additional keyword arguments, it attaches a MarkInfo object to the - function, containing all the arguments already stored internally in - the MarkDecorator. - 3. When called in any other case, it performs a 'fake construction' call, - i.e. it returns a new MarkDecorator instance with the original - MarkDecorator's content updated with the arguments passed to this - call. + + 1. If called with a single class as its only positional argument and no + additional keyword arguments, it attaches itself to the class so it + gets applied automatically to all test cases found in that class. + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches a MarkInfo object to the + function, containing all the arguments already stored internally in + the MarkDecorator. + 3. When called in any other case, it performs a 'fake construction' call, + i.e. it returns a new MarkDecorator instance with the original + MarkDecorator's content updated with the arguments passed to this + call. Note: The rules above prevent MarkDecorator objects from storing only a single function or class reference as their positional argument with no diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index cbd833946..bd0f4d59f 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -542,7 +542,7 @@ def raises(expected_exception, *args, **kwargs): string that may contain `special characters`__, the pattern can first be escaped with ``re.escape``. - __ https://docs.python.org/3/library/re.html#regular-expression-syntax + __ https://docs.python.org/3/library/re.html#regular-expression-syntax :kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message if the exception is not raised. See :ref:`the deprecation docs ` for a workaround. From 8a3f40996aca4497b0c2cff980fe36b2bd880ed4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Jul 2019 11:18:05 -0300 Subject: [PATCH 19/72] Remove obsolete "importorskip('unittest.mock')" calls --- testing/python/integration.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/testing/python/integration.py b/testing/python/integration.py index 0b87fea33..0a816bb95 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -126,7 +126,6 @@ class TestMockDecoration: assert values == ("y", "z") def test_unittest_mock(self, testdir): - pytest.importorskip("unittest.mock") testdir.makepyfile( """ import unittest.mock @@ -142,7 +141,6 @@ class TestMockDecoration: reprec.assertoutcome(passed=1) def test_unittest_mock_and_fixture(self, testdir): - pytest.importorskip("unittest.mock") testdir.makepyfile( """ import os.path @@ -164,7 +162,6 @@ class TestMockDecoration: reprec.assertoutcome(passed=1) def test_unittest_mock_and_pypi_mock(self, testdir): - pytest.importorskip("unittest.mock") pytest.importorskip("mock", "1.0.1") testdir.makepyfile( """ From bb7608c56f313096566956d87583f4e531983938 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 15 Jul 2019 16:25:33 +0200 Subject: [PATCH 20/72] Change section titles in docs --- CHANGELOG.rst | 6 +++--- doc/en/_templates/globaltoc.html | 2 +- doc/en/reference.rst | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c4ab6f1bd..3badb1936 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,6 @@ -================= -Changelog history -================= +========= +Changelog +========= Versions follow `Semantic Versioning `_ (``..``). diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 39cebb968..50c2239e5 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -4,7 +4,7 @@
  • Home
  • Install
  • Contents
  • -
  • Reference
  • +
  • API Reference
  • Examples
  • Customize
  • Changelog
  • diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 5abb01f50..afbef6b1e 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1,5 +1,5 @@ -Reference -========= +API Reference +============= This page contains the full reference to pytest's API. From 65c23017c7a3a95d147cc2a54aa1e031f4ba2f34 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Jul 2019 12:23:59 -0300 Subject: [PATCH 21/72] Update test_getfuncargnames_patching to work with modern mock --- testing/python/integration.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/testing/python/integration.py b/testing/python/integration.py index 0a816bb95..a6b8dddf3 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -104,21 +104,15 @@ class TestMockDecoration: values = getfuncargnames(f) assert values == ("x",) - @pytest.mark.xfail( - strict=False, reason="getfuncargnames breaks if mock is imported" - ) - def test_wrapped_getfuncargnames_patching(self): + def test_getfuncargnames_patching(self): from _pytest.compat import getfuncargnames + from unittest.mock import patch - def wrap(f): - def func(): + class T: + def original(self, x, y, z): pass - func.__wrapped__ = f - func.patchings = ["qwe"] - return func - - @wrap + @patch.object(T, "original") def f(x, y, z): pass From 2e756d698baa9d323a20a96754dbb48bac377836 Mon Sep 17 00:00:00 2001 From: Max R Date: Tue, 16 Jul 2019 08:32:24 -0400 Subject: [PATCH 22/72] Remove language_version specification in black hooks Resolves #3840 --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fce7978c4..c9940674d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,13 +5,11 @@ repos: hooks: - id: black args: [--safe, --quiet] - language_version: python3 - repo: https://github.com/asottile/blacken-docs rev: v1.0.0 hooks: - id: blacken-docs additional_dependencies: [black==19.3b0] - language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.2.3 hooks: From d9eab12ee0b7851b5ece66082bb1deb3b1f7e92d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 16 Jul 2019 15:03:54 +0200 Subject: [PATCH 23/72] Remove Europython training --- doc/en/talks.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/en/talks.rst b/doc/en/talks.rst index 20b6e5b09..e99dd7861 100644 --- a/doc/en/talks.rst +++ b/doc/en/talks.rst @@ -4,8 +4,6 @@ Talks and Tutorials .. sidebar:: Next Open Trainings - - `Training at Europython 2019 `_, 8th July 2019, Basel, Switzerland. - - `Training at Workshoptage 2019 `_ (German), 10th September 2019, Rapperswil, Switzerland. .. _`funcargs`: funcargs.html From 7a5c0a01bce3bbdba2a5dc48ba2b801ae2dbf99f Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:24:14 +0200 Subject: [PATCH 24/72] Fixed links were content was moved to 'historical' --- CHANGELOG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c4ab6f1bd..2209b729c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2173,10 +2173,10 @@ Features design. This introduces new ``Node.iter_markers(name)`` and ``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to read the `reasons for the revamp in the docs - `_, + `_, or jump over to details about `updating existing code to use the new APIs - `_. (`#3317 - `_) + `_. + (`#3317 `_) - Now when ``@pytest.fixture`` is applied more than once to the same function a ``ValueError`` is raised. This buggy behavior would cause surprising problems From 7337cce332b599166eeee16ba4cb3f467a3708f1 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:25:56 +0200 Subject: [PATCH 25/72] Fixed links were content was from 'writing_plugins' to 'reference' --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2209b729c..b12db6eff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2582,10 +2582,10 @@ Features `_) - New `pytest_runtest_logfinish - `_ + `_ hook which is called when a test item has finished executing, analogous to `pytest_runtest_logstart - `_. + `_. (`#3101 `_) - Improve performance when collecting tests using many fixtures. (`#3107 From 848b735a060568ebfc054fba6c19255f237c34c0 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:34:22 +0200 Subject: [PATCH 26/72] Fixed links to non existing GitHub users and py --- CHANGELOG.rst | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b12db6eff..9e54b4185 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3575,7 +3575,7 @@ Bug Fixes Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR. * Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). - Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. + Thanks to `@nicoddemus`_ for the PR. * Fix internal errors when an unprintable ``AssertionError`` is raised inside a test. Thanks `@omerhadari`_ for the PR. @@ -3706,7 +3706,7 @@ Bug Fixes .. _@syre: https://github.com/syre .. _@adler-j: https://github.com/adler-j -.. _@d-b-w: https://bitbucket.org/d-b-w/ +.. _@d-b-w: https://github.com/d-b-w .. _@DuncanBetts: https://github.com/DuncanBetts .. _@dupuy: https://bitbucket.org/dupuy/ .. _@kerrick-lyft: https://github.com/kerrick-lyft @@ -3766,7 +3766,7 @@ Bug Fixes .. _@adborden: https://github.com/adborden .. _@cwitty: https://github.com/cwitty -.. _@d_b_w: https://github.com/d_b_w +.. _@d_b_w: https://github.com/d-b-w .. _@gdyuldin: https://github.com/gdyuldin .. _@matclab: https://github.com/matclab .. _@MSeifert04: https://github.com/MSeifert04 @@ -3801,7 +3801,7 @@ Bug Fixes Thanks `@axil`_ for the PR. * Explain a bad scope value passed to ``@fixture`` declarations or - a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR. + a ``MetaFunc.parametrize()`` call. * This version includes ``pluggy-0.4.0``, which correctly handles ``VersionConflict`` errors in plugins (`#704`_). @@ -3811,7 +3811,6 @@ Bug Fixes .. _@philpep: https://github.com/philpep .. _@raquel-ucl: https://github.com/raquel-ucl .. _@axil: https://github.com/axil -.. _@tgoodlet: https://github.com/tgoodlet .. _@vlad-dragos: https://github.com/vlad-dragos .. _#1853: https://github.com/pytest-dev/pytest/issues/1853 @@ -4157,7 +4156,7 @@ time or change existing behaviors in order to make them less surprising/more use * Updated docstrings with a more uniform style. * Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. - Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and + Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@jgsonesen`_ and `@tomviner`_ for the PR. * No longer display the incorrect test deselection reason (`#1372`_). @@ -4205,7 +4204,7 @@ time or change existing behaviors in order to make them less surprising/more use Thanks to `@Stranger6667`_ for the PR. * Fixed the total tests tally in junit xml output (`#1798`_). - Thanks to `@cryporchild`_ for the PR. + Thanks to `@cboelsen`_ for the PR. * Fixed off-by-one error with lines from ``request.node.warn``. Thanks to `@blueyed`_ for the PR. @@ -4278,7 +4277,7 @@ time or change existing behaviors in order to make them less surprising/more use .. _@BeyondEvil: https://github.com/BeyondEvil .. _@blueyed: https://github.com/blueyed .. _@ceridwen: https://github.com/ceridwen -.. _@cryporchild: https://github.com/cryporchild +.. _@cboelsen: https://github.com/cboelsen .. _@csaftoiu: https://github.com/csaftoiu .. _@d6e: https://github.com/d6e .. _@davehunt: https://github.com/davehunt @@ -4289,7 +4288,7 @@ time or change existing behaviors in order to make them less surprising/more use .. _@gprasad84: https://github.com/gprasad84 .. _@graingert: https://github.com/graingert .. _@hartym: https://github.com/hartym -.. _@JonathonSonesen: https://github.com/JonathonSonesen +.. _@jgsonesen: https://github.com/jgsonesen .. _@kalekundert: https://github.com/kalekundert .. _@kvas-it: https://github.com/kvas-it .. _@marscher: https://github.com/marscher @@ -4426,7 +4425,7 @@ time or change existing behaviors in order to make them less surprising/more use **Changes** -* **Important**: `py.code `_ has been +* **Important**: `py.code `_ has been merged into the ``pytest`` repository as ``pytest._code``. This decision was made because ``py.code`` had very few uses outside ``pytest`` and the fact that it was in a different repository made it difficult to fix bugs on From eb7f950e20a5c2d66b614efda30275a947056c00 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:38:35 +0200 Subject: [PATCH 27/72] Fixed broken link to virtualenv --- doc/en/links.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/links.inc b/doc/en/links.inc index 1b7cbd05b..7c5e8d88c 100644 --- a/doc/en/links.inc +++ b/doc/en/links.inc @@ -14,7 +14,7 @@ .. _`distribute docs`: .. _`distribute`: https://pypi.org/project/distribute/ .. _`pip`: https://pypi.org/project/pip/ -.. _`venv`: https://docs.python.org/3/library/venv.html/ +.. _`venv`: https://docs.python.org/3/library/venv.html .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ From e03b8b9e95c896d12ebb011ee69738017a5f294e Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:40:08 +0200 Subject: [PATCH 28/72] Update/Fix links to using projects --- doc/en/projects.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/en/projects.rst b/doc/en/projects.rst index 606e9d47c..226358596 100644 --- a/doc/en/projects.rst +++ b/doc/en/projects.rst @@ -28,7 +28,6 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref: * `sentry `_, realtime app-maintenance and exception tracking * `Astropy `_ and `affiliated packages `_ * `tox `_, virtualenv/Hudson integration tool -* `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB * `applib `_ cross-platform utilities @@ -37,8 +36,7 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref: * `mwlib `_ mediawiki parser and utility library * `The Translate Toolkit `_ for localization and conversion * `execnet `_ rapid multi-Python deployment -* `pylib `_ cross-platform path, IO, dynamic code library -* `Pacha `_ configuration management in five minutes +* `pylib `_ cross-platform path, IO, dynamic code library * `bbfreeze `_ create standalone executables from Python scripts * `pdb++ `_ a fancier version of PDB * `py-s3fuse `_ Amazon S3 FUSE based filesystem @@ -77,7 +75,7 @@ Some organisations using pytest * `Tandberg `_ * `Shootq `_ * `Stups department of Heinrich Heine University Duesseldorf `_ -* `cellzome `_ +* cellzome * `Open End, Gothenborg `_ * `Laboratory of Bioinformatics, Warsaw `_ * `merlinux, Germany `_ From 3f46315a9db2bd2a1933f2630da3dc6ba79a6e65 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:40:58 +0200 Subject: [PATCH 29/72] Remove link to non-existing anchor --- doc/en/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 750de086e..c313f3849 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -142,7 +142,7 @@ The first test passed and the second failed. You can easily see the intermediate Request a unique temporary directory for functional tests -------------------------------------------------------------- -``pytest`` provides `Builtin fixtures/function arguments `_ to request arbitrary resources, like a unique temporary directory:: +``pytest`` provides `Builtin fixtures/function arguments `_ to request arbitrary resources, like a unique temporary directory:: # content of test_tmpdir.py def test_needsfiles(tmpdir): From 1f4ae789b8c2b795ca0819e7fe0e1a9347b7b9f5 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:43:07 +0200 Subject: [PATCH 30/72] Change link to "Where do Google's flaky tests come from?" Although it's available, linkchecker complained about it. It was changed to the permanent redirect. --- doc/en/flaky.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/flaky.rst b/doc/en/flaky.rst index 8e340316e..0f0eecab0 100644 --- a/doc/en/flaky.rst +++ b/doc/en/flaky.rst @@ -122,4 +122,4 @@ Resources * Google: * `Flaky Tests at Google and How We Mitigate Them `_ by John Micco, 2016 - * `Where do Google's flaky tests come from? `_ by Jeff Listfield, 2017 + * `Where do Google's flaky tests come from? `_ by Jeff Listfield, 2017 From 0cca7f831a37c9e706cac952e638270c128d9305 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:44:20 +0200 Subject: [PATCH 31/72] Fixed broken links and non-existing anchors in release announcements --- doc/en/announce/release-2.1.0.rst | 2 +- doc/en/announce/release-2.9.0.rst | 4 ++-- doc/en/announce/release-2.9.2.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/en/announce/release-2.1.0.rst b/doc/en/announce/release-2.1.0.rst index 831548ac2..2a2181d97 100644 --- a/doc/en/announce/release-2.1.0.rst +++ b/doc/en/announce/release-2.1.0.rst @@ -12,7 +12,7 @@ courtesy of Benjamin Peterson. You can now safely use ``assert`` statements in test modules without having to worry about side effects or python optimization ("-OO") options. This is achieved by rewriting assert statements in test modules upon import, using a PEP302 hook. -See http://pytest.org/assert.html#advanced-assertion-introspection for +See https://docs.pytest.org/en/latest/assert.html for detailed information. The work has been partly sponsored by my company, merlinux GmbH. diff --git a/doc/en/announce/release-2.9.0.rst b/doc/en/announce/release-2.9.0.rst index c079fdf6b..05d9a394f 100644 --- a/doc/en/announce/release-2.9.0.rst +++ b/doc/en/announce/release-2.9.0.rst @@ -75,7 +75,7 @@ The py.test Development Team **Changes** -* **Important**: `py.code `_ has been +* **Important**: `py.code `_ has been merged into the ``pytest`` repository as ``pytest._code``. This decision was made because ``py.code`` had very few uses outside ``pytest`` and the fact that it was in a different repository made it difficult to fix bugs on @@ -88,7 +88,7 @@ The py.test Development Team **experimental**, so you definitely should not import it explicitly! Please note that the original ``py.code`` is still available in - `pylib `_. + `pylib `_. * ``pytest_enter_pdb`` now optionally receives the pytest config object. Thanks `@nicoddemus`_ for the PR. diff --git a/doc/en/announce/release-2.9.2.rst b/doc/en/announce/release-2.9.2.rst index 8f274cdf3..b007a6d99 100644 --- a/doc/en/announce/release-2.9.2.rst +++ b/doc/en/announce/release-2.9.2.rst @@ -66,8 +66,8 @@ The py.test Development Team .. _#510: https://github.com/pytest-dev/pytest/issues/510 .. _#1506: https://github.com/pytest-dev/pytest/pull/1506 -.. _#1496: https://github.com/pytest-dev/pytest/issue/1496 -.. _#1524: https://github.com/pytest-dev/pytest/issue/1524 +.. _#1496: https://github.com/pytest-dev/pytest/issues/1496 +.. _#1524: https://github.com/pytest-dev/pytest/pull/1524 .. _@astraw38: https://github.com/astraw38 .. _@hackebrot: https://github.com/hackebrot From 79414164c2f01383715d4d9913fbf91be9fa2f23 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 21:58:40 +0200 Subject: [PATCH 32/72] Fixed broken links in adopt.rst --- doc/en/adopt.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/doc/en/adopt.rst b/doc/en/adopt.rst index 710f431be..e3c0477bc 100644 --- a/doc/en/adopt.rst +++ b/doc/en/adopt.rst @@ -24,11 +24,9 @@ The ideal pytest helper - feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents) - does not need to be an expert in every aspect! -`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March) +Pytest helpers, sign up here! (preferably in February, hard deadline 22 March) -.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P - The ideal partner project ----------------------------------------- @@ -40,11 +38,9 @@ The ideal partner project - has the support of the core development team, in trying out pytest adoption - has no tests... or 100% test coverage... or somewhere in between! -`Partner projects, sign up here`_! (by 22 March) +Partner projects, sign up here! (by 22 March) -.. _`Partner projects, sign up here`: http://goo.gl/forms/ZGyqlHiwk3 - What does it mean to "adopt pytest"? ----------------------------------------- @@ -68,11 +64,11 @@ Progressive success might look like: It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies. .. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest -.. _assert: asserts.html +.. _assert: assert.html .. _pycmd: https://bitbucket.org/hpk42/pycmd/overview .. _`setUp/tearDown methods`: xunit_setup.html .. _fixtures: fixture.html -.. _markers: markers.html +.. _markers: mark.html .. _distributed: xdist.html From 7c2e843358dd42a2b174ca6fd46639b39b66783b Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 22:36:48 +0200 Subject: [PATCH 33/72] Fixed link to correct anchor of Python's -w postion --- doc/en/warnings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index c2b256bb3..382f15928 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -127,7 +127,7 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable: *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 +.. _`-W option`: https://docs.python.org/3/using/cmdline.html#cmdoption-w .. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter .. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings From 1e34734f8f84cb7a8b262f474d4aeb76a63cca73 Mon Sep 17 00:00:00 2001 From: Steffen Schroeder Date: Mon, 15 Jul 2019 22:50:45 +0200 Subject: [PATCH 34/72] Fixed link to Python Classifiers --- doc/en/writing_plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 4bbc1afce..67a8fcecf 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -164,7 +164,7 @@ If a package is installed this way, ``pytest`` will load .. note:: Make sure to include ``Framework :: Pytest`` in your list of - `PyPI classifiers `_ + `PyPI classifiers `_ to make it easy for users to find your plugin. From c0231ae7808e75735748345426eff34bdbbd6345 Mon Sep 17 00:00:00 2001 From: Oliver Bestwalter Date: Thu, 18 Jul 2019 21:06:53 +0200 Subject: [PATCH 35/72] Add info about open training in Leipzig --- doc/en/talks.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/talks.rst b/doc/en/talks.rst index e99dd7861..f66192817 100644 --- a/doc/en/talks.rst +++ b/doc/en/talks.rst @@ -5,6 +5,7 @@ Talks and Tutorials .. sidebar:: Next Open Trainings - `Training at Workshoptage 2019 `_ (German), 10th September 2019, Rapperswil, Switzerland. + - `3 day hands-on workshop covering pytest, tox and devpi: "Professional Testing with Python" `_ (English), October 21 - 23, 2019, Leipzig, Germany. .. _`funcargs`: funcargs.html From dcbb9c1f5aa0583cb34b280f6bcd0da1a57514df Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 20 Jul 2019 01:22:40 +0200 Subject: [PATCH 36/72] tox.ini: clean up filterwarnings - path.local/path.readlines is not used anymore - enhance doc for "invalid escape sequence" filter --- tox.ini | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 95c49db04..4555c379c 100644 --- a/tox.ini +++ b/tox.ini @@ -133,10 +133,6 @@ filterwarnings = ignore::pytest.RemovedInPytest4Warning default:Using or importing the ABCs:DeprecationWarning:unittest2.* ignore:Module already imported so cannot be rewritten:pytest.PytestWarning - # produced by path.local - 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) @@ -144,6 +140,8 @@ filterwarnings = # pytest's own futurewarnings ignore::pytest.PytestExperimentalApiWarning # Do not cause SyntaxError for invalid escape sequences in py37. + # Those are caught/handled by pyupgrade, and not easy to filter with the + # module being the filename (with .py removed). default:invalid escape sequence:DeprecationWarning # ignore use of unregistered marks, because we use many to test the implementation ignore::_pytest.warning_types.PytestUnknownMarkWarning From f0feb6c83a5c21f151fb908ca475c614a04d4906 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 20 Jul 2019 01:51:01 +0200 Subject: [PATCH 37/72] fixup! tox.ini: clean up filterwarnings --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 4555c379c..52d400524 100644 --- a/tox.ini +++ b/tox.ini @@ -133,6 +133,8 @@ filterwarnings = ignore::pytest.RemovedInPytest4Warning default:Using or importing the ABCs:DeprecationWarning:unittest2.* ignore:Module already imported so cannot be rewritten:pytest.PytestWarning + # produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8). + ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest)) # produced by pytest-xdist ignore:.*type argument to addoption.*:DeprecationWarning # produced by python >=3.5 on execnet (pytest-xdist) From d35d09f82dd2e968e138213ddce66e7afa2ed261 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 20 Jul 2019 07:04:38 +0200 Subject: [PATCH 38/72] unittest: handle outcomes.Exit This is required for pytest to stop when using "quit" in pdb, and should get honored/handled in general. --- src/_pytest/unittest.py | 6 ++++++ testing/test_unittest.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 216266979..337490d13 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -6,6 +6,7 @@ import _pytest._code import pytest from _pytest.compat import getimfunc from _pytest.config import hookimpl +from _pytest.outcomes import exit from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.outcomes import xfail @@ -153,6 +154,11 @@ class TestCaseFunction(Function): self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError(self, testcase, rawexcinfo): + try: + if isinstance(rawexcinfo[1], exit.Exception): + exit(rawexcinfo[1].msg) + except TypeError: + pass self._addexcinfo(rawexcinfo) def addFailure(self, testcase, rawexcinfo): diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 2467ddd39..d86afd510 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1050,3 +1050,39 @@ def test_setup_inheritance_skipping(testdir, test_name, expected_outcome): testdir.copy_example("unittest/{}".format(test_name)) result = testdir.runpytest() result.stdout.fnmatch_lines(["* {} in *".format(expected_outcome)]) + + +def test_BdbQuit(testdir): + testdir.makepyfile( + test_foo=""" + import unittest + + class MyTestCase(unittest.TestCase): + def test_bdbquit(self): + import bdb + raise bdb.BdbQuit() + + def test_should_not_run(self): + pass + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(failed=1, passed=1) + + +def test_exit_outcome(testdir): + testdir.makepyfile( + test_foo=""" + import pytest + import unittest + + class MyTestCase(unittest.TestCase): + def test_exit_outcome(self): + pytest.exit("pytest_exit") + + def test_should_not_run(self): + pass + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome() From 27c9d80a7ec2c5e122748986f224e5549b799db4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Jul 2019 11:17:30 -0700 Subject: [PATCH 39/72] Fix ordering of sys modules snapshot --- testing/test_pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 37b63f31a..ba3135f65 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -231,8 +231,8 @@ class TestInlineRunModulesCleanup: ): spy_factory = self.spy_factory() monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) - original = dict(sys.modules) testdir.syspathinsert() + original = dict(sys.modules) testdir.makepyfile(import1="# you son of a silly person") testdir.makepyfile(import2="# my hovercraft is full of eels") test_mod = testdir.makepyfile( From 240d314f364061504bc0c7a75f3ada974490629d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 19 Jul 2019 21:59:43 +0200 Subject: [PATCH 40/72] copy test and changelog from #5607 by @niccodemus --- changelog/5606.bugfix.rst | 2 ++ testing/python/integration.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 changelog/5606.bugfix.rst diff --git a/changelog/5606.bugfix.rst b/changelog/5606.bugfix.rst new file mode 100644 index 000000000..82332ba99 --- /dev/null +++ b/changelog/5606.bugfix.rst @@ -0,0 +1,2 @@ +Fixed internal error when test functions were patched with objects that cannot be compared +for truth values against others, like ``numpy`` arrays. diff --git a/testing/python/integration.py b/testing/python/integration.py index a6b8dddf3..73419eef4 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -178,6 +178,34 @@ class TestMockDecoration: reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + def test_mock_sentinel_check_against_numpy_like(self, testdir): + """Ensure our function that detects mock arguments compares against sentinels using + identity to circumvent objects which can't be compared with equality against others + in a truth context, like with numpy arrays (#5606). + """ + testdir.makepyfile( + dummy=""" + class NumpyLike: + def __init__(self, value): + self.value = value + def __eq__(self, other): + raise ValueError("like numpy, cannot compare against others for truth") + FOO = NumpyLike(10) + """ + ) + testdir.makepyfile( + """ + from unittest.mock import patch + import dummy + class Test(object): + @patch("dummy.FOO", new=dummy.NumpyLike(50)) + def test_hello(self): + assert dummy.FOO.value == 50 + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + def test_mock(self, testdir): pytest.importorskip("mock", "1.0.1") testdir.makepyfile( From 8c7d9124bae66b9f4b0152b28e0729c7211b8488 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 19 Jul 2019 22:10:45 +0200 Subject: [PATCH 41/72] switch num_mock_patch_args to do identity checks for the sentinels --- src/_pytest/compat.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index d238061b4..52ffc36bc 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -64,13 +64,18 @@ def num_mock_patch_args(function): patchings = getattr(function, "patchings", None) if not patchings: return 0 - mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")] - if any(mock_modules): - sentinels = [m.DEFAULT for m in mock_modules if m is not None] - return len( - [p for p in patchings if not p.attribute_name and p.new in sentinels] - ) - return len(patchings) + + mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object()) + ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object()) + + return len( + [ + p + for p in patchings + if not p.attribute_name + and (p.new is mock_sentinel or p.new is ut_mock_sentinel) + ] + ) def getfuncargnames(function, is_method=False, cls=None): From 66cfc66d63f428342f5cfbd6b3c7e4c7c9b9fcde Mon Sep 17 00:00:00 2001 From: Xixi Zhao Date: Mon, 22 Jul 2019 16:01:56 +0800 Subject: [PATCH 42/72] Update index.rst --- doc/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 2d6ea620f..6c7c84865 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -61,7 +61,7 @@ Features - Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box; -- Python Python 3.5+ and PyPy 3; +- Python 3.5+ and PyPy 3; - Rich plugin architecture, with over 315+ `external plugins `_ and thriving community; From 13d8577451e9e68a9651cde122b9f279df824103 Mon Sep 17 00:00:00 2001 From: William Woodall Date: Mon, 22 Jul 2019 16:15:51 -0700 Subject: [PATCH 43/72] README: fix typo Closes https://github.com/pytest-dev/pytest/pull/5643. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 9739a1bda..301e49538 100644 --- a/README.rst +++ b/README.rst @@ -111,13 +111,13 @@ Consult the `Changelog `__ pag Support pytest -------------- -You can support pytest by obtaining a `Tideflift subscription`_. +You can support pytest by obtaining a `Tidelift subscription`_. Tidelift gives software development teams a single source for purchasing and maintaining their software, with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools. -.. _`Tideflift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme +.. _`Tidelift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme Security From 6e687c4354f5bb6dd156e2a7fc54c79428962826 Mon Sep 17 00:00:00 2001 From: Xixi Zhao Date: Tue, 23 Jul 2019 15:09:32 +0800 Subject: [PATCH 44/72] Doc fix: m1 isn't a dependency of f1. According to the file in `example/fixtures/test_fixtures_order.py`, m1 isn't a dependency of f1. --- doc/en/fixture.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 78fef13e8..d6c7bfd4d 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -307,7 +307,6 @@ The fixtures requested by ``test_foo`` will be instantiated in the following ord 1. ``s1``: is the highest-scoped fixture (``session``). 2. ``m1``: is the second highest-scoped fixture (``module``). - because it is a dependency of ``f1``. 3. ``a1``: is a ``function``-scoped ``autouse`` fixture: it will be instantiated before other fixtures within the same scope. 4. ``f3``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point @@ -501,7 +500,7 @@ of a fixture is needed multiple times in a single test. Instead of returning data directly, the fixture instead returns a function which generates the data. This function can then be called multiple times in the test. -Factories can have have parameters as needed:: +Factories can have parameters as needed:: @pytest.fixture def make_customer_record(): From d66b6c8371c33d9016646bd7a38f63177438939b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 08:07:38 -0300 Subject: [PATCH 45/72] Update MarkDecorator docs The MarkInfo class no longer exists --- src/_pytest/mark/structures.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index c1a9e70f9..2fb6ad851 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -179,9 +179,7 @@ class Mark: @attr.s 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 `. - MarkDecorator instances are often created like this:: + it will create :class:`Mark` objects which are often created like this:: mark1 = pytest.mark.NAME # simple MarkDecorator mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator From 3c94f32e771532040bb3e003998aedb273805ca7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 08:42:03 -0300 Subject: [PATCH 46/72] Make ExceptionInfo.repr change more prominent Related to #5579 --- CHANGELOG.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2021c05d..811f7475d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -90,6 +90,24 @@ Removals - `#5412 `_: ``ExceptionInfo`` objects (returned by ``pytest.raises``) now have the same ``str`` representation as ``repr``, which avoids some confusion when users use ``print(e)`` to inspect the object. + This means code like: + + .. code-block:: python + + with pytest.raises(SomeException) as e: + ... + assert "some message" in str(e) + + + Needs to be changed to: + + .. code-block:: python + + with pytest.raises(SomeException) as e: + ... + assert "some message" in str(e.value) + + Deprecations From 0824789459b40cc1c4126dfa33ea0d20493fbba5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 08:52:52 -0300 Subject: [PATCH 47/72] Improve test for pytest.exit handling --- testing/test_unittest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index d86afd510..153b76a89 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1078,11 +1078,11 @@ def test_exit_outcome(testdir): class MyTestCase(unittest.TestCase): def test_exit_outcome(self): - pytest.exit("pytest_exit") + pytest.exit("pytest_exit called") def test_should_not_run(self): pass """ ) - reprec = testdir.inline_run() - reprec.assertoutcome() + result = testdir.runpytest() + result.stdout.fnmatch_lines("*Exit: pytest_exit called*") From 5c09cc16f22df3b1853d28c551ea3738abf97479 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 08:55:14 -0300 Subject: [PATCH 48/72] Add changelog entry for #5634 --- changelog/5634.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/5634.bugfix.rst diff --git a/changelog/5634.bugfix.rst b/changelog/5634.bugfix.rst new file mode 100644 index 000000000..f4699919b --- /dev/null +++ b/changelog/5634.bugfix.rst @@ -0,0 +1 @@ +``pytest.exit`` and ``bdb.BdbQuit`` are now correctly handled in ``unittest`` cases. From 8c47db724c7965bb383ba1df9226f045d0b943ec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 23 Jul 2019 15:24:22 +0200 Subject: [PATCH 49/72] Improve output when parsing an ini configuration fails --- changelog/5650.bugfix.rst | 1 + src/_pytest/config/findpaths.py | 6 +++++- testing/test_config.py | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelog/5650.bugfix.rst diff --git a/changelog/5650.bugfix.rst b/changelog/5650.bugfix.rst new file mode 100644 index 000000000..db57a40b9 --- /dev/null +++ b/changelog/5650.bugfix.rst @@ -0,0 +1 @@ +Improved output when parsing an ini configuration file fails. diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index fa2024470..3f91bbd07 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -32,7 +32,11 @@ def getcfg(args, config=None): for inibasename in inibasenames: p = base.join(inibasename) if exists(p): - iniconfig = py.iniconfig.IniConfig(p) + try: + iniconfig = py.iniconfig.IniConfig(p) + except py.iniconfig.ParseError as exc: + raise UsageError(str(exc)) + if ( inibasename == "setup.cfg" and "tool:pytest" in iniconfig.sections diff --git a/testing/test_config.py b/testing/test_config.py index ff993e401..cacf218d4 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -122,6 +122,14 @@ class TestParseIni: config = testdir.parseconfigure(sub) assert config.getini("minversion") == "2.0" + def test_ini_parse_error(self, testdir): + testdir.tmpdir.join("pytest.ini").write('addopts = -x') + result = testdir.runpytest() + assert result.ret != 0 + result.stderr.fnmatch_lines([ + "ERROR: *pytest.ini:1: no section header defined" + ]) + @pytest.mark.xfail(reason="probably not needed") def test_confcutdir(self, testdir): sub = testdir.mkdir("sub") From 1873dc6a8a2551550d1c3b77eae5746c94e30304 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 09:42:05 -0400 Subject: [PATCH 50/72] Fix fixture example in docs --- doc/en/fixture.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 42fe7cf3a..4bb88cb7b 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -391,7 +391,12 @@ close all resources created by a fixture even if one of them fails to be created import pytest - from .utils import connect + + @contextlib.contextmanager + def connect(port): + ... # create connection + yield + ... # close connection @pytest.fixture @@ -441,7 +446,12 @@ Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup: import pytest - from .utils import connect + + @contextlib.contextmanager + def connect(port): + ... # create connection + yield + ... # close connection @pytest.fixture From a82dd2f064028c4865f267611fc461f86cf775e3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 10:55:22 -0300 Subject: [PATCH 51/72] Fix linting --- testing/test_config.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/testing/test_config.py b/testing/test_config.py index cacf218d4..c1a58848a 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -123,12 +123,10 @@ class TestParseIni: assert config.getini("minversion") == "2.0" def test_ini_parse_error(self, testdir): - testdir.tmpdir.join("pytest.ini").write('addopts = -x') + testdir.tmpdir.join("pytest.ini").write("addopts = -x") result = testdir.runpytest() assert result.ret != 0 - result.stderr.fnmatch_lines([ - "ERROR: *pytest.ini:1: no section header defined" - ]) + result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"]) @pytest.mark.xfail(reason="probably not needed") def test_confcutdir(self, testdir): From f163b37f6ab8729f78941ed53c7ced6f1cffe7e9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 23 Jul 2019 10:00:35 -0400 Subject: [PATCH 52/72] Run regen --- doc/en/example/parametrize.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 0a5ba8358..3dff2c559 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -552,13 +552,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker: platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collecting ... collected 17 items / 14 deselected / 3 selected + collecting ... collected 18 items / 15 deselected / 3 selected test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%] test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%] test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%] - ============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============ + ============ 2 passed, 15 deselected, 1 xfailed in 0.12 seconds ============ As the result: From 401c3d11097225e0081b41cfe5816e398812ba0c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 23 Jul 2019 19:35:51 +0200 Subject: [PATCH 53/72] tests: unittest: fix/harden "test_exit_outcome" Ref: https://github.com/pytest-dev/pytest/pull/5634#pullrequestreview-265565917 --- testing/test_unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 153b76a89..ec5f92e18 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1085,4 +1085,4 @@ def test_exit_outcome(testdir): """ ) result = testdir.runpytest() - result.stdout.fnmatch_lines("*Exit: pytest_exit called*") + result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"]) From b9111fe677fc1ae6756b3dddc7ed4a40b544dfa4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 23 Jul 2019 19:38:33 +0200 Subject: [PATCH 54/72] Fix changelog --- changelog/5634.bugfix.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/5634.bugfix.rst b/changelog/5634.bugfix.rst index f4699919b..a2a282f93 100644 --- a/changelog/5634.bugfix.rst +++ b/changelog/5634.bugfix.rst @@ -1 +1,2 @@ -``pytest.exit`` and ``bdb.BdbQuit`` are now correctly handled in ``unittest`` cases. +``pytest.exit`` is now correctly handled in ``unittest`` cases. +This makes ``unittest`` cases handle ``quit`` from pytest's pdb correctly. From 829cc5a24202c1688bfbc747a61afff86ffb24bc Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 24 Jul 2019 14:27:05 +0100 Subject: [PATCH 55/72] fix docs about syspath_prepend eg it does not modify $PATH --- doc/en/monkeypatch.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/en/monkeypatch.rst b/doc/en/monkeypatch.rst index 8e4622982..68ec0a023 100644 --- a/doc/en/monkeypatch.rst +++ b/doc/en/monkeypatch.rst @@ -46,10 +46,13 @@ environment variable is missing, or to set multiple values to a known variable. :py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for these patches. -4. Use :py:meth:`monkeypatch.syspath_prepend` to modify the system ``$PATH`` safely, and +4. Use `monkeypatch.setenv("PATH", value, prepend=True)` to modify ``$PATH``, and :py:meth:`monkeypatch.chdir` to change the context of the current working directory during a test. +5. Use py:meth:`monkeypatch.syspath_prepend` to modify ``sys.path`` which will also +call :py:meth:`pkg_resources.fixup_namespace_packages` and :py:meth:`importlib.invalidate_caches`. + See the `monkeypatch blog post`_ for some introduction material and a discussion of its motivation. From ebfe8eabf5db5f06cb0302b52ed8213e2f2092a4 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 24 Jul 2019 14:27:51 +0100 Subject: [PATCH 56/72] Update doc/en/monkeypatch.rst --- doc/en/monkeypatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/monkeypatch.rst b/doc/en/monkeypatch.rst index 68ec0a023..6cd7da746 100644 --- a/doc/en/monkeypatch.rst +++ b/doc/en/monkeypatch.rst @@ -46,7 +46,7 @@ environment variable is missing, or to set multiple values to a known variable. :py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for these patches. -4. Use `monkeypatch.setenv("PATH", value, prepend=True)` to modify ``$PATH``, and +4. Use ``monkeypatch.setenv("PATH", value, prepend=True)`` to modify ``$PATH``, and :py:meth:`monkeypatch.chdir` to change the context of the current working directory during a test. From 1d8f668e100ff5d0af0f9d4f49947b883938ab7a Mon Sep 17 00:00:00 2001 From: helloocc Date: Wed, 24 Jul 2019 23:44:37 +0800 Subject: [PATCH 57/72] Add author. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 8e2314283..8e545e126 100644 --- a/AUTHORS +++ b/AUTHORS @@ -258,6 +258,7 @@ Wil Cooley William Lee Wim Glenn Wouter van Ackooy +Xixi Zhao Xuan Luong Xuecong Liao Zac Hatfield-Dodds From 635916052c35aadd9465ddf8a0517cdbbf5b2105 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 24 Jul 2019 17:23:48 +0100 Subject: [PATCH 58/72] fix setenv prepend docs --- doc/en/monkeypatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/monkeypatch.rst b/doc/en/monkeypatch.rst index 6cd7da746..3248c5e1c 100644 --- a/doc/en/monkeypatch.rst +++ b/doc/en/monkeypatch.rst @@ -46,7 +46,7 @@ environment variable is missing, or to set multiple values to a known variable. :py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for these patches. -4. Use ``monkeypatch.setenv("PATH", value, prepend=True)`` to modify ``$PATH``, and +4. Use ``monkeypatch.setenv("PATH", value, prepend=":")`` to modify ``$PATH``, and :py:meth:`monkeypatch.chdir` to change the context of the current working directory during a test. From 3bdcd6b4f3be6c940a7c0e6b3d6561d2d859fc7d Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 24 Jul 2019 20:49:31 +0100 Subject: [PATCH 59/72] Update doc/en/monkeypatch.rst --- doc/en/monkeypatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/monkeypatch.rst b/doc/en/monkeypatch.rst index 3248c5e1c..fd277d234 100644 --- a/doc/en/monkeypatch.rst +++ b/doc/en/monkeypatch.rst @@ -46,7 +46,7 @@ environment variable is missing, or to set multiple values to a known variable. :py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for these patches. -4. Use ``monkeypatch.setenv("PATH", value, prepend=":")`` to modify ``$PATH``, and +4. Use ``monkeypatch.setenv("PATH", value, prepend=os.pathsep)`` to modify ``$PATH``, and :py:meth:`monkeypatch.chdir` to change the context of the current working directory during a test. From ab39502c98f40c03a2956dcf6c6b9daf5ee41a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 26 Jul 2019 02:23:14 +0200 Subject: [PATCH 60/72] In test_xfail_handling, only remove __pycache__ if it exists Previously, the test failed when the directory was not present, which could have been caused for example by invoking the tests with PYTHONDONTWRITEBYTECODE=1. Fixes https://github.com/pytest-dev/pytest/issues/5664 --- changelog/5664.trivial.rst | 2 ++ testing/test_stepwise.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog/5664.trivial.rst diff --git a/changelog/5664.trivial.rst b/changelog/5664.trivial.rst new file mode 100644 index 000000000..3928454ef --- /dev/null +++ b/changelog/5664.trivial.rst @@ -0,0 +1,2 @@ +When invoking pytest's own testsuite with ``PYTHONDONTWRITEBYTECODE=1``, +the ``test_xfail_handling`` test no longer fails. diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 591d67b6c..f61425b6b 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -207,7 +207,8 @@ def test_xfail_handling(testdir): # because we are writing to the same file, mtime might not be affected enough to # invalidate the cache, making this next run flaky - testdir.tmpdir.join("__pycache__").remove() + if testdir.tmpdir.join("__pycache__").exists(): + testdir.tmpdir.join("__pycache__").remove() testdir.makepyfile(contents.format(assert_value="0", strict="True")) result = testdir.runpytest("--sw", "-v") result.stdout.fnmatch_lines( From 6c2f673daf8da42f601ecb30ac15b2b1b227d5ca Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Sat, 27 Jul 2019 17:25:23 +0200 Subject: [PATCH 61/72] Have same name for fulltrace --- AUTHORS | 1 + src/_pytest/terminal.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 8e545e126..ad7bf4a1c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -132,6 +132,7 @@ Joseph Hunkeler Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn +Kaiqi Dong Kale Kundert Katarzyna Jachim Katerina Koukiou diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index bcd6e1f7c..125f3604c 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -114,7 +114,7 @@ def pytest_addoption(parser): ) group._addoption( "--fulltrace", - "--full-trace", + "--fulltrace", action="store_true", default=False, help="don't cut any tracebacks (default is to cut).", From 958374addbbbb8b700b5fe8b7fb8afd7534691e3 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Sat, 27 Jul 2019 17:26:52 +0200 Subject: [PATCH 62/72] Remove name from author --- AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index ad7bf4a1c..8e545e126 100644 --- a/AUTHORS +++ b/AUTHORS @@ -132,7 +132,6 @@ Joseph Hunkeler Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn -Kaiqi Dong Kale Kundert Katarzyna Jachim Katerina Koukiou From aa13c625da602bc6744c68fcb8606d328b327e55 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Sat, 27 Jul 2019 21:06:29 +0200 Subject: [PATCH 63/72] Change the warning message --- src/_pytest/terminal.py | 4 ++-- testing/test_terminal.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 125f3604c..05d5427c3 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -114,7 +114,7 @@ def pytest_addoption(parser): ) group._addoption( "--fulltrace", - "--fulltrace", + "--full-trace", action="store_true", default=False, help="don't cut any tracebacks (default is to cut).", @@ -692,7 +692,7 @@ class TerminalReporter: else: excrepr.reprcrash.toterminal(self._tw) self._tw.line( - "(to show a full traceback on KeyboardInterrupt use --fulltrace)", + "(to show a full traceback on KeyboardInterrupt use --full-trace)", yellow=True, ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index bf029fbc5..381a5b2e1 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -233,7 +233,7 @@ class TestTerminal: ) else: result.stdout.fnmatch_lines( - ["(to show a full traceback on KeyboardInterrupt use --fulltrace)"] + ["(to show a full traceback on KeyboardInterrupt use --full-trace)"] ) result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) From 2959fb319811d67812ef775113685bf0373084f9 Mon Sep 17 00:00:00 2001 From: Xixi Zhao Date: Tue, 30 Jul 2019 16:18:22 +0800 Subject: [PATCH 64/72] Doc fix: remove redundant parentheses. --- doc/en/example/markers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 004e77add..218ef2707 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -336,7 +336,7 @@ apply a marker to an individual test instance: @pytest.mark.foo @pytest.mark.parametrize( - ("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)] + ("n", "expected"), [(1, 2), pytest.param(1, 3, marks=pytest.mark.bar), (2, 3)] ) def test_increment(n, expected): assert n + 1 == expected From 942fd919958eb98aa1fbc53cb9afaf8947385532 Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Thu, 1 Aug 2019 15:09:14 +0200 Subject: [PATCH 65/72] shouldnt -> shouldn't --- testing/code/test_excinfo.py | 2 +- testing/test_junitxml.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index d7771833a..7428a459c 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -370,7 +370,7 @@ def test_excinfo_no_python_sourcecode(tmpdir): excinfo = pytest.raises(ValueError, template.render, h=h) for item in excinfo.traceback: print(item) # XXX: for some reason jinja.Template.render is printed in full - item.source # shouldnt fail + item.source # shouldn't fail if item.path.basename == "test.txt": assert str(item.source) == "{{ h()}}:" diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index bcf83b352..b56122d9b 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -705,7 +705,7 @@ def test_dont_configure_on_slaves(tmpdir): return "pytest" junitprefix = None - # XXX: shouldnt need tmpdir ? + # XXX: shouldn't need tmpdir ? xmlpath = str(tmpdir.join("junix.xml")) register = gotten.append From 2de145f372478171740da0639f6512efe5c7575b Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Thu, 1 Aug 2019 15:10:06 +0200 Subject: [PATCH 66/72] wasnt -> wasn't --- testing/test_junitxml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index b56122d9b..de5be7084 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -582,7 +582,7 @@ class TestPython: assert "hx" in fnode.toxml() def test_assertion_binchars(self, testdir): - """this test did fail when the escaping wasnt strict""" + """this test did fail when the escaping wasn't strict""" testdir.makepyfile( """ From d19fe3c410e915e1fea026570f9e138c0c90429d Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Thu, 1 Aug 2019 15:10:39 +0200 Subject: [PATCH 67/72] didnt -> didn't --- testing/test_mark.py | 2 +- testing/test_nose.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_mark.py b/testing/test_mark.py index c22e9dbb5..273ba2298 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -435,7 +435,7 @@ class TestFunctional: def test_b(self): assert True class TestC(object): - # this one didnt get marked + # this one didn't get marked def test_d(self): assert True """ diff --git a/testing/test_nose.py b/testing/test_nose.py index f60c3af53..16d8d1fc0 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -253,7 +253,7 @@ def test_apiwrapper_problem_issue260(testdir): def test_setup_teardown_linking_issue265(testdir): - # we accidentally didnt integrate nose setupstate with normal setupstate + # we accidentally didn't integrate nose setupstate with normal setupstate # this test ensures that won't happen again testdir.makepyfile( ''' From 2ca47cb3f5d07938103aed70481d22c79aef1dca Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Thu, 1 Aug 2019 15:11:26 +0200 Subject: [PATCH 68/72] programatically -> programmatically --- doc/en/mark.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/mark.rst b/doc/en/mark.rst index de6ab7822..3899dab88 100644 --- a/doc/en/mark.rst +++ b/doc/en/mark.rst @@ -40,7 +40,7 @@ You can register custom marks in your ``pytest.ini`` file like this: Note that everything after the ``:`` is an optional description. -Alternatively, you can register new markers programatically in a +Alternatively, you can register new markers programmatically in a :ref:`pytest_configure ` hook: .. code-block:: python From b3f4398d6462ec0058ab957e2c27a404d5ef07c9 Mon Sep 17 00:00:00 2001 From: Semen Zhydenko Date: Thu, 1 Aug 2019 15:11:38 +0200 Subject: [PATCH 69/72] surpressing -> suppressing --- src/_pytest/config/argparsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 43cf62ab1..8994ff7d9 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -399,7 +399,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): """shorten help for long options that differ only in extra hyphens - collapse **long** options that are the same except for extra hyphens - - special action attribute map_long_option allows surpressing additional + - special action attribute map_long_option allows suppressing additional long options - shortcut if there are only two options and one of them is a short one - cache result on action object as this is called at least 2 times From cd924b66ca6dc3980c0a53fb14b3b932b37b804f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 1 Aug 2019 09:37:02 -0300 Subject: [PATCH 70/72] Disable shallow cloning because of setuptools-scm setuptools-scm needs all tags to guess the version correctly --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8053eed65..af33d672e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ env: global: - PYTEST_ADDOPTS=-vv +# setuptools-scm needs all tags in order to obtain a proper version +git: + depth: false + install: - python -m pip install --upgrade --pre tox From b43ebb7d657944fd9a04dbfa189c68bc5ab6c06f Mon Sep 17 00:00:00 2001 From: Alexey Zankevich Date: Thu, 1 Aug 2019 20:46:27 -0400 Subject: [PATCH 71/72] Cache split nodes results to reduce long tests collection time on large test suites --- changelog/5516.trivial.rst | 1 + src/_pytest/nodes.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog/5516.trivial.rst diff --git a/changelog/5516.trivial.rst b/changelog/5516.trivial.rst new file mode 100644 index 000000000..2f6b4e35e --- /dev/null +++ b/changelog/5516.trivial.rst @@ -0,0 +1 @@ +Cache node splitting function which can improve collection performance in very large test suites. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 7e1c40bcb..9b78dca38 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,5 +1,6 @@ import os import warnings +from functools import lru_cache import py @@ -13,6 +14,7 @@ SEP = "/" tracebackcutdir = py.path.local(_pytest.__file__).dirpath() +@lru_cache(maxsize=None) def _splitnode(nodeid): """Split a nodeid into constituent 'parts'. @@ -30,11 +32,12 @@ def _splitnode(nodeid): """ if nodeid == "": # If there is no root node at all, return an empty list so the caller's logic can remain sane - return [] + return () parts = nodeid.split(SEP) # Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar' parts[-1:] = parts[-1].split("::") - return parts + # Convert parts into a tuple to avoid possible errors with caching of a mutable type + return tuple(parts) def ischildnode(baseid, nodeid): From 9064eea216953ef72f8c3dc0326503c072cdfe8b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 18 Jul 2019 20:07:45 +0200 Subject: [PATCH 72/72] Improve rm_rf to handle only known functions Warnings are emitted if we cannot safely remove paths. Fix #5626 --- src/_pytest/pathlib.py | 70 +++++++++++++++++++++++++----------------- testing/test_tmpdir.py | 62 +++++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b5f2bf0fb..1c0c45b14 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -8,6 +8,7 @@ import shutil import sys import uuid import warnings +from functools import partial from os.path import expanduser from os.path import expandvars from os.path import isabs @@ -38,38 +39,49 @@ def ensure_reset_dir(path): path.mkdir() -def rm_rf(path): +def on_rm_rf_error(func, path: str, exc, *, start_path): + """Handles known read-only errors during rmtree.""" + excvalue = exc[1] + + if not isinstance(excvalue, PermissionError): + warnings.warn( + PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue)) + ) + return + + if func not in (os.rmdir, os.remove, os.unlink): + warnings.warn( + PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue)) + ) + return + + # Chmod + retry. + import stat + + def chmod_rw(p: str): + mode = os.stat(p).st_mode + os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR) + + # For files, we need to recursively go upwards in the directories to + # ensure they all are also writable. + p = Path(path) + if p.is_file(): + for parent in p.parents: + chmod_rw(str(parent)) + # stop when we reach the original path passed to rm_rf + if parent == start_path: + break + chmod_rw(str(path)) + + func(path) + + +def rm_rf(path: Path): """Remove the path contents recursively, even if some elements are read-only. """ - - def chmod_w(p): - import stat - - mode = os.stat(str(p)).st_mode - os.chmod(str(p), mode | stat.S_IWRITE) - - def force_writable_and_retry(function, p, excinfo): - p = Path(p) - - # for files, we need to recursively go upwards - # in the directories to ensure they all are also - # writable - if p.is_file(): - for parent in p.parents: - chmod_w(parent) - # stop when we reach the original path passed to rm_rf - if parent == path: - break - - chmod_w(p) - try: - # retry the function that failed - function(str(p)) - except Exception as e: - warnings.warn(PytestWarning("(rm_rf) error removing {}: {}".format(p, e))) - - shutil.rmtree(str(path), onerror=force_writable_and_retry) + onerror = partial(on_rm_rf_error, start_path=path) + shutil.rmtree(str(path), onerror=onerror) def find_prefixed(root, prefix): diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 01f9d4652..2c6454b0c 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -312,7 +312,20 @@ class TestNumberedDir: p, consider_lock_dead_if_created_before=p.stat().st_mtime + 1 ) - def test_rmtree(self, tmp_path): + def test_cleanup_ignores_symlink(self, tmp_path): + the_symlink = tmp_path / (self.PREFIX + "current") + attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5")) + self._do_cleanup(tmp_path) + + def test_removal_accepts_lock(self, tmp_path): + folder = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX) + pathlib.create_cleanup_lock(folder) + pathlib.maybe_delete_a_numbered_dir(folder) + assert folder.is_dir() + + +class TestRmRf: + def test_rm_rf(self, tmp_path): from _pytest.pathlib import rm_rf adir = tmp_path / "adir" @@ -328,7 +341,7 @@ class TestNumberedDir: rm_rf(adir) assert not adir.exists() - def test_rmtree_with_read_only_file(self, tmp_path): + def test_rm_rf_with_read_only_file(self, tmp_path): """Ensure rm_rf can remove directories with read-only files in them (#5524)""" from _pytest.pathlib import rm_rf @@ -337,14 +350,17 @@ class TestNumberedDir: fn.touch() - mode = os.stat(str(fn)).st_mode - os.chmod(str(fn), mode & ~stat.S_IWRITE) + self.chmod_r(fn) rm_rf(fn.parent) assert not fn.parent.is_dir() - def test_rmtree_with_read_only_directory(self, tmp_path): + def chmod_r(self, path): + mode = os.stat(str(path)).st_mode + os.chmod(str(path), mode & ~stat.S_IWRITE) + + def test_rm_rf_with_read_only_directory(self, tmp_path): """Ensure rm_rf can remove read-only directories (#5524)""" from _pytest.pathlib import rm_rf @@ -352,23 +368,37 @@ class TestNumberedDir: adir.mkdir() (adir / "foo.txt").touch() - mode = os.stat(str(adir)).st_mode - os.chmod(str(adir), mode & ~stat.S_IWRITE) + self.chmod_r(adir) rm_rf(adir) assert not adir.is_dir() - def test_cleanup_ignores_symlink(self, tmp_path): - the_symlink = tmp_path / (self.PREFIX + "current") - attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5")) - self._do_cleanup(tmp_path) + def test_on_rm_rf_error(self, tmp_path): + from _pytest.pathlib import on_rm_rf_error - def test_removal_accepts_lock(self, tmp_path): - folder = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX) - pathlib.create_cleanup_lock(folder) - pathlib.maybe_delete_a_numbered_dir(folder) - assert folder.is_dir() + adir = tmp_path / "dir" + adir.mkdir() + + fn = adir / "foo.txt" + fn.touch() + self.chmod_r(fn) + + # unknown exception + with pytest.warns(pytest.PytestWarning): + exc_info = (None, RuntimeError(), None) + on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) + assert fn.is_file() + + # unknown function + with pytest.warns(pytest.PytestWarning): + exc_info = (None, PermissionError(), None) + on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path) + assert fn.is_file() + + exc_info = (None, PermissionError(), None) + on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) + assert not fn.is_file() def attempt_symlink_to(path, to_path):