Imported Upstream version 6.2.2 upstream/6.2.2
authorDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 18 Mar 2021 02:33:46 +0000 (11:33 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 18 Mar 2021 02:33:46 +0000 (11:33 +0900)
32 files changed:
AUTHORS
doc/en/announce/index.rst
doc/en/announce/release-6.2.2.rst [new file with mode: 0644]
doc/en/changelog.rst
doc/en/example/fixtures/fixture_availability.svg [new file with mode: 0644]
doc/en/example/fixtures/fixture_availability_plugins.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order.py [deleted file]
doc/en/example/fixtures/test_fixtures_order_autouse.py [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_autouse.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_dependencies.py [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_dependencies.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_scope.py [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_order_scope.svg [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_request_different_scope.py [new file with mode: 0644]
doc/en/example/fixtures/test_fixtures_request_different_scope.svg [new file with mode: 0644]
doc/en/example/parametrize.rst
doc/en/fixture.rst
doc/en/getting-started.rst
doc/en/index.rst
doc/en/reference.rst
setup.cfg
src/_pytest/faulthandler.py
src/_pytest/outcomes.py
src/_pytest/terminal.py
testing/test_faulthandler.py
testing/test_terminal.py

diff --git a/AUTHORS b/AUTHORS
index 72391122eb577ffd5b316d2a5aa699d138305264..b35bebf7af35008eb94424ff3841605c9e347059 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -21,6 +21,7 @@ Anders Hovmöller
 Andras Mitzki
 Andras Tim
 Andrea Cimatoribus
+Andreas Motl
 Andreas Zeidler
 Andrey Paramonov
 Andrzej Klajnert
@@ -56,6 +57,7 @@ Charles Cloud
 Charles Machalow
 Charnjit SiNGH (CCSJ)
 Chris Lamb
+Chris NeJame
 Christian Boelsen
 Christian Fetzer
 Christian Neumüller
index e7cac2a1c41c5ab558d6059c50d50b74874a3589..a7656c5ee267c8b9a00caa7376b75006c5f4ac08 100644 (file)
@@ -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 (file)
index 0000000..c3999c5
--- /dev/null
@@ -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
index 6d66ad1d8dc8b544655d22687a62001e49840434..3e854f59971187b4e85f440a41a9536c759c969d 100644 (file)
@@ -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/fixtures/fixture_availability.svg b/doc/en/example/fixtures/fixture_availability.svg
new file mode 100644 (file)
index 0000000..3ca2844
--- /dev/null
@@ -0,0 +1,132 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="572" height="542">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class, circle.module, circle.package {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class, text.module, text.package {
+            fill: #0e84b5;
+        }
+        line, path {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+    </style>
+
+    <!-- main scope -->
+    <circle class="package" r="270" cx="286" cy="271" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 26,271 A 260 260 0 0 1 546 271" id="testp"/>
+    </defs>
+    <text class="package">
+        <textPath href="#testp" startOffset="50%">tests</textPath>
+    </text>
+
+    <!-- subpackage -->
+    <circle class="package" r="140" cx="186" cy="271" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 56,271 A 130 130 0 0 1 316 271" id="subpackage"/>
+    </defs>
+    <text class="package">
+        <textPath href="#subpackage" startOffset="50%">subpackage</textPath>
+    </text>
+
+    <!-- test_subpackage.py -->
+    <circle class="module" r="90" cx="186" cy="311" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 106,311 A 80 80 0 0 1 266 311" id="testSubpackage"/>
+    </defs>
+    <text class="module">
+        <textPath href="#testSubpackage" startOffset="50%">test_subpackage.py</textPath>
+    </text>
+    <!-- innermost -->
+    <line x1="186" x2="186" y1="271" y2="351"/>
+    <!-- mid -->
+    <path d="M 186 351 L 136 351 L 106 331 L 106 196" />
+    <!-- order -->
+    <path d="M 186 351 L 256 351 L 316 291 L 316 136" />
+    <!-- top -->
+    <path d="M 186 351 L 186 391 L 231 436 L 331 436" />
+    <ellipse class="fixture" rx="50" ry="25" cx="186" cy="271" />
+    <text x="186" y="271">innermost</text>
+    <rect class="test" width="110" height="50" x="131" y="326" />
+    <text x="186" y="351">test_order</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="126" cy="196" />
+    <text x="126" y="196">mid</text>
+    <!-- scope order number -->
+    <mask id="testSubpackageOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="90" cx="186" cy="311" />
+    </mask>
+    <circle class="module" r="15" cx="96" cy="311" mask="url(#testSubpackageOrderMask)"/>
+    <text class="module" x="96" y="311">1</text>
+     <!-- scope order number -->
+    <mask id="subpackageOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="140" cx="186" cy="271" />
+    </mask>
+    <circle class="module" r="15" cx="46" cy="271" mask="url(#subpackageOrderMask)"/>
+    <text class="module" x="46" y="271">2</text>
+    <!-- scope order number -->
+    <mask id="testsOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="270" cx="286" cy="271" />
+    </mask>
+    <circle class="module" r="15" cx="16" cy="271" mask="url(#testsOrderMask)"/>
+    <text class="module" x="16" y="271">3</text>
+
+    <!-- test_top.py -->
+    <circle class="module" r="85" cx="441" cy="271" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 366,271 A 75 75 0 0 1 516 271" id="testTop"/>
+    </defs>
+    <text class="module">
+        <textPath href="#testTop" startOffset="50%">test_top.py</textPath>
+    </text>
+    <!-- innermost -->
+    <line x1="441" x2="441" y1="306" y2="236"/>
+    <!-- order -->
+    <path d="M 441 306 L 376 306 L 346 276 L 346 136" />
+    <!-- top -->
+    <path d="M 441 306 L 441 411 L 411 436 L 331 436" />
+    <ellipse class="fixture" rx="50" ry="25" cx="441" cy="236" />
+    <text x="441" y="236">innermost</text>
+    <rect class="test" width="110" height="50" x="386" y="281" />
+    <text x="441" y="306">test_order</text>
+    <!-- scope order number -->
+    <mask id="testTopOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="85" cx="441" cy="271" />
+    </mask>
+    <circle class="module" r="15" cx="526" cy="271" mask="url(#testTopOrderMask)"/>
+    <text class="module" x="526" y="271">1</text>
+    <!-- scope order number -->
+    <circle class="module" r="15" cx="556" cy="271" mask="url(#testsOrderMask)"/>
+    <text class="module" x="556" y="271">2</text>
+
+    <ellipse class="fixture" rx="50" ry="25" cx="331" cy="436" />
+    <text x="331" y="436">top</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="331" cy="136" />
+    <text x="331" y="136">order</text>
+</svg>
diff --git a/doc/en/example/fixtures/fixture_availability_plugins.svg b/doc/en/example/fixtures/fixture_availability_plugins.svg
new file mode 100644 (file)
index 0000000..88e32d9
--- /dev/null
@@ -0,0 +1,142 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="587" height="382">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+            alignment-baseline: center;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class, circle.module, circle.package, circle.plugin {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class, text.module, text.package, text.plugin {
+            fill: #0e84b5;
+        }
+        line, path {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+    </style>
+
+    <!-- plugin_a.py scope -->
+    <circle class="plugin" r="85" cx="486" cy="86" />
+    <!-- plugin name -->
+    <defs>
+        <path d="M 411,86 A 75 75 0 0 1 561 86" id="pluginA"/>
+    </defs>
+    <text class="plugin">
+        <textPath href="#pluginA" startOffset="50%">plugin_a</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="pluginAOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="85" cx="486" cy="86" />
+    </mask>
+    <circle class="module" r="15" cx="571" cy="86" mask="url(#pluginAOrderMask)"/>
+    <text class="module" x="571" y="86">4</text>
+
+    <!-- plugin_b.py scope -->
+    <circle class="plugin" r="85" cx="486" cy="296" />
+    <!-- plugin name -->
+    <defs>
+        <path d="M 411,296 A 75 75 0 0 1 561 296" id="pluginB"/>
+    </defs>
+    <text class="plugin">
+        <textPath href="#pluginB" startOffset="50%">plugin_b</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="pluginBOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="85" cx="486" cy="296" />
+    </mask>
+    <circle class="module" r="15" cx="571" cy="296" mask="url(#pluginBOrderMask)"/>
+    <text class="module" x="571" y="296">4</text>
+
+    <!-- main scope -->
+    <circle class="package" r="190" cx="191" cy="191" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 11,191 A 180 180 0 0 1 371 191" id="testp"/>
+    </defs>
+    <text class="package">
+        <textPath href="#testp" startOffset="50%">tests</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="mainOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="190" cx="191" cy="191" />
+    </mask>
+    <circle class="module" r="15" cx="381" cy="191" mask="url(#mainOrderMask)"/>
+    <text class="module" x="381" y="191">3</text>
+
+    <!-- subpackage -->
+    <circle class="package" r="140" cx="191" cy="231" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 61,231 A 130 130 0 0 1 321 231" id="subpackage"/>
+    </defs>
+    <text class="package">
+        <textPath href="#subpackage" startOffset="50%">subpackage</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="subpackageOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="140" cx="191" cy="231" />
+    </mask>
+    <circle class="module" r="15" cx="331" cy="231" mask="url(#subpackageOrderMask)"/>
+    <text class="module" x="331" y="231">2</text>
+
+    <!-- test_subpackage.py -->
+    <circle class="module" r="90" cx="191" cy="271" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 111,271 A 80 80 0 0 1 271 271" id="testSubpackage"/>
+    </defs>
+    <text class="module">
+        <textPath href="#testSubpackage" startOffset="50%">test_subpackage.py</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="testSubpackageOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="90" cx="191" cy="271" />
+    </mask>
+    <circle class="module" r="15" cx="281" cy="271" mask="url(#testSubpackageOrderMask)"/>
+    <text class="module" x="281" y="271">1</text>
+
+    <!-- innermost -->
+    <line x1="191" x2="191" y1="231" y2="311"/>
+    <!-- mid -->
+    <path d="M 191 306 L 101 306 L 91 296 L 91 156 L 101 146 L 191 146" />
+    <!-- order -->
+    <path d="M 191 316 L 91 316 L 81 306 L 81 61 L 91 51 L 191 51" />
+    <!-- a_fix -->
+    <path d="M 191 306 L 291 306 L 301 296 L 301 96 L 311 86 L 486 86" />
+    <!-- b_fix -->
+    <path d="M 191 316 L 316 316 L 336 296 L 486 296" />
+    <ellipse class="fixture" rx="50" ry="25" cx="191" cy="231" />
+    <text x="191" y="231">inner</text>
+    <rect class="test" width="110" height="50" x="136" y="286" />
+    <text x="191" y="311">test_order</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="191" cy="146" />
+    <text x="191" y="146">mid</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="191" cy="51" />
+    <text x="191" y="51">order</text>
+
+    <ellipse class="fixture" rx="50" ry="25" cx="486" cy="86" />
+    <text x="486" y="86">a_fix</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="486" cy="296" />
+    <text x="486" y="296">b_fix</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order.py b/doc/en/example/fixtures/test_fixtures_order.py
deleted file mode 100644 (file)
index 97b3e80..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-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/example/fixtures/test_fixtures_order_autouse.py b/doc/en/example/fixtures/test_fixtures_order_autouse.py
new file mode 100644 (file)
index 0000000..ec282ab
--- /dev/null
@@ -0,0 +1,45 @@
+import pytest
+
+
+@pytest.fixture
+def order():
+    return []
+
+
+@pytest.fixture
+def a(order):
+    order.append("a")
+
+
+@pytest.fixture
+def b(a, order):
+    order.append("b")
+
+
+@pytest.fixture(autouse=True)
+def c(b, order):
+    order.append("c")
+
+
+@pytest.fixture
+def d(b, order):
+    order.append("d")
+
+
+@pytest.fixture
+def e(d, order):
+    order.append("e")
+
+
+@pytest.fixture
+def f(e, order):
+    order.append("f")
+
+
+@pytest.fixture
+def g(f, c, order):
+    order.append("g")
+
+
+def test_order_and_g(g, order):
+    assert order == ["a", "b", "c", "d", "e", "f", "g"]
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.svg b/doc/en/example/fixtures/test_fixtures_order_autouse.svg
new file mode 100644 (file)
index 0000000..36362e4
--- /dev/null
@@ -0,0 +1,64 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="252" height="682">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        path, line {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+        rect.autouse {
+            fill: #ca7f3d;
+        }
+    </style>
+    <path d="M126,586 L26,506 L26,236" />
+    <path d="M226,446 L226,236 L126,166" />
+    <line x1="126" x2="126" y1="656" y2="516" />
+    <line x1="126" x2="226" y1="516" y2="446" />
+    <line x1="226" x2="126" y1="446" y2="376" />
+    <line x1="126" x2="126" y1="376" y2="166" />
+    <line x1="26" x2="126" y1="236" y2="166" />
+    <line x1="126" x2="126" y1="166" y2="26" />
+    <line x1="126" x2="126" y1="96" y2="26" />
+    <rect class="autouse" width="251" height="40" x="0" y="286" />
+    <text x="126" y="306">autouse</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="126" cy="26" />
+    <text x="126" y="26">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="96" />
+    <text x="126" y="96">a</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="166" />
+    <text x="126" y="166">b</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="26" cy="236" />
+    <text x="26" y="236">c</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="376" />
+    <text x="126" y="376">d</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="226" cy="446" />
+    <text x="226" y="446">e</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="516" />
+    <text x="126" y="516">f</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="586" />
+    <text x="126" y="586">g</text>
+    <rect class="test" width="110" height="50" x="71" y="631" />
+    <text x="126" y="656">test_order</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py
new file mode 100644 (file)
index 0000000..de0c264
--- /dev/null
@@ -0,0 +1,31 @@
+import pytest
+
+
+@pytest.fixture(scope="class")
+def order():
+    return []
+
+
+@pytest.fixture(scope="class", autouse=True)
+def c1(order):
+    order.append("c1")
+
+
+@pytest.fixture(scope="class")
+def c2(order):
+    order.append("c2")
+
+
+@pytest.fixture(scope="class")
+def c3(order, c1):
+    order.append("c3")
+
+
+class TestClassWithC1Request:
+    def test_order(self, order, c1, c3):
+        assert order == ["c1", "c3"]
+
+
+class TestClassWithoutC1Request:
+    def test_order(self, order, c2):
+        assert order == ["c1", "c2"]
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg
new file mode 100644 (file)
index 0000000..9f2180f
--- /dev/null
@@ -0,0 +1,76 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="862" height="402">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        line {
+            stroke: black;
+            stroke-width: 2;
+        }
+        rect.autouse {
+            fill: #ca7f3d;
+        }
+    </style>
+
+    <!-- TestWithC1Request -->
+    <circle class="class" r="200" cx="221" cy="201" />
+    <line x1="221" x2="221" y1="61" y2="316"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="221" cy="61" />
+    <text x="221" y="61">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="221" cy="131" />
+    <text x="221" y="131">c1</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="221" cy="271" />
+    <text x="221" y="271">c3</text>
+    <rect class="test" width="110" height="50" x="166" y="316" />
+    <text x="221" y="341">test_order</text>
+    <!-- scope name -->
+    <defs>
+        <path d="M31,201 A 190 190 0 0 1 411 201" id="testClassWith"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testClassWith" startOffset="50%">TestWithC1Request</textPath>
+    </text>
+
+    <!-- TestWithoutC1Request -->
+    <circle class="class" r="200" cx="641" cy="201" />
+    <line x1="641" x2="641" y1="61" y2="316"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="641" cy="61" />
+    <text x="641" y="61">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="641" cy="131" />
+    <text x="641" y="131">c1</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="641" cy="271" />
+    <text x="641" y="271">c2</text>
+    <rect class="test" width="110" height="50" x="586" y="316" />
+    <text x="641" y="341">test_order</text>
+    <!-- scope name -->
+    <defs>
+        <path d="M451,201 A 190 190 0 0 1 831 201" id="testClassWithout"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testClassWithout" startOffset="50%">TestWithoutC1Request</textPath>
+    </text>
+
+    <rect class="autouse" width="862" height="40" x="1" y="181" />
+    <rect width="10" height="100" class="autouse" x="426" y="151" />
+    <text x="431" y="201">autouse</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py
new file mode 100644 (file)
index 0000000..ba01ad3
--- /dev/null
@@ -0,0 +1,36 @@
+import pytest
+
+
+@pytest.fixture
+def order():
+    return []
+
+
+@pytest.fixture
+def c1(order):
+    order.append("c1")
+
+
+@pytest.fixture
+def c2(order):
+    order.append("c2")
+
+
+class TestClassWithAutouse:
+    @pytest.fixture(autouse=True)
+    def c3(self, order, c2):
+        order.append("c3")
+
+    def test_req(self, order, c1):
+        assert order == ["c2", "c3", "c1"]
+
+    def test_no_req(self, order):
+        assert order == ["c2", "c3"]
+
+
+class TestClassWithoutAutouse:
+    def test_req(self, order, c1):
+        assert order == ["c1"]
+
+    def test_no_req(self, order):
+        assert order == []
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg
new file mode 100644 (file)
index 0000000..ac62ae4
--- /dev/null
@@ -0,0 +1,100 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="862" height="502">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        line {
+            stroke: black;
+            stroke-width: 2;
+        }
+        rect.autouse {
+            fill: #ca7f3d;
+        }
+    </style>
+
+    <!-- TestWithAutouse -->
+    <circle class="class" r="250" cx="251" cy="251" />
+    <!-- scope name -->
+    <defs>
+        <path d="M11,251 A 240 240 0 0 1 491 251" id="testClassWith"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testClassWith" startOffset="50%">TestWithAutouse</textPath>
+    </text>
+    <mask id="autouseScope">
+        <circle fill="white" r="249" cx="251" cy="251" />
+    </mask>
+
+    <!-- TestWithAutouse.test_req -->
+    <line x1="176" x2="176" y1="76" y2="426"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="176" cy="76" />
+    <text x="176" y="76">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="176" cy="146" />
+    <text x="176" y="146">c2</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="176" cy="216" />
+    <text x="176" y="216">c3</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="176" cy="356" />
+    <text x="176" y="356">c1</text>
+    <rect class="test" width="100" height="50" x="126" y="401" />
+    <text x="176" y="426">test_req</text>
+
+    <!-- TestWithAutouse.test_no_req -->
+    <line x1="326" x2="326" y1="76" y2="346"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="326" cy="76" />
+    <text x="326" y="76">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="326" cy="146" />
+    <text x="326" y="146">c2</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="326" cy="216" />
+    <text x="326" y="216">c3</text>
+    <rect class="test" width="120" height="50" x="266" y="331" />
+    <text x="326" y="356">test_no_req</text>
+
+    <rect class="autouse" width="500" height="40" x="1" y="266" mask="url(#autouseScope)"/>
+    <text x="261" y="286">autouse</text>
+
+    <!-- TestWithoutAutouse -->
+    <circle class="class" r="170" cx="691" cy="251" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 531,251 A 160 160 0 0 1 851 251" id="testClassWithout"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testClassWithout" startOffset="50%">TestWithoutAutouse</textPath>
+    </text>
+
+    <!-- TestWithoutAutouse.test_req -->
+    <line x1="616" x2="616" y1="181" y2="321"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="616" cy="181" />
+    <text x="616" y="181">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="616" cy="251" />
+    <text x="616" y="251">c1</text>
+    <rect class="test" width="100" height="50" x="566" y="296" />
+    <text x="616" y="321">test_req</text>
+
+    <!-- TestWithoutAutouse.test_no_req -->
+    <line x1="766" x2="766" y1="181" y2="251"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="766" cy="181" />
+    <text x="766" y="181">order</text>
+    <rect class="test" width="120" height="50" x="706" y="226" />
+    <text x="766" y="251">test_no_req</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py
new file mode 100644 (file)
index 0000000..b3512c2
--- /dev/null
@@ -0,0 +1,45 @@
+import pytest
+
+
+@pytest.fixture
+def order():
+    return []
+
+
+@pytest.fixture
+def a(order):
+    order.append("a")
+
+
+@pytest.fixture
+def b(a, order):
+    order.append("b")
+
+
+@pytest.fixture
+def c(a, b, order):
+    order.append("c")
+
+
+@pytest.fixture
+def d(c, b, order):
+    order.append("d")
+
+
+@pytest.fixture
+def e(d, b, order):
+    order.append("e")
+
+
+@pytest.fixture
+def f(e, order):
+    order.append("f")
+
+
+@pytest.fixture
+def g(f, c, order):
+    order.append("g")
+
+
+def test_order(g, order):
+    assert order == ["a", "b", "c", "d", "e", "f", "g"]
diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.svg b/doc/en/example/fixtures/test_fixtures_order_dependencies.svg
new file mode 100644 (file)
index 0000000..24418e6
--- /dev/null
@@ -0,0 +1,60 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="252" height="612">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        path, line {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+    </style>
+    <path d="M126,516 L26,436 L26,236" />
+    <path d="M226,376 L226,236 L126,166" />
+    <line x1="126" x2="126" y1="586" y2="446" />
+    <line x1="126" x2="226" y1="446" y2="376" />
+    <line x1="226" x2="126" y1="376" y2="306" />
+    <line x1="126" x2="26" y1="306" y2="236" />
+    <line x1="126" x2="126" y1="306" y2="166" />
+    <line x1="26" x2="126" y1="236" y2="166" />
+    <line x1="126" x2="126" y1="166" y2="26" />
+    <line x1="126" x2="126" y1="96" y2="26" />
+    <ellipse class="fixture" rx="50" ry="25" cx="126" cy="26" />
+    <text x="126" y="26">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="96" />
+    <text x="126" y="96">a</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="166" />
+    <text x="126" y="166">b</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="26" cy="236" />
+    <text x="26" y="236">c</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="306" />
+    <text x="126" y="306">d</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="226" cy="376" />
+    <text x="226" y="376">e</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="446" />
+    <text x="126" y="446">f</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="516" />
+    <text x="126" y="516">g</text>
+    <rect class="test" width="110" height="50" x="71" y="561" />
+    <text x="126" y="586">test_order</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg b/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg
new file mode 100644 (file)
index 0000000..bbe7ad2
--- /dev/null
@@ -0,0 +1,51 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="112" height="612">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        path, line {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+    </style>
+    <line x1="56" x2="56" y1="611" y2="26" />
+    <ellipse class="fixture" rx="50" ry="25" cx="56" cy="26" />
+    <text x="56" y="26">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="96" />
+    <text x="56" y="96">a</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="166" />
+    <text x="56" y="166">b</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="236" />
+    <text x="56" y="236">c</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="306" />
+    <text x="56" y="306">d</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="376" />
+    <text x="56" y="376">e</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="446" />
+    <text x="56" y="446">f</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="516" />
+    <text x="56" y="516">g</text>
+    <rect class="test" width="110" height="50" x="1" y="561" />
+    <text x="56" y="586">test_order</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg b/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg
new file mode 100644 (file)
index 0000000..150724f
--- /dev/null
@@ -0,0 +1,60 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="252" height="542">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        path, line {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+    </style>
+    <path d="M126,446 L26,376 L26,236" />
+    <path d="M226,306 L126,236 L126,166" />
+    <line x1="126" x2="126" y1="516" y2="446" />
+    <line x1="226" x2="226" y1="376" y2="306" />
+    <line x1="226" x2="226" y1="306" y2="236" />
+    <line x1="226" x2="126" y1="236" y2="166" />
+    <line x1="126" x2="226" y1="446" y2="376" />
+    <line x1="26" x2="126" y1="236" y2="166" />
+    <line x1="126" x2="126" y1="166" y2="96" />
+    <line x1="126" x2="126" y1="96" y2="26" />
+    <ellipse class="fixture" rx="50" ry="25" cx="126" cy="26" />
+    <text x="126" y="26">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="96" />
+    <text x="126" y="96">a</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="166" />
+    <text x="126" y="166">b</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="26" cy="236" />
+    <text x="26" y="236">c</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="226" cy="236" />
+    <text x="226" y="236">d</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="226" cy="306" />
+    <text x="226" y="306">e</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="226" cy="376" />
+    <text x="226" y="376">f</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="126" cy="446" />
+    <text x="126" y="446">g</text>
+    <rect class="test" width="110" height="50" x="71" y="491" />
+    <text x="126" y="516">test_order</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.py b/doc/en/example/fixtures/test_fixtures_order_scope.py
new file mode 100644 (file)
index 0000000..5d9487c
--- /dev/null
@@ -0,0 +1,36 @@
+import pytest
+
+
+@pytest.fixture(scope="session")
+def order():
+    return []
+
+
+@pytest.fixture
+def func(order):
+    order.append("function")
+
+
+@pytest.fixture(scope="class")
+def cls(order):
+    order.append("class")
+
+
+@pytest.fixture(scope="module")
+def mod(order):
+    order.append("module")
+
+
+@pytest.fixture(scope="package")
+def pack(order):
+    order.append("package")
+
+
+@pytest.fixture(scope="session")
+def sess(order):
+    order.append("session")
+
+
+class TestClass:
+    def test_order(self, func, cls, mod, pack, sess, order):
+        assert order == ["session", "package", "module", "class", "function"]
diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.svg b/doc/en/example/fixtures/test_fixtures_order_scope.svg
new file mode 100644 (file)
index 0000000..ebaf7e4
--- /dev/null
@@ -0,0 +1,55 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="262" height="537">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        line {
+            stroke: black;
+            stroke-width: 2;
+        }
+    </style>
+    <!-- TestClass -->
+    <circle class="class" r="130" cx="131" cy="406" />
+    <line x1="131" x2="131" y1="21" y2="446"/>
+    <ellipse class="fixture" rx="50" ry="25" cx="131" cy="26" />
+    <text x="131" y="26">order</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="131" cy="96" />
+    <text x="131" y="96">sess</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="131" cy="166" />
+    <text x="131" y="166">pack</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="131" cy="236" />
+    <text x="131" y="236">mod</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="131" cy="306" />
+    <text x="131" y="306">cls</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="131" cy="376" />
+    <text x="131" y="376">func</text>
+    <rect class="test" width="110" height="50" x="76" y="421" />
+    <text x="131" y="446">test_order</text>
+    <!-- scope name -->
+    <defs>
+        <path d="M131,526 A 120 120 0 0 1 136 286" id="testClass"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testClass" startOffset="50%">TestClass</textPath>
+    </text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.py b/doc/en/example/fixtures/test_fixtures_request_different_scope.py
new file mode 100644 (file)
index 0000000..00e2e46
--- /dev/null
@@ -0,0 +1,29 @@
+import pytest
+
+
+@pytest.fixture
+def order():
+    return []
+
+
+@pytest.fixture
+def outer(order, inner):
+    order.append("outer")
+
+
+class TestOne:
+    @pytest.fixture
+    def inner(self, order):
+        order.append("one")
+
+    def test_order(self, order, outer):
+        assert order == ["one", "outer"]
+
+
+class TestTwo:
+    @pytest.fixture
+    def inner(self, order):
+        order.append("two")
+
+    def test_order(self, order, outer):
+        assert order == ["two", "outer"]
diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.svg b/doc/en/example/fixtures/test_fixtures_request_different_scope.svg
new file mode 100644 (file)
index 0000000..ad98469
--- /dev/null
@@ -0,0 +1,115 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="562" height="532">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        circle.module {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class, text.module {
+            fill: #0e84b5;
+        }
+        line, path {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+    </style>
+    <!-- main scope -->
+    <circle class="module" r="265" cx="281" cy="266" />
+    <!-- scope name -->
+    <defs>
+        <path d="M 26,266 A 255 255 0 0 1 536 266" id="testModule"/>
+    </defs>
+    <text class="module">
+        <textPath href="#testModule" startOffset="50%">test_fixtures_request_different_scope.py</textPath>
+    </text>
+
+    <!-- TestOne -->
+    <circle class="class" r="100" cx="141" cy="266" />
+    <!-- inner -->
+    <line x1="141" x2="141" y1="231" y2="301"/>
+    <!-- order -->
+    <path d="M 141 296 L 201 296 L 211 286 L 211 146 L 221 136 L 281 136" />
+    <!-- outer -->
+    <path d="M 141 306 L 201 306 L 211 316 L 211 386 L 221 396 L 281 396" />
+    <ellipse class="fixture" rx="50" ry="25" cx="141" cy="231" />
+    <text x="141" y="231">inner</text>
+    <rect class="test" width="110" height="50" x="86" y="276" />
+    <text x="141" y="301">test_order</text>
+    <!-- scope name -->
+    <defs>
+        <path d="M 51,266 A 90 90 0 0 1 231 266" id="testOne"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testOne" startOffset="50%">TestOne</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="testOneOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="100" cx="141" cy="266" />
+    </mask>
+    <circle class="module" r="15" cx="41" cy="266" mask="url(#testOneOrderMask)"/>
+    <text class="module" x="41" y="266">1</text>
+    <!-- scope order number -->
+    <mask id="testMainOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="265" cx="281" cy="266" />
+    </mask>
+    <circle class="module" r="15" cx="16" cy="266" mask="url(#testMainOrderMask)"/>
+    <text class="module" x="16" y="266">2</text>
+
+    <!-- TestTwo -->
+    <circle class="class" r="100" cx="421" cy="266" />
+    <!-- inner -->
+    <line x1="421" x2="421" y1="231" y2="301"/>
+    <!-- order -->
+    <path d="M 421 296 L 361 296 L 351 286 L 351 146 L 341 136 L 281 136" />
+    <!-- outer -->
+    <path d="M 421 306 L 361 306 L 351 316 L 351 386 L 341 396 L 281 396" />
+    <ellipse class="fixture" rx="50" ry="25" cx="421" cy="231" />
+    <text x="421" y="231">inner</text>
+    <rect class="test" width="110" height="50" x="366" y="276" />
+    <text x="421" y="301">test_order</text>
+    <!-- scope name -->
+    <defs>
+        <path d="M 331,266 A 90 90 0 0 1 511 266" id="testTwo"/>
+    </defs>
+    <text class="class">
+        <textPath href="#testTwo" startOffset="50%">TestTwo</textPath>
+    </text>
+    <!-- scope order number -->
+    <mask id="testTwoOrderMask">
+        <rect x="0" y="0" width="100%" height="100%" fill="white"/>
+        <circle fill="black" stroke="white" stroke-width="2" r="100" cx="421" cy="266" />
+    </mask>
+    <circle class="module" r="15" cx="521" cy="266" mask="url(#testTwoOrderMask)"/>
+    <text class="module" x="521" y="266">1</text>
+    <!-- scope order number -->
+    <circle class="module" r="15" cx="546" cy="266" mask="url(#testMainOrderMask)"/>
+    <text class="module" x="546" y="266">2</text>
+
+    <ellipse class="fixture" rx="50" ry="25" cx="281" cy="396" />
+    <text x="281" y="396">outer</text>
+    <ellipse class="fixture" rx="50" ry="25" cx="281" cy="136" />
+    <text x="281" y="136">order</text>
+</svg>
index 6e2f53984ee5b1e7defcf23293d8b5a627c5c087..1ab89f2956f10ffe75e6c491206c681562e18b06 100644 (file)
@@ -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:
 
index 963fc32e6b07b2fd70e2d6d2d53aa49143184ba6..c74984563ab00426b83799b859ec33470f8beafb 100644 (file)
@@ -12,6 +12,8 @@ pytest fixtures: explicit, modular, scalable
 .. _`xUnit`: https://en.wikipedia.org/wiki/XUnit
 .. _`Software test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software
 .. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
+.. _`Transaction`: https://en.wikipedia.org/wiki/Transaction_processing
+.. _`linearizable`: https://en.wikipedia.org/wiki/Linearizability
 
 `Software test fixtures`_ initialize test functions.  They provide a
 fixed baseline so that tests execute reliably and produce consistent,
@@ -35,6 +37,10 @@ style of setup/teardown functions:
   to configuration and component options, or to re-use fixtures
   across function, class, module or whole test session scopes.
 
+* teardown logic can be easily, and safely managed, no matter how many fixtures
+  are used, without the need to carefully handle errors by hand or micromanage
+  the order that cleanup steps are added.
+
 In addition, pytest continues to support :ref:`xunitsetup`.  You can mix
 both styles, moving incrementally from classic to new style, as you
 prefer.  You can also start out from existing :ref:`unittest.TestCase
@@ -115,32 +121,529 @@ for reference:
 .. _`@pytest.fixture`:
 .. _`pytest.fixture`:
 
-Fixtures as Function arguments
------------------------------------------
+What fixtures are
+-----------------
+
+Before we dive into what fixtures are, let's first look at what a test is.
+
+In the simplest terms, a test is meant to look at the result of a particular
+behavior, and make sure that result aligns with what you would expect.
+Behavior is not something that can be empirically measured, which is why writing
+tests can be challenging.
+
+"Behavior" is the way in which some system **acts in response** to a particular
+situation and/or stimuli. But exactly *how* or *why* something is done is not
+quite as important as *what* was done.
+
+You can think of a test as being broken down into four steps:
+
+1. **Arrange**
+2. **Act**
+3. **Assert**
+4. **Cleanup**
+
+**Arrange** is where we prepare everything for our test. This means pretty
+much everything except for the "**act**". It's lining up the dominoes so that
+the **act** can do its thing in one, state-changing step. This can mean
+preparing objects, starting/killing services, entering records into a database,
+or even things like defining a URL to query, generating some credentials for a
+user that doesn't exist yet, or just waiting for some process to finish.
+
+**Act** is the singular, state-changing action that kicks off the **behavior**
+we want to test. This behavior is what carries out the changing of the state of
+the system under test (SUT), and it's the resulting changed state that we can
+look at to make a judgement about the behavior. This typically takes the form of
+a function/method call.
+
+**Assert** is where we look at that resulting state and check if it looks how
+we'd expect after the dust has settled. It's where we gather evidence to say the
+behavior does or does not aligns with what we expect. The ``assert`` in our test
+is where we take that measurement/observation and apply our judgement to it. If
+something should be green, we'd say ``assert thing == "green"``.
+
+**Cleanup** is where the test picks up after itself, so other tests aren't being
+accidentally influenced by it.
+
+At it's core, the test is ultimately the **act** and **assert** steps, with the
+**arrange** step only providing the context. **Behavior** exists between **act**
+and **assert**.
+
+Back to fixtures
+^^^^^^^^^^^^^^^^
+
+"Fixtures", in the literal sense, are each of the **arrange** steps and data. They're
+everything that test needs to do its thing.
+
+At a basic level, test functions request fixtures by declaring them as
+arguments, as in the ``test_ehlo(smtp_connection):`` in the previous example.
 
-Test functions can receive fixture objects by naming them as an input
-argument. For each argument name, a fixture function with that name provides
-the fixture object.  Fixture functions are registered by marking them with
-:py:func:`@pytest.fixture <pytest.fixture>`.  Let's look at a simple
-self-contained test module containing a fixture and a test function
-using it:
+In pytest, "fixtures" are functions you define that serve this purpose. But they
+don't have to be limited to just the **arrange** steps. They can provide the
+**act** step, as well, and this can be a powerful technique for designing more
+complex tests, especially given how pytest's fixture system works. But we'll get
+into that further down.
+
+We can tell pytest that a particular function is a fixture by decorating it with
+:py:func:`@pytest.fixture <pytest.fixture>`. Here's a simple example of
+what a fixture in pytest might look like:
 
 .. code-block:: python
 
-    # content of ./test_smtpsimple.py
     import pytest
 
 
+    class Fruit:
+        def __init__(self, name):
+            self.name = name
+
+        def __eq__(self, other):
+            return self.name == other.name
+
+
     @pytest.fixture
-    def smtp_connection():
-        import smtplib
+    def my_fruit():
+        return Fruit("apple")
+
+
+    @pytest.fixture
+    def fruit_basket(my_fruit):
+        return [Fruit("banana"), my_fruit]
+
+
+    def test_my_fruit_in_basket(my_fruit, fruit_basket):
+        assert my_fruit in fruit_basket
+
+
+
+Tests don't have to be limited to a single fixture, either. They can depend on
+as many fixtures as you want, and fixtures can use other fixtures, as well. This
+is where pytest's fixture system really shines.
+
+Don't be afraid to break things up if it makes things cleaner.
+
+"Requesting" fixtures
+---------------------
+
+So fixtures are how we *prepare* for a test, but how do we tell pytest what
+tests and fixtures need which fixtures?
+
+At a basic level, test functions request fixtures by declaring them as
+arguments, as in the ``test_my_fruit_in_basket(my_fruit, fruit_basket):`` in the
+previous example.
+
+At a basic level, pytest depends on a test to tell it what fixtures it needs, so
+we have to build that information into the test itself. We have to make the test
+"**request**" the fixtures it depends on, and to do this, we have to
+list those fixtures as parameters in the test function's "signature" (which is
+the ``def test_something(blah, stuff, more):`` line).
+
+When pytest goes to run a test, it looks at the parameters in that test
+function's signature, and then searches for fixtures that have the same names as
+those parameters. Once pytest finds them, it runs those fixtures, captures what
+they returned (if anything), and passes those objects into the test function as
+arguments.
+
+Quick example
+^^^^^^^^^^^^^
+
+.. code-block:: python
+
+    import pytest
+
+
+    class Fruit:
+        def __init__(self, name):
+            self.name = name
+            self.cubed = False
+
+        def cube(self):
+            self.cubed = True
+
+
+    class FruitSalad:
+        def __init__(self, *fruit_bowl):
+            self.fruit = fruit_bowl
+            self._cube_fruit()
+
+        def _cube_fruit(self):
+            for fruit in self.fruit:
+                fruit.cube()
+
+
+    # Arrange
+    @pytest.fixture
+    def fruit_bowl():
+        return [Fruit("apple"), Fruit("banana")]
+
+
+    def test_fruit_salad(fruit_bowl):
+        # Act
+        fruit_salad = FruitSalad(*fruit_bowl)
+
+        # Assert
+        assert all(fruit.cubed for fruit in fruit_salad.fruit)
+
+In this example, ``test_fruit_salad`` "**requests**" ``fruit_bowl`` (i.e.
+``def test_fruit_salad(fruit_bowl):``), and when pytest sees this, it will
+execute the ``fruit_bowl`` fixture function and pass the object it returns into
+``test_fruit_salad`` as the ``fruit_bowl`` argument.
+
+Here's roughly
+what's happening if we were to do it by hand:
+
+.. code-block:: python
+
+    def fruit_bowl():
+        return [Fruit("apple"), Fruit("banana")]
+
+
+    def test_fruit_salad(fruit_bowl):
+        # Act
+        fruit_salad = FruitSalad(*fruit_bowl)
+
+        # Assert
+        assert all(fruit.cubed for fruit in fruit_salad.fruit)
+
+
+    # Arrange
+    bowl = fruit_bowl()
+    test_fruit_salad(fruit_bowl=bowl)
+
+Fixtures can **request** other fixtures
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+One of pytest's greatest strengths is its extremely flexible fixture system. It
+allows us to boil down complex requirements for tests into more simple and
+organized functions, where we only need to have each one describe the things
+they are dependent on. We'll get more into this further down, but for now,
+here's a quick example to demonstrate how fixtures can use other fixtures:
+
+.. code-block:: python
+
+    # contents of test_append.py
+    import pytest
+
+
+    # Arrange
+    @pytest.fixture
+    def first_entry():
+        return "a"
+
+
+    # Arrange
+    @pytest.fixture
+    def order(first_entry):
+        return [first_entry]
+
+
+    def test_string(order):
+        # Act
+        order.append("b")
+
+        # Assert
+        assert order == ["a", "b"]
+
+
+Notice that this is the same example from above, but very little changed. The
+fixtures in pytest **request** fixtures just like tests. All the same
+**requesting** rules apply to fixtures that do for tests. Here's how this
+example would work if we did it by hand:
+
+.. code-block:: python
+
+    def first_entry():
+        return "a"
+
+
+    def order(first_entry):
+        return [first_entry]
+
+
+    def test_string(order):
+        # Act
+        order.append("b")
+
+        # Assert
+        assert order == ["a", "b"]
+
+
+    entry = first_entry()
+    the_list = order(first_entry=entry)
+    test_string(order=the_list)
+
+Fixtures are reusable
+^^^^^^^^^^^^^^^^^^^^^
+
+One of the things that makes pytest's fixture system so powerful, is that it
+gives us the abilty to define a generic setup step that can reused over and
+over, just like a normal function would be used. Two different tests can request
+the same fixture and have pytest give each test their own result from that
+fixture.
+
+This is extremely useful for making sure tests aren't affected by each other. We
+can use this system to make sure each test gets its own fresh batch of data and
+is starting from a clean state so it can provide consistent, repeatable results.
+
+Here's an example of how this can come in handy:
+
+.. code-block:: python
+
+    # contents of test_append.py
+    import pytest
+
+
+    # Arrange
+    @pytest.fixture
+    def first_entry():
+        return "a"
+
+
+    # Arrange
+    @pytest.fixture
+    def order(first_entry):
+        return [first_entry]
+
+
+    def test_string(order):
+        # Act
+        order.append("b")
+
+        # Assert
+        assert order == ["a", "b"]
+
+
+    def test_int(order):
+        # Act
+        order.append(2)
+
+        # Assert
+        assert order == ["a", 2]
+
+
+Each test here is being given its own copy of that ``list`` object,
+which means the ``order`` fixture is getting executed twice (the same
+is true for the ``first_entry`` fixture). If we were to do this by hand as
+well, it would look something like this:
+
+.. code-block:: python
+
+    def first_entry():
+        return "a"
+
+
+    def order(first_entry):
+        return [first_entry]
+
 
+    def test_string(order):
+        # Act
+        order.append("b")
+
+        # Assert
+        assert order == ["a", "b"]
+
+
+    def test_int(order):
+        # Act
+        order.append(2)
+
+        # Assert
+        assert order == ["a", 2]
+
+
+    entry = first_entry()
+    the_list = order(first_entry=entry)
+    test_string(order=the_list)
+
+    entry = first_entry()
+    the_list = order(first_entry=entry)
+    test_int(order=the_list)
+
+A test/fixture can **request** more than one fixture at a time
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Tests and fixtures aren't limited to **requesting** a single fixture at a time.
+They can request as many as they like. Here's another quick example to
+demonstrate:
+
+.. code-block:: python
+
+    # contents of test_append.py
+    import pytest
+
+
+    # Arrange
+    @pytest.fixture
+    def first_entry():
+        return "a"
+
+
+    # Arrange
+    @pytest.fixture
+    def second_entry():
+        return 2
+
+
+    # Arrange
+    @pytest.fixture
+    def order(first_entry, second_entry):
+        return [first_entry, second_entry]
+
+
+    # Arrange
+    @pytest.fixture
+    def expected_list():
+        return ["a", 2, 3.0]
+
+
+    def test_string(order, expected_list):
+        # Act
+        order.append(3.0)
+
+        # Assert
+        assert order == expected_list
+
+Fixtures can be **requested** more than once per test (return values are cached)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Fixtures can also be **requested** more than once during the same test, and
+pytest won't execute them again for that test. This means we can **request**
+fixtures in multiple fixtures that are dependent on them (and even again in the
+test itself) without those fixtures being executed more than once.
+
+.. code-block:: python
+
+    # contents of test_append.py
+    import pytest
+
+
+    # Arrange
+    @pytest.fixture
+    def first_entry():
+        return "a"
+
+
+    # Arrange
+    @pytest.fixture
+    def order():
+        return []
+
+
+    # Act
+    @pytest.fixture
+    def append_first(order, first_entry):
+        return order.append(first_entry)
+
+
+    def test_string_only(append_first, order, first_entry):
+        # Assert
+        assert order == [first_entry]
+
+If a **requested** fixture was executed once for every time it was **requested**
+during a test, then this test would fail because both ``append_first`` and
+``test_string_only`` would see ``order`` as an empty list (i.e. ``[]``), but
+since the return value of ``order`` was cached (along with any side effects
+executing it may have had) after the first time it was called, both the test and
+``append_first`` were referencing the same object, and the test saw the effect
+``append_first`` had on that object.
+
+.. _`autouse`:
+.. _`autouse fixtures`:
+
+Autouse fixtures (fixtures you don't have to request)
+-----------------------------------------------------
+
+Sometimes you may want to have a fixture (or even several) that you know all
+your tests will depend on. "Autouse" fixtures are a convenient way to make all
+tests automatically **request** them. This can cut out a
+lot of redundant **requests**, and can even provide more advanced fixture usage
+(more on that further down).
+
+We can make a fixture an autouse fixture by passing in ``autouse=True`` to the
+fixture's decorator. Here's a simple example for how they can be used:
+
+.. code-block:: python
+
+    # contents of test_append.py
+    import pytest
+
+
+    @pytest.fixture
+    def first_entry():
+        return "a"
+
+
+    @pytest.fixture
+    def order(first_entry):
+        return []
+
+
+    @pytest.fixture(autouse=True)
+    def append_first(order, first_entry):
+        return order.append(first_entry)
+
+
+    def test_string_only(order, first_entry):
+        assert order == [first_entry]
+
+
+    def test_string_and_int(order, first_entry):
+        order.append(2)
+        assert order == [first_entry, 2]
+
+In this example, the ``append_first`` fixture is an autouse fixture. Because it
+happens automatically, both tests are affected by it, even though neither test
+**requested** it. That doesn't mean they *can't* be **requested** though; just
+that it isn't *necessary*.
+
+.. _smtpshared:
+
+Scope: sharing fixtures across classes, modules, packages or session
+--------------------------------------------------------------------
+
+.. regendoc:wipe
+
+Fixtures requiring network access depend on connectivity and are
+usually time-expensive to create.  Extending the previous example, we
+can add a ``scope="module"`` parameter to the
+:py:func:`@pytest.fixture <pytest.fixture>` invocation
+to cause a ``smtp_connection`` fixture function, responsible to create a connection to a preexisting SMTP server, to only be invoked
+once per test *module* (the default is to invoke once per test *function*).
+Multiple test functions in a test module will thus
+each receive the same ``smtp_connection`` fixture instance, thus saving time.
+Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``package`` or ``session``.
+
+The next example puts the fixture function into a separate ``conftest.py`` file
+so that tests from multiple test modules in the directory can
+access the fixture function:
+
+.. code-block:: python
+
+    # content of conftest.py
+    import pytest
+    import smtplib
+
+
+    @pytest.fixture(scope="module")
+    def smtp_connection():
         return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
 
 
+.. code-block:: python
+
+    # content of test_module.py
+
+
     def test_ehlo(smtp_connection):
         response, msg = smtp_connection.ehlo()
         assert response == 250
+        assert b"smtp.gmail.com" in msg
+        assert 0  # for demo purposes
+
+
+    def test_noop(smtp_connection):
+        response, msg = smtp_connection.noop()
+        assert response == 250
         assert 0  # for demo purposes
 
 Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value.  pytest
@@ -149,442 +652,967 @@ marked ``smtp_connection`` fixture function.  Running the test looks like this:
 
 .. code-block:: pytest
 
-    $ pytest test_smtpsimple.py
+    $ pytest test_module.py
     =========================== test session starts ============================
     platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
     cachedir: $PYTHON_PREFIX/.pytest_cache
     rootdir: $REGENDOC_TMPDIR
-    collected 1 item
+    collected 2 items
+
+    test_module.py FF                                                    [100%]
+
+    ================================= FAILURES =================================
+    ________________________________ test_ehlo _________________________________
+
+    smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
+
+        def test_ehlo(smtp_connection):
+            response, msg = smtp_connection.ehlo()
+            assert response == 250
+            assert b"smtp.gmail.com" in msg
+    >       assert 0  # for demo purposes
+    E       assert 0
+
+    test_module.py:7: AssertionError
+    ________________________________ test_noop _________________________________
+
+    smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
+
+        def test_noop(smtp_connection):
+            response, msg = smtp_connection.noop()
+            assert response == 250
+    >       assert 0  # for demo purposes
+    E       assert 0
+
+    test_module.py:13: AssertionError
+    ========================= short test summary info ==========================
+    FAILED test_module.py::test_ehlo - assert 0
+    FAILED test_module.py::test_noop - assert 0
+    ============================ 2 failed in 0.12s =============================
+
+You see the two ``assert 0`` failing and more importantly you can also see
+that the **exactly same** ``smtp_connection`` object was passed into the
+two test functions because pytest shows the incoming argument values in the
+traceback.  As a result, the two test functions using ``smtp_connection`` run
+as quick as a single one because they reuse the same instance.
+
+If you decide that you rather want to have a session-scoped ``smtp_connection``
+instance, you can simply declare it:
+
+.. code-block:: python
+
+    @pytest.fixture(scope="session")
+    def smtp_connection():
+        # the returned fixture value will be shared for
+        # all tests requesting it
+        ...
+
+
+Fixture scopes
+^^^^^^^^^^^^^^
+
+Fixtures are created when first requested by a test, and are destroyed based on their ``scope``:
+
+* ``function``: the default scope, the fixture is destroyed at the end of the test.
+* ``class``: the fixture is destroyed during teardown of the last test in the class.
+* ``module``: the fixture is destroyed during teardown of the last test in the module.
+* ``package``: the fixture is destroyed during teardown of the last test in the package.
+* ``session``: the fixture is destroyed at the end of the test session.
+
+.. note::
+
+    Pytest only caches one instance of a fixture at a time, which
+    means that when using a parametrized fixture, pytest may invoke a fixture more than once in
+    the given scope.
+
+.. _dynamic scope:
+
+Dynamic scope
+^^^^^^^^^^^^^
+
+.. versionadded:: 5.2
+
+In some cases, you might want to change the scope of the fixture without changing the code.
+To do that, pass a callable to ``scope``. The callable must return a string with a valid scope
+and will be executed only once - during the fixture definition. It will be called with two
+keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object.
+
+This can be especially useful when dealing with fixtures that need time for setup, like spawning
+a docker container. You can use the command-line argument to control the scope of the spawned
+containers for different environments. See the example below.
+
+.. code-block:: python
+
+    def determine_scope(fixture_name, config):
+        if config.getoption("--keep-containers", None):
+            return "session"
+        return "function"
+
+
+    @pytest.fixture(scope=determine_scope)
+    def docker_container():
+        yield spawn_container()
+
+Fixture errors
+--------------
+
+pytest does its best to put all the fixtures for a given test in a linear order
+so that it can see which fixture happens first, second, third, and so on. If an
+earlier fixture has a problem, though, and raises an exception, pytest will stop
+executing fixtures for that test and mark the test as having an error.
+
+When a test is marked as having an error, it doesn't mean the test failed,
+though. It just means the test couldn't even be attempted because one of the
+things it depends on had a problem.
+
+This is one reason why it's a good idea to cut out as many unnecessary
+dependencies as possible for a given test. That way a problem in something
+unrelated isn't causing us to have an incomplete picture of what may or may not
+have issues.
+
+Here's a quick example to help explain:
+
+.. code-block:: python
+
+    import pytest
+
+
+    @pytest.fixture
+    def order():
+        return []
+
+
+    @pytest.fixture
+    def append_first(order):
+        order.append(1)
+
+
+    @pytest.fixture
+    def append_second(order, append_first):
+        order.extend([2])
+
+
+    @pytest.fixture(autouse=True)
+    def append_third(order, append_second):
+        order += [3]
+
+
+    def test_order(order):
+        assert order == [1, 2, 3]
+
+
+If, for whatever reason, ``order.append(1)`` had a bug and it raises an exception,
+we wouldn't be able to know if ``order.extend([2])`` or ``order += [3]`` would
+also have problems. After ``append_first`` throws an exception, pytest won't run
+any more fixtures for ``test_order``, and it won't even try to run
+``test_order`` itself. The only things that would've run would be ``order`` and
+``append_first``.
+
+
+
+
+.. _`finalization`:
 
-    test_smtpsimple.py F                                                 [100%]
+Teardown/Cleanup (AKA Fixture finalization)
+-------------------------------------------
+
+When we run our tests, we'll want to make sure they clean up after themselves so
+they don't mess with any other tests (and also so that we don't leave behind a
+mountain of test data to bloat the system). Fixtures in pytest offer a very
+useful teardown system, which allows us to define the specific steps necessary
+for each fixture to clean up after itself.
+
+This system can be leveraged in two ways.
+
+.. _`yield fixtures`:
+
+1. ``yield`` fixtures (recommended)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+"Yield" fixtures ``yield`` instead of ``return``. With these
+fixtures, we can run some code and pass an object back to the requesting
+fixture/test, just like with the other fixtures. The only differences are:
+
+1. ``return`` is swapped out for ``yield``.
+2. Any teardown code for that fixture is placed *after* the ``yield``.
+
+Once pytest figures out a linear order for the fixtures, it will run each one up
+until it returns or yields, and then move on to the next fixture in the list to
+do the same thing.
+
+Once the test is finished, pytest will go back down the list of fixtures, but in
+the *reverse order*, taking each one that yielded, and running the code inside
+it that was *after* the ``yield`` statement.
+
+As a simple example, let's say we want to test sending email from one user to
+another. We'll have to first make each user, then send the email from one user
+to the other, and finally assert that the other user received that message in
+their inbox. If we want to clean up after the test runs, we'll likely have to
+make sure the other user's mailbox is emptied before deleting that user,
+otherwise the system may complain.
+
+Here's what that might look like:
+
+.. code-block:: python
+
+    import pytest
+
+    from emaillib import Email, MailAdminClient
+
+
+    @pytest.fixture
+    def mail_admin():
+        return MailAdminClient()
+
+
+    @pytest.fixture
+    def sending_user(mail_admin):
+        user = mail_admin.create_user()
+        yield user
+        admin_client.delete_user(user)
+
+
+    @pytest.fixture
+    def receiving_user(mail_admin):
+        user = mail_admin.create_user()
+        yield user
+        admin_client.delete_user(user)
+
+
+    def test_email_received(receiving_user, email):
+        email = Email(subject="Hey!", body="How's it going?")
+        sending_user.send_email(_email, receiving_user)
+        assert email in receiving_user.inbox
+
+Because ``receiving_user`` is the last fixture to run during setup, it's the first to run
+during teardown.
+
+There is a risk that even having the order right on the teardown side of things
+doesn't guarantee a safe cleanup. That's covered in a bit more detail in
+:ref:`safe teardowns`.
+
+Handling errors for yield fixture
+"""""""""""""""""""""""""""""""""
+
+If a yield fixture raises an exception before yielding, pytest won't try to run
+the teardown code after that yield fixture's ``yield`` statement. But, for every
+fixture that has already run successfully for that test, pytest will still
+attempt to tear them down as it normally would.
+
+2. Adding finalizers directly
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+While yield fixtures are considered to be the cleaner and more straighforward
+option, there is another choice, and that is to add "finalizer" functions
+directly to the test's `request-context`_ object. It brings a similar result as
+yield fixtures, but requires a bit more verbosity.
+
+In order to use this approach, we have to request the `request-context`_ object
+(just like we would request another fixture) in the fixture we need to add
+teardown code for, and then pass a callable, containing that teardown code, to
+its ``addfinalizer`` method.
+
+We have to be careful though, because pytest will run that finalizer once it's
+been added, even if that fixture raises an exception after adding the finalizer.
+So to make sure we don't run the finalizer code when we wouldn't need to, we
+would only add the finalizer once the fixture would have done something that
+we'd need to teardown.
+
+Here's how the previous example would look using the ``addfinalizer`` method:
+
+.. code-block:: python
+
+    import pytest
+
+    from emaillib import Email, MailAdminClient
+
+
+    @pytest.fixture
+    def mail_admin():
+        return MailAdminClient()
+
+
+    @pytest.fixture
+    def sending_user(mail_admin):
+        user = mail_admin.create_user()
+        yield user
+        admin_client.delete_user(user)
+
+
+    @pytest.fixture
+    def receiving_user(mail_admin, request):
+        user = mail_admin.create_user()
+
+        def delete_user():
+            admin_client.delete_user(user)
+
+        request.addfinalizer(delete_user)
+        return user
+
+
+    @pytest.fixture
+    def email(sending_user, receiving_user, request):
+        _email = Email(subject="Hey!", body="How's it going?")
+        sending_user.send_email(_email, receiving_user)
+
+        def empty_mailbox():
+            receiving_user.delete_email(_email)
+
+        request.addfinalizer(empty_mailbox)
+        return _email
+
+
+    def test_email_received(receiving_user, email):
+        assert email in receiving_user.inbox
+
+
+It's a bit longer than yield fixtures and a bit more complex, but it
+does offer some nuances for when you're in a pinch.
+
+.. _`safe teardowns`:
+
+Safe teardowns
+--------------
+
+The fixture system of pytest is *very* powerful, but it's still being run by a
+computer, so it isn't able to figure out how to safely teardown everything we
+throw at it. If we aren't careful, an error in the wrong spot might leave stuff
+from our tests behind, and that can cause further issues pretty quickly.
+
+For example, consider the following tests (based off of the mail example from
+above):
+
+.. code-block:: python
+
+    import pytest
+
+    from emaillib import Email, MailAdminClient
+
+
+    @pytest.fixture
+    def setup():
+        mail_admin = MailAdminClient()
+        sending_user = mail_admin.create_user()
+        receiving_user = mail_admin.create_user()
+        email = Email(subject="Hey!", body="How's it going?")
+        sending_user.send_emai(email, receiving_user)
+        yield receiving_user, email
+        receiving_user.delete_email(email)
+        admin_client.delete_user(sending_user)
+        admin_client.delete_user(receiving_user)
+
+
+    def test_email_received(setup):
+        receiving_user, email = setup
+        assert email in receiving_user.inbox
+
+This version is a lot more compact, but it's also harder to read, doesn't have a
+very descriptive fixture name, and none of the fixtures can be reused easily.
+
+There's also a more serious issue, which is that if any of those steps in the
+setup raise an exception, none of the teardown code will run.
+
+One option might be to go with the ``addfinalizer`` method instead of yield
+fixtures, but that might get pretty complex and difficult to maintain (and it
+wouldn't be compact anymore).
+
+.. _`safe fixture structure`:
+
+Safe fixture structure
+^^^^^^^^^^^^^^^^^^^^^^
+
+The safest and simplest fixture structure requires limiting fixtures to only
+making one state-changing action each, and then bundling them together with
+their teardown code, as :ref:`the email examples above <yield fixtures>` showed.
+
+The chance that a state-changing operation can fail but still modify state is
+neglibible, as most of these operations tend to be `transaction`_-based (at
+least at the level of testing where state could be left behind). So if we make
+sure that any successful state-changing action gets torn down by moving it to a
+separate fixture function and separating it from other, potentially failing
+state-changing actions, then our tests will stand the best chance at leaving the
+test environment the way they found it.
+
+For an example, let's say we have a website with a login page, and we have
+access to an admin API where we can generate users. For our test, we want to:
+
+1. Create a user through that admin API
+2. Launch a browser using Selenium
+3. Go to the login page of our site
+4. Log in as the user we created
+5. Assert that their name is in the header of the landing page
+
+We wouldn't want to leave that user in the system, nor would we want to leave
+that browser session running, so we'll want to make sure the fixtures that
+create those things clean up after themselves.
+
+Here's what that might look like:
+
+.. note::
+
+    For this example, certain fixtures (i.e. ``base_url`` and
+    ``admin_credentials``) are implied to exist elsewhere. So for now, let's
+    assume they exist, and we're just not looking at them.
+
+.. code-block:: python
+
+    from uuid import uuid4
+    from urllib.parse import urljoin
+
+    from selenium.webdriver import Chrome
+    import pytest
+
+    from src.utils.pages import LoginPage, LandingPage
+    from src.utils import AdminApiClient
+    from src.utils.data_types import User
+
+
+    @pytest.fixture
+    def admin_client(base_url, admin_credentials):
+        return AdminApiClient(base_url, **admin_credentials)
+
+
+    @pytest.fixture
+    def user(admin_client):
+        _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
+        admin_client.create_user(_user)
+        yield _user
+        admin_client.delete_user(_user)
+
+
+    @pytest.fixture
+    def driver():
+        _driver = Chrome()
+        yield _driver
+        _driver.quit()
+
+
+    @pytest.fixture
+    def login(driver, base_url, user):
+        driver.get(urljoin(base_url, "/login"))
+        page = LoginPage(driver)
+        page.login(user)
+
+
+    @pytest.fixture
+    def landing_page(driver, login):
+        return LandingPage(driver)
+
+
+    def test_name_on_landing_page_after_login(landing_page, user):
+        assert landing_page.header == f"Welcome, {user.name}!"
+
+The way the dependencies are laid out means it's unclear if the ``user`` fixture
+would execute before the ``driver`` fixture. But that's ok, because those are
+atomic operations, and so it doesn't matter which one runs first because the
+sequence of events for the test is still `linearizable`_. But what *does* matter
+is that, no matter which one runs first, if the one raises an exception while
+the other would not have, neither will have left anything behind. If ``driver``
+executes before ``user``, and ``user`` raises an exception, the driver will
+still quit, and the user was never made. And if ``driver`` was the one to raise
+the exception, then the driver would never have been started and the user would
+never have been made.
+
+.. note:
+
+    While the ``user`` fixture doesn't *actually* need to happen before the
+    ``driver`` fixture, if we made ``driver`` request ``user``, it might save
+    some time in the event that making the user raises an exception, since it
+    won't bother trying to start the driver, which is a fairly expensive
+    operation.
+
+.. _`conftest.py`:
+.. _`conftest`:
 
-    ================================= FAILURES =================================
-    ________________________________ test_ehlo _________________________________
+Fixture availabiility
+---------------------
 
-    smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
+Fixture availability is determined from the perspective of the test. A fixture
+is only available for tests to request if they are in the scope that fixture is
+defined in. If a fixture is defined inside a class, it can only be requested by
+tests inside that class. But if a fixture is defined inside the global scope of
+the module, than every test in that module, even if it's defined inside a class,
+can request it.
 
-        def test_ehlo(smtp_connection):
-            response, msg = smtp_connection.ehlo()
-            assert response == 250
-    >       assert 0  # for demo purposes
-    E       assert 0
+Similarly, a test can also only be affected by an autouse fixture if that test
+is in the same scope that autouse fixture is defined in (see
+:ref:`autouse order`).
 
-    test_smtpsimple.py:14: AssertionError
-    ========================= short test summary info ==========================
-    FAILED test_smtpsimple.py::test_ehlo - assert 0
-    ============================ 1 failed in 0.12s =============================
+A fixture can also request any other fixture, no matter where it's defined, so
+long as the test requesting them can see all fixtures involved.
 
-In the failure traceback we see that the test function was called with a
-``smtp_connection`` argument, the ``smtplib.SMTP()`` instance created by the fixture
-function.  The test function fails on our deliberate ``assert 0``.  Here is
-the exact protocol used by ``pytest`` to call the test function this way:
+For example, here's a test file with a fixture (``outer``) that requests a
+fixture (``inner``) from a scope it wasn't defined in:
 
-1. pytest :ref:`finds <test discovery>` the test ``test_ehlo`` because
-   of the ``test_`` prefix.  The test function needs a function argument
-   named ``smtp_connection``.  A matching fixture function is discovered by
-   looking for a fixture-marked function named ``smtp_connection``.
+.. literalinclude:: example/fixtures/test_fixtures_request_different_scope.py
 
-2. ``smtp_connection()`` is called to create an instance.
+From the tests' perspectives, they have no problem seeing each of the fixtures
+they're dependent on:
 
-3. ``test_ehlo(<smtp_connection instance>)`` is called and fails in the last
-   line of the test function.
+.. image:: example/fixtures/test_fixtures_request_different_scope.svg
+    :align: center
 
-Note that if you misspell a function argument or want
-to use one that isn't available, you'll see an error
-with a list of available function arguments.
+So when they run, ``outer`` will have no problem finding ``inner``, because
+pytest searched from the tests' perspectives.
 
 .. note::
+    The scope a fixture is defined in has no bearing on the order it will be
+    instantiated in: the order is mandated by the logic described
+    :ref:`here <fixture order>`.
 
-    You can always issue:
+``conftest.py``: sharing fixtures across multiple files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-    .. code-block:: bash
+The ``conftest.py`` file serves as a means of providing fixtures for an entire
+directory. Fixtures defined in a ``conftest.py`` can be used by any test
+in that package without needing to import them (pytest will automatically
+discover them).
 
-        pytest --fixtures test_simplefactory.py
+You can have multiple nested directories/packages containing your tests, and
+each directory can have its own ``conftest.py`` with its own fixtures, adding on
+to the ones provided by the ``conftest.py`` files in parent directories.
 
-    to see available fixtures (fixtures with leading ``_`` are only shown if you add the ``-v`` option).
+For example, given a test file structure like this:
 
-Fixtures: a prime example of dependency injection
----------------------------------------------------
+::
 
-Fixtures allow test functions to easily receive and work
-against specific pre-initialized application objects without having
-to care about import/setup/cleanup details.
-It's a prime example of `dependency injection`_ where fixture
-functions take the role of the *injector* and test functions are the
-*consumers* of fixture objects.
+    tests/
+        __init__.py
 
-.. _`conftest.py`:
-.. _`conftest`:
+        conftest.py
+            # content of tests/conftest.py
+            import pytest
 
-``conftest.py``: sharing fixture functions
-------------------------------------------
+            @pytest.fixture
+            def order():
+                return []
 
-If during implementing your tests you realize that you
-want to use a fixture function from multiple test files you can move it
-to a ``conftest.py`` file.
-You don't need to import the fixture you want to use in a test, it
-automatically gets discovered by pytest. The discovery of
-fixture functions starts at test classes, then test modules, then
-``conftest.py`` files and finally builtin and third party plugins.
+            @pytest.fixture
+            def top(order, innermost):
+                order.append("top")
 
-You can also use the ``conftest.py`` file to implement
-:ref:`local per-directory plugins <conftest.py plugins>`.
+        test_top.py
+            # content of tests/test_top.py
+            import pytest
 
-Sharing test data
------------------
+            @pytest.fixture
+            def innermost(order):
+                order.append("innermost top")
 
-If you want to make test data from files available to your tests, a good way
-to do this is by loading these data in a fixture for use by your tests.
-This makes use of the automatic caching mechanisms of pytest.
+            def test_order(order, top):
+                assert order == ["innermost top", "top"]
 
-Another good approach is by adding the data files in the ``tests`` folder.
-There are also community plugins available to help managing this aspect of
-testing, e.g. `pytest-datadir <https://pypi.org/project/pytest-datadir/>`__
-and `pytest-datafiles <https://pypi.org/project/pytest-datafiles/>`__.
+        subpackage/
+            __init__.py
 
-.. _smtpshared:
+            conftest.py
+                # content of tests/subpackage/conftest.py
+                import pytest
 
-Scope: sharing fixtures across classes, modules, packages or session
---------------------------------------------------------------------
+                @pytest.fixture
+                def mid(order):
+                    order.append("mid subpackage")
 
-.. regendoc:wipe
+            test_subpackage.py
+                # content of tests/subpackage/test_subpackage.py
+                import pytest
 
-Fixtures requiring network access depend on connectivity and are
-usually time-expensive to create.  Extending the previous example, we
-can add a ``scope="module"`` parameter to the
-:py:func:`@pytest.fixture <pytest.fixture>` invocation
-to cause the decorated ``smtp_connection`` fixture function to only be invoked
-once per test *module* (the default is to invoke once per test *function*).
-Multiple test functions in a test module will thus
-each receive the same ``smtp_connection`` fixture instance, thus saving time.
-Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``package`` or ``session``.
+                @pytest.fixture
+                def innermost(order, mid):
+                    order.append("innermost subpackage")
 
-The next example puts the fixture function into a separate ``conftest.py`` file
-so that tests from multiple test modules in the directory can
-access the fixture function:
+                def test_order(order, top):
+                    assert order == ["mid subpackage", "innermost subpackage", "top"]
 
-.. code-block:: python
+The boundaries of the scopes can be visualized like this:
 
-    # content of conftest.py
-    import pytest
-    import smtplib
+.. image:: example/fixtures/fixture_availability.svg
+    :align: center
 
+The directories become their own sort of scope where fixtures that are defined
+in a ``conftest.py`` file in that directory become available for that whole
+scope.
 
-    @pytest.fixture(scope="module")
-    def smtp_connection():
-        return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
+Tests are allowed to search upward (stepping outside a circle) for fixtures, but
+can never go down (stepping inside a circle) to continue their search. So
+``tests/subpackage/test_subpackage.py::test_order`` would be able to find the
+``innermost`` fixture defined in ``tests/subpackage/test_subpackage.py``, but
+the one defined in ``tests/test_top.py`` would be unavailable to it because it
+would have to step down a level (step inside a circle) to find it.
 
-The name of the fixture again is ``smtp_connection`` and you can access its
-result by listing the name ``smtp_connection`` as an input parameter in any
-test or fixture function (in or below the directory where ``conftest.py`` is
-located):
+The first fixture the test finds is the one that will be used, so
+:ref:`fixtures can be overriden <override fixtures>` if you need to change or
+extend what one does for a particular scope.
 
-.. code-block:: python
+You can also use the ``conftest.py`` file to implement
+:ref:`local per-directory plugins <conftest.py plugins>`.
 
-    # content of test_module.py
+Fixtures from third-party plugins
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+Fixtures don't have to be defined in this structure to be available for tests,
+though. They can also be provided by third-party plugins that are installed, and
+this is how many pytest plugins operate. As long as those plugins are installed,
+the fixtures they provide can be requested from anywhere in your test suite.
 
-    def test_ehlo(smtp_connection):
-        response, msg = smtp_connection.ehlo()
-        assert response == 250
-        assert b"smtp.gmail.com" in msg
-        assert 0  # for demo purposes
+Because they're provided from outside the structure of your test suite,
+third-party plugins don't really provide a scope like `conftest.py` files and
+the directories in your test suite do. As a result, pytest will search for
+fixtures stepping out through scopes as explained previously, only reaching
+fixtures defined in plugins *last*.
 
+For example, given the following file structure:
 
-    def test_noop(smtp_connection):
-        response, msg = smtp_connection.noop()
-        assert response == 250
-        assert 0  # for demo purposes
+::
 
-We deliberately insert failing ``assert 0`` statements in order to
-inspect what is going on and can now run the tests:
+    tests/
+        __init__.py
 
-.. code-block:: pytest
+        conftest.py
+            # content of tests/conftest.py
+            import pytest
 
-    $ pytest test_module.py
-    =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
-    cachedir: $PYTHON_PREFIX/.pytest_cache
-    rootdir: $REGENDOC_TMPDIR
-    collected 2 items
+            @pytest.fixture
+            def order():
+                return []
 
-    test_module.py FF                                                    [100%]
+        subpackage/
+            __init__.py
 
-    ================================= FAILURES =================================
-    ________________________________ test_ehlo _________________________________
+            conftest.py
+                # content of tests/subpackage/conftest.py
+                import pytest
 
-    smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
+                @pytest.fixture(autouse=True)
+                def mid(order, b_fix):
+                    order.append("mid subpackage")
 
-        def test_ehlo(smtp_connection):
-            response, msg = smtp_connection.ehlo()
-            assert response == 250
-            assert b"smtp.gmail.com" in msg
-    >       assert 0  # for demo purposes
-    E       assert 0
+            test_subpackage.py
+                # content of tests/subpackage/test_subpackage.py
+                import pytest
 
-    test_module.py:7: AssertionError
-    ________________________________ test_noop _________________________________
+                @pytest.fixture
+                def inner(order, mid, a_fix):
+                    order.append("inner subpackage")
 
-    smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
+                def test_order(order, inner):
+                    assert order == ["b_fix", "mid subpackage", "a_fix", "inner subpackage"]
 
-        def test_noop(smtp_connection):
-            response, msg = smtp_connection.noop()
-            assert response == 250
-    >       assert 0  # for demo purposes
-    E       assert 0
+If ``plugin_a`` is installed and provides the fixture ``a_fix``, and
+``plugin_b`` is installed and provides the fixture ``b_fix``, then this is what
+the test's search for fixtures would look like:
 
-    test_module.py:13: AssertionError
-    ========================= short test summary info ==========================
-    FAILED test_module.py::test_ehlo - assert 0
-    FAILED test_module.py::test_noop - assert 0
-    ============================ 2 failed in 0.12s =============================
+.. image:: example/fixtures/fixture_availability_plugins.svg
+    :align: center
 
-You see the two ``assert 0`` failing and more importantly you can also see
-that the same (module-scoped) ``smtp_connection`` object was passed into the
-two test functions because pytest shows the incoming argument values in the
-traceback.  As a result, the two test functions using ``smtp_connection`` run
-as quick as a single one because they reuse the same instance.
+pytest will only search for ``a_fix`` and ``b_fix`` in the plugins after
+searching for them first in the scopes inside ``tests/``.
 
-If you decide that you rather want to have a session-scoped ``smtp_connection``
-instance, you can simply declare it:
+.. note:
 
-.. code-block:: python
+    pytest can tell you what fixtures are available for a given test if you call
+    ``pytests`` along with the test's name (or the scope it's in), and provide
+    the ``--fixtures`` flag, e.g. ``pytest --fixtures test_something.py``
+    (fixtures with names that start with ``_`` will only be shown if you also
+    provide the ``-v`` flag).
 
-    @pytest.fixture(scope="session")
-    def smtp_connection():
-        # the returned fixture value will be shared for
-        # all tests needing it
-        ...
+Sharing test data
+-----------------
 
+If you want to make test data from files available to your tests, a good way
+to do this is by loading these data in a fixture for use by your tests.
+This makes use of the automatic caching mechanisms of pytest.
 
-Fixture scopes
-^^^^^^^^^^^^^^
+Another good approach is by adding the data files in the ``tests`` folder.
+There are also community plugins available to help managing this aspect of
+testing, e.g. `pytest-datadir <https://pypi.org/project/pytest-datadir/>`__
+and `pytest-datafiles <https://pypi.org/project/pytest-datafiles/>`__.
 
-Fixtures are created when first requested by a test, and are destroyed based on their ``scope``:
+.. _`fixture order`:
 
-* ``function``: the default scope, the fixture is destroyed at the end of the test.
-* ``class``: the fixture is destroyed during teardown of the last test in the class.
-* ``module``: the fixture is destroyed during teardown of the last test in the module.
-* ``package``: the fixture is destroyed during teardown of the last test in the package.
-* ``session``: the fixture is destroyed at the end of the test session.
+Fixture instantiation order
+---------------------------
 
-.. note::
+When pytest wants to execute a test, once it knows what fixtures will be
+executed, it has to figure out the order they'll be executed in. To do this, it
+considers 3 factors:
 
-    Pytest only caches one instance of a fixture at a time, which
-    means that when using a parametrized fixture, pytest may invoke a fixture more than once in
-    the given scope.
+1. scope
+2. dependencies
+3. autouse
 
-.. _dynamic scope:
+Names of fixtures or tests, where they're defined, the order they're defined in,
+and the order fixtures are requested in have no bearing on execution order
+beyond coincidence. While pytest will try to make sure coincidences like these
+stay consistent from run to run, it's not something that should be depended on.
+If you want to control the order, it's safest to rely on these 3 things and make
+sure dependencies are clearly established.
 
-Dynamic scope
-^^^^^^^^^^^^^
+Higher-scoped fixtures are executed first
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-.. versionadded:: 5.2
+Within a function request for fixtures, those of higher-scopes (such as
+``session``) are executed before lower-scoped fixtures (such as ``function`` or
+``class``).
 
-In some cases, you might want to change the scope of the fixture without changing the code.
-To do that, pass a callable to ``scope``. The callable must return a string with a valid scope
-and will be executed only once - during the fixture definition. It will be called with two
-keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object.
+Here's an example:
 
-This can be especially useful when dealing with fixtures that need time for setup, like spawning
-a docker container. You can use the command-line argument to control the scope of the spawned
-containers for different environments. See the example below.
+.. literalinclude:: example/fixtures/test_fixtures_order_scope.py
 
-.. code-block:: python
+The test will pass because the larger scoped fixtures are executing first.
 
-    def determine_scope(fixture_name, config):
-        if config.getoption("--keep-containers", None):
-            return "session"
-        return "function"
+The order breaks down to this:
 
+.. image:: example/fixtures/test_fixtures_order_scope.svg
+    :align: center
 
-    @pytest.fixture(scope=determine_scope)
-    def docker_container():
-        yield spawn_container()
+Fixtures of the same order execute based on dependencies
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+When a fixture requests another fixture, the other fixture is executed first.
+So if fixture ``a`` requests fixture ``b``, fixture ``b`` will execute first,
+because ``a`` depends on ``b`` and can't operate without it. Even if ``a``
+doesn't need the result of ``b``, it can still request ``b`` if it needs to make
+sure it is executed after ``b``.
 
+For example:
 
-Order: Higher-scoped fixtures are instantiated first
-----------------------------------------------------
+.. literalinclude:: example/fixtures/test_fixtures_order_dependencies.py
 
+If we map out what depends on what, we get something that look like this:
 
+.. image:: example/fixtures/test_fixtures_order_dependencies.svg
+    :align: center
 
-Within a function request for fixtures, those of higher-scopes (such as ``session``) are instantiated before
-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. Autouse fixtures will be
-instantiated before explicitly used fixtures.
+The rules provided by each fixture (as to what fixture(s) each one has to come
+after) are comprehensive enough that it can be flattened to this:
 
-Consider the code below:
+.. image:: example/fixtures/test_fixtures_order_dependencies_flat.svg
+    :align: center
 
-.. literalinclude:: example/fixtures/test_fixtures_order.py
+Enough information has to be provided through these requests in order for pytest
+to be able to figure out a clear, linear chain of dependencies, and as a result,
+an order of operations for a given test. If there's any ambiguity, and the order
+of operations can be interpreted more than one way, you should assume pytest
+could go with any one of those interpretations at any point.
 
-The fixtures requested by ``test_order`` will be instantiated in the following order:
+For example, if ``d`` didn't request ``c``, i.e.the graph would look like this:
 
-1. ``s1``: is the highest-scoped fixture (``session``).
-2. ``m1``: is the second highest-scoped fixture (``module``).
-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_order`` parameter list.
-6. ``f2``: is the last ``function``-scoped fixture in ``test_order`` parameter list.
+.. image:: example/fixtures/test_fixtures_order_dependencies_unclear.svg
+    :align: center
 
+Because nothing requested ``c`` other than ``g``, and ``g`` also requests ``f``,
+it's now unclear if ``c`` should go before/after ``f``, ``e``, or ``d``. The
+only rules that were set for ``c`` is that it must execute after ``b`` and
+before ``g``.
 
-.. _`finalization`:
+pytest doesn't know where ``c`` should go in the case, so it should be assumed
+that it could go anywhere between ``g`` and ``b``.
 
-Fixture finalization / executing teardown code
--------------------------------------------------------------
+This isn't necessarily bad, but it's something to keep in mind. If the order
+they execute in could affect the behavior a test is targetting, or could
+otherwise influence the result of a test, then the order should be defined
+explicitely in a way that allows pytest to linearize/"flatten" that order.
 
-pytest supports execution of fixture specific finalization code
-when the fixture goes out of scope.  By using a ``yield`` statement instead of ``return``, all
-the code after the *yield* statement serves as the teardown code:
+.. _`autouse order`:
 
-.. code-block:: python
+Autouse fixtures are executed first within their scope
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-    # content of conftest.py
+Autouse fixtures are assumed to apply to every test that could reference them,
+so they are executed before other fixtures in that scope. Fixtures that are
+requested by autouse fixtures effectively become autouse fixtures themselves for
+the tests that the real autouse fixture applies to.
 
-    import smtplib
-    import pytest
+So if fixture ``a`` is autouse and fixture ``b`` is not, but fixture ``a``
+requests fixture ``b``, then fixture ``b`` will effectively be an autouse
+fixture as well, but only for the tests that ``a`` applies to.
 
+In the last example, the graph became unclear if ``d`` didn't request ``c``. But
+if ``c`` was autouse, then ``b`` and ``a`` would effectively also be autouse
+because ``c`` depends on them. As a result, they would all be shifted above
+non-autouse fixtures within that scope.
 
-    @pytest.fixture(scope="module")
-    def smtp_connection():
-        smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
-        yield smtp_connection  # provide the fixture value
-        print("teardown smtp")
-        smtp_connection.close()
+So if the test file looked like this:
 
-The ``print`` and ``smtp.close()`` statements will execute when the last test in
-the module has finished execution, regardless of the exception status of the
-tests.
+.. literalinclude:: example/fixtures/test_fixtures_order_autouse.py
 
-Let's execute it:
+the graph would look like this:
 
-.. code-block:: pytest
+.. image:: example/fixtures/test_fixtures_order_autouse.svg
+    :align: center
 
-    $ pytest -s -q --tb=no
-    FFteardown smtp
+Because ``c`` can now be put above ``d`` in the graph, pytest can once again
+linearize the graph to this:
 
-    ========================= short test summary info ==========================
-    FAILED test_module.py::test_ehlo - assert 0
-    FAILED test_module.py::test_noop - assert 0
-    2 failed in 0.12s
+In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as
+well.
 
-We see that the ``smtp_connection`` instance is finalized after the two
-tests finished execution.  Note that if we decorated our fixture
-function with ``scope='function'`` then fixture setup and cleanup would
-occur around each single test.  In either case the test
-module itself does not need to change or know about these details
-of fixture setup.
+Be careful with autouse, though, as an autouse fixture will automatically
+execute for every test that can reach it, even if they don't request it. For
+example, consider this file:
 
-Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements:
+.. literalinclude:: example/fixtures/test_fixtures_order_autouse_multiple_scopes.py
 
-.. code-block:: python
+Even though nothing in ``TestClassWithC1Request`` is requesting ``c1``, it still
+is executed for the tests inside it anyway:
 
-    # content of test_yield2.py
+.. image:: example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg
+    :align: center
 
-    import smtplib
-    import pytest
+But just because one autouse fixture requested a non-autouse fixture, that
+doesn't mean the non-autouse fixture becomes an autouse fixture for all contexts
+that it can apply to. It only effectively becomes an auotuse fixture for the
+contexts the real autouse fixture (the one that requested the non-autouse
+fixture) can apply to.
 
+For example, take a look at this test file:
 
-    @pytest.fixture(scope="module")
-    def smtp_connection():
-        with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
-            yield smtp_connection  # provide the fixture value
+.. literalinclude:: example/fixtures/test_fixtures_order_autouse_temp_effects.py
 
+It would break down to something like this:
 
-The ``smtp_connection`` connection will be closed after the test finished
-execution because the ``smtp_connection`` object automatically closes when
-the ``with`` statement ends.
+.. image:: example/fixtures/test_fixtures_order_autouse_temp_effects.svg
+    :align: center
 
-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:
+For ``test_req`` and ``test_no_req`` inside ``TestClassWithAutouse``, ``c3``
+effectively makes ``c2`` an autouse fixture, which is why ``c2`` and ``c3`` are
+executed for both tests, despite not being requested, and why ``c2`` and ``c3``
+are executed before ``c1`` for ``test_req``.
 
-.. code-block:: python
+If this made ``c2`` an *actual* autouse fixture, then ``c2`` would also execute
+for the tests inside ``TestClassWithoutAutouse``, since they can reference
+``c2`` if they wanted to. But it doesn't, because from the perspective of the
+``TestClassWithoutAutouse`` tests, ``c2`` isn't an autouse fixture, since they
+can't see ``c3``.
 
-    # content of test_yield3.py
 
-    import contextlib
+.. note:
 
-    import pytest
+    pytest can tell you what order the fixtures will execute in for a given test
+    if you call ``pytests`` along with the test's name (or the scope it's in),
+    and provide the ``--setup-plan`` flag, e.g.
+    ``pytest --setup-plan test_something.py`` (fixtures with names that start
+    with ``_`` will only be shown if you also provide the ``-v`` flag).
 
 
-    @contextlib.contextmanager
-    def connect(port):
-        ...  # create connection
-        yield
-        ...  # close connection
+Running multiple ``assert`` statements safely
+---------------------------------------------
 
+Sometimes you may want to run multiple asserts after doing all that setup, which
+makes sense as, in more complex systems, a single action can kick off multiple
+behaviors. pytest has a convenient way of handling this and it combines a bunch
+of what we've gone over so far.
 
-    @pytest.fixture
-    def equipments():
-        with contextlib.ExitStack() as stack:
-            yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")]
+All that's needed is stepping up to a larger scope, then having the **act**
+step defined as an autouse fixture, and finally, making sure all the fixtures
+are targetting that highler level scope.
 
-In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
-be properly closed.
+Let's pull :ref:`an example from above <safe fixture structure>`, and tweak it a
+bit. Let's say that in addition to checking for a welcome message in the header,
+we also want to check for a sign out button, and a link to the user's profile.
 
-Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
-*teardown* code (after the ``yield``) will not be called.
+Let's take a look at how we can structure that so we can run multiple asserts
+without having to repeat all those steps again.
 
-An alternative option for executing *teardown* code is to
-make use of the ``addfinalizer`` method of the `request-context`_ object to register
-finalization functions.
+.. note::
 
-Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for cleanup:
+    For this example, certain fixtures (i.e. ``base_url`` and
+    ``admin_credentials``) are implied to exist elsewhere. So for now, let's
+    assume they exist, and we're just not looking at them.
 
 .. code-block:: python
 
-    # content of conftest.py
-    import smtplib
+    # contents of tests/end_to_end/test_login.py
+    from uuid import uuid4
+    from urllib.parse import urljoin
+
+    from selenium.webdriver import Chrome
     import pytest
 
+    from src.utils.pages import LoginPage, LandingPage
+    from src.utils import AdminApiClient
+    from src.utils.data_types import User
 
-    @pytest.fixture(scope="module")
-    def smtp_connection(request):
-        smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
 
-        def fin():
-            print("teardown smtp_connection")
-            smtp_connection.close()
+    @pytest.fixture(scope="class")
+    def admin_client(base_url, admin_credentials):
+        return AdminApiClient(base_url, **admin_credentials)
 
-        request.addfinalizer(fin)
-        return smtp_connection  # provide the fixture value
 
+    @pytest.fixture(scope="class")
+    def user(admin_client):
+        _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
+        admin_client.create_user(_user)
+        yield _user
+        admin_client.delete_user(_user)
 
-Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup:
 
-.. code-block:: python
+    @pytest.fixture(scope="class")
+    def driver():
+        _driver = Chrome()
+        yield _driver
+        _driver.quit()
 
-    # content of test_yield3.py
 
-    import contextlib
-    import functools
+    @pytest.fixture(scope="class")
+    def landing_page(driver, login):
+        return LandingPage(driver)
 
-    import pytest
 
+    class TestLandingPageSuccess:
+        @pytest.fixture(scope="class", autouse=True)
+        def login(self, driver, base_url, user):
+            driver.get(urljoin(base_url, "/login"))
+            page = LoginPage(driver)
+            page.login(user)
 
-    @contextlib.contextmanager
-    def connect(port):
-        ...  # create connection
-        yield
-        ...  # close connection
+        def test_name_in_header(self, landing_page, user):
+            assert landing_page.header == f"Welcome, {user.name}!"
 
+        def test_sign_out_button(self, landing_page):
+            assert landing_page.sign_out_button.is_displayed()
 
-    @pytest.fixture
-    def equipments(request):
-        r = []
-        for port in ("C1", "C3", "C28"):
-            cm = connect(port)
-            equip = cm.__enter__()
-            request.addfinalizer(functools.partial(cm.__exit__, None, None, None))
-            r.append(equip)
-        return r
+        def test_profile_link(self, landing_page, user):
+            profile_href = urljoin(base_url, f"/profile?id={user.profile_id}")
+            assert landing_page.profile_link.get_attribute("href") == profile_href
 
+Notice that the methods are only referencing ``self`` in the signature as a
+formality. No state is tied to the actual test class as it might be in the
+``unittest.TestCase`` framework. Everything is managed by the pytest fixture
+system.
 
-Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
-ends. Of course, if an exception happens before the finalize function is registered then it
-will not be executed.
+Each method only has to request the fixtures that it actually needs without
+worrying about order. This is because the **act** fixture is an autouse fixture,
+and it made sure all the other fixtures executed before it. There's no more
+changes of state that need to take place, so the tests are free to make as many
+non-state-changing queries as they want without risking stepping on the toes of
+the other tests.
+
+The ``login`` fixture is defined inside the class as well, because not every one
+of the other tests in the module will be expecting a successful login, and the **act** may need to
+be handled a little differently for another test class. For example, if we
+wanted to write another test scenario around submitting bad credentials, we
+could handle it by adding something like this to the test file:
+
+.. note:
+
+    It's assumed that the page object for this (i.e. ``LoginPage``) raises a
+    custom exception, ``BadCredentialsException``, when it recognizes text
+    signifying that on the login form after attempting to log in.
+
+.. code-block:: python
+
+    class TestLandingPageBadCredentials:
+        @pytest.fixture(scope="class")
+        def faux_user(self, user):
+            _user = deepcopy(user)
+            _user.password = "badpass"
+            return _user
+
+        def test_raises_bad_credentials_exception(self, login_page, faux_user):
+            with pytest.raises(BadCredentialsException):
+                login_page.login(faux_user)
 
 
 .. _`request-context`:
@@ -1239,116 +2267,7 @@ into an ini-file:
     Currently this will not generate any error or warning, but this is intended
     to be handled by `#3664 <https://github.com/pytest-dev/pytest/issues/3664>`_.
 
-
-.. _`autouse`:
-.. _`autouse fixtures`:
-
-Autouse fixtures (xUnit setup on steroids)
-----------------------------------------------------------------------
-
-.. regendoc:wipe
-
-Occasionally, you may want to have fixtures get invoked automatically
-without declaring a function argument explicitly or a `usefixtures`_ decorator.
-As a practical example, suppose we have a database fixture which has a
-begin/rollback/commit architecture and we want to automatically surround
-each test method by a transaction and a rollback.  Here is a dummy
-self-contained implementation of this idea:
-
-.. code-block:: python
-
-    # content of test_db_transact.py
-
-    import pytest
-
-
-    class DB:
-        def __init__(self):
-            self.intransaction = []
-
-        def begin(self, name):
-            self.intransaction.append(name)
-
-        def rollback(self):
-            self.intransaction.pop()
-
-
-    @pytest.fixture(scope="module")
-    def db():
-        return DB()
-
-
-    class TestClass:
-        @pytest.fixture(autouse=True)
-        def transact(self, request, db):
-            db.begin(request.function.__name__)
-            yield
-            db.rollback()
-
-        def test_method1(self, db):
-            assert db.intransaction == ["test_method1"]
-
-        def test_method2(self, db):
-            assert db.intransaction == ["test_method2"]
-
-The class-level ``transact`` fixture is marked with *autouse=true*
-which implies that all test methods in the class will use this fixture
-without a need to state it in the test function signature or with a
-class-level ``usefixtures`` decorator.
-
-If we run it, we get two passing tests:
-
-.. code-block:: pytest
-
-    $ pytest -q
-    ..                                                                   [100%]
-    2 passed in 0.12s
-
-Here is how autouse fixtures work in other scopes:
-
-- autouse fixtures obey the ``scope=`` keyword-argument: if an autouse fixture
-  has ``scope='session'`` it will only be run once, no matter where it is
-  defined. ``scope='class'`` means it will be run once per class, etc.
-
-- if an autouse fixture is defined in a test module, all its test
-  functions automatically use it.
-
-- if an autouse fixture is defined in a conftest.py file then all tests in
-  all test modules below its directory will invoke the fixture.
-
-- lastly, and **please use that with care**: if you define an autouse
-  fixture in a plugin, it will be invoked for all tests in all projects
-  where the plugin is installed.  This can be useful if a fixture only
-  anyway works in the presence of certain settings e. g. in the ini-file.  Such
-  a global fixture should always quickly determine if it should do
-  any work and avoid otherwise expensive imports or computation.
-
-Note that the above ``transact`` fixture may very well be a fixture that
-you want to make available in your project without having it generally
-active.  The canonical way to do that is to put the transact definition
-into a conftest.py file **without** using ``autouse``:
-
-.. code-block:: python
-
-    # content of conftest.py
-    @pytest.fixture
-    def transact(request, db):
-        db.begin()
-        yield
-        db.rollback()
-
-and then e.g. have a TestClass using it by declaring the need:
-
-.. code-block:: python
-
-    @pytest.mark.usefixtures("transact")
-    class TestClass:
-        def test_method1(self):
-            ...
-
-All test methods in this TestClass will use the transaction fixture while
-other test classes or functions in the module will not use it unless
-they also add a ``transact`` reference.
+.. _`override fixtures`:
 
 Overriding fixtures on various levels
 -------------------------------------
index 09410585dc7af8444e6f2bf3db15401ce3075c95..1275dff902e621ff85bfc99dc545841a878bc4e1 100644 (file)
@@ -28,7 +28,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 6.2.1
+    pytest 6.2.2
 
 .. _`simpletest`:
 
index ad2057ff14a9a248cd8e50ab72a1dd0c9a0c860e..58f6c1d86c7a11f4029715a9f5014f8abe79d833 100644 (file)
@@ -2,7 +2,7 @@
 
 .. sidebar:: Next Open Trainings
 
-   - `Professional testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via Python Academy, February 1-3 2021, Leipzig (Germany) and remote.
+   - `Professional testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via Python Academy, February 1-3 2021, remote and Leipzig (Germany). **Early-bird discount available until January 15th**.
 
    Also see `previous talks and blogposts <talks.html>`_.
 
index 8aa95ca644835cb40804613239d8f6ce84b7aa44..658516a50d20e69093812c0a17a4cbdcf0693c2e 100644 (file)
@@ -3,6 +3,8 @@
 API Reference
 =============
 
+.. module:: pytest
+
 This page contains the full reference to pytest's API.
 
 .. contents::
index 09c07d5bb6c34140302e6a59ed608bd5e18e285c..14fdb6df5c020bdae5c240830024f9407ae67412 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,6 +26,8 @@ classifiers =
     Topic :: Utilities
 keywords = test, unittest
 project_urls =
+    Changelog=https://docs.pytest.org/en/stable/changelog.html
+    Twitter=https://twitter.com/pytestdotorg
     Source=https://github.com/pytest-dev/pytest
     Tracker=https://github.com/pytest-dev/pytest/issues
 
index d0cc0430c490dd4923ff6818a333df67bf74fd73..ff673b5b164e94b7e8348cc50e4fdb846be6e787 100644 (file)
@@ -69,7 +69,12 @@ class FaultHandlerHooks:
     @staticmethod
     def _get_stderr_fileno():
         try:
-            return sys.stderr.fileno()
+            fileno = sys.stderr.fileno()
+            # The Twisted Logger will return an invalid file descriptor since it is not backed
+            # by an FD. So, let's also forward this to the same code path as with pytest-xdist.
+            if fileno == -1:
+                raise AttributeError()
+            return fileno
         except (AttributeError, io.UnsupportedOperation):
             # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file.
             # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors
index f0607cbd849f10232a48d394267ed2629b1e4554..8f6203fd7fa64e525c0b545c8f2d5706c6edf1e8 100644 (file)
@@ -38,7 +38,7 @@ class OutcomeException(BaseException):
         self.pytrace = pytrace
 
     def __repr__(self) -> str:
-        if self.msg:
+        if self.msg is not None:
             return self.msg
         return f"<{self.__class__.__name__} instance>"
 
index 0e0ed70e5be9bc7970c6a712ed077179170b5726..fbfb09aecfb0869ff6f423cd2f4df8ec2c47d97d 100644 (file)
@@ -1400,4 +1400,6 @@ def _get_raw_skip_reason(report: TestReport) -> str:
         _, _, reason = report.longrepr
         if reason.startswith("Skipped: "):
             reason = reason[len("Skipped: ") :]
+        elif reason == "Skipped":
+            reason = ""
         return reason
index caf39813cf4a1c461aa4365a7c739e75d64c712f..370084c125f08f850f7901250bd7e589103524a8 100644 (file)
@@ -1,3 +1,4 @@
+import io
 import sys
 
 import pytest
@@ -135,3 +136,27 @@ def test_already_initialized(faulthandler_timeout: int, pytester: Pytester) -> N
         result.stdout.no_fnmatch_line(warning_line)
     result.stdout.fnmatch_lines("*1 passed*")
     assert result.ret == 0
+
+
+def test_get_stderr_fileno_invalid_fd() -> None:
+    """Test for faulthandler being able to handle invalid file descriptors for stderr (#8249)."""
+    from _pytest.faulthandler import FaultHandlerHooks
+
+    class StdErrWrapper(io.StringIO):
+        """
+        Mimic ``twisted.logger.LoggingFile`` to simulate returning an invalid file descriptor.
+
+        https://github.com/twisted/twisted/blob/twisted-20.3.0/src/twisted/logger/_io.py#L132-L139
+        """
+
+        def fileno(self):
+            return -1
+
+    wrapper = StdErrWrapper()
+
+    with pytest.MonkeyPatch.context() as mp:
+        mp.setattr("sys.stderr", wrapper)
+
+        # Even when the stderr wrapper signals an invalid file descriptor,
+        # ``_get_stderr_fileno()`` should return the real one.
+        assert FaultHandlerHooks._get_stderr_fileno() == 2
index 7ad5849d4b96ea224209131b8ce62dd009c97091..5e833f40d4395928511bf17b743fcae6b9d24425 100644 (file)
@@ -366,6 +366,26 @@ class TestTerminal:
             @pytest.mark.xfail(reason="")
             def test_4():
                 assert False
+
+            @pytest.mark.skip
+            def test_5():
+                pass
+
+            @pytest.mark.xfail
+            def test_6():
+                pass
+
+            def test_7():
+                pytest.skip()
+
+            def test_8():
+                pytest.skip("888 is great")
+
+            def test_9():
+                pytest.xfail()
+
+            def test_10():
+                pytest.xfail("It's 🕙 o'clock")
         """
         )
         result = pytester.runpytest("-v")
@@ -375,6 +395,12 @@ class TestTerminal:
                 "test_verbose_skip_reason.py::test_2 XPASS (456) *",
                 "test_verbose_skip_reason.py::test_3 XFAIL (789) *",
                 "test_verbose_skip_reason.py::test_4 XFAIL  *",
+                "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *",
+                "test_verbose_skip_reason.py::test_6 XPASS  *",
+                "test_verbose_skip_reason.py::test_7 SKIPPED  *",
+                "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *",
+                "test_verbose_skip_reason.py::test_9 XFAIL  *",
+                "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *",
             ]
         )