Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/stepwise.py: 42%
74 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
1from typing import List
2from typing import Optional
3from typing import TYPE_CHECKING
5import pytest
6from _pytest import nodes
7from _pytest.config import Config
8from _pytest.config.argparsing import Parser
9from _pytest.main import Session
10from _pytest.reports import TestReport
12if TYPE_CHECKING:
13 from _pytest.cacheprovider import Cache
15STEPWISE_CACHE_DIR = "cache/stepwise"
18def pytest_addoption(parser: Parser) -> None:
19 group = parser.getgroup("general")
20 group.addoption(
21 "--sw",
22 "--stepwise",
23 action="store_true",
24 default=False,
25 dest="stepwise",
26 help="Exit on test failure and continue from last failing test next time",
27 )
28 group.addoption(
29 "--sw-skip",
30 "--stepwise-skip",
31 action="store_true",
32 default=False,
33 dest="stepwise_skip",
34 help="Ignore the first failing test but stop on the next failing test. "
35 "Implicitly enables --stepwise.",
36 )
39@pytest.hookimpl
40def pytest_configure(config: Config) -> None:
41 if config.option.stepwise_skip:
42 # allow --stepwise-skip to work on it's own merits.
43 config.option.stepwise = True
44 if config.getoption("stepwise"):
45 config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin")
48def pytest_sessionfinish(session: Session) -> None:
49 if not session.config.getoption("stepwise"):
50 assert session.config.cache is not None
51 if hasattr(session.config, "workerinput"):
52 # Do not update cache if this process is a xdist worker to prevent
53 # race conditions (#10641).
54 return
55 # Clear the list of failing tests if the plugin is not active.
56 session.config.cache.set(STEPWISE_CACHE_DIR, [])
59class StepwisePlugin:
60 def __init__(self, config: Config) -> None:
61 self.config = config
62 self.session: Optional[Session] = None
63 self.report_status = ""
64 assert config.cache is not None
65 self.cache: Cache = config.cache
66 self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None)
67 self.skip: bool = config.getoption("stepwise_skip")
69 def pytest_sessionstart(self, session: Session) -> None:
70 self.session = session
72 def pytest_collection_modifyitems(
73 self, config: Config, items: List[nodes.Item]
74 ) -> None:
75 if not self.lastfailed:
76 self.report_status = "no previously failed tests, not skipping."
77 return
79 # check all item nodes until we find a match on last failed
80 failed_index = None
81 for index, item in enumerate(items):
82 if item.nodeid == self.lastfailed:
83 failed_index = index
84 break
86 # If the previously failed test was not found among the test items,
87 # do not skip any tests.
88 if failed_index is None:
89 self.report_status = "previously failed test not found, not skipping."
90 else:
91 self.report_status = f"skipping {failed_index} already passed items."
92 deselected = items[:failed_index]
93 del items[:failed_index]
94 config.hook.pytest_deselected(items=deselected)
96 def pytest_runtest_logreport(self, report: TestReport) -> None:
97 if report.failed:
98 if self.skip:
99 # Remove test from the failed ones (if it exists) and unset the skip option
100 # to make sure the following tests will not be skipped.
101 if report.nodeid == self.lastfailed:
102 self.lastfailed = None
104 self.skip = False
105 else:
106 # Mark test as the last failing and interrupt the test session.
107 self.lastfailed = report.nodeid
108 assert self.session is not None
109 self.session.shouldstop = (
110 "Test failed, continuing from this test next run."
111 )
113 else:
114 # If the test was actually run and did pass.
115 if report.when == "call":
116 # Remove test from the failed ones, if exists.
117 if report.nodeid == self.lastfailed:
118 self.lastfailed = None
120 def pytest_report_collectionfinish(self) -> Optional[str]:
121 if self.config.getoption("verbose") >= 0 and self.report_status:
122 return f"stepwise: {self.report_status}"
123 return None
125 def pytest_sessionfinish(self) -> None:
126 if hasattr(self.config, "workerinput"):
127 # Do not update cache if this process is a xdist worker to prevent
128 # race conditions (#10641).
129 return
130 self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)