Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
PRESUBMIT_test_mocks.py
Go to the documentation of this file.
1# Copyright 2023 Google Inc.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# This is a copy of PRESUBMIT_test_mocks.py from the Chromium project.
6
7from collections import defaultdict
8import fnmatch
9import json
10import os
11import re
12import subprocess
13import sys
14
15
16def _ReportErrorFileAndLine(filename, line_num, dummy_line):
17 """Default error formatter for _FindNewViolationsOfRule."""
18 return '%s:%s' % (filename, line_num)
19
20
21class MockCannedChecks(object):
22 def _FindNewViolationsOfRule(self, callable_rule, input_api,
23 source_file_filter=None,
24 error_formatter=_ReportErrorFileAndLine):
25 """Find all newly introduced violations of a per-line rule (a callable).
26
27 Arguments:
28 callable_rule: a callable taking a file extension and line of input and
29 returning True if the rule is satisfied and False if there was a
30 problem.
31 input_api: object to enumerate the affected files.
32 source_file_filter: a filter to be passed to the input api.
33 error_formatter: a callable taking (filename, line_number, line) and
34 returning a formatted error string.
35
36 Returns:
37 A list of the newly-introduced violations reported by the rule.
38 """
39 errors = []
40 for f in input_api.AffectedFiles(include_deletes=False,
41 file_filter=source_file_filter):
42 # For speed, we do two passes, checking first the full file. Shelling out
43 # to the SCM to determine the changed region can be quite expensive on
44 # Win32. Assuming that most files will be kept problem-free, we can
45 # skip the SCM operations most of the time.
46 extension = str(f.LocalPath()).rsplit('.', 1)[-1]
47 if all(callable_rule(extension, line) for line in f.NewContents()):
48 # No violation found in full text: can skip considering diff.
49 continue
50
51 for line_num, line in f.ChangedContents():
52 if not callable_rule(extension, line):
53 errors.append(error_formatter(
54 f.LocalPath(), line_num, line))
55
56 return errors
57
58
59class MockInputApi(object):
60 """Mock class for the InputApi class.
61
62 This class can be used for unittests for presubmit by initializing the files
63 attribute as the list of changed files.
64 """
65
66 DEFAULT_FILES_TO_SKIP = ()
67
68 def __init__(self):
70 self.fnmatch = fnmatch
71 self.json = json
72 self.re = re
73 self.os_path = os.path
74 self.platform = sys.platform
75 self.python_executable = sys.executable
76 self.python3_executable = sys.executable
77 self.platform = sys.platform
78 self.subprocess = subprocess
79 self.sys = sys
80 self.files = []
81 self.is_committing = False
82 self.change = MockChange([])
83 self.presubmit_local_path = os.path.dirname(__file__)
84 self.is_windows = sys.platform == 'win32'
85 self.no_diffs = False
86 # Although this makes assumptions about command line arguments used by test
87 # scripts that create mocks, it is a convenient way to set up the verbosity
88 # via the input api.
89 self.verbose = '--verbose' in sys.argv
90
91 def CreateMockFileInPath(self, f_list):
92 self.os_path.exists = lambda x: x in f_list
93
94 def AffectedFiles(self, file_filter=None, include_deletes=True):
95 for file in self.files:
96 if file_filter and not file_filter(file):
97 continue
98 if not include_deletes and file.Action() == 'D':
99 continue
100 yield file
101
102 def RightHandSideLines(self, source_file_filter=None):
103 affected_files = self.AffectedSourceFiles(source_file_filter)
104 for af in affected_files:
105 lines = af.ChangedContents()
106 for line in lines:
107 yield (af, line[0], line[1])
108
109 def AffectedSourceFiles(self, file_filter=None):
110 return self.AffectedFiles(file_filter=file_filter)
111
112 def FilterSourceFile(self, file,
113 files_to_check=(), files_to_skip=()):
114 local_path = file.LocalPath()
115 found_in_files_to_check = not files_to_check
116 if files_to_check:
117 if type(files_to_check) is str:
118 raise TypeError(
119 'files_to_check should be an iterable of strings')
120 for pattern in files_to_check:
121 compiled_pattern = re.compile(pattern)
122 if compiled_pattern.match(local_path):
123 found_in_files_to_check = True
124 break
125 if files_to_skip:
126 if type(files_to_skip) is str:
127 raise TypeError(
128 'files_to_skip should be an iterable of strings')
129 for pattern in files_to_skip:
130 compiled_pattern = re.compile(pattern)
131 if compiled_pattern.match(local_path):
132 return False
133 return found_in_files_to_check
134
135 def LocalPaths(self):
136 return [file.LocalPath() for file in self.files]
137
139 return self.presubmit_local_path
140
141 def ReadFile(self, filename, mode='r'):
142 if hasattr(filename, 'AbsoluteLocalPath'):
143 filename = filename.AbsoluteLocalPath()
144 for file_ in self.files:
145 if file_.LocalPath() == filename:
146 return '\n'.join(file_.NewContents())
147 # Otherwise, file is not in our mock API.
148 raise IOError("No such file or directory: '%s'" % filename)
149
150
151class MockOutputApi(object):
152 """Mock class for the OutputApi class.
153
154 An instance of this class can be passed to presubmit unittests for outputting
155 various types of results.
156 """
157
158 class PresubmitResult(object):
159 def __init__(self, message, items=None, long_text=''):
160 self.message = message
161 self.items = items
162 self.long_text = long_text
163
164 def __repr__(self):
165 return self.message
166
168 def __init__(self, message, items=None, long_text=''):
169 MockOutputApi.PresubmitResult.__init__(
170 self, message, items, long_text)
171 self.type = 'error'
172
174 def __init__(self, message, items=None, long_text=''):
175 MockOutputApi.PresubmitResult.__init__(
176 self, message, items, long_text)
177 self.type = 'warning'
178
180 def __init__(self, message, items=None, long_text=''):
181 MockOutputApi.PresubmitResult.__init__(
182 self, message, items, long_text)
183 self.type = 'notify'
184
186 def __init__(self, message, items=None, long_text=''):
187 MockOutputApi.PresubmitResult.__init__(
188 self, message, items, long_text)
189 self.type = 'promptOrNotify'
190
191 def __init__(self):
192 self.more_cc = []
193
194 def AppendCC(self, more_cc):
195 self.more_cc.append(more_cc)
196
197
198class MockFile(object):
199 """Mock class for the File class.
200
201 This class can be used to form the mock list of changed files in
202 MockInputApi for presubmit unittests.
203 """
204
205 def __init__(self, local_path, new_contents, old_contents=None, action='A',
206 scm_diff=None):
207 self._local_path = local_path
208 self._new_contents = new_contents
209 self._changed_contents = [(i + 1, l)
210 for i, l in enumerate(new_contents)]
211 self._action = action
212 if scm_diff:
213 self._scm_diff = scm_diff
214 else:
215 self._scm_diff = (
216 "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
217 (local_path, len(new_contents)))
218 for l in new_contents:
219 self._scm_diff += "+%s\n" % l
220 self._old_contents = old_contents
221
222 def Action(self):
223 return self._action
224
226 return self._changed_contents
227
228 def NewContents(self):
229 return self._new_contents
230
231 def LocalPath(self):
232 return self._local_path
233
235 return self._local_path
236
238 return self._scm_diff
239
240 def OldContents(self):
241 return self._old_contents
242
243 def rfind(self, p):
244 """os.path.basename is called on MockFile so we need an rfind method."""
245 return self._local_path.rfind(p)
246
247 def __getitem__(self, i):
248 """os.path.basename is called on MockFile so we need a get method."""
249 return self._local_path[i]
250
251 def __len__(self):
252 """os.path.basename is called on MockFile so we need a len method."""
253 return len(self._local_path)
254
255 def replace(self, altsep, sep):
256 """os.path.basename is called on MockFile so we need a replace method."""
257 return self._local_path.replace(altsep, sep)
258
259
262 return self._local_path
263
264
265class MockChange(object):
266 """Mock class for Change class.
267
268 This class can be used in presubmit unittests to mock the query of the
269 current change.
270 """
271
272 def __init__(self, changed_files):
273 self._changed_files = changed_files
274 self.author_email = None
275 self.footers = defaultdict(list)
276
277 def LocalPaths(self):
278 return self._changed_files
279
280 def AffectedFiles(self, include_dirs=False, include_deletes=True,
281 file_filter=None):
282 return self._changed_files
283
285 return self.footers
_FindNewViolationsOfRule(self, callable_rule, input_api, source_file_filter=None, error_formatter=_ReportErrorFileAndLine)
AffectedFiles(self, include_dirs=False, include_deletes=True, file_filter=None)
__init__(self, local_path, new_contents, old_contents=None, action='A', scm_diff=None)
AffectedFiles(self, file_filter=None, include_deletes=True)
RightHandSideLines(self, source_file_filter=None)
AffectedSourceFiles(self, file_filter=None)
FilterSourceFile(self, file, files_to_check=(), files_to_skip=())
__init__(self, message, items=None, long_text='')
__init__(self, message, items=None, long_text='')
static void append(char **dst, size_t *count, const char *src, size_t n)
Definition editor.cpp:211
_ReportErrorFileAndLine(filename, line_num, dummy_line)