blob: c7c93be6ca5b484d5b70e99a1479bffc73a01d2d [file] [log] [blame]
maruel@chromium.orgba551772010-02-03 18:21:42 +00001# Copyright (c) 2010 The Chromium Authors. All rights reserved.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Generic presubmit checks that can be reused by other presubmit checks."""
6
maruel@chromium.org3410d912009-06-09 20:56:16 +00007### Description checks
8
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +00009def CheckChangeHasTestField(input_api, output_api):
10 """Requires that the changelist have a TEST= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000011 if input_api.change.TEST:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000012 return []
13 else:
14 return [output_api.PresubmitNotifyResult(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000015 'Changelist should have a TEST= field. TEST=none is allowed.')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000016
17
18def CheckChangeHasBugField(input_api, output_api):
19 """Requires that the changelist have a BUG= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000020 if input_api.change.BUG:
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000021 return []
22 else:
23 return [output_api.PresubmitNotifyResult(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000024 'Changelist should have a BUG= field. BUG=none is allowed.')]
kuchhal@chromium.org00c41e42009-05-12 21:43:13 +000025
26
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027def CheckChangeHasTestedField(input_api, output_api):
28 """Requires that the changelist have a TESTED= field."""
maruel@chromium.orge1a524f2009-05-27 14:43:46 +000029 if input_api.change.TESTED:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000030 return []
31 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000032 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000033
34
35def CheckChangeHasQaField(input_api, output_api):
36 """Requires that the changelist have a QA= field."""
37 if input_api.change.QA:
38 return []
39 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000040 return [output_api.PresubmitError('Changelist must have a QA= field.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000041
42
43def CheckDoNotSubmitInDescription(input_api, output_api):
44 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description.
45 """
46 keyword = 'DO NOT ' + 'SUBMIT'
47 if keyword in input_api.change.DescriptionText():
48 return [output_api.PresubmitError(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000049 keyword + ' is present in the changelist description.')]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050 else:
51 return []
52
53
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000054def CheckChangeHasDescription(input_api, output_api):
55 """Checks the CL description is not empty."""
56 text = input_api.change.DescriptionText()
57 if text.strip() == '':
58 if input_api.is_committing:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000059 return [output_api.PresubmitError('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000060 else:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000061 return [output_api.PresubmitNotifyResult('Add a description.')]
maruel@chromium.orgbc50eb42009-06-10 18:22:47 +000062 return []
63
maruel@chromium.org3410d912009-06-09 20:56:16 +000064### Content checks
65
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066def CheckDoNotSubmitInFiles(input_api, output_api):
67 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
68 keyword = 'DO NOT ' + 'SUBMIT'
maruel@chromium.org3410d912009-06-09 20:56:16 +000069 # We want to check every text files, not just source files.
70 for f, line_num, line in input_api.RightHandSideLines(lambda x: x):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071 if keyword in line:
72 text = 'Found ' + keyword + ' in %s, line %s' % (f.LocalPath(), line_num)
73 return [output_api.PresubmitError(text)]
74 return []
75
76
erg@google.com26970fa2009-11-17 18:07:32 +000077def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000078 """Checks that all '.cc' and '.h' files pass cpplint.py."""
erg@google.com26970fa2009-11-17 18:07:32 +000079 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
80 result = []
81
82 # Initialize cpplint.
83 import cpplint
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +000084 # Access to a protected member _XX of a client class
85 # pylint: disable=W0212
erg@google.com26970fa2009-11-17 18:07:32 +000086 cpplint._cpplint_state.ResetErrorCounts()
87
88 # Justifications for each filter:
89 #
90 # - build/include : Too many; fix in the future.
91 # - build/include_order : Not happening; #ifdefed includes.
92 # - build/namespace : I'm surprised by how often we violate this rule.
93 # - readability/casting : Mistakes a whole bunch of function pointer.
94 # - runtime/int : Can be fixed long term; volume of errors too high
95 # - runtime/virtual : Broken now, but can be fixed in the future?
96 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +000097 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
98 '-readability/casting,-runtime/int,-runtime/virtual,'
99 '-whitespace/braces')
erg@google.com26970fa2009-11-17 18:07:32 +0000100
101 # We currently are more strict with normal code than unit tests; 4 and 5 are
102 # the verbosity level that would normally be passed to cpplint.py through
103 # --verbose=#. Hopefully, in the future, we can be more verbose.
104 files = [f.AbsoluteLocalPath() for f in
105 input_api.AffectedSourceFiles(source_file_filter)]
106 for file_name in files:
107 if _RE_IS_TEST.match(file_name):
108 level = 5
109 else:
110 level = 4
111
112 cpplint.ProcessFile(file_name, level)
113
114 if cpplint._cpplint_state.error_count > 0:
115 if input_api.is_committing:
116 res_type = output_api.PresubmitError
117 else:
118 res_type = output_api.PresubmitPromptWarning
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000119 result = [res_type('Changelist failed cpplint.py check.')]
erg@google.com26970fa2009-11-17 18:07:32 +0000120
121 return result
122
123
maruel@chromium.org3410d912009-06-09 20:56:16 +0000124def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000125 """Checks no '\r' (CR) character is in any source files."""
126 cr_files = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000127 for f in input_api.AffectedSourceFiles(source_file_filter):
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000128 if '\r' in input_api.ReadFile(f, 'rb'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000129 cr_files.append(f.LocalPath())
130 if cr_files:
131 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000132 'Found a CR character in these files:', items=cr_files)]
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000133 return []
134
135
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000136def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
137 """Checks for files in svn modified directories.
138
139 They will get submitted on accident because svn commits recursively by
140 default, and that's very dangerous.
141 """
142 if input_api.change.scm != 'svn':
143 return []
144
145 errors = []
146 current_cl_files = input_api.change.GetModifiedFiles()
147 all_modified_files = input_api.change.GetAllModifiedFiles()
148 # Filter out files in the current CL.
149 modified_files = [f for f in all_modified_files if f not in current_cl_files]
150 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
151
152 for f in input_api.AffectedFiles(source_file_filter):
153 if f.Action() == 'M' and f.IsDirectory():
154 curpath = f.AbsoluteLocalPath()
155 bad_files = []
156 # Check if any of the modified files in other CLs are under curpath.
157 for i in xrange(len(modified_files)):
158 abspath = modified_abspaths[i]
159 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
160 bad_files.append(modified_files[i])
161 if bad_files:
162 if input_api.is_committing:
163 error_type = output_api.PresubmitPromptWarning
164 else:
165 error_type = output_api.PresubmitNotifyResult
166 errors.append(error_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000167 'Potential accidental commits in changelist %s:' % f.LocalPath(),
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000168 items=bad_files))
169 return errors
170
171
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000172def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
173 """Checks the files ends with one and only one \n (LF)."""
174 eof_files = []
175 for f in input_api.AffectedSourceFiles(source_file_filter):
176 contents = input_api.ReadFile(f, 'rb')
177 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000178 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000179 eof_files.append(f.LocalPath())
180
181 if eof_files:
182 return [output_api.PresubmitPromptWarning(
183 'These files should end in one (and only one) newline character:',
184 items=eof_files)]
185 return []
186
187
188def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
189 source_file_filter=None):
190 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
191
192 It is faster because it is reading the file only once.
193 """
194 cr_files = []
195 eof_files = []
196 for f in input_api.AffectedSourceFiles(source_file_filter):
197 contents = input_api.ReadFile(f, 'rb')
198 if '\r' in contents:
199 cr_files.append(f.LocalPath())
200 # Check that the file ends in one and only one newline character.
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000201 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000202 eof_files.append(f.LocalPath())
203 outputs = []
204 if cr_files:
205 outputs.append(output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000206 'Found a CR character in these files:', items=cr_files))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000207 if eof_files:
208 outputs.append(output_api.PresubmitPromptWarning(
209 'These files should end in one (and only one) newline character:',
210 items=eof_files))
maruel@chromium.org44a17ad2009-06-08 14:14:35 +0000211 return outputs
212
213
maruel@chromium.org3410d912009-06-09 20:56:16 +0000214def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000215 """Checks that there are no tab characters in any of the text files to be
216 submitted.
217 """
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000218 # In addition to the filter, make sure that makefiles are blacklisted.
219 if not source_file_filter:
220 # It's the default filter.
221 source_file_filter = input_api.FilterSourceFile
222 def filter_more(affected_file):
223 return (not input_api.os_path.basename(affected_file.LocalPath()) in
224 ('Makefile', 'makefile') and
225 source_file_filter(affected_file))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000226 tabs = []
maruel@chromium.org115ae6c2010-06-18 17:11:43 +0000227 for f, line_num, line in input_api.RightHandSideLines(filter_more):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000228 if '\t' in line:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000229 tabs.append('%s, line %s' % (f.LocalPath(), line_num))
maruel@chromium.orge9b71c92009-06-10 18:10:01 +0000230 if tabs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000231 return [output_api.PresubmitPromptWarning('Found a tab character in:',
232 long_text='\n'.join(tabs))]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000233 return []
234
235
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000236def CheckChangeHasNoStrayWhitespace(input_api, output_api,
237 source_file_filter=None):
238 """Checks that there is no stray whitespace at source lines end."""
239 errors = []
240 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
241 if line.rstrip() != line:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000242 errors.append('%s, line %s' % (f.LocalPath(), line_num))
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000243 if errors:
244 return [output_api.PresubmitPromptWarning(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000245 'Found line ending with white spaces in:',
246 long_text='\n'.join(errors))]
maruel@chromium.orgf5888bb2009-06-10 20:26:37 +0000247 return []
248
249
maruel@chromium.org3410d912009-06-09 20:56:16 +0000250def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000251 """Checks that there aren't any lines longer than maxlen characters in any of
252 the text files to be submitted.
253 """
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000254 bad = []
maruel@chromium.org3410d912009-06-09 20:56:16 +0000255 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
maruel@chromium.org5c2720e2009-06-09 14:04:08 +0000256 # Allow lines with http://, https:// and #define/#pragma/#include/#if/#endif
257 # to exceed the maxlen rule.
258 if (len(line) > maxlen and
259 not 'http://' in line and
260 not 'https://' in line and
261 not line.startswith('#define') and
262 not line.startswith('#include') and
thakis@chromium.org09829bf2010-10-02 01:58:17 +0000263 not line.startswith('#import') and
maruel@chromium.org5c2720e2009-06-09 14:04:08 +0000264 not line.startswith('#pragma') and
265 not line.startswith('#if') and
266 not line.startswith('#endif')):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000267 bad.append(
268 '%s, line %s, %s chars' %
maruel@chromium.org1487d532009-06-06 00:22:57 +0000269 (f.LocalPath(), line_num, len(line)))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000270 if len(bad) == 5: # Just show the first 5 errors.
271 break
272
273 if bad:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000274 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000275 return [output_api.PresubmitPromptWarning(msg, items=bad)]
276 else:
277 return []
278
279
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000280def CheckLicense(input_api, output_api, license_re, source_file_filter=None,
maruel@chromium.org71626852010-11-03 13:14:25 +0000281 accept_empty_files=True):
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000282 """Verifies the license header.
283 """
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000284 license_re = input_api.re.compile(license_re, input_api.re.MULTILINE)
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000285 bad_files = []
286 for f in input_api.AffectedSourceFiles(source_file_filter):
287 contents = input_api.ReadFile(f, 'rb')
maruel@chromium.org71626852010-11-03 13:14:25 +0000288 if accept_empty_files and not contents:
289 continue
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000290 if not license_re.search(contents):
291 bad_files.append(f.LocalPath())
292 if bad_files:
293 if input_api.is_committing:
294 res_type = output_api.PresubmitPromptWarning
295 else:
296 res_type = output_api.PresubmitNotifyResult
297 return [res_type(
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000298 'Found a bad license header in these files:', items=bad_files)]
maruel@chromium.orgb9e7ada2010-01-27 23:12:39 +0000299 return []
300
301
maruel@chromium.org1a0e3cb2009-06-10 18:03:04 +0000302def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000303 """Checks that the source files have svn:eol-style=LF."""
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000304 return CheckSvnProperty(input_api, output_api,
305 'svn:eol-style', 'LF',
306 input_api.AffectedSourceFiles(source_file_filter))
307
308
309def CheckSvnForCommonMimeTypes(input_api, output_api):
310 """Checks that common binary file types have the correct svn:mime-type."""
311 output = []
312 files = input_api.AffectedFiles(include_deletes=False)
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000313 def IsExts(x, exts):
314 path = x.LocalPath()
315 for extension in exts:
316 if path.endswith(extension):
317 return True
318 return False
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000319 def FilterFiles(extension):
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000320 return filter(lambda x: IsExts(x, extension), files)
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000321 def RunCheck(mime_type, files):
322 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
323 mime_type, files))
maruel@chromium.orge49187c2009-06-26 22:44:53 +0000324 RunCheck('application/pdf', FilterFiles(['.pdf']))
325 RunCheck('image/bmp', FilterFiles(['.bmp']))
326 RunCheck('image/gif', FilterFiles(['.gif']))
327 RunCheck('image/png', FilterFiles(['.png']))
328 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
329 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
maruel@chromium.org46e832a2009-06-18 19:58:07 +0000330 return output
331
332
333def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
334 """Checks that affected_files files have prop=expected."""
thestig@chromium.orgda8cddd2009-08-13 00:25:55 +0000335 if input_api.change.scm != 'svn':
336 return []
337
338 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000339 if bad:
maruel@chromium.org0874d472009-06-10 19:08:33 +0000340 if input_api.is_committing:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000341 res_type = output_api.PresubmitError
maruel@chromium.org0874d472009-06-10 19:08:33 +0000342 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000343 res_type = output_api.PresubmitNotifyResult
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000344 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000345 return [res_type(message, items=bad)]
maruel@chromium.orgb7d46902009-06-10 14:12:10 +0000346 return []
347
348
maruel@chromium.org3410d912009-06-09 20:56:16 +0000349### Other checks
350
351def CheckDoNotSubmit(input_api, output_api):
352 return (
353 CheckDoNotSubmitInDescription(input_api, output_api) +
354 CheckDoNotSubmitInFiles(input_api, output_api)
355 )
356
357
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000358def CheckTreeIsOpen(input_api, output_api,
359 url=None, closed=None, json_url=None):
360 """Check whether to allow commit without prompt.
361
362 Supports two styles:
363 1. Checks that an url's content doesn't match a regexp that would mean that
364 the tree is closed. (old)
365 2. Check the json_url to decide whether to allow commit without prompt.
366 Args:
367 input_api: input related apis.
368 output_api: output related apis.
369 url: url to use for regex based tree status.
370 closed: regex to match for closed status.
371 json_url: url to download json style status.
372 """
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000373 if not input_api.is_committing:
374 return []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375 try:
bradnelson@google.comc0b332a2010-08-26 00:30:37 +0000376 if json_url:
377 connection = input_api.urllib2.urlopen(json_url)
378 status = input_api.json.loads(connection.read())
379 connection.close()
380 if not status['can_commit_freely']:
381 short_text = 'Tree state is: ' + status['general_state']
382 long_text = status['message'] + '\n' + json_url
383 return [output_api.PresubmitError(short_text, long_text=long_text)]
384 else:
385 # TODO(bradnelson): drop this once all users are gone.
386 connection = input_api.urllib2.urlopen(url)
387 status = connection.read()
388 connection.close()
389 if input_api.re.match(closed, status):
390 long_text = status + '\n' + url
391 return [output_api.PresubmitError('The tree is closed.',
392 long_text=long_text)]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000393 except IOError:
394 pass
395 return []
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000396
397
398def RunPythonUnitTests(input_api, output_api, unit_tests):
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000399 """Run the unit tests out of process, capture the output and use the result
400 code to determine success.
401 """
maruel@chromium.orgd7dccf52009-06-06 18:51:58 +0000402 # We don't want to hinder users from uploading incomplete patches.
403 if input_api.is_committing:
404 message_type = output_api.PresubmitError
405 else:
406 message_type = output_api.PresubmitNotifyResult
maruel@chromium.org7b305e82009-05-19 18:24:20 +0000407 outputs = []
408 for unit_test in unit_tests:
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000409 # Run the unit tests out of process. This is because some unit tests
410 # stub out base libraries and don't clean up their mess. It's too easy to
411 # get subtle bugs.
412 cwd = None
413 env = None
414 unit_test_name = unit_test
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000415 # 'python -m test.unit_test' doesn't work. We need to change to the right
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000416 # directory instead.
417 if '.' in unit_test:
418 # Tests imported in submodules (subdirectories) assume that the current
419 # directory is in the PYTHONPATH. Manually fix that.
420 unit_test = unit_test.replace('.', '/')
421 cwd = input_api.os_path.dirname(unit_test)
422 unit_test = input_api.os_path.basename(unit_test)
423 env = input_api.environ.copy()
kbr@google.comab318592009-09-04 00:54:55 +0000424 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
425 backpath = [
426 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
427 ]
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000428 if env.get('PYTHONPATH'):
429 backpath.append(env.get('PYTHONPATH'))
ukai@chromium.orga301f1f2009-08-05 10:37:33 +0000430 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000431 subproc = input_api.subprocess.Popen(
432 [
433 input_api.python_executable,
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000434 '-m',
435 '%s' % unit_test
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000436 ],
437 cwd=cwd,
438 env=env,
439 stdin=input_api.subprocess.PIPE,
440 stdout=input_api.subprocess.PIPE,
441 stderr=input_api.subprocess.PIPE)
442 stdoutdata, stderrdata = subproc.communicate()
443 # Discard the output if returncode == 0
444 if subproc.returncode:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000445 outputs.append('Test \'%s\' failed with code %d\n%s\n%s\n' % (
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000446 unit_test_name, subproc.returncode, stdoutdata, stderrdata))
447 if outputs:
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000448 return [message_type('%d unit tests failed.' % len(outputs),
maruel@chromium.orgc0b22972009-06-25 16:19:14 +0000449 long_text='\n'.join(outputs))]
450 return []
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000451
452
maruel@chromium.orge94aedc2010-12-13 21:11:30 +0000453def RunPylint(input_api, output_api, source_file_filter=None):
454 """Run pylint on python files."""
455 import warnings
456 # On certain pylint/python version combination, running pylint throws a lot of
457 # warning messages.
458 warnings.filterwarnings('ignore', category=DeprecationWarning)
459 try:
460 if not source_file_filter:
461 source_file_filter = lambda f: f.LocalPath().endswith('.py')
462 files = [f.LocalPath()
463 for f in input_api.AffectedSourceFiles(source_file_filter)]
464 try:
465 from pylint import lint
466 if lint.Run(sorted(files)):
467 return [output_api.PresubmitPromptWarning('Fix pylint errors first.')]
468 return []
469 except ImportError:
470 if input_api.platform == 'win32':
471 return [output_api.PresubmitNotifyResult(
472 'Warning: Can\'t run pylint because it is not installed. Please '
473 'install manually\n'
474 'Cannot do static analysis of python files.')]
475 return [output_api.PresubmitError(
476 'Please install pylint with "sudo apt-get install python-setuptools; '
477 'sudo easy_install pylint"\n'
478 'Cannot do static analysis of python files.')]
479 finally:
480 warnings.filterwarnings('default', category=DeprecationWarning)
481
482
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000483def CheckRietveldTryJobExecution(input_api, output_api, host_url, platforms,
484 owner):
485 if not input_api.is_committing:
486 return []
487 if not input_api.change.issue or not input_api.change.patchset:
488 return []
489 url = '%s/%d/get_build_results/%d' % (
490 host_url, input_api.change.issue, input_api.change.patchset)
491 try:
492 connection = input_api.urllib2.urlopen(url)
493 # platform|status|url
494 values = [item.split('|', 2) for item in connection.read().splitlines()]
495 connection.close()
496 except input_api.urllib2.HTTPError, e:
497 if e.code == 404:
498 # Fallback to no try job.
499 return [output_api.PresubmitPromptWarning(
500 'You should try the patch first.')]
501 else:
502 # Another HTTP error happened, warn the user.
503 return [output_api.PresubmitPromptWarning(
504 'Got %s while looking for try job status.' % str(e))]
505
506 if not values:
507 # It returned an empty list. Probably a private review.
508 return []
509 # Reformat as an dict of platform: [status, url]
510 values = dict([[v[0], [v[1], v[2]]] for v in values if len(v) == 3])
511 if not values:
512 # It returned useless data.
513 return [output_api.PresubmitNotifyResult('Failed to parse try job results')]
514
515 for platform in platforms:
516 values.setdefault(platform, ['not started', ''])
517 message = None
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000518 non_success = [k.upper() for k, v in values.iteritems() if v[0] != 'success']
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000519 if 'failure' in [v[0] for v in values.itervalues()]:
520 message = 'Try job failures on %s!\n' % ', '.join(non_success)
521 elif non_success:
522 message = ('Unfinished (or not even started) try jobs on '
523 '%s.\n') % ', '.join(non_success)
524 if message:
525 message += (
526 'Is try server wrong or broken? Please notify %s. '
527 'Thanks.\n' % owner)
528 return [output_api.PresubmitPromptWarning(message=message)]
529 return []
530
531
532def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
533 ignored):
534 if not input_api.json:
535 return [output_api.PresubmitPromptWarning(
536 'Please install simplejson or upgrade to python 2.6+')]
537 try:
538 connection = input_api.urllib2.urlopen(url)
539 raw_data = connection.read()
540 connection.close()
541 except IOError:
542 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
543
544 try:
545 data = input_api.json.loads(raw_data)
546 except ValueError:
547 return [output_api.PresubmitNotifyResult('Received malformed json while '
548 'looking up buildbot status')]
549
550 out = []
551 for (builder_name, builder) in data.iteritems():
552 if builder_name in ignored:
553 continue
maruel@chromium.orgcf1982c2010-10-04 15:08:28 +0000554 if builder.get('state', '') == 'offline':
555 continue
maruel@chromium.org3fbcb082010-03-19 14:03:28 +0000556 pending_builds_len = len(builder.get('pending_builds', []))
557 if pending_builds_len > max_pendings:
558 out.append('%s has %d build(s) pending' %
559 (builder_name, pending_builds_len))
560 if out:
561 return [output_api.PresubmitPromptWarning(
562 'Build(s) pending. It is suggested to wait that no more than %d '
563 'builds are pending.' % max_pendings,
564 long_text='\n'.join(out))]
565 return []