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."""
16return '%s:%s' % (filename, line_num)
17
18
19class MockCannedChecks(object):
20def _FindNewViolationsOfRule(self, callable_rule, input_api,
21source_file_filter=None,
22error_formatter=_ReportErrorFileAndLine):
23"""Find all newly introduced violations of a per-line rule (a callable).
24
25Arguments:
26callable_rule: a callable taking a file extension and line of input and
27returning True if the rule is satisfied and False if there was a
28problem.
29input_api: object to enumerate the affected files.
30source_file_filter: a filter to be passed to the input api.
31error_formatter: a callable taking (filename, line_number, line) and
32returning a formatted error string.
33
34Returns:
35A list of the newly-introduced violations reported by the rule.
36"""
37errors = []
38for f in input_api.AffectedFiles(include_deletes=False,
39file_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.
44extension = str(f.LocalPath()).rsplit('.', 1)[-1]
45if all(callable_rule(extension, line) for line in f.NewContents()):
46continue # No violation found in full text: can skip considering diff.
47
48for line_num, line in f.ChangedContents():
49if not callable_rule(extension, line):
50errors.append(error_formatter(f.LocalPath(), line_num, line))
51
52return 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
58This class can be used for unittests for presubmit by initializing the files
59attribute as the list of changed files.
60"""
61
Sylvain Defresnea8b73d252018-02-28 15:45:5462DEFAULT_BLACK_LIST = ()
63
gayane3dff8c22014-12-04 17:09:5164def __init__(self):
Daniel Cheng264a447d2017-09-28 22:17:5965self.canned_checks = MockCannedChecks()
Daniel Cheng13ca61a882017-08-25 15:11:2566self.fnmatch = fnmatch
gayane3dff8c22014-12-04 17:09:5167self.json = json
68self.re = re
69self.os_path = os.path
agrievebb9c5b472016-04-22 15:13:0070self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5171self.python_executable = sys.executable
pastarmovj89f7ee12016-09-20 14:58:1372self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5173self.subprocess = subprocess
74self.files = []
75self.is_committing = False
gayanee1702662014-12-13 03:48:0976self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3477self.presubmit_local_path = os.path.dirname(__file__)
gayane3dff8c22014-12-04 17:09:5178
Zhiling Huang45cabf32018-03-10 00:50:0379def CreateMockFileInPath(self, f_list):
80self.os_path.exists = lambda x: x in f_list
81
agrievef32bcc72016-04-04 14:57:4082def AffectedFiles(self, file_filter=None, include_deletes=False):
Sylvain Defresnea8b73d252018-02-28 15:45:5483for file in self.files:
84if file_filter and not file_filter(file):
85continue
86if not include_deletes and file.Action() == 'D':
87continue
88yield file
gayane3dff8c22014-12-04 17:09:5189
glidere61efad2015-02-18 17:39:4390def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:5491return self.AffectedFiles(file_filter=file_filter)
92
93def FilterSourceFile(self, file, white_list=(), black_list=()):
94local_path = file.LocalPath()
95if white_list:
96for pattern in white_list:
97compiled_pattern = re.compile(pattern)
98if compiled_pattern.search(local_path):
99return True
100if black_list:
101for pattern in black_list:
102compiled_pattern = re.compile(pattern)
103if compiled_pattern.search(local_path):
104return False
105return True
glidere61efad2015-02-18 17:39:43106
davileene0426252015-03-02 21:10:41107def LocalPaths(self):
108return self.files
109
gayane3dff8c22014-12-04 17:09:51110def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34111return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51112
113def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43114if hasattr(filename, 'AbsoluteLocalPath'):
115filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51116for file_ in self.files:
117if file_.LocalPath() == filename:
118return '\n'.join(file_.NewContents())
119# Otherwise, file is not in our mock API.
120raise 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
126An instance of this class can be passed to presubmit unittests for outputing
127various types of results.
128"""
129
130class PresubmitResult(object):
131def __init__(self, message, items=None, long_text=''):
132self.message = message
133self.items = items
134self.long_text = long_text
135
gayane940df072015-02-24 14:28:30136def __repr__(self):
137return self.message
138
gayane3dff8c22014-12-04 17:09:51139class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41140def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51141MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
142self.type = 'error'
143
144class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41145def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51146MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
147self.type = 'warning'
148
149class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41150def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51151MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
152self.type = 'notify'
153
154class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41155def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51156MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
157self.type = 'promptOrNotify'
158
Daniel Cheng7052cdf2017-11-21 19:23:29159def __init__(self):
160self.more_cc = []
161
162def AppendCC(self, more_cc):
163self.more_cc.extend(more_cc)
164
gayane3dff8c22014-12-04 17:09:51165
166class MockFile(object):
167"""Mock class for the File class.
168
169This class can be used to form the mock list of changed files in
170MockInputApi for presubmit unittests.
171"""
172
Yoland Yanb92fa522017-08-28 17:37:06173def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51174self._local_path = local_path
175self._new_contents = new_contents
176self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40177self._action = action
jbriance9e12f162016-11-25 07:57:50178self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
179len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06180self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50181for l in new_contents:
182self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51183
dbeam37e8e7402016-02-10 22:58:20184def Action(self):
agrievef32bcc72016-04-04 14:57:40185return self._action
dbeam37e8e7402016-02-10 22:58:20186
gayane3dff8c22014-12-04 17:09:51187def ChangedContents(self):
188return self._changed_contents
189
190def NewContents(self):
191return self._new_contents
192
193def LocalPath(self):
194return self._local_path
195
rdevlin.cronin9ab806c2016-02-26 23:17:13196def AbsoluteLocalPath(self):
197return self._local_path
198
jbriance9e12f162016-11-25 07:57:50199def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31200return self._scm_diff
jbriance9e12f162016-11-25 07:57:50201
Yoland Yanb92fa522017-08-28 17:37:06202def OldContents(self):
203return self._old_contents
204
davileene0426252015-03-02 21:10:41205def rfind(self, p):
206"""os.path.basename is called on MockFile so we need an rfind method."""
207return self._local_path.rfind(p)
208
209def __getitem__(self, i):
210"""os.path.basename is called on MockFile so we need a get method."""
211return self._local_path[i]
212
pastarmovj89f7ee12016-09-20 14:58:13213def __len__(self):
214"""os.path.basename is called on MockFile so we need a len method."""
215return len(self._local_path)
216
gayane3dff8c22014-12-04 17:09:51217
glidere61efad2015-02-18 17:39:43218class MockAffectedFile(MockFile):
219def AbsoluteLocalPath(self):
220return self._local_path
221
222
gayane3dff8c22014-12-04 17:09:51223class MockChange(object):
224"""Mock class for Change class.
225
226This class can be used in presubmit unittests to mock the query of the
227current change.
228"""
229
230def __init__(self, changed_files):
231self._changed_files = changed_files
232
233def LocalPaths(self):
234return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54235
236def AffectedFiles(self, include_dirs=False, include_deletes=True,
237file_filter=None):
238return self._changed_files