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