[go: nahoru, domu]

blob: cc930aa7cba8011cec904b083802c9c1c95d1865 [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
pastarmovj89f7ee12016-09-20 14:58:1373 self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5174 self.subprocess = subprocess
Dan Beam35b10c12019-11-27 01:17:3475 self.sys = sys
gayane3dff8c22014-12-04 17:09:5176 self.files = []
77 self.is_committing = False
gayanee1702662014-12-13 03:48:0978 self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3479 self.presubmit_local_path = os.path.dirname(__file__)
Bruce Dawson3740e0732022-04-07 16:17:2280 self.is_windows = sys.platform == 'win32'
gayane3dff8c22014-12-04 17:09:5181
Zhiling Huang45cabf32018-03-10 00:50:0382 def CreateMockFileInPath(self, f_list):
83 self.os_path.exists = lambda x: x in f_list
84
Giovanni Ortuño Urquidiab84da62021-12-10 00:53:2185 def AffectedFiles(self, file_filter=None, include_deletes=True):
Sylvain Defresnea8b73d252018-02-28 15:45:5486 for file in self.files:
87 if file_filter and not file_filter(file):
88 continue
89 if not include_deletes and file.Action() == 'D':
90 continue
91 yield file
gayane3dff8c22014-12-04 17:09:5192
Lukasz Anforowicz7016d05e2021-11-30 03:56:2793 def RightHandSideLines(self, source_file_filter=None):
94 affected_files = self.AffectedSourceFiles(source_file_filter)
95 for af in affected_files:
96 lines = af.ChangedContents()
97 for line in lines:
98 yield (af, line[0], line[1])
99
glidere61efad2015-02-18 17:39:43100 def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:54101 return self.AffectedFiles(file_filter=file_filter)
102
Robert Ma0303a3ad2020-07-22 18:48:48103 def FilterSourceFile(self, file,
Josip Sokcevic8b6cc432020-08-05 17:45:33104 files_to_check=(), files_to_skip=()):
Sylvain Defresnea8b73d252018-02-28 15:45:54105 local_path = file.LocalPath()
Robert Ma0303a3ad2020-07-22 18:48:48106 found_in_files_to_check = not files_to_check
107 if files_to_check:
108 if type(files_to_check) is str:
109 raise TypeError('files_to_check should be an iterable of strings')
110 for pattern in files_to_check:
Sylvain Defresnea8b73d252018-02-28 15:45:54111 compiled_pattern = re.compile(pattern)
Henrique Ferreiro81d580022021-11-29 21:27:19112 if compiled_pattern.match(local_path):
Robert Ma0303a3ad2020-07-22 18:48:48113 found_in_files_to_check = True
Vaclav Brozekf01ed502018-03-16 19:38:24114 break
Robert Ma0303a3ad2020-07-22 18:48:48115 if files_to_skip:
116 if type(files_to_skip) is str:
117 raise TypeError('files_to_skip should be an iterable of strings')
118 for pattern in files_to_skip:
Sylvain Defresnea8b73d252018-02-28 15:45:54119 compiled_pattern = re.compile(pattern)
Henrique Ferreiro81d580022021-11-29 21:27:19120 if compiled_pattern.match(local_path):
Sylvain Defresnea8b73d252018-02-28 15:45:54121 return False
Robert Ma0303a3ad2020-07-22 18:48:48122 return found_in_files_to_check
glidere61efad2015-02-18 17:39:43123
davileene0426252015-03-02 21:10:41124 def LocalPaths(self):
Alexei Svitkine137d4c662019-07-17 21:28:24125 return [file.LocalPath() for file in self.files]
davileene0426252015-03-02 21:10:41126
gayane3dff8c22014-12-04 17:09:51127 def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34128 return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51129
130 def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43131 if hasattr(filename, 'AbsoluteLocalPath'):
132 filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51133 for file_ in self.files:
134 if file_.LocalPath() == filename:
135 return '\n'.join(file_.NewContents())
136 # Otherwise, file is not in our mock API.
Dirk Prankee3c9c62d2021-05-18 18:35:59137 raise IOError("No such file or directory: '%s'" % filename)
gayane3dff8c22014-12-04 17:09:51138
139
140class MockOutputApi(object):
gayane860db5c2014-12-05 16:16:46141 """Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51142
143 An instance of this class can be passed to presubmit unittests for outputing
144 various types of results.
145 """
146
147 class PresubmitResult(object):
148 def __init__(self, message, items=None, long_text=''):
149 self.message = message
150 self.items = items
151 self.long_text = long_text
152
gayane940df072015-02-24 14:28:30153 def __repr__(self):
154 return self.message
155
gayane3dff8c22014-12-04 17:09:51156 class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41157 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51158 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
159 self.type = 'error'
160
161 class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41162 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51163 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
164 self.type = 'warning'
165
166 class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41167 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51168 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
169 self.type = 'notify'
170
171 class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41172 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51173 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
174 self.type = 'promptOrNotify'
175
Daniel Cheng7052cdf2017-11-21 19:23:29176 def __init__(self):
177 self.more_cc = []
178
179 def AppendCC(self, more_cc):
Kevin McNee967dd2d22021-11-15 16:09:29180 self.more_cc.append(more_cc)
Daniel Cheng7052cdf2017-11-21 19:23:29181
gayane3dff8c22014-12-04 17:09:51182
183class MockFile(object):
184 """Mock class for the File class.
185
186 This class can be used to form the mock list of changed files in
187 MockInputApi for presubmit unittests.
188 """
189
Dominic Battre645d42342020-12-04 16:14:10190 def __init__(self, local_path, new_contents, old_contents=None, action='A',
191 scm_diff=None):
gayane3dff8c22014-12-04 17:09:51192 self._local_path = local_path
193 self._new_contents = new_contents
194 self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40195 self._action = action
Dominic Battre645d42342020-12-04 16:14:10196 if scm_diff:
197 self._scm_diff = scm_diff
198 else:
199 self._scm_diff = (
200 "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
201 (local_path, len(new_contents)))
202 for l in new_contents:
203 self._scm_diff += "+%s\n" % l
Yoland Yanb92fa522017-08-28 17:37:06204 self._old_contents = old_contents
gayane3dff8c22014-12-04 17:09:51205
dbeam37e8e7402016-02-10 22:58:20206 def Action(self):
agrievef32bcc72016-04-04 14:57:40207 return self._action
dbeam37e8e7402016-02-10 22:58:20208
gayane3dff8c22014-12-04 17:09:51209 def ChangedContents(self):
210 return self._changed_contents
211
212 def NewContents(self):
213 return self._new_contents
214
215 def LocalPath(self):
216 return self._local_path
217
rdevlin.cronin9ab806c2016-02-26 23:17:13218 def AbsoluteLocalPath(self):
219 return self._local_path
220
jbriance9e12f162016-11-25 07:57:50221 def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31222 return self._scm_diff
jbriance9e12f162016-11-25 07:57:50223
Yoland Yanb92fa522017-08-28 17:37:06224 def OldContents(self):
225 return self._old_contents
226
davileene0426252015-03-02 21:10:41227 def rfind(self, p):
228 """os.path.basename is called on MockFile so we need an rfind method."""
229 return self._local_path.rfind(p)
230
231 def __getitem__(self, i):
232 """os.path.basename is called on MockFile so we need a get method."""
233 return self._local_path[i]
234
pastarmovj89f7ee12016-09-20 14:58:13235 def __len__(self):
236 """os.path.basename is called on MockFile so we need a len method."""
237 return len(self._local_path)
238
Julian Pastarmov4f7af532019-07-17 19:25:37239 def replace(self, altsep, sep):
240 """os.path.basename is called on MockFile so we need a replace method."""
241 return self._local_path.replace(altsep, sep)
242
gayane3dff8c22014-12-04 17:09:51243
glidere61efad2015-02-18 17:39:43244class MockAffectedFile(MockFile):
245 def AbsoluteLocalPath(self):
246 return self._local_path
247
248
gayane3dff8c22014-12-04 17:09:51249class MockChange(object):
250 """Mock class for Change class.
251
252 This class can be used in presubmit unittests to mock the query of the
253 current change.
254 """
255
256 def __init__(self, changed_files):
257 self._changed_files = changed_files
Chris Hall59f8d0c72020-05-01 07:31:19258 self.footers = defaultdict(list)
gayane3dff8c22014-12-04 17:09:51259
260 def LocalPaths(self):
261 return self._changed_files
rdevlin.cronin11366822016-05-02 17:05:54262
263 def AffectedFiles(self, include_dirs=False, include_deletes=True,
264 file_filter=None):
265 return self._changed_files
Chris Hall59f8d0c72020-05-01 07:31:19266
267 def GitFootersFromDescription(self):
268 return self.footers