[go: nahoru, domu]

blob: f8143ae44cf7dd4feb216877e3186c09717ed07c [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__)
gayane3dff8c22014-12-04 17:09:5180
Zhiling Huang45cabf32018-03-10 00:50:0381 def CreateMockFileInPath(self, f_list):
82 self.os_path.exists = lambda x: x in f_list
83
agrievef32bcc72016-04-04 14:57:4084 def AffectedFiles(self, file_filter=None, include_deletes=False):
Sylvain Defresnea8b73d252018-02-28 15:45:5485 for file in self.files:
86 if file_filter and not file_filter(file):
87 continue
88 if not include_deletes and file.Action() == 'D':
89 continue
90 yield file
gayane3dff8c22014-12-04 17:09:5191
glidere61efad2015-02-18 17:39:4392 def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:5493 return self.AffectedFiles(file_filter=file_filter)
94
Robert Ma0303a3ad2020-07-22 18:48:4895 def FilterSourceFile(self, file,
Josip Sokcevic8b6cc432020-08-05 17:45:3396 files_to_check=(), files_to_skip=()):
Sylvain Defresnea8b73d252018-02-28 15:45:5497 local_path = file.LocalPath()
Robert Ma0303a3ad2020-07-22 18:48:4898 found_in_files_to_check = not files_to_check
99 if files_to_check:
100 if type(files_to_check) is str:
101 raise TypeError('files_to_check should be an iterable of strings')
102 for pattern in files_to_check:
Sylvain Defresnea8b73d252018-02-28 15:45:54103 compiled_pattern = re.compile(pattern)
104 if compiled_pattern.search(local_path):
Robert Ma0303a3ad2020-07-22 18:48:48105 found_in_files_to_check = True
Vaclav Brozekf01ed502018-03-16 19:38:24106 break
Robert Ma0303a3ad2020-07-22 18:48:48107 if files_to_skip:
108 if type(files_to_skip) is str:
109 raise TypeError('files_to_skip should be an iterable of strings')
110 for pattern in files_to_skip:
Sylvain Defresnea8b73d252018-02-28 15:45:54111 compiled_pattern = re.compile(pattern)
112 if compiled_pattern.search(local_path):
113 return False
Robert Ma0303a3ad2020-07-22 18:48:48114 return found_in_files_to_check
glidere61efad2015-02-18 17:39:43115
davileene0426252015-03-02 21:10:41116 def LocalPaths(self):
Alexei Svitkine137d4c662019-07-17 21:28:24117 return [file.LocalPath() for file in self.files]
davileene0426252015-03-02 21:10:41118
gayane3dff8c22014-12-04 17:09:51119 def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34120 return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51121
122 def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43123 if hasattr(filename, 'AbsoluteLocalPath'):
124 filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51125 for file_ in self.files:
126 if file_.LocalPath() == filename:
127 return '\n'.join(file_.NewContents())
128 # Otherwise, file is not in our mock API.
129 raise IOError, "No such file or directory: '%s'" % filename
130
131
132class MockOutputApi(object):
gayane860db5c2014-12-05 16:16:46133 """Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51134
135 An instance of this class can be passed to presubmit unittests for outputing
136 various types of results.
137 """
138
139 class PresubmitResult(object):
140 def __init__(self, message, items=None, long_text=''):
141 self.message = message
142 self.items = items
143 self.long_text = long_text
144
gayane940df072015-02-24 14:28:30145 def __repr__(self):
146 return self.message
147
gayane3dff8c22014-12-04 17:09:51148 class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41149 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51150 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
151 self.type = 'error'
152
153 class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41154 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51155 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
156 self.type = 'warning'
157
158 class PresubmitNotifyResult(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 = 'notify'
162
163 class PresubmitPromptOrNotify(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 = 'promptOrNotify'
167
Daniel Cheng7052cdf2017-11-21 19:23:29168 def __init__(self):
169 self.more_cc = []
170
171 def AppendCC(self, more_cc):
172 self.more_cc.extend(more_cc)
173
gayane3dff8c22014-12-04 17:09:51174
175class MockFile(object):
176 """Mock class for the File class.
177
178 This class can be used to form the mock list of changed files in
179 MockInputApi for presubmit unittests.
180 """
181
Dominic Battre645d42342020-12-04 16:14:10182 def __init__(self, local_path, new_contents, old_contents=None, action='A',
183 scm_diff=None):
gayane3dff8c22014-12-04 17:09:51184 self._local_path = local_path
185 self._new_contents = new_contents
186 self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40187 self._action = action
Dominic Battre645d42342020-12-04 16:14:10188 if scm_diff:
189 self._scm_diff = scm_diff
190 else:
191 self._scm_diff = (
192 "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
193 (local_path, len(new_contents)))
194 for l in new_contents:
195 self._scm_diff += "+%s\n" % l
Yoland Yanb92fa522017-08-28 17:37:06196 self._old_contents = old_contents
gayane3dff8c22014-12-04 17:09:51197
dbeam37e8e7402016-02-10 22:58:20198 def Action(self):
agrievef32bcc72016-04-04 14:57:40199 return self._action
dbeam37e8e7402016-02-10 22:58:20200
gayane3dff8c22014-12-04 17:09:51201 def ChangedContents(self):
202 return self._changed_contents
203
204 def NewContents(self):
205 return self._new_contents
206
207 def LocalPath(self):
208 return self._local_path
209
rdevlin.cronin9ab806c2016-02-26 23:17:13210 def AbsoluteLocalPath(self):
211 return self._local_path
212
jbriance9e12f162016-11-25 07:57:50213 def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31214 return self._scm_diff
jbriance9e12f162016-11-25 07:57:50215
Yoland Yanb92fa522017-08-28 17:37:06216 def OldContents(self):
217 return self._old_contents
218
davileene0426252015-03-02 21:10:41219 def rfind(self, p):
220 """os.path.basename is called on MockFile so we need an rfind method."""
221 return self._local_path.rfind(p)
222
223 def __getitem__(self, i):
224 """os.path.basename is called on MockFile so we need a get method."""
225 return self._local_path[i]
226
pastarmovj89f7ee12016-09-20 14:58:13227 def __len__(self):
228 """os.path.basename is called on MockFile so we need a len method."""
229 return len(self._local_path)
230
Julian Pastarmov4f7af532019-07-17 19:25:37231 def replace(self, altsep, sep):
232 """os.path.basename is called on MockFile so we need a replace method."""
233 return self._local_path.replace(altsep, sep)
234
gayane3dff8c22014-12-04 17:09:51235
glidere61efad2015-02-18 17:39:43236class MockAffectedFile(MockFile):
237 def AbsoluteLocalPath(self):
238 return self._local_path
239
240
gayane3dff8c22014-12-04 17:09:51241class MockChange(object):
242 """Mock class for Change class.
243
244 This class can be used in presubmit unittests to mock the query of the
245 current change.
246 """
247
248 def __init__(self, changed_files):
249 self._changed_files = changed_files
Chris Hall59f8d0c72020-05-01 07:31:19250 self.footers = defaultdict(list)
gayane3dff8c22014-12-04 17:09:51251
252 def LocalPaths(self):
253 return self._changed_files
rdevlin.cronin11366822016-05-02 17:05:54254
255 def AffectedFiles(self, include_dirs=False, include_deletes=True,
256 file_filter=None):
257 return self._changed_files
Chris Hall59f8d0c72020-05-01 07:31:19258
259 def GitFootersFromDescription(self):
260 return self.footers
261