[go: nahoru, domu]

blob: 9928c71f1beba210b00c1d8e6e550f422719befc [file] [log] [blame]
gayane3dff8c22014-12-04 17:09:511# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Chris Hall59f8d0c72020-05-01 07:31:195from collections import defaultdict
Daniel Cheng13ca61a882017-08-25 15:11:256import fnmatch
gayane3dff8c22014-12-04 17:09:517import json
8import os
9import re
10import subprocess
11import sys
12
Daniel Cheng264a447d2017-09-28 22:17:5913# TODO(dcheng): It's kind of horrible that this is copy and pasted from
14# presubmit_canned_checks.py, but it's far easier than any of the alternatives.
15def _ReportErrorFileAndLine(filename, line_num, dummy_line):
16 """Default error formatter for _FindNewViolationsOfRule."""
17 return '%s:%s' % (filename, line_num)
18
19
20class MockCannedChecks(object):
21 def _FindNewViolationsOfRule(self, callable_rule, input_api,
22 source_file_filter=None,
23 error_formatter=_ReportErrorFileAndLine):
24 """Find all newly introduced violations of a per-line rule (a callable).
25
26 Arguments:
27 callable_rule: a callable taking a file extension and line of input and
28 returning True if the rule is satisfied and False if there was a
29 problem.
30 input_api: object to enumerate the affected files.
31 source_file_filter: a filter to be passed to the input api.
32 error_formatter: a callable taking (filename, line_number, line) and
33 returning a formatted error string.
34
35 Returns:
36 A list of the newly-introduced violations reported by the rule.
37 """
38 errors = []
39 for f in input_api.AffectedFiles(include_deletes=False,
40 file_filter=source_file_filter):
41 # For speed, we do two passes, checking first the full file. Shelling out
42 # to the SCM to determine the changed region can be quite expensive on
43 # Win32. Assuming that most files will be kept problem-free, we can
44 # skip the SCM operations most of the time.
45 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
46 if all(callable_rule(extension, line) for line in f.NewContents()):
47 continue # No violation found in full text: can skip considering diff.
48
49 for line_num, line in f.ChangedContents():
50 if not callable_rule(extension, line):
51 errors.append(error_formatter(f.LocalPath(), line_num, line))
52
53 return errors
gayane3dff8c22014-12-04 17:09:5154
Zhiling Huang45cabf32018-03-10 00:50:0355
gayane3dff8c22014-12-04 17:09:5156class MockInputApi(object):
57 """Mock class for the InputApi class.
58
59 This class can be used for unittests for presubmit by initializing the files
60 attribute as the list of changed files.
61 """
62
Robert Ma0303a3ad2020-07-22 18:48:4863 DEFAULT_FILES_TO_SKIP = ()
Sylvain Defresnea8b73d252018-02-28 15:45:5464
gayane3dff8c22014-12-04 17:09:5165 def __init__(self):
Daniel Cheng264a447d2017-09-28 22:17:5966 self.canned_checks = MockCannedChecks()
Daniel Cheng13ca61a882017-08-25 15:11:2567 self.fnmatch = fnmatch
gayane3dff8c22014-12-04 17:09:5168 self.json = json
69 self.re = re
70 self.os_path = os.path
agrievebb9c5b472016-04-22 15:13:0071 self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5172 self.python_executable = sys.executable
Takuto Ikutadca10222022-04-13 02:51:2173 self.python3_executable = sys.executable
pastarmovj89f7ee12016-09-20 14:58:1374 self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5175 self.subprocess = subprocess
Dan Beam35b10c12019-11-27 01:17:3476 self.sys = sys
gayane3dff8c22014-12-04 17:09:5177 self.files = []
78 self.is_committing = False
gayanee1702662014-12-13 03:48:0979 self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3480 self.presubmit_local_path = os.path.dirname(__file__)
Bruce Dawson3740e0732022-04-07 16:17:2281 self.is_windows = sys.platform == 'win32'
Bruce Dawson344ab262022-06-04 11:35:1082 self.no_diffs = False
gayane3dff8c22014-12-04 17:09:5183
Zhiling Huang45cabf32018-03-10 00:50:0384 def CreateMockFileInPath(self, f_list):
85 self.os_path.exists = lambda x: x in f_list
86
Giovanni Ortuño Urquidiab84da62021-12-10 00:53:2187 def AffectedFiles(self, file_filter=None, include_deletes=True):
Sylvain Defresnea8b73d252018-02-28 15:45:5488 for file in self.files:
89 if file_filter and not file_filter(file):
90 continue
91 if not include_deletes and file.Action() == 'D':
92 continue
93 yield file
gayane3dff8c22014-12-04 17:09:5194
Lukasz Anforowicz7016d05e2021-11-30 03:56:2795 def RightHandSideLines(self, source_file_filter=None):
96 affected_files = self.AffectedSourceFiles(source_file_filter)
97 for af in affected_files:
98 lines = af.ChangedContents()
99 for line in lines:
100 yield (af, line[0], line[1])
101
glidere61efad2015-02-18 17:39:43102 def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:54103 return self.AffectedFiles(file_filter=file_filter)
104
Robert Ma0303a3ad2020-07-22 18:48:48105 def FilterSourceFile(self, file,
Josip Sokcevic8b6cc432020-08-05 17:45:33106 files_to_check=(), files_to_skip=()):
Sylvain Defresnea8b73d252018-02-28 15:45:54107 local_path = file.LocalPath()
Robert Ma0303a3ad2020-07-22 18:48:48108 found_in_files_to_check = not files_to_check
109 if files_to_check:
110 if type(files_to_check) is str:
111 raise TypeError('files_to_check should be an iterable of strings')
112 for pattern in files_to_check:
Sylvain Defresnea8b73d252018-02-28 15:45:54113 compiled_pattern = re.compile(pattern)
Henrique Ferreiro81d580022021-11-29 21:27:19114 if compiled_pattern.match(local_path):
Robert Ma0303a3ad2020-07-22 18:48:48115 found_in_files_to_check = True
Vaclav Brozekf01ed502018-03-16 19:38:24116 break
Robert Ma0303a3ad2020-07-22 18:48:48117 if files_to_skip:
118 if type(files_to_skip) is str:
119 raise TypeError('files_to_skip should be an iterable of strings')
120 for pattern in files_to_skip:
Sylvain Defresnea8b73d252018-02-28 15:45:54121 compiled_pattern = re.compile(pattern)
Henrique Ferreiro81d580022021-11-29 21:27:19122 if compiled_pattern.match(local_path):
Sylvain Defresnea8b73d252018-02-28 15:45:54123 return False
Robert Ma0303a3ad2020-07-22 18:48:48124 return found_in_files_to_check
glidere61efad2015-02-18 17:39:43125
davileene0426252015-03-02 21:10:41126 def LocalPaths(self):
Alexei Svitkine137d4c662019-07-17 21:28:24127 return [file.LocalPath() for file in self.files]
davileene0426252015-03-02 21:10:41128
gayane3dff8c22014-12-04 17:09:51129 def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34130 return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51131
132 def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43133 if hasattr(filename, 'AbsoluteLocalPath'):
134 filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51135 for file_ in self.files:
136 if file_.LocalPath() == filename:
137 return '\n'.join(file_.NewContents())
138 # Otherwise, file is not in our mock API.
Dirk Prankee3c9c62d2021-05-18 18:35:59139 raise IOError("No such file or directory: '%s'" % filename)
gayane3dff8c22014-12-04 17:09:51140
141
142class MockOutputApi(object):
gayane860db5c2014-12-05 16:16:46143 """Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51144
Gao Shenga79ebd42022-08-08 17:25:59145 An instance of this class can be passed to presubmit unittests for outputting
gayane3dff8c22014-12-04 17:09:51146 various types of results.
147 """
148
149 class PresubmitResult(object):
150 def __init__(self, message, items=None, long_text=''):
151 self.message = message
152 self.items = items
153 self.long_text = long_text
154
gayane940df072015-02-24 14:28:30155 def __repr__(self):
156 return self.message
157
gayane3dff8c22014-12-04 17:09:51158 class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41159 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51160 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
161 self.type = 'error'
162
163 class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41164 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51165 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
166 self.type = 'warning'
167
168 class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41169 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51170 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
171 self.type = 'notify'
172
173 class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41174 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51175 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
176 self.type = 'promptOrNotify'
177
Daniel Cheng7052cdf2017-11-21 19:23:29178 def __init__(self):
179 self.more_cc = []
180
181 def AppendCC(self, more_cc):
Kevin McNee967dd2d22021-11-15 16:09:29182 self.more_cc.append(more_cc)
Daniel Cheng7052cdf2017-11-21 19:23:29183
gayane3dff8c22014-12-04 17:09:51184
185class MockFile(object):
186 """Mock class for the File class.
187
188 This class can be used to form the mock list of changed files in
189 MockInputApi for presubmit unittests.
190 """
191
Dominic Battre645d42342020-12-04 16:14:10192 def __init__(self, local_path, new_contents, old_contents=None, action='A',
193 scm_diff=None):
gayane3dff8c22014-12-04 17:09:51194 self._local_path = local_path
195 self._new_contents = new_contents
196 self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40197 self._action = action
Dominic Battre645d42342020-12-04 16:14:10198 if scm_diff:
199 self._scm_diff = scm_diff
200 else:
201 self._scm_diff = (
202 "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
203 (local_path, len(new_contents)))
204 for l in new_contents:
205 self._scm_diff += "+%s\n" % l
Yoland Yanb92fa522017-08-28 17:37:06206 self._old_contents = old_contents
gayane3dff8c22014-12-04 17:09:51207
dbeam37e8e7402016-02-10 22:58:20208 def Action(self):
agrievef32bcc72016-04-04 14:57:40209 return self._action
dbeam37e8e7402016-02-10 22:58:20210
gayane3dff8c22014-12-04 17:09:51211 def ChangedContents(self):
212 return self._changed_contents
213
214 def NewContents(self):
215 return self._new_contents
216
217 def LocalPath(self):
218 return self._local_path
219
rdevlin.cronin9ab806c2016-02-26 23:17:13220 def AbsoluteLocalPath(self):
221 return self._local_path
222
jbriance9e12f162016-11-25 07:57:50223 def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31224 return self._scm_diff
jbriance9e12f162016-11-25 07:57:50225
Yoland Yanb92fa522017-08-28 17:37:06226 def OldContents(self):
227 return self._old_contents
228
davileene0426252015-03-02 21:10:41229 def rfind(self, p):
230 """os.path.basename is called on MockFile so we need an rfind method."""
231 return self._local_path.rfind(p)
232
233 def __getitem__(self, i):
234 """os.path.basename is called on MockFile so we need a get method."""
235 return self._local_path[i]
236
pastarmovj89f7ee12016-09-20 14:58:13237 def __len__(self):
238 """os.path.basename is called on MockFile so we need a len method."""
239 return len(self._local_path)
240
Julian Pastarmov4f7af532019-07-17 19:25:37241 def replace(self, altsep, sep):
242 """os.path.basename is called on MockFile so we need a replace method."""
243 return self._local_path.replace(altsep, sep)
244
gayane3dff8c22014-12-04 17:09:51245
glidere61efad2015-02-18 17:39:43246class MockAffectedFile(MockFile):
247 def AbsoluteLocalPath(self):
248 return self._local_path
249
250
gayane3dff8c22014-12-04 17:09:51251class MockChange(object):
252 """Mock class for Change class.
253
254 This class can be used in presubmit unittests to mock the query of the
255 current change.
256 """
257
258 def __init__(self, changed_files):
259 self._changed_files = changed_files
Chris Hall59f8d0c72020-05-01 07:31:19260 self.footers = defaultdict(list)
gayane3dff8c22014-12-04 17:09:51261
262 def LocalPaths(self):
263 return self._changed_files
rdevlin.cronin11366822016-05-02 17:05:54264
265 def AffectedFiles(self, include_dirs=False, include_deletes=True,
266 file_filter=None):
267 return self._changed_files
Chris Hall59f8d0c72020-05-01 07:31:19268
269 def GitFootersFromDescription(self):
270 return self.footers