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."""
17return '%s:%s' % (filename, line_num)
18
19
20class MockCannedChecks(object):
21def _FindNewViolationsOfRule(self, callable_rule, input_api,
22source_file_filter=None,
23error_formatter=_ReportErrorFileAndLine):
24"""Find all newly introduced violations of a per-line rule (a callable).
25
26Arguments:
27callable_rule: a callable taking a file extension and line of input and
28returning True if the rule is satisfied and False if there was a
29problem.
30input_api: object to enumerate the affected files.
31source_file_filter: a filter to be passed to the input api.
32error_formatter: a callable taking (filename, line_number, line) and
33returning a formatted error string.
34
35Returns:
36A list of the newly-introduced violations reported by the rule.
37"""
38errors = []
39for f in input_api.AffectedFiles(include_deletes=False,
40file_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.
45extension = str(f.LocalPath()).rsplit('.', 1)[-1]
46if all(callable_rule(extension, line) for line in f.NewContents()):
47continue # No violation found in full text: can skip considering diff.
48
49for line_num, line in f.ChangedContents():
50if not callable_rule(extension, line):
51errors.append(error_formatter(f.LocalPath(), line_num, line))
52
53return 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
59This class can be used for unittests for presubmit by initializing the files
60attribute as the list of changed files.
61"""
62
Robert Ma0303a3ad2020-07-22 18:48:4863DEFAULT_FILES_TO_SKIP = ()
Sylvain Defresnea8b73d252018-02-28 15:45:5464
gayane3dff8c22014-12-04 17:09:5165def __init__(self):
Daniel Cheng264a447d2017-09-28 22:17:5966self.canned_checks = MockCannedChecks()
Daniel Cheng13ca61a882017-08-25 15:11:2567self.fnmatch = fnmatch
gayane3dff8c22014-12-04 17:09:5168self.json = json
69self.re = re
70self.os_path = os.path
agrievebb9c5b472016-04-22 15:13:0071self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5172self.python_executable = sys.executable
pastarmovj89f7ee12016-09-20 14:58:1373self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5174self.subprocess = subprocess
Dan Beam35b10c12019-11-27 01:17:3475self.sys = sys
gayane3dff8c22014-12-04 17:09:5176self.files = []
77self.is_committing = False
gayanee1702662014-12-13 03:48:0978self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3479self.presubmit_local_path = os.path.dirname(__file__)
gayane3dff8c22014-12-04 17:09:5180
Zhiling Huang45cabf32018-03-10 00:50:0381def CreateMockFileInPath(self, f_list):
82self.os_path.exists = lambda x: x in f_list
83
agrievef32bcc72016-04-04 14:57:4084def AffectedFiles(self, file_filter=None, include_deletes=False):
Sylvain Defresnea8b73d252018-02-28 15:45:5485for file in self.files:
86if file_filter and not file_filter(file):
87continue
88if not include_deletes and file.Action() == 'D':
89continue
90yield file
gayane3dff8c22014-12-04 17:09:5191
glidere61efad2015-02-18 17:39:4392def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:5493return self.AffectedFiles(file_filter=file_filter)
94
Robert Ma0303a3ad2020-07-22 18:48:4895def FilterSourceFile(self, file,
Josip Sokcevic8b6cc432020-08-05 17:45:3396files_to_check=(), files_to_skip=()):
Sylvain Defresnea8b73d252018-02-28 15:45:5497local_path = file.LocalPath()
Robert Ma0303a3ad2020-07-22 18:48:4898found_in_files_to_check = not files_to_check
99if files_to_check:
100if type(files_to_check) is str:
101raise TypeError('files_to_check should be an iterable of strings')
102for pattern in files_to_check:
Sylvain Defresnea8b73d252018-02-28 15:45:54103compiled_pattern = re.compile(pattern)
104if compiled_pattern.search(local_path):
Robert Ma0303a3ad2020-07-22 18:48:48105found_in_files_to_check = True
Vaclav Brozekf01ed502018-03-16 19:38:24106break
Robert Ma0303a3ad2020-07-22 18:48:48107if files_to_skip:
108if type(files_to_skip) is str:
109raise TypeError('files_to_skip should be an iterable of strings')
110for pattern in files_to_skip:
Sylvain Defresnea8b73d252018-02-28 15:45:54111compiled_pattern = re.compile(pattern)
112if compiled_pattern.search(local_path):
113return False
Robert Ma0303a3ad2020-07-22 18:48:48114return found_in_files_to_check
glidere61efad2015-02-18 17:39:43115
davileene0426252015-03-02 21:10:41116def LocalPaths(self):
Alexei Svitkine137d4c662019-07-17 21:28:24117return [file.LocalPath() for file in self.files]
davileene0426252015-03-02 21:10:41118
gayane3dff8c22014-12-04 17:09:51119def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34120return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51121
122def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43123if hasattr(filename, 'AbsoluteLocalPath'):
124filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51125for file_ in self.files:
126if file_.LocalPath() == filename:
127return '\n'.join(file_.NewContents())
128# Otherwise, file is not in our mock API.
129raise IOError, "No such file or directory: '%s'" % filename
130
131
132class MockOutputApi(object):
gayane860db5c32014-12-05 16:16:46133"""Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51134
135An instance of this class can be passed to presubmit unittests for outputing
136various types of results.
137"""
138
139class PresubmitResult(object):
140def __init__(self, message, items=None, long_text=''):
141self.message = message
142self.items = items
143self.long_text = long_text
144
gayane940df072015-02-24 14:28:30145def __repr__(self):
146return self.message
147
gayane3dff8c22014-12-04 17:09:51148class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41149def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51150MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
151self.type = 'error'
152
153class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41154def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51155MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
156self.type = 'warning'
157
158class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41159def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51160MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
161self.type = 'notify'
162
163class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41164def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51165MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
166self.type = 'promptOrNotify'
167
Daniel Cheng7052cdf2017-11-21 19:23:29168def __init__(self):
169self.more_cc = []
170
171def AppendCC(self, more_cc):
172self.more_cc.extend(more_cc)
173
gayane3dff8c22014-12-04 17:09:51174
175class MockFile(object):
176"""Mock class for the File class.
177
178This class can be used to form the mock list of changed files in
179MockInputApi for presubmit unittests.
180"""
181
Dominic Battre645d42342020-12-04 16:14:10182def __init__(self, local_path, new_contents, old_contents=None, action='A',
183scm_diff=None):
gayane3dff8c22014-12-04 17:09:51184self._local_path = local_path
185self._new_contents = new_contents
186self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40187self._action = action
Dominic Battre645d42342020-12-04 16:14:10188if scm_diff:
189self._scm_diff = scm_diff
190else:
191self._scm_diff = (
192"--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
193(local_path, len(new_contents)))
194for l in new_contents:
195self._scm_diff += "+%s\n" % l
Yoland Yanb92fa522017-08-28 17:37:06196self._old_contents = old_contents
gayane3dff8c22014-12-04 17:09:51197
dbeam37e8e7402016-02-10 22:58:20198def Action(self):
agrievef32bcc72016-04-04 14:57:40199return self._action
dbeam37e8e7402016-02-10 22:58:20200
gayane3dff8c22014-12-04 17:09:51201def ChangedContents(self):
202return self._changed_contents
203
204def NewContents(self):
205return self._new_contents
206
207def LocalPath(self):
208return self._local_path
209
rdevlin.cronin9ab806c2016-02-26 23:17:13210def AbsoluteLocalPath(self):
211return self._local_path
212
jbriance9e12f162016-11-25 07:57:50213def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31214return self._scm_diff
jbriance9e12f162016-11-25 07:57:50215
Yoland Yanb92fa522017-08-28 17:37:06216def OldContents(self):
217return self._old_contents
218
davileene0426252015-03-02 21:10:41219def rfind(self, p):
220"""os.path.basename is called on MockFile so we need an rfind method."""
221return self._local_path.rfind(p)
222
223def __getitem__(self, i):
224"""os.path.basename is called on MockFile so we need a get method."""
225return self._local_path[i]
226
pastarmovj89f7ee12016-09-20 14:58:13227def __len__(self):
228"""os.path.basename is called on MockFile so we need a len method."""
229return len(self._local_path)
230
Julian Pastarmov4f7af532019-07-17 19:25:37231def replace(self, altsep, sep):
232"""os.path.basename is called on MockFile so we need a replace method."""
233return self._local_path.replace(altsep, sep)
234
gayane3dff8c22014-12-04 17:09:51235
glidere61efad2015-02-18 17:39:43236class MockAffectedFile(MockFile):
237def AbsoluteLocalPath(self):
238return self._local_path
239
240
gayane3dff8c22014-12-04 17:09:51241class MockChange(object):
242"""Mock class for Change class.
243
244This class can be used in presubmit unittests to mock the query of the
245current change.
246"""
247
248def __init__(self, changed_files):
249self._changed_files = changed_files
Chris Hall59f8d0c72020-05-01 07:31:19250self.footers = defaultdict(list)
gayane3dff8c22014-12-04 17:09:51251
252def LocalPaths(self):
253return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54254
255def AffectedFiles(self, include_dirs=False, include_deletes=True,
256file_filter=None):
257return self._changed_files
Chris Hall59f8d0c72020-05-01 07:31:19258
259def GitFootersFromDescription(self):
260return self.footers
261