7
7
import json
8
8
import os
9
9
from collections import OrderedDict
10
+ from typing import Dict
11
+ from typing import Generator
10
12
from typing import List
13
+ from typing import Optional
11
14
12
15
import attr
13
16
import py
16
19
from .pathlib import Path
17
20
from .pathlib import resolve_from_str
18
21
from .pathlib import rm_rf
22
+ from .reports import CollectReport
19
23
from _pytest import nodes
20
24
from _pytest ._io import TerminalWriter
21
25
from _pytest .config import Config
22
26
from _pytest .main import Session
27
+ from _pytest .python import Module
23
28
24
29
README_CONTENT = """\
25
30
# pytest cache directory #
@@ -161,18 +166,83 @@ def _ensure_supporting_files(self):
161
166
cachedir_tag_path .write_bytes (CACHEDIR_TAG_CONTENT )
162
167
163
168
169
+ class LFPluginCollWrapper :
170
+ def __init__ (self , lfplugin : "LFPlugin" ):
171
+ self .lfplugin = lfplugin
172
+ self ._collected_at_least_one_failure = False
173
+
174
+ @pytest .hookimpl (hookwrapper = True )
175
+ def pytest_make_collect_report (self , collector ) -> Generator :
176
+ lf_paths = self .lfplugin .last_failed_paths ()
177
+ if isinstance (collector , Session ):
178
+ out = yield
179
+ res = out .get_result () # type: CollectReport
180
+
181
+ # Sort any lf-paths to the beginning.
182
+ res .result = sorted (
183
+ res .result , key = lambda x : 0 if Path (x .fspath ) in lf_paths else 1
184
+ )
185
+ out .force_result (res )
186
+ return
187
+
188
+ elif isinstance (collector , Module ):
189
+ if Path (collector .fspath ) in lf_paths :
190
+ out = yield
191
+ res = out .get_result ()
192
+
193
+ filtered_result = [
194
+ x for x in res .result if x .nodeid in self .lfplugin .lastfailed
195
+ ]
196
+ if filtered_result :
197
+ res .result = filtered_result
198
+ out .force_result (res )
199
+
200
+ if not self ._collected_at_least_one_failure :
201
+ self .lfplugin .config .pluginmanager .register (
202
+ LFPluginCollSkipfiles (self .lfplugin ), "lfplugin-collskip"
203
+ )
204
+ self ._collected_at_least_one_failure = True
205
+ return res
206
+ yield
207
+
208
+
209
+ class LFPluginCollSkipfiles :
210
+ def __init__ (self , lfplugin : "LFPlugin" ):
211
+ self .lfplugin = lfplugin
212
+
213
+ @pytest .hookimpl
214
+ def pytest_make_collect_report (self , collector ) -> Optional [CollectReport ]:
215
+ if isinstance (collector , Module ):
216
+ lf_paths = self .lfplugin .last_failed_paths ()
217
+ if Path (collector .fspath ) not in lf_paths :
218
+ self .lfplugin ._skipped_files += 1
219
+
220
+ return CollectReport (
221
+ collector .nodeid , "passed" , longrepr = None , result = []
222
+ )
223
+ return None
224
+
225
+
164
226
class LFPlugin :
165
227
""" Plugin which implements the --lf (run last-failing) option """
166
228
167
- def __init__ (self , config ) :
229
+ def __init__ (self , config : Config ) -> None :
168
230
self .config = config
169
231
active_keys = "lf" , "failedfirst"
170
232
self .active = any (config .getoption (key ) for key in active_keys )
171
- self .lastfailed = config .cache .get ("cache/lastfailed" , {})
233
+ assert config .cache
234
+ self .lastfailed = config .cache .get (
235
+ "cache/lastfailed" , {}
236
+ ) # type: Dict[str, bool]
172
237
self ._previously_failed_count = None
173
238
self ._report_status = None
174
239
self ._skipped_files = 0 # count skipped files during collection due to --lf
175
240
241
+ if config .getoption ("lf" ):
242
+ config .pluginmanager .register (
243
+ LFPluginCollWrapper (self ), "lfplugin-collwrapper"
244
+ )
245
+
176
246
def last_failed_paths (self ):
177
247
"""Returns a set with all Paths()s of the previously failed nodeids (cached).
178
248
"""
@@ -185,19 +255,6 @@ def last_failed_paths(self):
185
255
self ._last_failed_paths = result
186
256
return result
187
257
188
- def pytest_ignore_collect (self , path ):
189
- """
190
- Ignore this file path if we are in --lf mode and it is not in the list of
191
- previously failed files.
192
- """
193
- if self .active and self .config .getoption ("lf" ) and path .isfile ():
194
- last_failed_paths = self .last_failed_paths ()
195
- if last_failed_paths :
196
- skip_it = Path (path ) not in last_failed_paths
197
- if skip_it :
198
- self ._skipped_files += 1
199
- return skip_it
200
-
201
258
def pytest_report_collectionfinish (self ):
202
259
if self .active and self .config .getoption ("verbose" ) >= 0 :
203
260
return "run-last-failure: %s" % self ._report_status
@@ -380,7 +437,7 @@ def pytest_cmdline_main(config):
380
437
381
438
382
439
@pytest .hookimpl (tryfirst = True )
383
- def pytest_configure (config ) :
440
+ def pytest_configure (config : Config ) -> None :
384
441
config .cache = Cache .for_config (config )
385
442
config .pluginmanager .register (LFPlugin (config ), "lfplugin" )
386
443
config .pluginmanager .register (NFPlugin (config ), "nfplugin" )
0 commit comments