blob: 846f841343966e427ae4a7bb8da1915df1416161 [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()
95 if white_list:
96 for pattern in white_list:
97 compiled_pattern = re.compile(pattern)
98 if compiled_pattern.search(local_path):
99 return True
100 if black_list:
101 for pattern in black_list:
102 compiled_pattern = re.compile(pattern)
103 if compiled_pattern.search(local_path):
104 return False
105 return True
glidere61efad2015-02-18 17:39:43106
davileene0426252015-03-02 21:10:41107 def LocalPaths(self):
108 return self.files
109
gayane3dff8c22014-12-04 17:09:51110 def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34111 return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51112
113 def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43114 if hasattr(filename, 'AbsoluteLocalPath'):
115 filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51116 for file_ in self.files:
117 if file_.LocalPath() == filename:
118 return '\n'.join(file_.NewContents())
119 # Otherwise, file is not in our mock API.
120 raise IOError, "No such file or directory: '%s'" % filename
121
122
123class MockOutputApi(object):
gayane860db5c32014-12-05 16:16:46124 """Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51125
126 An instance of this class can be passed to presubmit unittests for outputing
127 various types of results.
128 """
129
130 class PresubmitResult(object):
131 def __init__(self, message, items=None, long_text=''):
132 self.message = message
133 self.items = items
134 self.long_text = long_text
135
gayane940df072015-02-24 14:28:30136 def __repr__(self):
137 return self.message
138
gayane3dff8c22014-12-04 17:09:51139 class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41140 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51141 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
142 self.type = 'error'
143
144 class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41145 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51146 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
147 self.type = 'warning'
148
149 class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41150 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51151 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
152 self.type = 'notify'
153
154 class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41155 def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51156 MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
157 self.type = 'promptOrNotify'
158
Daniel Cheng7052cdf2017-11-21 19:23:29159 def __init__(self):
160 self.more_cc = []
161
162 def AppendCC(self, more_cc):
163 self.more_cc.extend(more_cc)
164
gayane3dff8c22014-12-04 17:09:51165
166class MockFile(object):
167 """Mock class for the File class.
168
169 This class can be used to form the mock list of changed files in
170 MockInputApi for presubmit unittests.
171 """
172
Yoland Yanb92fa522017-08-28 17:37:06173 def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51174 self._local_path = local_path
175 self._new_contents = new_contents
176 self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40177 self._action = action
jbriance9e12f162016-11-25 07:57:50178 self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
179 len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06180 self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50181 for l in new_contents:
182 self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51183
dbeam37e8e7402016-02-10 22:58:20184 def Action(self):
agrievef32bcc72016-04-04 14:57:40185 return self._action
dbeam37e8e7402016-02-10 22:58:20186
gayane3dff8c22014-12-04 17:09:51187 def ChangedContents(self):
188 return self._changed_contents
189
190 def NewContents(self):
191 return self._new_contents
192
193 def LocalPath(self):
194 return self._local_path
195
rdevlin.cronin9ab806c2016-02-26 23:17:13196 def AbsoluteLocalPath(self):
197 return self._local_path
198
jbriance9e12f162016-11-25 07:57:50199 def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31200 return self._scm_diff
jbriance9e12f162016-11-25 07:57:50201
Yoland Yanb92fa522017-08-28 17:37:06202 def OldContents(self):
203 return self._old_contents
204
davileene0426252015-03-02 21:10:41205 def rfind(self, p):
206 """os.path.basename is called on MockFile so we need an rfind method."""
207 return self._local_path.rfind(p)
208
209 def __getitem__(self, i):
210 """os.path.basename is called on MockFile so we need a get method."""
211 return self._local_path[i]
212
pastarmovj89f7ee12016-09-20 14:58:13213 def __len__(self):
214 """os.path.basename is called on MockFile so we need a len method."""
215 return len(self._local_path)
216
gayane3dff8c22014-12-04 17:09:51217
glidere61efad2015-02-18 17:39:43218class MockAffectedFile(MockFile):
219 def AbsoluteLocalPath(self):
220 return self._local_path
221
222
gayane3dff8c22014-12-04 17:09:51223class MockChange(object):
224 """Mock class for Change class.
225
226 This class can be used in presubmit unittests to mock the query of the
227 current change.
228 """
229
230 def __init__(self, changed_files):
231 self._changed_files = changed_files
232
233 def LocalPaths(self):
234 return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54235
236 def AffectedFiles(self, include_dirs=False, include_deletes=True,
237 file_filter=None):
238 return self._changed_files