2020
2121import pytest
2222
23-
2423TRACE = True
2524
2625"""
3433branches all the tests run: none are skipped.
3534"""
3635
37-
38-
3936################################################################################
4037# pytest custom markers and CLI options
4138################################################################################
4441
4542
4643def 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+ )
4955
5056
5157TEST_SUITES = 'standard' , 'all' , 'validate'
@@ -71,62 +77,20 @@ def pytest_addoption(parser):
7177 'Use the @pytest.mark.scanvalidate marker to mark a test as a "validate" test.'
7278 )
7379
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-
10280################################################################################
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
10482################################################################################
10583
84+
10685@pytest .mark .trylast
10786def pytest_collection_modifyitems (config , items ):
10887 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-
11388 run_everything = test_suite == 'validate'
11489 run_slow_test = test_suite in ('all' , 'validate' )
11590
11691 tests_to_run = []
11792 tests_to_skip = []
11893
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-
13094 for item in items :
13195 is_validate = bool (item .get_closest_marker (VALIDATION_TEST ))
13296 is_slow = bool (item .get_closest_marker (SLOW_TEST ))
@@ -139,162 +103,10 @@ def pytest_collection_modifyitems(config, items):
139103 tests_to_skip .append (item )
140104 continue
141105
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-
147106 tests_to_run .append (item )
148107
149108 print ()
150109 print ('{} tests selected, {} tests skipped.' .format (len (tests_to_run ), len (tests_to_skip )))
151110
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-
169111 items [:] = tests_to_run
170112 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