blob: 3927f26569b53685743e1698de7cd2347944b745 [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
134
135class MockFile(object):
136"""Mock class for the File class.
137
138This class can be used to form the mock list of changed files in
139MockInputApi for presubmit unittests.
140"""
141
Yoland Yanb92fa522017-08-28 17:37:06142def __init__(self, local_path, new_contents, old_contents=None, action='A'):
gayane3dff8c22014-12-04 17:09:51143self._local_path = local_path
144self._new_contents = new_contents
145self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
agrievef32bcc72016-04-04 14:57:40146self._action = action
jbriance9e12f162016-11-25 07:57:50147self._scm_diff = "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % (local_path,
148len(new_contents))
Yoland Yanb92fa522017-08-28 17:37:06149self._old_contents = old_contents
jbriance9e12f162016-11-25 07:57:50150for l in new_contents:
151self._scm_diff += "+%s\n" % l
gayane3dff8c22014-12-04 17:09:51152
dbeam37e8e7402016-02-10 22:58:20153def Action(self):
agrievef32bcc72016-04-04 14:57:40154return self._action
dbeam37e8e7402016-02-10 22:58:20155
gayane3dff8c22014-12-04 17:09:51156def ChangedContents(self):
157return self._changed_contents
158
159def NewContents(self):
160return self._new_contents
161
162def LocalPath(self):
163return self._local_path
164
rdevlin.cronin9ab806c2016-02-26 23:17:13165def AbsoluteLocalPath(self):
166return self._local_path
167
jbriance9e12f162016-11-25 07:57:50168def GenerateScmDiff(self):
jbriance2c51e821a2016-12-12 08:24:31169return self._scm_diff
jbriance9e12f162016-11-25 07:57:50170
Yoland Yanb92fa522017-08-28 17:37:06171def OldContents(self):
172return self._old_contents
173
davileene0426252015-03-02 21:10:41174def rfind(self, p):
175"""os.path.basename is called on MockFile so we need an rfind method."""
176return self._local_path.rfind(p)
177
178def __getitem__(self, i):
179"""os.path.basename is called on MockFile so we need a get method."""
180return self._local_path[i]
181
pastarmovj89f7ee12016-09-20 14:58:13182def __len__(self):
183"""os.path.basename is called on MockFile so we need a len method."""
184return len(self._local_path)
185
gayane3dff8c22014-12-04 17:09:51186
glidere61efad2015-02-18 17:39:43187class MockAffectedFile(MockFile):
188def AbsoluteLocalPath(self):
189return self._local_path
190
191
gayane3dff8c22014-12-04 17:09:51192class MockChange(object):
193"""Mock class for Change class.
194
195This class can be used in presubmit unittests to mock the query of the
196current change.
197"""
198
199def __init__(self, changed_files):
200self._changed_files = changed_files
201
202def LocalPaths(self):
203return self._changed_files
rdevlin.cronin113668252016-05-02 17:05:54204
205def AffectedFiles(self, include_dirs=False, include_deletes=True,
206file_filter=None):
207return self._changed_files