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

1from typing import List 

2from typing import Optional 

3from typing import TYPE_CHECKING 

4 

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 

11 

12if TYPE_CHECKING: 

13 from _pytest.cacheprovider import Cache 

14 

15STEPWISE_CACHE_DIR = "cache/stepwise" 

16 

17 

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 ) 

37 

38 

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") 

46 

47 

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, []) 

57 

58 

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") 

68 

69 def pytest_sessionstart(self, session: Session) -> None: 

70 self.session = session 

71 

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 

78 

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 

85 

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) 

95 

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 

103 

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 ) 

112 

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 

119 

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 

124 

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)