blob: a5b28f1e0842a6081da87de677ea1daf045ba5c7 [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
[email protected]09829bf2010-10-02 01:58:17261 not line.startswith('#import') and
[email protected]5c2720e2009-06-09 14:04:08262 not line.startswith('#pragma') and
263 not line.startswith('#if') and
264 not line.startswith('#endif')):
[email protected]fb2b8eb2009-04-23 21:03:42265 bad.append(
266 '%s, line %s, %s chars' %
[email protected]1487d532009-06-06 00:22:57267 (f.LocalPath(), line_num, len(line)))
[email protected]fb2b8eb2009-04-23 21:03:42268 if len(bad) == 5: # Just show the first 5 errors.
269 break
270
271 if bad:
[email protected]3fbcb082010-03-19 14:03:28272 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen
[email protected]fb2b8eb2009-04-23 21:03:42273 return [output_api.PresubmitPromptWarning(msg, items=bad)]
274 else:
275 return []
276
277
[email protected]b9e7ada2010-01-27 23:12:39278def CheckLicense(input_api, output_api, license, source_file_filter=None):
279 """Verifies the license header.
280 """
281 license_re = input_api.re.compile(license, input_api.re.MULTILINE)
282 bad_files = []
283 for f in input_api.AffectedSourceFiles(source_file_filter):
284 contents = input_api.ReadFile(f, 'rb')
285 if not license_re.search(contents):
286 bad_files.append(f.LocalPath())
287 if bad_files:
288 if input_api.is_committing:
289 res_type = output_api.PresubmitPromptWarning
290 else:
291 res_type = output_api.PresubmitNotifyResult
292 return [res_type(
[email protected]3fbcb082010-03-19 14:03:28293 'Found a bad license header in these files:', items=bad_files)]
[email protected]b9e7ada2010-01-27 23:12:39294 return []
295
296
[email protected]1a0e3cb2009-06-10 18:03:04297def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None):
[email protected]b7d46902009-06-10 14:12:10298 """Checks that the source files have svn:eol-style=LF."""
[email protected]46e832a2009-06-18 19:58:07299 return CheckSvnProperty(input_api, output_api,
300 'svn:eol-style', 'LF',
301 input_api.AffectedSourceFiles(source_file_filter))
302
303
304def CheckSvnForCommonMimeTypes(input_api, output_api):
305 """Checks that common binary file types have the correct svn:mime-type."""
306 output = []
307 files = input_api.AffectedFiles(include_deletes=False)
[email protected]e49187c2009-06-26 22:44:53308 def IsExts(x, exts):
309 path = x.LocalPath()
310 for extension in exts:
311 if path.endswith(extension):
312 return True
313 return False
[email protected]46e832a2009-06-18 19:58:07314 def FilterFiles(extension):
[email protected]e49187c2009-06-26 22:44:53315 return filter(lambda x: IsExts(x, extension), files)
[email protected]46e832a2009-06-18 19:58:07316 def RunCheck(mime_type, files):
317 output.extend(CheckSvnProperty(input_api, output_api, 'svn:mime-type',
318 mime_type, files))
[email protected]e49187c2009-06-26 22:44:53319 RunCheck('application/pdf', FilterFiles(['.pdf']))
320 RunCheck('image/bmp', FilterFiles(['.bmp']))
321 RunCheck('image/gif', FilterFiles(['.gif']))
322 RunCheck('image/png', FilterFiles(['.png']))
323 RunCheck('image/jpeg', FilterFiles(['.jpg', '.jpeg', '.jpe']))
324 RunCheck('image/vnd.microsoft.icon', FilterFiles(['.ico']))
[email protected]46e832a2009-06-18 19:58:07325 return output
326
327
328def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
329 """Checks that affected_files files have prop=expected."""
[email protected]da8cddd2009-08-13 00:25:55330 if input_api.change.scm != 'svn':
331 return []
332
333 bad = filter(lambda f: f.Property(prop) != expected, affected_files)
[email protected]b7d46902009-06-10 14:12:10334 if bad:
[email protected]0874d472009-06-10 19:08:33335 if input_api.is_committing:
[email protected]e3608df2009-11-10 20:22:57336 res_type = output_api.PresubmitError
[email protected]0874d472009-06-10 19:08:33337 else:
[email protected]e3608df2009-11-10 20:22:57338 res_type = output_api.PresubmitNotifyResult
[email protected]3fbcb082010-03-19 14:03:28339 message = 'Run the command: svn pset %s %s \\' % (prop, expected)
[email protected]e3608df2009-11-10 20:22:57340 return [res_type(message, items=bad)]
[email protected]b7d46902009-06-10 14:12:10341 return []
342
343
[email protected]3410d912009-06-09 20:56:16344### Other checks
345
346def CheckDoNotSubmit(input_api, output_api):
347 return (
348 CheckDoNotSubmitInDescription(input_api, output_api) +
349 CheckDoNotSubmitInFiles(input_api, output_api)
350 )
351
352
[email protected]c0b332a2010-08-26 00:30:37353def CheckTreeIsOpen(input_api, output_api,
354 url=None, closed=None, json_url=None):
355 """Check whether to allow commit without prompt.
356
357 Supports two styles:
358 1. Checks that an url's content doesn't match a regexp that would mean that
359 the tree is closed. (old)
360 2. Check the json_url to decide whether to allow commit without prompt.
361 Args:
362 input_api: input related apis.
363 output_api: output related apis.
364 url: url to use for regex based tree status.
365 closed: regex to match for closed status.
366 json_url: url to download json style status.
367 """
[email protected]3fbcb082010-03-19 14:03:28368 if not input_api.is_committing:
369 return []
[email protected]fb2b8eb2009-04-23 21:03:42370 try:
[email protected]c0b332a2010-08-26 00:30:37371 if json_url:
372 connection = input_api.urllib2.urlopen(json_url)
373 status = input_api.json.loads(connection.read())
374 connection.close()
375 if not status['can_commit_freely']:
376 short_text = 'Tree state is: ' + status['general_state']
377 long_text = status['message'] + '\n' + json_url
378 return [output_api.PresubmitError(short_text, long_text=long_text)]
379 else:
380 # TODO(bradnelson): drop this once all users are gone.
381 connection = input_api.urllib2.urlopen(url)
382 status = connection.read()
383 connection.close()
384 if input_api.re.match(closed, status):
385 long_text = status + '\n' + url
386 return [output_api.PresubmitError('The tree is closed.',
387 long_text=long_text)]
[email protected]fb2b8eb2009-04-23 21:03:42388 except IOError:
389 pass
390 return []
[email protected]7b305e82009-05-19 18:24:20391
392
393def RunPythonUnitTests(input_api, output_api, unit_tests):
[email protected]c0b22972009-06-25 16:19:14394 """Run the unit tests out of process, capture the output and use the result
395 code to determine success.
396 """
[email protected]d7dccf52009-06-06 18:51:58397 # We don't want to hinder users from uploading incomplete patches.
398 if input_api.is_committing:
399 message_type = output_api.PresubmitError
400 else:
401 message_type = output_api.PresubmitNotifyResult
[email protected]7b305e82009-05-19 18:24:20402 outputs = []
403 for unit_test in unit_tests:
[email protected]c0b22972009-06-25 16:19:14404 # Run the unit tests out of process. This is because some unit tests
405 # stub out base libraries and don't clean up their mess. It's too easy to
406 # get subtle bugs.
407 cwd = None
408 env = None
409 unit_test_name = unit_test
[email protected]3fbcb082010-03-19 14:03:28410 # 'python -m test.unit_test' doesn't work. We need to change to the right
[email protected]c0b22972009-06-25 16:19:14411 # directory instead.
412 if '.' in unit_test:
413 # Tests imported in submodules (subdirectories) assume that the current
414 # directory is in the PYTHONPATH. Manually fix that.
415 unit_test = unit_test.replace('.', '/')
416 cwd = input_api.os_path.dirname(unit_test)
417 unit_test = input_api.os_path.basename(unit_test)
418 env = input_api.environ.copy()
[email protected]ab318592009-09-04 00:54:55419 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
420 backpath = [
421 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
422 ]
[email protected]c0b22972009-06-25 16:19:14423 if env.get('PYTHONPATH'):
424 backpath.append(env.get('PYTHONPATH'))
[email protected]a301f1f2009-08-05 10:37:33425 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
[email protected]c0b22972009-06-25 16:19:14426 subproc = input_api.subprocess.Popen(
427 [
428 input_api.python_executable,
[email protected]3fbcb082010-03-19 14:03:28429 '-m',
430 '%s' % unit_test
[email protected]c0b22972009-06-25 16:19:14431 ],
432 cwd=cwd,
433 env=env,
434 stdin=input_api.subprocess.PIPE,
435 stdout=input_api.subprocess.PIPE,
436 stderr=input_api.subprocess.PIPE)
437 stdoutdata, stderrdata = subproc.communicate()
438 # Discard the output if returncode == 0
439 if subproc.returncode:
[email protected]3fbcb082010-03-19 14:03:28440 outputs.append('Test \'%s\' failed with code %d\n%s\n%s\n' % (
[email protected]c0b22972009-06-25 16:19:14441 unit_test_name, subproc.returncode, stdoutdata, stderrdata))
442 if outputs:
[email protected]3fbcb082010-03-19 14:03:28443 return [message_type('%d unit tests failed.' % len(outputs),
[email protected]c0b22972009-06-25 16:19:14444 long_text='\n'.join(outputs))]
445 return []
[email protected]3fbcb082010-03-19 14:03:28446
447
448def CheckRietveldTryJobExecution(input_api, output_api, host_url, platforms,
449 owner):
450 if not input_api.is_committing:
451 return []
452 if not input_api.change.issue or not input_api.change.patchset:
453 return []
454 url = '%s/%d/get_build_results/%d' % (
455 host_url, input_api.change.issue, input_api.change.patchset)
456 try:
457 connection = input_api.urllib2.urlopen(url)
458 # platform|status|url
459 values = [item.split('|', 2) for item in connection.read().splitlines()]
460 connection.close()
461 except input_api.urllib2.HTTPError, e:
462 if e.code == 404:
463 # Fallback to no try job.
464 return [output_api.PresubmitPromptWarning(
465 'You should try the patch first.')]
466 else:
467 # Another HTTP error happened, warn the user.
468 return [output_api.PresubmitPromptWarning(
469 'Got %s while looking for try job status.' % str(e))]
470
471 if not values:
472 # It returned an empty list. Probably a private review.
473 return []
474 # Reformat as an dict of platform: [status, url]
475 values = dict([[v[0], [v[1], v[2]]] for v in values if len(v) == 3])
476 if not values:
477 # It returned useless data.
478 return [output_api.PresubmitNotifyResult('Failed to parse try job results')]
479
480 for platform in platforms:
481 values.setdefault(platform, ['not started', ''])
482 message = None
483 non_success = [k.upper() for k,v in values.iteritems() if v[0] != 'success']
484 if 'failure' in [v[0] for v in values.itervalues()]:
485 message = 'Try job failures on %s!\n' % ', '.join(non_success)
486 elif non_success:
487 message = ('Unfinished (or not even started) try jobs on '
488 '%s.\n') % ', '.join(non_success)
489 if message:
490 message += (
491 'Is try server wrong or broken? Please notify %s. '
492 'Thanks.\n' % owner)
493 return [output_api.PresubmitPromptWarning(message=message)]
494 return []
495
496
497def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
498 ignored):
499 if not input_api.json:
500 return [output_api.PresubmitPromptWarning(
501 'Please install simplejson or upgrade to python 2.6+')]
502 try:
503 connection = input_api.urllib2.urlopen(url)
504 raw_data = connection.read()
505 connection.close()
506 except IOError:
507 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)]
508
509 try:
510 data = input_api.json.loads(raw_data)
511 except ValueError:
512 return [output_api.PresubmitNotifyResult('Received malformed json while '
513 'looking up buildbot status')]
514
515 out = []
516 for (builder_name, builder) in data.iteritems():
517 if builder_name in ignored:
518 continue
519 pending_builds_len = len(builder.get('pending_builds', []))
520 if pending_builds_len > max_pendings:
521 out.append('%s has %d build(s) pending' %
522 (builder_name, pending_builds_len))
523 if out:
524 return [output_api.PresubmitPromptWarning(
525 'Build(s) pending. It is suggested to wait that no more than %d '
526 'builds are pending.' % max_pendings,
527 long_text='\n'.join(out))]
528 return []