blob: 3b491d963ed2c85fda57f2583243e9f4407600d9 [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
Daniel Cheng13ca61a882017-08-25 15:11:255import fnmatch
gayane3dff8c22014-12-04 17:09:516import json
7import os
8import re
9import subprocess
10import sys
11
Daniel Cheng264a447d2017-09-28 22:17:5912# TODO(dcheng): It's kind of horrible that this is copy and pasted from
13# presubmit_canned_checks.py, but it's far easier than any of the alternatives.
14def _ReportErrorFileAndLine(filename, line_num, dummy_line):
15 """Default error formatter for _FindNewViolationsOfRule."""
16 return '%s:%s' % (filename, line_num)
17
18
19class MockCannedChecks(object):
20 def _FindNewViolationsOfRule(self, callable_rule, input_api,
21 source_file_filter=None,
22 error_formatter=_ReportErrorFileAndLine):
23 """Find all newly introduced violations of a per-line rule (a callable).
24
25 Arguments:
26 callable_rule: a callable taking a file extension and line of input and
27 returning True if the rule is satisfied and False if there was a
28 problem.
29 input_api: object to enumerate the affected files.
30 source_file_filter: a filter to be passed to the input api.
31 error_formatter: a callable taking (filename, line_number, line) and
32 returning a formatted error string.
33
34 Returns:
35 A list of the newly-introduced violations reported by the rule.
36 """
37 errors = []
38 for f in input_api.AffectedFiles(include_deletes=False,
39 file_filter=source_file_filter):
40 # For speed, we do two passes, checking first the full file. Shelling out
41 # to the SCM to determine the changed region can be quite expensive on
42 # Win32. Assuming that most files will be kept problem-free, we can
43 # skip the SCM operations most of the time.
44 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
45 if all(callable_rule(extension, line) for line in f.NewContents()):
46 continue # No violation found in full text: can skip considering diff.
47
48 for line_num, line in f.ChangedContents():
49 if not callable_rule(extension, line):
50 errors.append(error_formatter(f.LocalPath(), line_num, line))
51
52 return errors
gayane3dff8c22014-12-04 17:09:5153
Zhiling Huang45cabf32018-03-10 00:50:0354
gayane3dff8c22014-12-04 17:09:5155class MockInputApi(object):
56 """Mock class for the InputApi class.
57
58 This class can be used for unittests for presubmit by initializing the files
59 attribute as the list of changed files.
60 """
61
Sylvain Defresnea8b73d252018-02-28 15:45:5462 DEFAULT_BLACK_LIST = ()
63
gayane3dff8c22014-12-04 17:09:5164 def __init__(self):
Daniel Cheng264a447d2017-09-28 22:17:5965 self.canned_checks = MockCannedChecks()
Daniel Cheng13ca61a882017-08-25 15:11:2566 self.fnmatch = fnmatch
gayane3dff8c22014-12-04 17:09:5167 self.json = json
68 self.re = re
69 self.os_path = os.path
agrievebb9c5b472016-04-22 15:13:0070 self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5171 self.python_executable = sys.executable
pastarmovj89f7ee12016-09-20 14:58:1372 self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5173 self.subprocess = subprocess
74 self.files = []
75 self.is_committing = False
gayanee1702662014-12-13 03:48:0976 self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3477 self.presubmit_local_path = os.path.dirname(__file__)
gayane3dff8c22014-12-04 17:09:5178
Zhiling Huang45cabf32018-03-10 00:50:0379 def CreateMockFileInPath(self, f_list):
80 self.os_path.exists = lambda x: x in f_list
81
agrievef32bcc72016-04-04 14:57:4082 def AffectedFiles(self, file_filter=None, include_deletes=False):
Sylvain Defresnea8b73d252018-02-28 15:45:5483 for file in self.files:
84 if file_filter and not file_filter(file):
85 continue
86 if not include_deletes and file.Action() == 'D':
87 continue
88 yield file
gayane3dff8c22014-12-04 17:09:5189
glidere61efad2015-02-18 17:39:4390 def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:5491 return self.AffectedFiles(file_filter=file_filter)
92
93 def FilterSourceFile(self, file, white_list=(), black_list=()):
94 local_path = file.LocalPath()
Vaclav Brozekf01ed502018-03-16 19:38:2495 found_in_white_list = not white_list
Sylvain Defresnea8b73d252018-02-28 15:45:5496 if white_list:
97 for pattern in white_list:
98 compiled_pattern = re.compile(pattern)
99 if compiled_pattern.search(local_path):
Vaclav Brozekf01ed502018-03-16 19:38:24100 found_in_white_list = True
101 break
Sylvain Defresnea8b73d252018-02-28 15:45:54102 if black_list:
103 for pattern in black_list:
104 compiled_pattern = re.compile(pattern)
105 if compiled_pattern.search(local_path):
106 return False
Vaclav Brozekf01ed502018-03-16 19:38:24107 return found_in_white_list
glidere61efad2015-02-18 17:39:43108
davileene0426252015-03-02 21:10:41109 def LocalPaths(self):
110 return self.files
111
gayane3dff8c22014-12-04 17:09:51112 def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34113 return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51114
115 def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43116 if hasattr(filename, 'AbsoluteLocalPath'):
117 filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51118 for file_ in self.files:
119 if file_.LocalPath() == filename:
120 return '\n'.join(file_.NewContents())
121 # Otherwise, file is not in our mock API.
122 raise IOError, "No such file or directory: '%s'" % filename
123
124
125class MockOutputApi(object):
gayane860db5c32014-12-05 16:16:46126 """Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51127
128 An instance of this class can be passed to presubmit unittests for outputing
129 various types of results.
130 """
131
132 class PresubmitResult(object):
133 def __init__(self, message, items=None, long_text=''):
134 self.message = message
135 self.items = items
136 self.long_text = long_text
137
gayane940df072015-02-24 14:28:30138 def __repr__(self):
139 return self.message
140
gayane3dff8c22014-12-04 17:09:51141 class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41142 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51143 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
144 self.type = 'error'
145
146 class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41147 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51148 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
149 self.type = 'warning'
150
151 class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41152 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51153 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
154 self.type = 'notify'
155
156 class PresubmitPromptOrNotify(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 = 'promptOrNotify'
160
Daniel Cheng7052cdf2017-11-21 19:23:29161 def __init__(self):
162 self.more_cc = []
163
164 def AppendCC(self, more_cc):
165 self.more_cc.extend(more_cc)
166
gayane3dff8c22014-12-04 17:09:51167
168class MockFile(object):
169 """Mock class for the File class.
170
171 This class can be used to form the mock list of changed files in
172 MockInputApi for presubmit unittests.
173 """
174
Yoland Yanb92fa522017-08-28 17:37:06175 def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51176 self._local_path = local_path
177 self._new_contents = new_contents
178 self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40179 self._action = action
jbriance9e12f162016-11-25 07:57:50180 self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
181 len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06182 self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50183 for l in new_contents:
184 self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51185
dbeam37e8e7402016-02-10 22:58:20186 def Action(self):
agrievef32bcc72016-04-04 14:57:40187 return self._action
dbeam37e8e7402016-02-10 22:58:20188
gayane3dff8c22014-12-04 17:09:51189 def ChangedContents(self):
190 return self._changed_contents
191
192 def NewContents(self):
193 return self._new_contents
194
195 def LocalPath(self):
196 return self._local_path
197
rdevlin.cronin9ab806c2016-02-26 23:17:13198 def AbsoluteLocalPath(self):
199 return self._local_path
200
jbriance9e12f162016-11-25 07:57:50201 def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31202 return self._scm_diff
jbriance9e12f162016-11-25 07:57:50203
Yoland Yanb92fa522017-08-28 17:37:06204 def OldContents(self):
205 return self._old_contents
206
davileene0426252015-03-02 21:10:41207 def rfind(self, p):
208 """os.path.basename is called on MockFile so we need an rfind method."""
209 return self._local_path.rfind(p)
210
211 def __getitem__(self, i):
212 """os.path.basename is called on MockFile so we need a get method."""
213 return self._local_path[i]
214
pastarmovj89f7ee12016-09-20 14:58:13215 def __len__(self):
216 """os.path.basename is called on MockFile so we need a len method."""
217 return len(self._local_path)
218
gayane3dff8c22014-12-04 17:09:51219
glidere61efad2015-02-18 17:39:43220class MockAffectedFile(MockFile):
221 def AbsoluteLocalPath(self):
222 return self._local_path
223
224
gayane3dff8c22014-12-04 17:09:51225class MockChange(object):
226 """Mock class for Change class.
227
228 This class can be used in presubmit unittests to mock the query of the
229 current change.
230 """
231
232 def __init__(self, changed_files):
233 self._changed_files = changed_files
234
235 def LocalPaths(self):
236 return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54237
238 def AffectedFiles(self, include_dirs=False, include_deletes=True,
239 file_filter=None):
240 return self._changed_files