20
20
21
21
import pytest
22
22
23
-
24
23
TRACE = True
25
24
26
25
"""
34
33
branches all the tests run: none are skipped.
35
34
"""
36
35
37
-
38
-
39
36
################################################################################
40
37
# pytest custom markers and CLI options
41
38
################################################################################
44
41
45
42
46
43
def pytest_configure (config ):
47
- config .addinivalue_line ('markers' , SLOW_TEST + ': Mark a ScanCode test as a slow, long running test.' )
48
- config .addinivalue_line ('markers' , VALIDATION_TEST + ': Mark a ScanCode test as a validation test, super slow, long running test.' )
44
+ config .addinivalue_line (
45
+ 'markers' ,
46
+ SLOW_TEST +
47
+ ': Mark a ScanCode test as a slow, long running test.' ,
48
+ )
49
+
50
+ config .addinivalue_line (
51
+ 'markers' ,
52
+ VALIDATION_TEST +
53
+ ': Mark a ScanCode test as a validation test, super slow, long running test.' ,
54
+ )
49
55
50
56
51
57
TEST_SUITES = 'standard' , 'all' , 'validate'
@@ -71,62 +77,20 @@ def pytest_addoption(parser):
71
77
'Use the @pytest.mark.scanvalidate marker to mark a test as a "validate" test.'
72
78
)
73
79
74
- group .addoption (
75
- '--changed-only' ,
76
- dest = 'changed_only' ,
77
- action = 'store_true' ,
78
- default = False ,
79
- help = 'Run only the subset of tests impacted by your changes.'
80
- 'If selected, you can provide an optional --base-branch and the '
81
- 'changes are checked against that branch. '
82
- 'Otherwise, a git diff is made against the current branch.' ,
83
- )
84
-
85
- group .addoption (
86
- '--base-branch' ,
87
- dest = 'base_branch' ,
88
- action = 'store' ,
89
- default = None ,
90
- help = 'Optional name branch of the branch diff against to find what has '
91
- 'changed if --changed-only is selected.' ,
92
- )
93
-
94
- group .addoption (
95
- '--dry-run' ,
96
- dest = 'dry_run' ,
97
- action = 'store_true' ,
98
- default = False ,
99
- help = 'Only print selected and deselected tests. Do not run anything.' ,
100
- )
101
-
102
80
################################################################################
103
- # Filter whcih tests to collect based on our CLI options and our custom markers
81
+ # Filter which tests to collect based on our CLI options and our custom markers
104
82
################################################################################
105
83
84
+
106
85
@pytest .mark .trylast
107
86
def pytest_collection_modifyitems (config , items ):
108
87
test_suite = config .getvalue ('test_suite' )
109
- changed_only = config .getoption ('changed_only' )
110
- base_branch = config .getoption ('base_branch' )
111
- dry_run = config .getoption ('dry_run' )
112
-
113
88
run_everything = test_suite == 'validate'
114
89
run_slow_test = test_suite in ('all' , 'validate' )
115
90
116
91
tests_to_run = []
117
92
tests_to_skip = []
118
93
119
- if changed_only :
120
- base_branch = base_branch or get_git_branch ()
121
- impacted_modules = get_impacted_modules (base_branch ) or set ()
122
- all_is_changed = not (impacted_modules )
123
- impacted_modules_paths = ['/{}/' .format (m ) for m in impacted_modules ]
124
- print ()
125
- if not impacted_modules :
126
- print ('All modules impacted' )
127
- else :
128
- print ('Run tests only in these changed modules:' , ', ' .join (sorted (impacted_modules )))
129
-
130
94
for item in items :
131
95
is_validate = bool (item .get_closest_marker (VALIDATION_TEST ))
132
96
is_slow = bool (item .get_closest_marker (SLOW_TEST ))
@@ -139,162 +103,10 @@ def pytest_collection_modifyitems(config, items):
139
103
tests_to_skip .append (item )
140
104
continue
141
105
142
- if changed_only and not all_is_changed :
143
- if not is_changed (item .fspath , impacted_modules_paths ):
144
- tests_to_skip .append (item )
145
- continue
146
-
147
106
tests_to_run .append (item )
148
107
149
108
print ()
150
109
print ('{} tests selected, {} tests skipped.' .format (len (tests_to_run ), len (tests_to_skip )))
151
110
152
- if dry_run :
153
- if config .getvalue ('verbose' ):
154
- print ()
155
- print ('The following tests will run: ' )
156
- for test in tests_to_run :
157
- print (test .nodeid )
158
-
159
- print ('The following tests are skipped: ' )
160
- for test in tests_to_skip :
161
- print (test .nodeid )
162
-
163
- tests = items [:]
164
- items [:] = []
165
- config .hook .pytest_deselected (items = tests )
166
- return
167
-
168
-
169
111
items [:] = tests_to_run
170
112
config .hook .pytest_deselected (items = tests_to_skip )
171
-
172
-
173
- ################################################################################
174
- # Retest only tests for changed modules
175
- ################################################################################
176
-
177
-
178
- def is_changed (path_string , impacted_module_paths , _cache = {}):
179
- """
180
- Return True if a `path_string` is for a path that has changed.
181
- """
182
- path_string = str (path_string ).replace ('\\ ' , '/' )
183
- cached = _cache .get (path_string )
184
- if cached is not None :
185
- return cached
186
-
187
- if path_string .endswith (('setup.py' , 'conftest.py' )):
188
- return False
189
- changed = any (p in path_string for p in impacted_module_paths )
190
- if TRACE and changed :
191
- print ('is_changed:' , path_string , changed )
192
- _cache [path_string ] = changed
193
- return changed
194
-
195
-
196
- def get_all_modules ():
197
- """
198
- Return a set of top level modules.
199
- """
200
- all_modules = set ([p for p in os .listdir ('src' ) if p .endswith ('code' )])
201
- if TRACE :
202
- print ()
203
- print ('get_all_modules:' , all_modules )
204
- return all_modules
205
-
206
-
207
- def get_impacted_modules (base_branch = None ):
208
- """
209
- Return a set of impacted top level modules under tests or src.
210
- Return None if all modules are impacted and should be re-tested.
211
- """
212
- try :
213
- base_branch = base_branch or get_git_branch ()
214
- changed_files = get_changed_files (base_branch )
215
- locally_changed_files = get_changed_files (None )
216
- changed_files .extend (locally_changed_files )
217
- except Exception as e :
218
- # we test it all if we cannot get proper git information
219
- print (e )
220
- return
221
-
222
- changed_modules = set ()
223
- for changed_file in changed_files :
224
- segments = [s for s in changed_file .split ('/' ) if s ]
225
-
226
- if segments [0 ] == 'thirdparty' :
227
- # test it all when changing thirdparty deps
228
- return
229
-
230
- if segments [0 ] not in ('src' , 'tests' ):
231
- # test none on changes to other files
232
- continue
233
-
234
- module = segments [1 ]
235
- changed_modules .add (module )
236
-
237
- force_full_test = [
238
- 'scancode' ,
239
- 'commoncode' ,
240
- 'typecode' ,
241
- 'textcode' ,
242
- 'plugincode' ,
243
- ]
244
-
245
- if any (m in changed_modules for m in force_full_test ):
246
- # test it all when certain widely dependended modules
247
- return
248
-
249
- # add dependencies
250
- if 'licensedcode' in changed_modules :
251
- changed_modules .add ('packagedcode' )
252
- changed_modules .add ('summarycode' )
253
- changed_modules .add ('formattedcode' )
254
- changed_modules .add ('scancode' )
255
-
256
- if 'cluecode' in changed_modules :
257
- changed_modules .add ('summarycode' )
258
- changed_modules .add ('formattedcode' )
259
- changed_modules .add ('scancode' )
260
-
261
- if TRACE :
262
- print ()
263
- print ('get_impacted_modules:' , changed_modules )
264
- return changed_modules
265
-
266
-
267
- def get_changed_files (base_branch = 'develop' ):
268
- """
269
- Return a list of changed file paths against the `base_branch`.
270
- Or locally only if `base_branch` is None.
271
- Raise an exception on errors.
272
- """
273
- # this may fail with exceptions
274
- cmd = 'git' , 'diff' , '--name-only' ,
275
- if base_branch :
276
- cmd += base_branch + '...' ,
277
- changed_files = check_output (cmd , stderr = STDOUT )
278
- changed_files = changed_files .replace ('\\ ' , '/' )
279
- changed_files = changed_files .splitlines (False )
280
- changed_files = [cf for cf in changed_files if cf .strip ()]
281
- if TRACE :
282
- print ()
283
- print ('get_changed_files:' , changed_files )
284
- return changed_files
285
-
286
-
287
- def get_git_branch ():
288
- """
289
- Return the current branch or raise an exception.
290
- """
291
- from subprocess import check_output , STDOUT
292
- # this may fail with exceptions
293
- cmd = 'git' , 'status' ,
294
- branch = check_output (cmd , stderr = STDOUT ).splitlines (False )[0 ]
295
- _ , _ , branch = branch .partition ('On branch' )
296
- branch = branch .strip ()
297
- if TRACE :
298
- print ()
299
- print ('get_git_branch:' , branch )
300
- return branch
0 commit comments