[go: nahoru, domu]

blob: fbd00a028d1dee4cd292058d4bc2c53fc625ee2b [file] [log] [blame]
Avi Drissman24976592022-09-12 15:24:311# Copyright 2014 The Chromium Authors
gayane3dff8c22014-12-04 17:09:512# 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
Ian Vollick9d42a072023-02-14 01:21:1483 # Although this makes assumptions about command line arguments used by test
84 # scripts that create mocks, it is a convenient way to set up the verbosity
85 # via the input api.
86 self.verbose = '--verbose' in sys.argv
gayane3dff8c22014-12-04 17:09:5187
Zhiling Huang45cabf32018-03-10 00:50:0388 def CreateMockFileInPath(self, f_list):
89 self.os_path.exists = lambda x: x in f_list
90
Giovanni Ortuño Urquidiab84da62021-12-10 00:53:2191 def AffectedFiles(self, file_filter=None, include_deletes=True):
Sylvain Defresnea8b73d252018-02-28 15:45:5492 for file in self.files:
93 if file_filter and not file_filter(file):
94 continue
95 if not include_deletes and file.Action() == 'D':
96 continue
97 yield file
gayane3dff8c22014-12-04 17:09:5198
Lukasz Anforowicz7016d05e2021-11-30 03:56:2799 def RightHandSideLines(self, source_file_filter=None):
100 affected_files = self.AffectedSourceFiles(source_file_filter)
101 for af in affected_files:
102 lines = af.ChangedContents()
103 for line in lines:
104 yield (af, line[0], line[1])
105
glidere61efad2015-02-18 17:39:43106 def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:54107 return self.AffectedFiles(file_filter=file_filter)
108
Robert Ma0303a3ad2020-07-22 18:48:48109 def FilterSourceFile(self, file,
Josip Sokcevic8b6cc432020-08-05 17:45:33110 files_to_check=(), files_to_skip=()):
Sylvain Defresnea8b73d252018-02-28 15:45:54111 local_path = file.LocalPath()
Robert Ma0303a3ad2020-07-22 18:48:48112 found_in_files_to_check = not files_to_check
113 if files_to_check:
114 if type(files_to_check) is str:
115 raise TypeError('files_to_check should be an iterable of strings')
116 for pattern in files_to_check:
Sylvain Defresnea8b73d252018-02-28 15:45:54117 compiled_pattern = re.compile(pattern)
Henrique Ferreiro81d580022021-11-29 21:27:19118 if compiled_pattern.match(local_path):
Robert Ma0303a3ad2020-07-22 18:48:48119 found_in_files_to_check = True
Vaclav Brozekf01ed502018-03-16 19:38:24120 break
Robert Ma0303a3ad2020-07-22 18:48:48121 if files_to_skip:
122 if type(files_to_skip) is str:
123 raise TypeError('files_to_skip should be an iterable of strings')
124 for pattern in files_to_skip:
Sylvain Defresnea8b73d252018-02-28 15:45:54125 compiled_pattern = re.compile(pattern)
Henrique Ferreiro81d580022021-11-29 21:27:19126 if compiled_pattern.match(local_path):
Sylvain Defresnea8b73d252018-02-28 15:45:54127 return False
Robert Ma0303a3ad2020-07-22 18:48:48128 return found_in_files_to_check
glidere61efad2015-02-18 17:39:43129
davileene0426252015-03-02 21:10:41130 def LocalPaths(self):
Alexei Svitkine137d4c662019-07-17 21:28:24131 return [file.LocalPath() for file in self.files]
davileene0426252015-03-02 21:10:41132
gayane3dff8c22014-12-04 17:09:51133 def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34134 return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51135
Joanmarie Diggs0991fc62022-08-30 06:00:13136 def ReadFile(self, filename, mode='r'):
glidere61efad2015-02-18 17:39:43137 if hasattr(filename, 'AbsoluteLocalPath'):
138 filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51139 for file_ in self.files:
140 if file_.LocalPath() == filename:
141 return '\n'.join(file_.NewContents())
142 # Otherwise, file is not in our mock API.
Dirk Prankee3c9c62d2021-05-18 18:35:59143 raise IOError("No such file or directory: '%s'" % filename)
gayane3dff8c22014-12-04 17:09:51144
145
146class MockOutputApi(object):
gayane860db5c2014-12-05 16:16:46147 """Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51148
Gao Shenga79ebd42022-08-08 17:25:59149 An instance of this class can be passed to presubmit unittests for outputting
gayane3dff8c22014-12-04 17:09:51150 various types of results.
151 """
152
153 class PresubmitResult(object):
154 def __init__(self, message, items=None, long_text=''):
155 self.message = message
156 self.items = items
157 self.long_text = long_text
158
gayane940df072015-02-24 14:28:30159 def __repr__(self):
160 return self.message
161
gayane3dff8c22014-12-04 17:09:51162 class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41163 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51164 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
165 self.type = 'error'
166
167 class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41168 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51169 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
170 self.type = 'warning'
171
172 class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41173 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51174 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
175 self.type = 'notify'
176
177 class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41178 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51179 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
180 self.type = 'promptOrNotify'
181
Daniel Cheng7052cdf2017-11-21 19:23:29182 def __init__(self):
183 self.more_cc = []
184
185 def AppendCC(self, more_cc):
Kevin McNee967dd2d22021-11-15 16:09:29186 self.more_cc.append(more_cc)
Daniel Cheng7052cdf2017-11-21 19:23:29187
gayane3dff8c22014-12-04 17:09:51188
189class MockFile(object):
190 """Mock class for the File class.
191
192 This class can be used to form the mock list of changed files in
193 MockInputApi for presubmit unittests.
194 """
195
Daniel Cheng99f90e6b2023-11-28 22:54:40196 def __init__(self, local_path, new_contents, old_contents=None, action='A',
Dominic Battre645d42342020-12-04 16:14:10197 scm_diff=None):
gayane3dff8c22014-12-04 17:09:51198 self._local_path = local_path
199 self._new_contents = new_contents
200 self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40201 self._action = action
Dominic Battre645d42342020-12-04 16:14:10202 if scm_diff:
203 self._scm_diff = scm_diff
204 else:
205 self._scm_diff = (
206 "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
207 (local_path, len(new_contents)))
208 for l in new_contents:
209 self._scm_diff += "+%s\n" % l
Yoland Yanb92fa522017-08-28 17:37:06210 self._old_contents = old_contents
gayane3dff8c22014-12-04 17:09:51211
Luciano Pacheco23d752b02023-10-25 22:49:36212 def __str__(self):
213 return self._local_path
214
dbeam37e8e7402016-02-10 22:58:20215 def Action(self):
agrievef32bcc72016-04-04 14:57:40216 return self._action
dbeam37e8e7402016-02-10 22:58:20217
gayane3dff8c22014-12-04 17:09:51218 def ChangedContents(self):
219 return self._changed_contents
220
221 def NewContents(self):
222 return self._new_contents
223
224 def LocalPath(self):
225 return self._local_path
226
rdevlin.cronin9ab806c2016-02-26 23:17:13227 def AbsoluteLocalPath(self):
228 return self._local_path
229
jbriance9e12f162016-11-25 07:57:50230 def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31231 return self._scm_diff
jbriance9e12f162016-11-25 07:57:50232
Yoland Yanb92fa522017-08-28 17:37:06233 def OldContents(self):
234 return self._old_contents
235
davileene0426252015-03-02 21:10:41236 def rfind(self, p):
237 """os.path.basename is called on MockFile so we need an rfind method."""
238 return self._local_path.rfind(p)
239
240 def __getitem__(self, i):
241 """os.path.basename is called on MockFile so we need a get method."""
242 return self._local_path[i]
243
pastarmovj89f7ee12016-09-20 14:58:13244 def __len__(self):
245 """os.path.basename is called on MockFile so we need a len method."""
246 return len(self._local_path)
247
Julian Pastarmov4f7af532019-07-17 19:25:37248 def replace(self, altsep, sep):
249 """os.path.basename is called on MockFile so we need a replace method."""
250 return self._local_path.replace(altsep, sep)
251
gayane3dff8c22014-12-04 17:09:51252
glidere61efad2015-02-18 17:39:43253class MockAffectedFile(MockFile):
254 def AbsoluteLocalPath(self):
255 return self._local_path
256
257
gayane3dff8c22014-12-04 17:09:51258class MockChange(object):
259 """Mock class for Change class.
260
261 This class can be used in presubmit unittests to mock the query of the
262 current change.
263 """
264
265 def __init__(self, changed_files):
266 self._changed_files = changed_files
Thorben Troebst2d24c7062022-08-10 20:20:16267 self.author_email = None
Chris Hall59f8d0c72020-05-01 07:31:19268 self.footers = defaultdict(list)
gayane3dff8c22014-12-04 17:09:51269
270 def LocalPaths(self):
271 return self._changed_files
rdevlin.cronin11366822016-05-02 17:05:54272
273 def AffectedFiles(self, include_dirs=False, include_deletes=True,
274 file_filter=None):
275 return self._changed_files
Chris Hall59f8d0c72020-05-01 07:31:19276
277 def GitFootersFromDescription(self):
278 return self.footers