blob: f6410093acd598a7bf5b8c55d21a1a2654ccee1e [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()
Vaclav Brozekf01ed502018-03-16 19:38:2495found_in_white_list = not white_list
Sylvain Defresnea8b73d252018-02-28 15:45:5496if white_list:
Wei-Yin Chen (陳威尹)b1ce35492018-07-31 02:37:0197if type(white_list) is str:
98raise TypeError('white_list should be an iterable of strings')
Sylvain Defresnea8b73d252018-02-28 15:45:5499for pattern in white_list:
100compiled_pattern = re.compile(pattern)
101if compiled_pattern.search(local_path):
Vaclav Brozekf01ed502018-03-16 19:38:24102found_in_white_list = True
103break
Sylvain Defresnea8b73d252018-02-28 15:45:54104if black_list:
Wei-Yin Chen (陳威尹)b1ce35492018-07-31 02:37:01105if type(black_list) is str:
106raise TypeError('black_list should be an iterable of strings')
Sylvain Defresnea8b73d252018-02-28 15:45:54107for pattern in black_list:
108compiled_pattern = re.compile(pattern)
109if compiled_pattern.search(local_path):
110return False
Vaclav Brozekf01ed502018-03-16 19:38:24111return found_in_white_list
glidere61efad2015-02-18 17:39:43112
davileene0426252015-03-02 21:10:41113def LocalPaths(self):
114return self.files
115
gayane3dff8c22014-12-04 17:09:51116def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34117return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51118
119def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43120if hasattr(filename, 'AbsoluteLocalPath'):
121filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51122for file_ in self.files:
123if file_.LocalPath() == filename:
124return '\n'.join(file_.NewContents())
125# Otherwise, file is not in our mock API.
126raise IOError, "No such file or directory: '%s'" % filename
127
128
129class MockOutputApi(object):
gayane860db5c32014-12-05 16:16:46130"""Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51131
132An instance of this class can be passed to presubmit unittests for outputing
133various types of results.
134"""
135
136class PresubmitResult(object):
137def __init__(self, message, items=None, long_text=''):
138self.message = message
139self.items = items
140self.long_text = long_text
141
gayane940df072015-02-24 14:28:30142def __repr__(self):
143return self.message
144
gayane3dff8c22014-12-04 17:09:51145class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41146def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51147MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
148self.type = 'error'
149
150class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41151def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51152MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
153self.type = 'warning'
154
155class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41156def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51157MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
158self.type = 'notify'
159
160class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41161def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51162MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
163self.type = 'promptOrNotify'
164
Daniel Cheng7052cdf2017-11-21 19:23:29165def __init__(self):
166self.more_cc = []
167
168def AppendCC(self, more_cc):
169self.more_cc.extend(more_cc)
170
gayane3dff8c22014-12-04 17:09:51171
172class MockFile(object):
173"""Mock class for the File class.
174
175This class can be used to form the mock list of changed files in
176MockInputApi for presubmit unittests.
177"""
178
Yoland Yanb92fa522017-08-28 17:37:06179def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51180self._local_path = local_path
181self._new_contents = new_contents
182self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40183self._action = action
jbriance9e12f162016-11-25 07:57:50184self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
185len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06186self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50187for l in new_contents:
188self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51189
dbeam37e8e7402016-02-10 22:58:20190def Action(self):
agrievef32bcc72016-04-04 14:57:40191return self._action
dbeam37e8e7402016-02-10 22:58:20192
gayane3dff8c22014-12-04 17:09:51193def ChangedContents(self):
194return self._changed_contents
195
196def NewContents(self):
197return self._new_contents
198
199def LocalPath(self):
200return self._local_path
201
rdevlin.cronin9ab806c2016-02-26 23:17:13202def AbsoluteLocalPath(self):
203return self._local_path
204
jbriance9e12f162016-11-25 07:57:50205def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31206return self._scm_diff
jbriance9e12f162016-11-25 07:57:50207
Yoland Yanb92fa522017-08-28 17:37:06208def OldContents(self):
209return self._old_contents
210
davileene0426252015-03-02 21:10:41211def rfind(self, p):
212"""os.path.basename is called on MockFile so we need an rfind method."""
213return self._local_path.rfind(p)
214
215def __getitem__(self, i):
216"""os.path.basename is called on MockFile so we need a get method."""
217return self._local_path[i]
218
pastarmovj89f7ee12016-09-20 14:58:13219def __len__(self):
220"""os.path.basename is called on MockFile so we need a len method."""
221return len(self._local_path)
222
Julian Pastarmov4f7af532019-07-17 19:25:37223def replace(self, altsep, sep):
224"""os.path.basename is called on MockFile so we need a replace method."""
225return self._local_path.replace(altsep, sep)
226
gayane3dff8c22014-12-04 17:09:51227
glidere61efad2015-02-18 17:39:43228class MockAffectedFile(MockFile):
229def AbsoluteLocalPath(self):
230return self._local_path
231
232
gayane3dff8c22014-12-04 17:09:51233class MockChange(object):
234"""Mock class for Change class.
235
236This class can be used in presubmit unittests to mock the query of the
237current change.
238"""
239
240def __init__(self, changed_files):
241self._changed_files = changed_files
242
243def LocalPaths(self):
244return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54245
246def AffectedFiles(self, include_dirs=False, include_deletes=True,
247file_filter=None):
248return self._changed_files