blob: 9588fb0eb9f9b0a4bc71cbfded9d3618e8ddff6d [file] [log] [blame]
[email protected]ba551772010-02-03 18:21:421# Copyright (c) 2010 The Chromium Authors. All rights reserved.
[email protected]fb2b8eb2009-04-23 21:03:422# 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
[email protected]3410d912009-06-09 20:56:167### Description checks
8
[email protected]00c41e42009-05-12 21:43:139def CheckChangeHasTestField(input_api, output_api):
10 """Requires that the changelist have a TEST= field."""
[email protected]e1a524f2009-05-27 14:43:4611 if input_api.change.TEST:
[email protected]00c41e42009-05-12 21:43:1312 return []
13 else:
14 return [output_api.PresubmitNotifyResult(
[email protected]3fbcb082010-03-19 14:03:2815 'Changelist should have a TEST= field. TEST=none is allowed.')]
[email protected]00c41e42009-05-12 21:43:1316
17
18def CheckChangeHasBugField(input_api, output_api):
19 """Requires that the changelist have a BUG= field."""
[email protected]e1a524f2009-05-27 14:43:4620 if input_api.change.BUG:
[email protected]00c41e42009-05-12 21:43:1321 return []
22 else:
23 return [output_api.PresubmitNotifyResult(
[email protected]3fbcb082010-03-19 14:03:2824 'Changelist should have a BUG= field. BUG=none is allowed.')]
[email protected]00c41e42009-05-12 21:43:1325
26
[email protected]fb2b8eb2009-04-23 21:03:4227def CheckChangeHasTestedField(input_api, output_api):
28 """Requires that the changelist have a TESTED= field."""
[email protected]e1a524f2009-05-27 14:43:4629 if input_api.change.TESTED:
[email protected]fb2b8eb2009-04-23 21:03:4230 return []
31 else:
[email protected]3fbcb082010-03-19 14:03:2832 return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
[email protected]fb2b8eb2009-04-23 21:03:4233
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:
[email protected]3fbcb082010-03-19 14:03:2840 return [output_api.PresubmitError('Changelist must have a QA= field.')]
[email protected]fb2b8eb2009-04-23 21:03:4241
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(
[email protected]3fbcb082010-03-19 14:03:2849 keyword + ' is present in the changelist description.')]
[email protected]fb2b8eb2009-04-23 21:03:4250 else:
51 return []
52
53
[email protected]bc50eb42009-06-10 18:22:4754def 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:
[email protected]3fbcb082010-03-19 14:03:2859 return [output_api.PresubmitError('Add a description.')]
[email protected]bc50eb42009-06-10 18:22:4760 else:
[email protected]3fbcb082010-03-19 14:03:2861 return [output_api.PresubmitNotifyResult('Add a description.')]
[email protected]bc50eb42009-06-10 18:22:4762 return []
63
[email protected]3410d912009-06-09 20:56:1664### Content checks
65
[email protected]fb2b8eb2009-04-23 21:03:4266def CheckDoNotSubmitInFiles(input_api, output_api):
67 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files."""
68 keyword = 'DO NOT ' + 'SUBMIT'
[email protected]3410d912009-06-09 20:56:1669 # We want to check every text files, not just source files.
70 for f, line_num, line in input_api.RightHandSideLines(lambda x: x):
[email protected]fb2b8eb2009-04-23 21:03:4271 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
[email protected]26970fa2009-11-17 18:07:3277def CheckChangeLintsClean(input_api, output_api, source_file_filter=None):
[email protected]3fbcb082010-03-19 14:03:2878 """Checks that all '.cc' and '.h' files pass cpplint.py."""
[email protected]26970fa2009-11-17 18:07:3279 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$')
80 result = []
81
82 # Initialize cpplint.
83 import cpplint
84 cpplint._cpplint_state.ResetErrorCounts()
85
86 # Justifications for each filter:
87 #
88 # - build/include : Too many; fix in the future.
89 # - build/include_order : Not happening; #ifdefed includes.
90 # - build/namespace : I'm surprised by how often we violate this rule.
91 # - readability/casting : Mistakes a whole bunch of function pointer.
92 # - runtime/int : Can be fixed long term; volume of errors too high
93 # - runtime/virtual : Broken now, but can be fixed in the future?
94 # - whitespace/braces : We have a lot of explicit scoping in chrome code.
[email protected]3fbcb082010-03-19 14:03:2895 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,'
96 '-readability/casting,-runtime/int,-runtime/virtual,'
97 '-whitespace/braces')
[email protected]26970fa2009-11-17 18:07:3298
99 # We currently are more strict with normal code than unit tests; 4 and 5 are
100 # the verbosity level that would normally be passed to cpplint.py through
101 # --verbose=#. Hopefully, in the future, we can be more verbose.
102 files = [f.AbsoluteLocalPath() for f in
103 input_api.AffectedSourceFiles(source_file_filter)]
104 for file_name in files:
105 if _RE_IS_TEST.match(file_name):
106 level = 5
107 else:
108 level = 4
109
110 cpplint.ProcessFile(file_name, level)
111
112 if cpplint._cpplint_state.error_count > 0:
113 if input_api.is_committing:
114 res_type = output_api.PresubmitError
115 else:
116 res_type = output_api.PresubmitPromptWarning
[email protected]3fbcb082010-03-19 14:03:28117 result = [res_type('Changelist failed cpplint.py check.')]
[email protected]26970fa2009-11-17 18:07:32118
119 return result
120
121
[email protected]3410d912009-06-09 20:56:16122def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
[email protected]e9b71c92009-06-10 18:10:01123 """Checks no '\r' (CR) character is in any source files."""
124 cr_files = []
[email protected]3410d912009-06-09 20:56:16125 for f in input_api.AffectedSourceFiles(source_file_filter):
[email protected]44a17ad2009-06-08 14:14:35126 if '\r' in input_api.ReadFile(f, 'rb'):
[email protected]e9b71c92009-06-10 18:10:01127 cr_files.append(f.LocalPath())
128 if cr_files:
129 return [output_api.PresubmitPromptWarning(
[email protected]3fbcb082010-03-19 14:03:28130 'Found a CR character in these files:', items=cr_files)]
[email protected]e9b71c92009-06-10 18:10:01131 return []
132
133
[email protected]da8cddd2009-08-13 00:25:55134def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
135 """Checks for files in svn modified directories.
136
137 They will get submitted on accident because svn commits recursively by
138 default, and that's very dangerous.
139 """
140 if input_api.change.scm != 'svn':
141 return []
142
143 errors = []
144 current_cl_files = input_api.change.GetModifiedFiles()
145 all_modified_files = input_api.change.GetAllModifiedFiles()
146 # Filter out files in the current CL.
147 modified_files = [f for f in all_modified_files if f not in current_cl_files]
148 modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
149
150 for f in input_api.AffectedFiles(source_file_filter):
151 if f.Action() == 'M' and f.IsDirectory():
152 curpath = f.AbsoluteLocalPath()
153 bad_files = []
154 # Check if any of the modified files in other CLs are under curpath.
155 for i in xrange(len(modified_files)):
156 abspath = modified_abspaths[i]
157 if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
158 bad_files.append(modified_files[i])
159 if bad_files:
160 if input_api.is_committing:
161 error_type = output_api.PresubmitPromptWarning
162 else:
163 error_type = output_api.PresubmitNotifyResult
164 errors.append(error_type(
[email protected]3fbcb082010-03-19 14:03:28165 'Potential accidental commits in changelist %s:' % f.LocalPath(),
[email protected]da8cddd2009-08-13 00:25:55166 items=bad_files))
167 return errors
168
169
[email protected]e9b71c92009-06-10 18:10:01170def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
171 """Checks the files ends with one and only one \n (LF)."""
172 eof_files = []
173 for f in input_api.AffectedSourceFiles(source_file_filter):
174 contents = input_api.ReadFile(f, 'rb')
175 # Check that the file ends in one and only one newline character.
[email protected]3fbcb082010-03-19 14:03:28176 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
[email protected]e9b71c92009-06-10 18:10:01177 eof_files.append(f.LocalPath())
178
179 if eof_files:
180 return [output_api.PresubmitPromptWarning(
181 'These files should end in one (and only one) newline character:',
182 items=eof_files)]
183 return []
184
185
186def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api,
187 source_file_filter=None):
188 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass.
189
190 It is faster because it is reading the file only once.
191 """
192 cr_files = []
193 eof_files = []
194 for f in input_api.AffectedSourceFiles(source_file_filter):
195 contents = input_api.ReadFile(f, 'rb')
196 if '\r' in contents:
197 cr_files.append(f.LocalPath())
198 # Check that the file ends in one and only one newline character.
[email protected]3fbcb082010-03-19 14:03:28199 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'):
[email protected]e9b71c92009-06-10 18:10:01200 eof_files.append(f.LocalPath())
201 outputs = []
202 if cr_files:
203 outputs.append(output_api.PresubmitPromptWarning(
[email protected]3fbcb082010-03-19 14:03:28204 'Found a CR character in these files:', items=cr_files))
[email protected]e9b71c92009-06-10 18:10:01205 if eof_files:
206 outputs.append(output_api.PresubmitPromptWarning(
207 'These files should end in one (and only one) newline character:',
208 items=eof_files))
[email protected]44a17ad2009-06-08 14:14:35209 return outputs
210
211
[email protected]3410d912009-06-09 20:56:16212def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None):
[email protected]fb2b8eb2009-04-23 21:03:42213 """Checks that there are no tab characters in any of the text files to be
214 submitted.
215 """
[email protected]115ae6c2010-06-18 17:11:43216 # In addition to the filter, make sure that makefiles are blacklisted.
217 if not source_file_filter:
218 # It's the default filter.
219 source_file_filter = input_api.FilterSourceFile
220 def filter_more(affected_file):
221 return (not input_api.os_path.basename(affected_file.LocalPath()) in
222 ('Makefile', 'makefile') and
223 source_file_filter(affected_file))
[email protected]e9b71c92009-06-10 18:10:01224 tabs = []
[email protected]115ae6c2010-06-18 17:11:43225 for f, line_num, line in input_api.RightHandSideLines(filter_more):
[email protected]fb2b8eb2009-04-23 21:03:42226 if '\t' in line:
[email protected]3fbcb082010-03-19 14:03:28227 tabs.append('%s, line %s' % (f.LocalPath(), line_num))
[email protected]e9b71c92009-06-10 18:10:01228 if tabs:
[email protected]3fbcb082010-03-19 14:03:28229 return [output_api.PresubmitPromptWarning('Found a tab character in:',
230 long_text='\n'.join(tabs))]
[email protected]fb2b8eb2009-04-23 21:03:42231 return []
232
233
[email protected]f5888bb2009-06-10 20:26:37234def CheckChangeHasNoStrayWhitespace(input_api, output_api,
235 source_file_filter=None):
236 """Checks that there is no stray whitespace at source lines end."""
237 errors = []
238 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
239 if line.rstrip() != line:
[email protected]3fbcb082010-03-19 14:03:28240 errors.append('%s, line %s' % (f.LocalPath(), line_num))
[email protected]f5888bb2009-06-10 20:26:37241 if errors:
242 return [output_api.PresubmitPromptWarning(
[email protected]3fbcb082010-03-19 14:03:28243 'Found line ending with white spaces in:',
244 long_text='\n'.join(errors))]
[email protected]f5888bb2009-06-10 20:26:37245 return []
246
247
[email protected]3410d912009-06-09 20:56:16248def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None):
[email protected]fb2b8eb2009-04-23 21:03:42249 """Checks that there aren't any lines longer than maxlen characters in any of
250 the text files to be submitted.
251 """
[email protected]fb2b8eb2009-04-23 21:03:42252 bad = []
[email protected]3410d912009-06-09 20:56:16253 for f, line_num, line in input_api.RightHandSideLines(source_file_filter):
[email protected]5c2720e2009-06-09 14:04:08254 # Allow lines with http://, https:// and #define/#pragma/#include/#if/#endif
255 # to exceed the maxlen rule.
256 if (len(line) > maxlen and
257 not 'http://' in line and
258 not 'https://' in line and
259 not line.startswith('#define') and
260 not line.startswith('#include') and
261 not line.startswith('#pragma') and
262 not line.startswith('#if') and
263 not line.startswith('#endif')):
[email protected]fb2b8eb2009-04-23 21:03:42264 bad.append(
265 '%s, line %s, %s chars' %
[email protected]1487d532009-06-06 00:22:57266 (f.LocalPath(), line_num, len(line)))
[email protected]fb2b8eb2009-04-23 21:03:42267 if len(bad) == 5: # Just show the first 5 errors.
268 break
269
270 if bad:
[email protected]3fbcb082010-03-19 14:03:28271 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
[email protected]fb2b8eb2009-04-23 21:03:42272 return [output_api.PresubmitPromptWarning(msg, items=bad)]
273 else:
274 return []
275
276
[email protected]b9e7ada2010-01-27 23:12:39277def CheckLicense(input_api, output_api, license, source_file_filter=None):
278 """Verifies the license header.
279 """
280 license_re = input_api.re.compile(license, input_api.re.MULTILINE)
281 bad_files = []
282 for f in input_api.AffectedSourceFiles(source_file_filter):
283 contents = input_api.ReadFile(f, 'rb')
284 if not license_re.search(contents):
285 bad_files.append(f.LocalPath())
286 if bad_files:
287 if input_api.is_committing:
288 res_type = output_api.PresubmitPromptWarning
289 else:
290 res_type = output_api.PresubmitNotifyResult
291 return [res_type(
[email protected]3fbcb082010-03-19 14:03:28292 'Found a bad license header in these files:', items=bad_files)]
[email protected]b9e7ada2010-01-27 23:12:39293 return []
294
295
[email protected]1a0e3cb2009-06-10 18:03:04296def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
[email protected]b7d46902009-06-10 14:12:10297 """Checks that the source files have svn:eol-style=LF."""
[email protected]46e832a2009-06-18 19:58:07298 return CheckSvnProperty(input_api, output_api,
299 'svn:eol-style', 'LF',
300 input_api.AffectedSourceFiles(source_file_filter))
301
302
303def CheckSvnForCommonMimeTypes(input_api, output_api):
304 """Checks that common binary file types have the correct svn:mime-type."""
305 output = []
306 files = input_api.AffectedFiles(include_deletes=False)
[email protected]e49187c2009-06-26 22:44:53307 def IsExts(x, exts):
308 path = x.LocalPath()
309 for extension in exts:
310 if path.endswith(extension):
311 return True
312 return False
[email protected]46e832a2009-06-18 19:58:07313 def FilterFiles(extension):
[email protected]e49187c2009-06-26 22:44:53314 return filter(lambda x: IsExts(x, extension), files)
[email protected]46e832a2009-06-18 19:58:07315 def RunCheck(mime_type, files):
316 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
317 mime_type, files))
[email protected]e49187c2009-06-26 22:44:53318 RunCheck('application/pdf', FilterFiles(['.pdf']))
319 RunCheck('image/bmp', FilterFiles(['.bmp']))
320 RunCheck('image/gif', FilterFiles(['.gif']))
321 RunCheck('image/png', FilterFiles(['.png']))
322 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
323 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
[email protected]46e832a2009-06-18 19:58:07324 return output
325
326
327def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
328 """Checks that affected_files files have prop=expected."""
[email protected]da8cddd2009-08-13 00:25:55329 if input_api.change.scm != 'svn':
330 return []
331
332 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
[email protected]b7d46902009-06-10 14:12:10333 if bad:
[email protected]0874d472009-06-10 19:08:33334 if input_api.is_committing:
[email protected]e3608df2009-11-10 20:22:57335 res_type = output_api.PresubmitError
[email protected]0874d472009-06-10 19:08:33336 else:
[email protected]e3608df2009-11-10 20:22:57337 res_type = output_api.PresubmitNotifyResult
[email protected]3fbcb082010-03-19 14:03:28338 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
[email protected]e3608df2009-11-10 20:22:57339 return [res_type(message, items=bad)]
[email protected]b7d46902009-06-10 14:12:10340 return []
341
342
[email protected]3410d912009-06-09 20:56:16343### Other checks
344
345def CheckDoNotSubmit(input_api, output_api):
346 return (
347 CheckDoNotSubmitInDescription(input_api, output_api) +
348 CheckDoNotSubmitInFiles(input_api, output_api)
349 )
350
351
[email protected]fb2b8eb2009-04-23 21:03:42352def CheckTreeIsOpen(input_api, output_api, url, closed):
353 """Checks that an url's content doesn't match a regexp that would mean that
354 the tree is closed."""
[email protected]3fbcb082010-03-19 14:03:28355 if not input_api.is_committing:
356 return []
[email protected]fb2b8eb2009-04-23 21:03:42357 try:
358 connection = input_api.urllib2.urlopen(url)
359 status = connection.read()
360 connection.close()
[email protected]b109e6a2010-05-28 15:47:58361 if input_api.re.match(closed, status, input_api.re.IGNORECASE):
[email protected]fb2b8eb2009-04-23 21:03:42362 long_text = status + '\n' + url
[email protected]eea4dfb2010-03-25 01:56:37363 return [output_api.PresubmitError('The tree is closed dude!',
364 long_text=long_text)]
[email protected]fb2b8eb2009-04-23 21:03:42365 except IOError:
366 pass
367 return []
[email protected]7b305e82009-05-19 18:24:20368
369
370def RunPythonUnitTests(input_api, output_api, unit_tests):
[email protected]c0b22972009-06-25 16:19:14371 """Run the unit tests out of process, capture the output and use the result
372 code to determine success.
373 """
[email protected]d7dccf52009-06-06 18:51:58374 # We don't want to hinder users from uploading incomplete patches.
375 if input_api.is_committing:
376 message_type = output_api.PresubmitError
377 else:
378 message_type = output_api.PresubmitNotifyResult
[email protected]7b305e82009-05-19 18:24:20379 outputs = []
380 for unit_test in unit_tests:
[email protected]c0b22972009-06-25 16:19:14381 # Run the unit tests out of process. This is because some unit tests
382 # stub out base libraries and don't clean up their mess. It's too easy to
383 # get subtle bugs.
384 cwd = None
385 env = None
386 unit_test_name = unit_test
[email protected]3fbcb082010-03-19 14:03:28387 # 'python -m test.unit_test' doesn't work. We need to change to the right
[email protected]c0b22972009-06-25 16:19:14388 # directory instead.
389 if '.' in unit_test:
390 # Tests imported in submodules (subdirectories) assume that the current
391 # directory is in the PYTHONPATH. Manually fix that.
392 unit_test = unit_test.replace('.', '/')
393 cwd = input_api.os_path.dirname(unit_test)
394 unit_test = input_api.os_path.basename(unit_test)
395 env = input_api.environ.copy()
[email protected]ab318592009-09-04 00:54:55396 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
397 backpath = [
398 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
399 ]
[email protected]c0b22972009-06-25 16:19:14400 if env.get('PYTHONPATH'):
401 backpath.append(env.get('PYTHONPATH'))
[email protected]a301f1f2009-08-05 10:37:33402 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
[email protected]c0b22972009-06-25 16:19:14403 subproc = input_api.subprocess.Popen(
404 [
405 input_api.python_executable,
[email protected]3fbcb082010-03-19 14:03:28406 '-m',
407 '%s' % unit_test
[email protected]c0b22972009-06-25 16:19:14408 ],
409 cwd=cwd,
410 env=env,
411 stdin=input_api.subprocess.PIPE,
412 stdout=input_api.subprocess.PIPE,
413 stderr=input_api.subprocess.PIPE)
414 stdoutdata, stderrdata = subproc.communicate()
415 # Discard the output if returncode == 0
416 if subproc.returncode:
[email protected]3fbcb082010-03-19 14:03:28417 outputs.append('Test \'%s\' failed with code %d\n%s\n%s\n' % (
[email protected]c0b22972009-06-25 16:19:14418 unit_test_name, subproc.returncode, stdoutdata, stderrdata))
419 if outputs:
[email protected]3fbcb082010-03-19 14:03:28420 return [message_type('%d unit tests failed.' % len(outputs),
[email protected]c0b22972009-06-25 16:19:14421 long_text='\n'.join(outputs))]
422 return []
[email protected]3fbcb082010-03-19 14:03:28423
424
425def CheckRietveldTryJobExecution(input_api, output_api, host_url, platforms,
426 owner):
427 if not input_api.is_committing:
428 return []
429 if not input_api.change.issue or not input_api.change.patchset:
430 return []
431 url = '%s/%d/get_build_results/%d' % (
432 host_url, input_api.change.issue, input_api.change.patchset)
433 try:
434 connection = input_api.urllib2.urlopen(url)
435 # platform|status|url
436 values = [item.split('|', 2) for item in connection.read().splitlines()]
437 connection.close()
438 except input_api.urllib2.HTTPError, e:
439 if e.code == 404:
440 # Fallback to no try job.
441 return [output_api.PresubmitPromptWarning(
442 'You should try the patch first.')]
443 else:
444 # Another HTTP error happened, warn the user.
445 return [output_api.PresubmitPromptWarning(
446 'Got %s while looking for try job status.' % str(e))]
447
448 if not values:
449 # It returned an empty list. Probably a private review.
450 return []
451 # Reformat as an dict of platform: [status, url]
452 values = dict([[v[0], [v[1], v[2]]] for v in values if len(v) == 3])
453 if not values:
454 # It returned useless data.
455 return [output_api.PresubmitNotifyResult('Failed to parse try job results')]
456
457 for platform in platforms:
458 values.setdefault(platform, ['not started', ''])
459 message = None
460 non_success = [k.upper() for k,v in values.iteritems() if v[0] != 'success']
461 if 'failure' in [v[0] for v in values.itervalues()]:
462 message = 'Try job failures on %s!\n' % ', '.join(non_success)
463 elif non_success:
464 message = ('Unfinished (or not even started) try jobs on '
465 '%s.\n') % ', '.join(non_success)
466 if message:
467 message += (
468 'Is try server wrong or broken? Please notify %s. '
469 'Thanks.\n' % owner)
470 return [output_api.PresubmitPromptWarning(message=message)]
471 return []
472
473
474def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
475 ignored):
476 if not input_api.json:
477 return [output_api.PresubmitPromptWarning(
478 'Please install simplejson or upgrade to python 2.6+')]
479 try:
480 connection = input_api.urllib2.urlopen(url)
481 raw_data = connection.read()
482 connection.close()
483 except IOError:
484 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
485
486 try:
487 data = input_api.json.loads(raw_data)
488 except ValueError:
489 return [output_api.PresubmitNotifyResult('Received malformed json while '
490 'looking up buildbot status')]
491
492 out = []
493 for (builder_name, builder) in data.iteritems():
494 if builder_name in ignored:
495 continue
496 pending_builds_len = len(builder.get('pending_builds', []))
497 if pending_builds_len > max_pendings:
498 out.append('%s has %d build(s) pending' %
499 (builder_name, pending_builds_len))
500 if out:
501 return [output_api.PresubmitPromptWarning(
502 'Build(s) pending. It is suggested to wait that no more than %d '
503 'builds are pending.' % max_pendings,
504 long_text='\n'.join(out))]
505 return []