summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.pre-commit-config.yaml2
-rw-r--r--changelog/8152.bugfix.rst1
-rw-r--r--changelog/8249.bugfix.rst1
-rw-r--r--doc/en/announce/index.rst1
-rw-r--r--doc/en/announce/release-6.2.2.rst21
-rw-r--r--doc/en/changelog.rst12
-rw-r--r--doc/en/example/parametrize.rst4
-rw-r--r--doc/en/getting-started.rst4
-rw-r--r--doc/en/index.rst1
-rw-r--r--doc/en/reference.rst2
-rw-r--r--doc/en/usage.rst2
-rw-r--r--src/_pytest/fixtures.py5
-rw-r--r--src/_pytest/pytester.py8
-rw-r--r--src/_pytest/runner.py183
-rw-r--r--src/_pytest/threadexception.py7
-rw-r--r--testing/plugins_integration/requirements.txt2
-rw-r--r--testing/python/fixtures.py17
-rw-r--r--testing/test_runner.py44
18 files changed, 209 insertions, 108 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c8e19b283..9130a79a0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -47,7 +47,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.790
+ rev: v0.800
hooks:
- id: mypy
files: ^(src/|testing/)
diff --git a/changelog/8152.bugfix.rst b/changelog/8152.bugfix.rst
deleted file mode 100644
index d79a832de..000000000
--- a/changelog/8152.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed "(<Skipped instance>)" being shown as a skip reason in the verbose test summary line when the reason is empty.
diff --git a/changelog/8249.bugfix.rst b/changelog/8249.bugfix.rst
deleted file mode 100644
index aa084c757..000000000
--- a/changelog/8249.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``.
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index e7cac2a1c..a7656c5ee 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2
+ release-6.2.2
release-6.2.1
release-6.2.0
release-6.1.2
diff --git a/doc/en/announce/release-6.2.2.rst b/doc/en/announce/release-6.2.2.rst
new file mode 100644
index 000000000..c3999c538
--- /dev/null
+++ b/doc/en/announce/release-6.2.2.rst
@@ -0,0 +1,21 @@
+pytest-6.2.2
+=======================================
+
+pytest 6.2.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+ pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Adam Johnson
+* Bruno Oliveira
+* Chris NeJame
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index 6d66ad1d8..3e854f599 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -28,6 +28,18 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
+pytest 6.2.2 (2021-01-25)
+=========================
+
+Bug Fixes
+---------
+
+- `#8152 <https://github.com/pytest-dev/pytest/issues/8152>`_: Fixed "(<Skipped instance>)" being shown as a skip reason in the verbose test summary line when the reason is empty.
+
+
+- `#8249 <https://github.com/pytest-dev/pytest/issues/8249>`_: Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``.
+
+
pytest 6.2.1 (2020-12-15)
=========================
diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst
index a65ee5f2f..771c7e16f 100644
--- a/doc/en/example/parametrize.rst
+++ b/doc/en/example/parametrize.rst
@@ -637,13 +637,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
- collecting ... collected 14 items / 11 deselected / 3 selected
+ collecting ... collected 24 items / 21 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, 11 deselected, 1 xfailed in 0.12s ================
+ =============== 2 passed, 21 deselected, 1 xfailed in 0.12s ================
As the result:
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index 09410585d..28fd862cf 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
- pytest 6.2.1
+ pytest 6.2.2
.. _`simpletest`:
@@ -210,6 +210,8 @@ This is outlined below:
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0
2 failed in 0.12s
+Note that attributes added at class level are *class attributes*, so they will be shared between tests.
+
Request a unique temporary directory for functional tests
--------------------------------------------------------------
diff --git a/doc/en/index.rst b/doc/en/index.rst
index 58f6c1d86..7c4d9394d 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -11,6 +11,7 @@
pytest: helps you write better programs
=======================================
+.. module:: pytest
The ``pytest`` framework makes it easy to write small tests, yet
scales to support complex functional testing for applications and libraries.
diff --git a/doc/en/reference.rst b/doc/en/reference.rst
index 51c52b33a..bc6c5670a 100644
--- a/doc/en/reference.rst
+++ b/doc/en/reference.rst
@@ -3,8 +3,6 @@
API Reference
=============
-.. module:: pytest
-
This page contains the full reference to pytest's API.
.. contents::
diff --git a/doc/en/usage.rst b/doc/en/usage.rst
index fbd3333da..0a26182d4 100644
--- a/doc/en/usage.rst
+++ b/doc/en/usage.rst
@@ -497,7 +497,7 @@ The plugins are automatically enabled for pytest runs, unless the
``-p no:threadexception`` (for thread exceptions) options are given on the
command-line.
-The warnings may be silenced selectivly using the :ref:`pytest.mark.filterwarnings ref`
+The warnings may be silenced selectively using the :ref:`pytest.mark.filterwarnings ref`
mark. The warning categories are :class:`pytest.PytestUnraisableExceptionWarning` and
:class:`pytest.PytestUnhandledThreadExceptionWarning`.
diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py
index 43a40a864..269369642 100644
--- a/src/_pytest/fixtures.py
+++ b/src/_pytest/fixtures.py
@@ -372,6 +372,7 @@ def _fill_fixtures_impl(function: "Function") -> None:
fi = fm.getfixtureinfo(function.parent, function.obj, None)
function._fixtureinfo = fi
request = function._request = FixtureRequest(function, _ispytest=True)
+ fm.session._setupstate.prepare(function)
request._fillfixtures()
# Prune out funcargs for jstests.
newfuncargs = {}
@@ -543,8 +544,8 @@ class FixtureRequest:
self._addfinalizer(finalizer, scope=self.scope)
def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:
- item = self._getscopeitem(scope)
- item.addfinalizer(finalizer)
+ node = self._getscopeitem(scope)
+ node.addfinalizer(finalizer)
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
"""Apply a marker to a single test function invocation.
diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py
index 95b22b3b2..8ca21d1c5 100644
--- a/src/_pytest/pytester.py
+++ b/src/_pytest/pytester.py
@@ -777,7 +777,7 @@ class Pytester:
return ret
def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
- r"""Create new file(s) in the test directory.
+ r"""Create new text file(s) in the test directory.
:param str ext:
The extension the file(s) should use, including the dot, e.g. `.py`.
@@ -797,6 +797,12 @@ class Pytester:
pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
+ To create binary files, use :meth:`pathlib.Path.write_bytes` directly:
+
+ .. code-block:: python
+
+ filename = pytester.path.joinpath("foo.bin")
+ filename.write_bytes(b"...")
"""
return self._makefile(ext, args, kwargs)
diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py
index 844e41f80..ae76a2472 100644
--- a/src/_pytest/runner.py
+++ b/src/_pytest/runner.py
@@ -33,6 +33,7 @@ from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
from _pytest.outcomes import Exit
+from _pytest.outcomes import OutcomeException
from _pytest.outcomes import Skipped
from _pytest.outcomes import TEST_OUTCOME
@@ -103,7 +104,7 @@ def pytest_sessionstart(session: "Session") -> None:
def pytest_sessionfinish(session: "Session") -> None:
- session._setupstate.teardown_all()
+ session._setupstate.teardown_exact(None)
def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool:
@@ -175,7 +176,7 @@ def pytest_runtest_call(item: Item) -> None:
def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None:
_update_current_test_var(item, "teardown")
- item.session._setupstate.teardown_exact(item, nextitem)
+ item.session._setupstate.teardown_exact(nextitem)
_update_current_test_var(item, None)
@@ -401,88 +402,136 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
class SetupState:
- """Shared state for setting up/tearing down test items or collectors."""
+ """Shared state for setting up/tearing down test items or collectors
+ in a session.
- def __init__(self):
- self.stack: List[Node] = []
- self._finalizers: Dict[Node, List[Callable[[], object]]] = {}
+ Suppose we have a collection tree as follows:
- def addfinalizer(self, finalizer: Callable[[], object], colitem) -> None:
- """Attach a finalizer to the given colitem."""
- assert colitem and not isinstance(colitem, tuple)
- assert callable(finalizer)
- # assert colitem in self.stack # some unit tests don't setup stack :/
- self._finalizers.setdefault(colitem, []).append(finalizer)
+ <Session session>
+ <Module mod1>
+ <Function item1>
+ <Module mod2>
+ <Function item2>
- def _pop_and_teardown(self):
- colitem = self.stack.pop()
- self._teardown_with_finalization(colitem)
+ The SetupState maintains a stack. The stack starts out empty:
- def _callfinalizers(self, colitem) -> None:
- finalizers = self._finalizers.pop(colitem, None)
- exc = None
- while finalizers:
- fin = finalizers.pop()
- try:
- fin()
- except TEST_OUTCOME as e:
- # XXX Only first exception will be seen by user,
- # ideally all should be reported.
- if exc is None:
- exc = e
- if exc:
- raise exc
+ []
- def _teardown_with_finalization(self, colitem) -> None:
- self._callfinalizers(colitem)
- colitem.teardown()
- for colitem in self._finalizers:
- assert colitem in self.stack
+ During the setup phase of item1, prepare(item1) is called. What it does
+ is:
- def teardown_all(self) -> None:
- while self.stack:
- self._pop_and_teardown()
- for key in list(self._finalizers):
- self._teardown_with_finalization(key)
- assert not self._finalizers
+ push session to stack, run session.setup()
+ push mod1 to stack, run mod1.setup()
+ push item1 to stack, run item1.setup()
- def teardown_exact(self, item, nextitem) -> None:
- needed_collectors = nextitem and nextitem.listchain() or []
- self._teardown_towards(needed_collectors)
+ The stack is:
- def _teardown_towards(self, needed_collectors) -> None:
- exc = None
- while self.stack:
- if self.stack == needed_collectors[: len(self.stack)]:
- break
- try:
- self._pop_and_teardown()
- except TEST_OUTCOME as e:
- # XXX Only first exception will be seen by user,
- # ideally all should be reported.
- if exc is None:
- exc = e
- if exc:
- raise exc
+ [session, mod1, item1]
+
+ While the stack is in this shape, it is allowed to add finalizers to
+ each of session, mod1, item1 using addfinalizer().
+
+ During the teardown phase of item1, teardown_exact(item2) is called,
+ where item2 is the next item to item1. What it does is:
+
+ pop item1 from stack, run its teardowns
+ pop mod1 from stack, run its teardowns
+
+ mod1 was popped because it ended its purpose with item1. The stack is:
+
+ [session]
+
+ During the setup phase of item2, prepare(item2) is called. What it does
+ is:
- def prepare(self, colitem) -> None:
- """Setup objects along the collector chain to the test-method."""
+ push mod2 to stack, run mod2.setup()
+ push item2 to stack, run item2.setup()
- # Check if the last collection node has raised an error.
- for col in self.stack:
- if hasattr(col, "_prepare_exc"):
- exc = col._prepare_exc # type: ignore[attr-defined]
- raise exc
+ Stack:
- needed_collectors = colitem.listchain()
+ [session, mod2, item2]
+
+ During the teardown phase of item2, teardown_exact(None) is called,
+ because item2 is the last item. What it does is:
+
+ pop item2 from stack, run its teardowns
+ pop mod2 from stack, run its teardowns
+ pop session from stack, run its teardowns
+
+ Stack:
+
+ []
+
+ The end!
+ """
+
+ def __init__(self) -> None:
+ # The stack is in the dict insertion order.
+ self.stack: Dict[
+ Node,
+ Tuple[
+ # Node's finalizers.
+ List[Callable[[], object]],
+ # Node's exception, if its setup raised.
+ Optional[Union[OutcomeException, Exception]],
+ ],
+ ] = {}
+
+ def prepare(self, item: Item) -> None:
+ """Setup objects along the collector chain to the item."""
+ # If a collector fails its setup, fail its entire subtree of items.
+ # The setup is not retried for each item - the same exception is used.
+ for col, (finalizers, prepare_exc) in self.stack.items():
+ if prepare_exc:
+ raise prepare_exc
+
+ needed_collectors = item.listchain()
for col in needed_collectors[len(self.stack) :]:
- self.stack.append(col)
+ assert col not in self.stack
+ self.stack[col] = ([col.teardown], None)
try:
col.setup()
except TEST_OUTCOME as e:
- col._prepare_exc = e # type: ignore[attr-defined]
+ self.stack[col] = (self.stack[col][0], e)
raise e
+ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
+ """Attach a finalizer to the given node.
+
+ The node must be currently active in the stack.
+ """
+ assert node and not isinstance(node, tuple)
+ assert callable(finalizer)
+ assert node in self.stack, (node, self.stack)
+ self.stack[node][0].append(finalizer)
+
+ def teardown_exact(self, nextitem: Optional[Item]) -> None:
+ """Teardown the current stack up until reaching nodes that nextitem
+ also descends from.
+
+ When nextitem is None (meaning we're at the last item), the entire
+ stack is torn down.
+ """
+ needed_collectors = nextitem and nextitem.listchain() or []
+ exc = None
+ while self.stack:
+ if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
+ break
+ node, (finalizers, prepare_exc) = self.stack.popitem()
+ while finalizers:
+ fin = finalizers.pop()
+ try:
+ fin()
+ except TEST_OUTCOME as e:
+ # XXX Only first exception will be seen by user,
+ # ideally all should be reported.
+ if exc is None:
+ exc = e
+ if exc:
+ raise exc
+ if nextitem is None:
+ assert not self.stack
+
def collect_one_node(collector: Collector) -> CollectReport:
ihook = collector.ihook
diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py
index d084dc6e6..b250a5234 100644
--- a/src/_pytest/threadexception.py
+++ b/src/_pytest/threadexception.py
@@ -34,11 +34,10 @@ class catch_threading_exception:
"""
def __init__(self) -> None:
- # See https://github.com/python/typeshed/issues/4767 regarding the underscore.
- self.args: Optional["threading._ExceptHookArgs"] = None
- self._old_hook: Optional[Callable[["threading._ExceptHookArgs"], Any]] = None
+ self.args: Optional["threading.ExceptHookArgs"] = None
+ self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None
- def _hook(self, args: "threading._ExceptHookArgs") -> None:
+ def _hook(self, args: "threading.ExceptHookArgs") -> None:
self.args = args
def __enter__(self) -> "catch_threading_exception":
diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt
index ae5c9a93f..86c2a862c 100644
--- a/testing/plugins_integration/requirements.txt
+++ b/testing/plugins_integration/requirements.txt
@@ -2,7 +2,7 @@ anyio[curio,trio]==2.0.2
django==3.1.5
pytest-asyncio==0.14.0
pytest-bdd==4.0.2
-pytest-cov==2.10.1
+pytest-cov==2.11.1
pytest-django==4.1.0
pytest-flakes==4.0.3
pytest-html==3.1.1
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 12340e690..3d78ebf58 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -130,7 +130,8 @@ class TestFillFixtures:
pytester.copy_example()
item = pytester.getitem(Path("test_funcarg_basic.py"))
assert isinstance(item, Function)
- item._request._fillfixtures()
+ # Execute's item's setup, which fills fixtures.
+ item.session._setupstate.prepare(item)
del item.funcargs["request"]
assert len(get_public_names(item.funcargs)) == 2
assert item.funcargs["some"] == "test_func"
@@ -809,18 +810,25 @@ class TestRequestBasic:
item = pytester.getitem(
"""
import pytest
- values = [2]
+
@pytest.fixture
- def something(request): return 1
+ def something(request):
+ return 1
+
+ values = [2]
@pytest.fixture
def other(request):
return values.pop()
+
def test_func(something): pass
"""
)
assert isinstance(item, Function)
req = item._request
+ # Execute item's setup.
+ item.session._setupstate.prepare(item)
+
with pytest.raises(pytest.FixtureLookupError):
req.getfixturevalue("notexists")
val = req.getfixturevalue("something")
@@ -831,7 +839,6 @@ class TestRequestBasic:
assert val2 == 2
val2 = req.getfixturevalue("other") # see about caching
assert val2 == 2
- item._request._fillfixtures()
assert item.funcargs["something"] == 1
assert len(get_public_names(item.funcargs)) == 2
assert "request" in item.funcargs
@@ -856,7 +863,7 @@ class TestRequestBasic:
teardownlist = parent.obj.teardownlist
ss = item.session._setupstate
assert not teardownlist
- ss.teardown_exact(item, None)
+ ss.teardown_exact(None)
print(ss.stack)
assert teardownlist == [1]
diff --git a/testing/test_runner.py b/testing/test_runner.py
index 8ce0f6735..e3f286307 100644
--- a/testing/test_runner.py
+++ b/testing/test_runner.py
@@ -22,21 +22,22 @@ from _pytest.pytester import Pytester
class TestSetupState:
def test_setup(self, pytester: Pytester) -> None:
- ss = runner.SetupState()
item = pytester.getitem("def test_func(): pass")
+ ss = item.session._setupstate
values = [1]
ss.prepare(item)
- ss.addfinalizer(values.pop, colitem=item)
+ ss.addfinalizer(values.pop, item)
assert values
- ss._pop_and_teardown()
+ ss.teardown_exact(None)
assert not values
def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None:
item = pytester.getitem("def test_func(): pass")
- ss = runner.SetupState()
- ss.teardown_exact(item, None)
- ss.teardown_exact(item, None)
- ss.teardown_exact(item, None)
+ ss = item.session._setupstate
+ ss.prepare(item)
+ ss.teardown_exact(None)
+ ss.teardown_exact(None)
+ ss.teardown_exact(None)
def test_setup_fails_and_failure_is_cached(self, pytester: Pytester) -> None:
item = pytester.getitem(
@@ -46,9 +47,11 @@ class TestSetupState:
def test_func(): pass
"""
)
- ss = runner.SetupState()
- pytest.raises(ValueError, lambda: ss.prepare(item))
- pytest.raises(ValueError, lambda: ss.prepare(item))
+ ss = item.session._setupstate
+ with pytest.raises(ValueError):
+ ss.prepare(item)
+ with pytest.raises(ValueError):
+ ss.prepare(item)
def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None:
r = []
@@ -63,12 +66,13 @@ class TestSetupState:
r.append("fin3")
item = pytester.getitem("def test_func(): pass")
- ss = runner.SetupState()
+ ss = item.session._setupstate
+ ss.prepare(item)
ss.addfinalizer(fin1, item)
ss.addfinalizer(fin2, item)
ss.addfinalizer(fin3, item)
with pytest.raises(Exception) as err:
- ss._callfinalizers(item)
+ ss.teardown_exact(None)
assert err.value.args == ("oops",)
assert r == ["fin3", "fin1"]
@@ -82,11 +86,12 @@ class TestSetupState:
raise Exception("oops2")
item = pytester.getitem("def test_func(): pass")
- ss = runner.SetupState()
+ ss = item.session._setupstate
+ ss.prepare(item)
ss.addfinalizer(fin1, item)
ss.addfinalizer(fin2, item)
with pytest.raises(Exception) as err:
- ss._callfinalizers(item)
+ ss.teardown_exact(None)
assert err.value.args == ("oops2",)
def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None:
@@ -99,13 +104,14 @@ class TestSetupState:
module_teardown.append("fin_module")
item = pytester.getitem("def test_func(): pass")
- ss = runner.SetupState()
- ss.addfinalizer(fin_module, item.listchain()[-2])
- ss.addfinalizer(fin_func, item)
+ mod = item.listchain()[-2]
+ ss = item.session._setupstate
ss.prepare(item)
+ ss.addfinalizer(fin_module, mod)
+ ss.addfinalizer(fin_func, item)
with pytest.raises(Exception, match="oops1"):
- ss.teardown_exact(item, None)
- assert module_teardown
+ ss.teardown_exact(None)
+ assert module_teardown == ["fin_module"]
class BaseFunctionalTests: