blob: e15afcc9101a450580ed2077fa283f6d3cc730c3 [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
61def __init__(self):
Daniel Cheng264a447d2017-09-28 22:17:5962self.canned_checks = MockCannedChecks()
Daniel Cheng13ca61a882017-08-25 15:11:2563self.fnmatch = fnmatch
gayane3dff8c22014-12-04 17:09:5164self.json = json
65self.re = re
66self.os_path = os.path
agrievebb9c5b472016-04-22 15:13:0067self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5168self.python_executable = sys.executable
pastarmovj89f7ee12016-09-20 14:58:1369self.platform = sys.platform
gayane3dff8c22014-12-04 17:09:5170self.subprocess = subprocess
71self.files = []
72self.is_committing = False
gayanee1702662014-12-13 03:48:0973self.change = MockChange([])
dpapad5c9c24e2017-05-31 20:51:3474self.presubmit_local_path = os.path.dirname(__file__)
gayane3dff8c22014-12-04 17:09:5175
agrievef32bcc72016-04-04 14:57:4076def AffectedFiles(self, file_filter=None, include_deletes=False):
gayane3dff8c22014-12-04 17:09:5177return self.files
78
glidere61efad2015-02-18 17:39:4379def AffectedSourceFiles(self, file_filter=None):
80return self.files
81
davileene0426252015-03-02 21:10:4182def LocalPaths(self):
83return self.files
84
gayane3dff8c22014-12-04 17:09:5185def PresubmitLocalPath(self):
dpapad5c9c24e2017-05-31 20:51:3486return self.presubmit_local_path
gayane3dff8c22014-12-04 17:09:5187
88def ReadFile(self, filename, mode='rU'):
glidere61efad2015-02-18 17:39:4389if hasattr(filename, 'AbsoluteLocalPath'):
90filename = filename.AbsoluteLocalPath()
gayane3dff8c22014-12-04 17:09:5191for file_ in self.files:
92if file_.LocalPath() == filename:
93return '\n'.join(file_.NewContents())
94# Otherwise, file is not in our mock API.
95raise IOError, "No such file or directory: '%s'" % filename
96
97
98class MockOutputApi(object):
gayane860db5c32014-12-05 16:16:4699"""Mock class for the OutputApi class.
gayane3dff8c22014-12-04 17:09:51100
101An instance of this class can be passed to presubmit unittests for outputing
102various types of results.
103"""
104
105class PresubmitResult(object):
106def __init__(self, message, items=None, long_text=''):
107self.message = message
108self.items = items
109self.long_text = long_text
110
gayane940df072015-02-24 14:28:30111def __repr__(self):
112return self.message
113
gayane3dff8c22014-12-04 17:09:51114class PresubmitError(PresubmitResult):
davileene0426252015-03-02 21:10:41115def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51116MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
117self.type = 'error'
118
119class PresubmitPromptWarning(PresubmitResult):
davileene0426252015-03-02 21:10:41120def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51121MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
122self.type = 'warning'
123
124class PresubmitNotifyResult(PresubmitResult):
davileene0426252015-03-02 21:10:41125def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51126MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
127self.type = 'notify'
128
129class PresubmitPromptOrNotify(PresubmitResult):
davileene0426252015-03-02 21:10:41130def __init__(self, message, items=None, long_text=''):
gayane3dff8c22014-12-04 17:09:51131MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
132self.type = 'promptOrNotify'
133
Daniel Cheng7052cdf2017-11-21 19:23:29134def __init__(self):
135self.more_cc = []
136
137def AppendCC(self, more_cc):
138self.more_cc.extend(more_cc)
139
gayane3dff8c22014-12-04 17:09:51140
141class MockFile(object):
142"""Mock class for the File class.
143
144This class can be used to form the mock list of changed files in
145MockInputApi for presubmit unittests.
146"""
147
Yoland Yanb92fa522017-08-28 17:37:06148def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51149self._local_path = local_path
150self._new_contents = new_contents
151self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40152self._action = action
jbriance9e12f162016-11-25 07:57:50153self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
154len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06155self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50156for l in new_contents:
157self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51158
dbeam37e8e7402016-02-10 22:58:20159def Action(self):
agrievef32bcc72016-04-04 14:57:40160return self._action
dbeam37e8e7402016-02-10 22:58:20161
gayane3dff8c22014-12-04 17:09:51162def ChangedContents(self):
163return self._changed_contents
164
165def NewContents(self):
166return self._new_contents
167
168def LocalPath(self):
169return self._local_path
170
rdevlin.cronin9ab806c2016-02-26 23:17:13171def AbsoluteLocalPath(self):
172return self._local_path
173
jbriance9e12f162016-11-25 07:57:50174def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31175return self._scm_diff
jbriance9e12f162016-11-25 07:57:50176
Yoland Yanb92fa522017-08-28 17:37:06177def OldContents(self):
178return self._old_contents
179
davileene0426252015-03-02 21:10:41180def rfind(self, p):
181"""os.path.basename is called on MockFile so we need an rfind method."""
182return self._local_path.rfind(p)
183
184def __getitem__(self, i):
185"""os.path.basename is called on MockFile so we need a get method."""
186return self._local_path[i]
187
pastarmovj89f7ee12016-09-20 14:58:13188def __len__(self):
189"""os.path.basename is called on MockFile so we need a len method."""
190return len(self._local_path)
191
gayane3dff8c22014-12-04 17:09:51192
glidere61efad2015-02-18 17:39:43193class MockAffectedFile(MockFile):
194def AbsoluteLocalPath(self):
195return self._local_path
196
197
gayane3dff8c22014-12-04 17:09:51198class MockChange(object):
199"""Mock class for Change class.
200
201This class can be used in presubmit unittests to mock the query of the
202current change.
203"""
204
205def __init__(self, changed_files):
206self._changed_files = changed_files
207
208def LocalPaths(self):
209return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54210
211def AffectedFiles(self, include_dirs=False, include_deletes=True,
212file_filter=None):
213return self._changed_files