blob: 732da0ea38c71ad405ea94f726759cbc4202e610 [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
54class MockInputApi(object):
55"""Mock class for the InputApi class.
56
57This class can be used for unittests for presubmit by initializing the files
58attribute as the list of changed files.
59"""
60
Sylvain Defresnea8b73d252018-02-28 15:45:5461DEFAULT_BLACK_LIST = ()
62
gayane3dff8c22014-12-04 17:09:5163def __init__(self):
Daniel Cheng264a447d2017-09-28 22:17:5964self.canned_checks = MockCannedChecks()
Daniel Cheng13ca61a882017-08-25 15:11:2565self.fnmatch = fnmatch
gayane3dff8c22014-12-04 17:09:5166self.json = json
67self.re = re
68self.os_path = os.path
agrievebb9c5b472016-04-22 15:13:0069self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5170self.python_executable = sys.executable
pastarmovj89f7ee12016-09-20 14:58:1371self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5172self.subprocess = subprocess
73self.files = []
74self.is_committing = False
gayanee1702662014-12-13 03:48:0975self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3476self.presubmit_local_path = os.path.dirname(__file__)
gayane3dff8c22014-12-04 17:09:5177
agrievef32bcc72016-04-04 14:57:4078def AffectedFiles(self, file_filter=None, include_deletes=False):
Sylvain Defresnea8b73d252018-02-28 15:45:5479for file in self.files:
80if file_filter and not file_filter(file):
81continue
82if not include_deletes and file.Action() == 'D':
83continue
84yield file
gayane3dff8c22014-12-04 17:09:5185
glidere61efad2015-02-18 17:39:4386def AffectedSourceFiles(self, file_filter=None):
Sylvain Defresnea8b73d252018-02-28 15:45:5487return self.AffectedFiles(file_filter=file_filter)
88
89def FilterSourceFile(self, file, white_list=(), black_list=()):
90local_path = file.LocalPath()
91if white_list:
92for pattern in white_list:
93compiled_pattern = re.compile(pattern)
94if compiled_pattern.search(local_path):
95return True
96if black_list:
97for pattern in black_list:
98compiled_pattern = re.compile(pattern)
99if compiled_pattern.search(local_path):
100return False
101return True
glidere61efad2015-02-18 17:39:43102
davileene0426252015-03-02 21:10:41103def LocalPaths(self):
104return self.files
105
gayane3dff8c22014-12-04 17:09:51106def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:34107return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:51108
109def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:43110if hasattr(filename, 'AbsoluteLocalPath'):
111filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:51112for file_ in self.files:
113if file_.LocalPath() == filename:
114return '\n'.join(file_.NewContents())
115# Otherwise, file is not in our mock API.
116raise IOError, "No such file or directory: '%s'" % filename
117
118
119class MockOutputApi(object):
gayane860db5c32014-12-05 16:16:46120"""Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51121
122An instance of this class can be passed to presubmit unittests for outputing
123various types of results.
124"""
125
126class PresubmitResult(object):
127def __init__(self, message, items=None, long_text=''):
128self.message = message
129self.items = items
130self.long_text = long_text
131
gayane940df072015-02-24 14:28:30132def __repr__(self):
133return self.message
134
gayane3dff8c22014-12-04 17:09:51135class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41136def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51137MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
138self.type = 'error'
139
140class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41141def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51142MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
143self.type = 'warning'
144
145class PresubmitNotifyResult(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 = 'notify'
149
150class PresubmitPromptOrNotify(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 = 'promptOrNotify'
154
Daniel Cheng7052cdf2017-11-21 19:23:29155def __init__(self):
156self.more_cc = []
157
158def AppendCC(self, more_cc):
159self.more_cc.extend(more_cc)
160
gayane3dff8c22014-12-04 17:09:51161
162class MockFile(object):
163"""Mock class for the File class.
164
165This class can be used to form the mock list of changed files in
166MockInputApi for presubmit unittests.
167"""
168
Yoland Yanb92fa522017-08-28 17:37:06169def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51170self._local_path = local_path
171self._new_contents = new_contents
172self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40173self._action = action
jbriance9e12f162016-11-25 07:57:50174self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
175len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06176self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50177for l in new_contents:
178self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51179
dbeam37e8e7402016-02-10 22:58:20180def Action(self):
agrievef32bcc72016-04-04 14:57:40181return self._action
dbeam37e8e7402016-02-10 22:58:20182
gayane3dff8c22014-12-04 17:09:51183def ChangedContents(self):
184return self._changed_contents
185
186def NewContents(self):
187return self._new_contents
188
189def LocalPath(self):
190return self._local_path
191
rdevlin.cronin9ab806c2016-02-26 23:17:13192def AbsoluteLocalPath(self):
193return self._local_path
194
jbriance9e12f162016-11-25 07:57:50195def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31196return self._scm_diff
jbriance9e12f162016-11-25 07:57:50197
Yoland Yanb92fa522017-08-28 17:37:06198def OldContents(self):
199return self._old_contents
200
davileene0426252015-03-02 21:10:41201def rfind(self, p):
202"""os.path.basename is called on MockFile so we need an rfind method."""
203return self._local_path.rfind(p)
204
205def __getitem__(self, i):
206"""os.path.basename is called on MockFile so we need a get method."""
207return self._local_path[i]
208
pastarmovj89f7ee12016-09-20 14:58:13209def __len__(self):
210"""os.path.basename is called on MockFile so we need a len method."""
211return len(self._local_path)
212
gayane3dff8c22014-12-04 17:09:51213
glidere61efad2015-02-18 17:39:43214class MockAffectedFile(MockFile):
215def AbsoluteLocalPath(self):
216return self._local_path
217
218
gayane3dff8c22014-12-04 17:09:51219class MockChange(object):
220"""Mock class for Change class.
221
222This class can be used in presubmit unittests to mock the query of the
223current change.
224"""
225
226def __init__(self, changed_files):
227self._changed_files = changed_files
228
229def LocalPaths(self):
230return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54231
232def AffectedFiles(self, include_dirs=False, include_deletes=True,
233file_filter=None):
234return self._changed_files