blob: 577dfc99526935b7f4b997cfa944647bd4523713 [file] [log] [blame]
[email protected]fb2b8eb2009-04-23 21:03:421#!/usr/bin/env python
2# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Generic presubmit checks that can be reused by other presubmit checks."""
7
[email protected]3410d912009-06-09 20:56:168### Description checks
9
[email protected]00c41e42009-05-12 21:43:1310def CheckChangeHasTestField(input_api, output_api):
11 """Requires that the changelist have a TEST= field."""
[email protected]e1a524f2009-05-27 14:43:4612 if input_api.change.TEST:
[email protected]00c41e42009-05-12 21:43:1313 return []
14 else:
15 return [output_api.PresubmitNotifyResult(
16 "Changelist should have a TEST= field. TEST=none is allowed.")]
17
18
19def CheckChangeHasBugField(input_api, output_api):
20 """Requires that the changelist have a BUG= field."""
[email protected]e1a524f2009-05-27 14:43:4621 if input_api.change.BUG:
[email protected]00c41e42009-05-12 21:43:1322 return []
23 else:
24 return [output_api.PresubmitNotifyResult(
25 "Changelist should have a BUG= field. BUG=none is allowed.")]
26
27
[email protected]fb2b8eb2009-04-23 21:03:4228def CheckChangeHasTestedField(input_api, output_api):
29 """Requires that the changelist have a TESTED= field."""
[email protected]e1a524f2009-05-27 14:43:4630 if input_api.change.TESTED:
[email protected]fb2b8eb2009-04-23 21:03:4231 return []
32 else:
33 return [output_api.PresubmitError("Changelist must have a TESTED= field.")]
34
35
36def CheckChangeHasQaField(input_api, output_api):
37 """Requires that the changelist have a QA= field."""
38 if input_api.change.QA:
39 return []
40 else:
41 return [output_api.PresubmitError("Changelist must have a QA= field.")]
42
43
44def CheckDoNotSubmitInDescription(input_api, output_api):
45 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description.
46 """
47 keyword = 'DO NOT ' + 'SUBMIT'
48 if keyword in input_api.change.DescriptionText():
49 return [output_api.PresubmitError(
50 keyword + " is present in the changelist description.")]
51 else:
52 return []
53
54
[email protected]bc50eb42009-06-10 18:22:4755def CheckChangeHasDescription(input_api, output_api):
56 """Checks the CL description is not empty."""
57 text = input_api.change.DescriptionText()
58 if text.strip() == '':
59 if input_api.is_committing:
60 return [output_api.PresubmitError("Add a description.")]
61 else:
62 return [output_api.PresubmitNotifyResult("Add a description.")]
63 return []
64
[email protected]3410d912009-06-09 20:56:1665### Content checks
66
[email protected]fb2b8eb2009-04-23 21:03:4267def CheckDoNotSubmitInFiles(input_api, output_api):
68 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
69 keyword = 'DO NOT ' + 'SUBMIT'
[email protected]3410d912009-06-09 20:56:1670 # We want to check every text files, not just source files.
71 for f, line_num, line in input_api.RightHandSideLines(lambda x: x):
[email protected]fb2b8eb2009-04-23 21:03:4272 if keyword in line:
73 text = 'Found ' + keyword + ' in %s, line %s' % (f.LocalPath(), line_num)
74 return [output_api.PresubmitError(text)]
75 return []
76
77
[email protected]26970fa2009-11-17 18:07:3278def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
79 """Checks that all ".cc" and ".h" files pass cpplint.py."""
80 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
81 result = []
82
83 # Initialize cpplint.
84 import cpplint
85 cpplint._cpplint_state.ResetErrorCounts()
86
87 # Justifications for each filter:
88 #
89 # - build/include : Too many; fix in the future.
90 # - build/include_order : Not happening; #ifdefed includes.
91 # - build/namespace : I'm surprised by how often we violate this rule.
92 # - readability/casting : Mistakes a whole bunch of function pointer.
93 # - runtime/int : Can be fixed long term; volume of errors too high
94 # - runtime/virtual : Broken now, but can be fixed in the future?
95 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
96 cpplint._SetFilters("-build/include,-build/include_order,-build/namespace,"
97 "-readability/casting,-runtime/int,-runtime/virtual,"
98 "-whitespace/braces")
99
100 # We currently are more strict with normal code than unit tests; 4 and 5 are
101 # the verbosity level that would normally be passed to cpplint.py through
102 # --verbose=#. Hopefully, in the future, we can be more verbose.
103 files = [f.AbsoluteLocalPath() for f in
104 input_api.AffectedSourceFiles(source_file_filter)]
105 for file_name in files:
106 if _RE_IS_TEST.match(file_name):
107 level = 5
108 else:
109 level = 4
110
111 cpplint.ProcessFile(file_name, level)
112
113 if cpplint._cpplint_state.error_count > 0:
114 if input_api.is_committing:
115 res_type = output_api.PresubmitError
116 else:
117 res_type = output_api.PresubmitPromptWarning
118 result = [res_type("Changelist failed cpplint.py check.")]
119
120 return result
121
122
[email protected]3410d912009-06-09 20:56:16123def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
[email protected]e9b71c92009-06-10 18:10:01124 """Checks no '\r' (CR) character is in any source files."""
125 cr_files = []
[email protected]3410d912009-06-09 20:56:16126 for f in input_api.AffectedSourceFiles(source_file_filter):
[email protected]44a17ad2009-06-08 14:14:35127 if '\r' in input_api.ReadFile(f, 'rb'):
[email protected]e9b71c92009-06-10 18:10:01128 cr_files.append(f.LocalPath())
129 if cr_files:
130 return [output_api.PresubmitPromptWarning(
131 "Found a CR character in these files:", items=cr_files)]
132 return []
133
134
[email protected]da8cddd2009-08-13 00:25:55135def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
136 """Checks for files in svn modified directories.
137
138 They will get submitted on accident because svn commits recursively by
139 default, and that's very dangerous.
140 """
141 if input_api.change.scm != 'svn':
142 return []
143
144 errors = []
145 current_cl_files = input_api.change.GetModifiedFiles()
146 all_modified_files = input_api.change.GetAllModifiedFiles()
147 # Filter out files in the current CL.
148 modified_files = [f for f in all_modified_files if f not in current_cl_files]
149 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
150
151 for f in input_api.AffectedFiles(source_file_filter):
152 if f.Action() == 'M' and f.IsDirectory():
153 curpath = f.AbsoluteLocalPath()
154 bad_files = []
155 # Check if any of the modified files in other CLs are under curpath.
156 for i in xrange(len(modified_files)):
157 abspath = modified_abspaths[i]
158 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
159 bad_files.append(modified_files[i])
160 if bad_files:
161 if input_api.is_committing:
162 error_type = output_api.PresubmitPromptWarning
163 else:
164 error_type = output_api.PresubmitNotifyResult
165 errors.append(error_type(
166 "Potential accidental commits in changelist %s:" % f.LocalPath(),
167 items=bad_files))
168 return errors
169
170
[email protected]e9b71c92009-06-10 18:10:01171def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
172 """Checks the files ends with one and only one \n (LF)."""
173 eof_files = []
174 for f in input_api.AffectedSourceFiles(source_file_filter):
175 contents = input_api.ReadFile(f, 'rb')
176 # Check that the file ends in one and only one newline character.
177 if len(contents) > 1 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"):
178 eof_files.append(f.LocalPath())
179
180 if eof_files:
181 return [output_api.PresubmitPromptWarning(
182 'These files should end in one (and only one) newline character:',
183 items=eof_files)]
184 return []
185
186
187def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
188 source_file_filter=None):
189 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
190
191 It is faster because it is reading the file only once.
192 """
193 cr_files = []
194 eof_files = []
195 for f in input_api.AffectedSourceFiles(source_file_filter):
196 contents = input_api.ReadFile(f, 'rb')
197 if '\r' in contents:
198 cr_files.append(f.LocalPath())
199 # Check that the file ends in one and only one newline character.
200 if len(contents) > 1 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"):
201 eof_files.append(f.LocalPath())
202 outputs = []
203 if cr_files:
204 outputs.append(output_api.PresubmitPromptWarning(
205 "Found a CR character in these files:", items=cr_files))
206 if eof_files:
207 outputs.append(output_api.PresubmitPromptWarning(
208 'These files should end in one (and only one) newline character:',
209 items=eof_files))
[email protected]44a17ad2009-06-08 14:14:35210 return outputs
211
212
[email protected]3410d912009-06-09 20:56:16213def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
[email protected]fb2b8eb2009-04-23 21:03:42214 """Checks that there are no tab characters in any of the text files to be
215 submitted.
216 """
[email protected]e9b71c92009-06-10 18:10:01217 tabs = []
[email protected]3410d912009-06-09 20:56:16218 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
[email protected]fb2b8eb2009-04-23 21:03:42219 if '\t' in line:
[email protected]e9b71c92009-06-10 18:10:01220 tabs.append("%s, line %s" % (f.LocalPath(), line_num))
221 if tabs:
222 return [output_api.PresubmitPromptWarning("Found a tab character in:",
223 long_text="\n".join(tabs))]
[email protected]fb2b8eb2009-04-23 21:03:42224 return []
225
226
[email protected]f5888bb2009-06-10 20:26:37227def CheckChangeHasNoStrayWhitespace(input_api, output_api,
228 source_file_filter=None):
229 """Checks that there is no stray whitespace at source lines end."""
230 errors = []
231 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
232 if line.rstrip() != line:
233 errors.append("%s, line %s" % (f.LocalPath(), line_num))
234 if errors:
235 return [output_api.PresubmitPromptWarning(
236 "Found line ending with white spaces in:",
237 long_text="\n".join(errors))]
238 return []
239
240
[email protected]3410d912009-06-09 20:56:16241def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None):
[email protected]fb2b8eb2009-04-23 21:03:42242 """Checks that there aren't any lines longer than maxlen characters in any of
243 the text files to be submitted.
244 """
[email protected]fb2b8eb2009-04-23 21:03:42245 bad = []
[email protected]3410d912009-06-09 20:56:16246 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
[email protected]5c2720e2009-06-09 14:04:08247 # Allow lines with http://, https:// and #define/#pragma/#include/#if/#endif
248 # to exceed the maxlen rule.
249 if (len(line) > maxlen and
250 not 'http://' in line and
251 not 'https://' in line and
252 not line.startswith('#define') and
253 not line.startswith('#include') and
254 not line.startswith('#pragma') and
255 not line.startswith('#if') and
256 not line.startswith('#endif')):
[email protected]fb2b8eb2009-04-23 21:03:42257 bad.append(
258 '%s, line %s, %s chars' %
[email protected]1487d532009-06-06 00:22:57259 (f.LocalPath(), line_num, len(line)))
[email protected]fb2b8eb2009-04-23 21:03:42260 if len(bad) == 5: # Just show the first 5 errors.
261 break
262
263 if bad:
264 msg = "Found lines longer than %s characters (first 5 shown)." % maxlen
265 return [output_api.PresubmitPromptWarning(msg, items=bad)]
266 else:
267 return []
268
269
[email protected]b9e7ada2010-01-27 23:12:39270def CheckLicense(input_api, output_api, license, source_file_filter=None):
271 """Verifies the license header.
272 """
273 license_re = input_api.re.compile(license, input_api.re.MULTILINE)
274 bad_files = []
275 for f in input_api.AffectedSourceFiles(source_file_filter):
276 contents = input_api.ReadFile(f, 'rb')
277 if not license_re.search(contents):
278 bad_files.append(f.LocalPath())
279 if bad_files:
280 if input_api.is_committing:
281 res_type = output_api.PresubmitPromptWarning
282 else:
283 res_type = output_api.PresubmitNotifyResult
284 return [res_type(
285 "Found a bad license header in these files:", items=bad_files)]
286 return []
287
288
[email protected]1a0e3cb2009-06-10 18:03:04289def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
[email protected]b7d46902009-06-10 14:12:10290 """Checks that the source files have svn:eol-style=LF."""
[email protected]46e832a2009-06-18 19:58:07291 return CheckSvnProperty(input_api, output_api,
292 'svn:eol-style', 'LF',
293 input_api.AffectedSourceFiles(source_file_filter))
294
295
296def CheckSvnForCommonMimeTypes(input_api, output_api):
297 """Checks that common binary file types have the correct svn:mime-type."""
298 output = []
299 files = input_api.AffectedFiles(include_deletes=False)
[email protected]e49187c2009-06-26 22:44:53300 def IsExts(x, exts):
301 path = x.LocalPath()
302 for extension in exts:
303 if path.endswith(extension):
304 return True
305 return False
[email protected]46e832a2009-06-18 19:58:07306 def FilterFiles(extension):
[email protected]e49187c2009-06-26 22:44:53307 return filter(lambda x: IsExts(x, extension), files)
[email protected]46e832a2009-06-18 19:58:07308 def RunCheck(mime_type, files):
309 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
310 mime_type, files))
[email protected]e49187c2009-06-26 22:44:53311 RunCheck('application/pdf', FilterFiles(['.pdf']))
312 RunCheck('image/bmp', FilterFiles(['.bmp']))
313 RunCheck('image/gif', FilterFiles(['.gif']))
314 RunCheck('image/png', FilterFiles(['.png']))
315 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
316 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
[email protected]46e832a2009-06-18 19:58:07317 return output
318
319
320def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
321 """Checks that affected_files files have prop=expected."""
[email protected]da8cddd2009-08-13 00:25:55322 if input_api.change.scm != 'svn':
323 return []
324
325 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
[email protected]b7d46902009-06-10 14:12:10326 if bad:
[email protected]0874d472009-06-10 19:08:33327 if input_api.is_committing:
[email protected]e3608df2009-11-10 20:22:57328 res_type = output_api.PresubmitError
[email protected]0874d472009-06-10 19:08:33329 else:
[email protected]e3608df2009-11-10 20:22:57330 res_type = output_api.PresubmitNotifyResult
[email protected]46e832a2009-06-18 19:58:07331 message = "Run `svn pset %s %s <item>` on these files:" % (prop, expected)
[email protected]e3608df2009-11-10 20:22:57332 return [res_type(message, items=bad)]
[email protected]b7d46902009-06-10 14:12:10333 return []
334
335
[email protected]3410d912009-06-09 20:56:16336### Other checks
337
338def CheckDoNotSubmit(input_api, output_api):
339 return (
340 CheckDoNotSubmitInDescription(input_api, output_api) +
341 CheckDoNotSubmitInFiles(input_api, output_api)
342 )
343
344
[email protected]fb2b8eb2009-04-23 21:03:42345def CheckTreeIsOpen(input_api, output_api, url, closed):
346 """Checks that an url's content doesn't match a regexp that would mean that
347 the tree is closed."""
[email protected]89491382009-06-06 18:58:39348 assert(input_api.is_committing)
[email protected]fb2b8eb2009-04-23 21:03:42349 try:
350 connection = input_api.urllib2.urlopen(url)
351 status = connection.read()
352 connection.close()
353 if input_api.re.match(closed, status):
354 long_text = status + '\n' + url
[email protected]89491382009-06-06 18:58:39355 return [output_api.PresubmitPromptWarning("The tree is closed.",
356 long_text=long_text)]
[email protected]fb2b8eb2009-04-23 21:03:42357 except IOError:
358 pass
359 return []
[email protected]7b305e82009-05-19 18:24:20360
361
362def RunPythonUnitTests(input_api, output_api, unit_tests):
[email protected]c0b22972009-06-25 16:19:14363 """Run the unit tests out of process, capture the output and use the result
364 code to determine success.
365 """
[email protected]d7dccf52009-06-06 18:51:58366 # We don't want to hinder users from uploading incomplete patches.
367 if input_api.is_committing:
368 message_type = output_api.PresubmitError
369 else:
370 message_type = output_api.PresubmitNotifyResult
[email protected]7b305e82009-05-19 18:24:20371 outputs = []
372 for unit_test in unit_tests:
[email protected]c0b22972009-06-25 16:19:14373 # Run the unit tests out of process. This is because some unit tests
374 # stub out base libraries and don't clean up their mess. It's too easy to
375 # get subtle bugs.
376 cwd = None
377 env = None
378 unit_test_name = unit_test
379 # "python -m test.unit_test" doesn't work. We need to change to the right
380 # directory instead.
381 if '.' in unit_test:
382 # Tests imported in submodules (subdirectories) assume that the current
383 # directory is in the PYTHONPATH. Manually fix that.
384 unit_test = unit_test.replace('.', '/')
385 cwd = input_api.os_path.dirname(unit_test)
386 unit_test = input_api.os_path.basename(unit_test)
387 env = input_api.environ.copy()
[email protected]ab318592009-09-04 00:54:55388 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
389 backpath = [
390 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
391 ]
[email protected]c0b22972009-06-25 16:19:14392 if env.get('PYTHONPATH'):
393 backpath.append(env.get('PYTHONPATH'))
[email protected]a301f1f2009-08-05 10:37:33394 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
[email protected]c0b22972009-06-25 16:19:14395 subproc = input_api.subprocess.Popen(
396 [
397 input_api.python_executable,
398 "-m",
399 "%s" % unit_test
400 ],
401 cwd=cwd,
402 env=env,
403 stdin=input_api.subprocess.PIPE,
404 stdout=input_api.subprocess.PIPE,
405 stderr=input_api.subprocess.PIPE)
406 stdoutdata, stderrdata = subproc.communicate()
407 # Discard the output if returncode == 0
408 if subproc.returncode:
409 outputs.append("Test '%s' failed with code %d\n%s\n%s\n" % (
410 unit_test_name, subproc.returncode, stdoutdata, stderrdata))
411 if outputs:
412 return [message_type("%d unit tests failed." % len(outputs),
413 long_text='\n'.join(outputs))]
414 return []