OLD | NEW |
1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Generic presubmit checks that can be reused by other presubmit checks.""" | 5 """Generic presubmit checks that can be reused by other presubmit checks.""" |
6 | 6 |
7 ### Description checks | 7 ### Description checks |
8 | 8 |
9 def CheckChangeHasTestField(input_api, output_api): | 9 def CheckChangeHasTestField(input_api, output_api): |
10 """Requires that the changelist have a TEST= field.""" | 10 """Requires that the changelist have a TEST= field.""" |
11 if input_api.change.TEST: | 11 if input_api.change.TEST: |
12 return [] | 12 return [] |
13 else: | 13 else: |
14 return [output_api.PresubmitNotifyResult( | 14 return [output_api.PresubmitNotifyResult( |
15 "Changelist should have a TEST= field. TEST=none is allowed.")] | 15 'Changelist should have a TEST= field. TEST=none is allowed.')] |
16 | 16 |
17 | 17 |
18 def CheckChangeHasBugField(input_api, output_api): | 18 def CheckChangeHasBugField(input_api, output_api): |
19 """Requires that the changelist have a BUG= field.""" | 19 """Requires that the changelist have a BUG= field.""" |
20 if input_api.change.BUG: | 20 if input_api.change.BUG: |
21 return [] | 21 return [] |
22 else: | 22 else: |
23 return [output_api.PresubmitNotifyResult( | 23 return [output_api.PresubmitNotifyResult( |
24 "Changelist should have a BUG= field. BUG=none is allowed.")] | 24 'Changelist should have a BUG= field. BUG=none is allowed.')] |
25 | 25 |
26 | 26 |
27 def CheckChangeHasTestedField(input_api, output_api): | 27 def CheckChangeHasTestedField(input_api, output_api): |
28 """Requires that the changelist have a TESTED= field.""" | 28 """Requires that the changelist have a TESTED= field.""" |
29 if input_api.change.TESTED: | 29 if input_api.change.TESTED: |
30 return [] | 30 return [] |
31 else: | 31 else: |
32 return [output_api.PresubmitError("Changelist must have a TESTED= field.")] | 32 return [output_api.PresubmitError('Changelist must have a TESTED= field.')] |
33 | 33 |
34 | 34 |
35 def CheckChangeHasQaField(input_api, output_api): | 35 def CheckChangeHasQaField(input_api, output_api): |
36 """Requires that the changelist have a QA= field.""" | 36 """Requires that the changelist have a QA= field.""" |
37 if input_api.change.QA: | 37 if input_api.change.QA: |
38 return [] | 38 return [] |
39 else: | 39 else: |
40 return [output_api.PresubmitError("Changelist must have a QA= field.")] | 40 return [output_api.PresubmitError('Changelist must have a QA= field.')] |
41 | 41 |
42 | 42 |
43 def CheckDoNotSubmitInDescription(input_api, output_api): | 43 def CheckDoNotSubmitInDescription(input_api, output_api): |
44 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description. | 44 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to the CL description. |
45 """ | 45 """ |
46 keyword = 'DO NOT ' + 'SUBMIT' | 46 keyword = 'DO NOT ' + 'SUBMIT' |
47 if keyword in input_api.change.DescriptionText(): | 47 if keyword in input_api.change.DescriptionText(): |
48 return [output_api.PresubmitError( | 48 return [output_api.PresubmitError( |
49 keyword + " is present in the changelist description.")] | 49 keyword + ' is present in the changelist description.')] |
50 else: | 50 else: |
51 return [] | 51 return [] |
52 | 52 |
53 | 53 |
54 def CheckChangeHasDescription(input_api, output_api): | 54 def CheckChangeHasDescription(input_api, output_api): |
55 """Checks the CL description is not empty.""" | 55 """Checks the CL description is not empty.""" |
56 text = input_api.change.DescriptionText() | 56 text = input_api.change.DescriptionText() |
57 if text.strip() == '': | 57 if text.strip() == '': |
58 if input_api.is_committing: | 58 if input_api.is_committing: |
59 return [output_api.PresubmitError("Add a description.")] | 59 return [output_api.PresubmitError('Add a description.')] |
60 else: | 60 else: |
61 return [output_api.PresubmitNotifyResult("Add a description.")] | 61 return [output_api.PresubmitNotifyResult('Add a description.')] |
62 return [] | 62 return [] |
63 | 63 |
64 ### Content checks | 64 ### Content checks |
65 | 65 |
66 def CheckDoNotSubmitInFiles(input_api, output_api): | 66 def CheckDoNotSubmitInFiles(input_api, output_api): |
67 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files.""" | 67 """Checks that the user didn't add 'DO NOT ' + 'SUBMIT' to any files.""" |
68 keyword = 'DO NOT ' + 'SUBMIT' | 68 keyword = 'DO NOT ' + 'SUBMIT' |
69 # We want to check every text files, not just source files. | 69 # We want to check every text files, not just source files. |
70 for f, line_num, line in input_api.RightHandSideLines(lambda x: x): | 70 for f, line_num, line in input_api.RightHandSideLines(lambda x: x): |
71 if keyword in line: | 71 if keyword in line: |
72 text = 'Found ' + keyword + ' in %s, line %s' % (f.LocalPath(), line_num) | 72 text = 'Found ' + keyword + ' in %s, line %s' % (f.LocalPath(), line_num) |
73 return [output_api.PresubmitError(text)] | 73 return [output_api.PresubmitError(text)] |
74 return [] | 74 return [] |
75 | 75 |
76 | 76 |
77 def CheckChangeLintsClean(input_api, output_api, source_file_filter=None): | 77 def CheckChangeLintsClean(input_api, output_api, source_file_filter=None): |
78 """Checks that all ".cc" and ".h" files pass cpplint.py.""" | 78 """Checks that all '.cc' and '.h' files pass cpplint.py.""" |
79 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$') | 79 _RE_IS_TEST = input_api.re.compile(r'.*tests?.(cc|h)$') |
80 result = [] | 80 result = [] |
81 | 81 |
82 # Initialize cpplint. | 82 # Initialize cpplint. |
83 import cpplint | 83 import cpplint |
84 cpplint._cpplint_state.ResetErrorCounts() | 84 cpplint._cpplint_state.ResetErrorCounts() |
85 | 85 |
86 # Justifications for each filter: | 86 # Justifications for each filter: |
87 # | 87 # |
88 # - build/include : Too many; fix in the future. | 88 # - build/include : Too many; fix in the future. |
89 # - build/include_order : Not happening; #ifdefed includes. | 89 # - build/include_order : Not happening; #ifdefed includes. |
90 # - build/namespace : I'm surprised by how often we violate this rule. | 90 # - build/namespace : I'm surprised by how often we violate this rule. |
91 # - readability/casting : Mistakes a whole bunch of function pointer. | 91 # - readability/casting : Mistakes a whole bunch of function pointer. |
92 # - runtime/int : Can be fixed long term; volume of errors too high | 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? | 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. | 94 # - whitespace/braces : We have a lot of explicit scoping in chrome code. |
95 cpplint._SetFilters("-build/include,-build/include_order,-build/namespace," | 95 cpplint._SetFilters('-build/include,-build/include_order,-build/namespace,' |
96 "-readability/casting,-runtime/int,-runtime/virtual," | 96 '-readability/casting,-runtime/int,-runtime/virtual,' |
97 "-whitespace/braces") | 97 '-whitespace/braces') |
98 | 98 |
99 # We currently are more strict with normal code than unit tests; 4 and 5 are | 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 | 100 # the verbosity level that would normally be passed to cpplint.py through |
101 # --verbose=#. Hopefully, in the future, we can be more verbose. | 101 # --verbose=#. Hopefully, in the future, we can be more verbose. |
102 files = [f.AbsoluteLocalPath() for f in | 102 files = [f.AbsoluteLocalPath() for f in |
103 input_api.AffectedSourceFiles(source_file_filter)] | 103 input_api.AffectedSourceFiles(source_file_filter)] |
104 for file_name in files: | 104 for file_name in files: |
105 if _RE_IS_TEST.match(file_name): | 105 if _RE_IS_TEST.match(file_name): |
106 level = 5 | 106 level = 5 |
107 else: | 107 else: |
108 level = 4 | 108 level = 4 |
109 | 109 |
110 cpplint.ProcessFile(file_name, level) | 110 cpplint.ProcessFile(file_name, level) |
111 | 111 |
112 if cpplint._cpplint_state.error_count > 0: | 112 if cpplint._cpplint_state.error_count > 0: |
113 if input_api.is_committing: | 113 if input_api.is_committing: |
114 res_type = output_api.PresubmitError | 114 res_type = output_api.PresubmitError |
115 else: | 115 else: |
116 res_type = output_api.PresubmitPromptWarning | 116 res_type = output_api.PresubmitPromptWarning |
117 result = [res_type("Changelist failed cpplint.py check.")] | 117 result = [res_type('Changelist failed cpplint.py check.')] |
118 | 118 |
119 return result | 119 return result |
120 | 120 |
121 | 121 |
122 def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None): | 122 def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None): |
123 """Checks no '\r' (CR) character is in any source files.""" | 123 """Checks no '\r' (CR) character is in any source files.""" |
124 cr_files = [] | 124 cr_files = [] |
125 for f in input_api.AffectedSourceFiles(source_file_filter): | 125 for f in input_api.AffectedSourceFiles(source_file_filter): |
126 if '\r' in input_api.ReadFile(f, 'rb'): | 126 if '\r' in input_api.ReadFile(f, 'rb'): |
127 cr_files.append(f.LocalPath()) | 127 cr_files.append(f.LocalPath()) |
128 if cr_files: | 128 if cr_files: |
129 return [output_api.PresubmitPromptWarning( | 129 return [output_api.PresubmitPromptWarning( |
130 "Found a CR character in these files:", items=cr_files)] | 130 'Found a CR character in these files:', items=cr_files)] |
131 return [] | 131 return [] |
132 | 132 |
133 | 133 |
134 def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None): | 134 def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None): |
135 """Checks for files in svn modified directories. | 135 """Checks for files in svn modified directories. |
136 | 136 |
137 They will get submitted on accident because svn commits recursively by | 137 They will get submitted on accident because svn commits recursively by |
138 default, and that's very dangerous. | 138 default, and that's very dangerous. |
139 """ | 139 """ |
140 if input_api.change.scm != 'svn': | 140 if input_api.change.scm != 'svn': |
(...skipping 14 matching lines...) Expand all Loading... |
155 for i in xrange(len(modified_files)): | 155 for i in xrange(len(modified_files)): |
156 abspath = modified_abspaths[i] | 156 abspath = modified_abspaths[i] |
157 if input_api.os_path.commonprefix([curpath, abspath]) == curpath: | 157 if input_api.os_path.commonprefix([curpath, abspath]) == curpath: |
158 bad_files.append(modified_files[i]) | 158 bad_files.append(modified_files[i]) |
159 if bad_files: | 159 if bad_files: |
160 if input_api.is_committing: | 160 if input_api.is_committing: |
161 error_type = output_api.PresubmitPromptWarning | 161 error_type = output_api.PresubmitPromptWarning |
162 else: | 162 else: |
163 error_type = output_api.PresubmitNotifyResult | 163 error_type = output_api.PresubmitNotifyResult |
164 errors.append(error_type( | 164 errors.append(error_type( |
165 "Potential accidental commits in changelist %s:" % f.LocalPath(), | 165 'Potential accidental commits in changelist %s:' % f.LocalPath(), |
166 items=bad_files)) | 166 items=bad_files)) |
167 return errors | 167 return errors |
168 | 168 |
169 | 169 |
170 def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None): | 170 def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None): |
171 """Checks the files ends with one and only one \n (LF).""" | 171 """Checks the files ends with one and only one \n (LF).""" |
172 eof_files = [] | 172 eof_files = [] |
173 for f in input_api.AffectedSourceFiles(source_file_filter): | 173 for f in input_api.AffectedSourceFiles(source_file_filter): |
174 contents = input_api.ReadFile(f, 'rb') | 174 contents = input_api.ReadFile(f, 'rb') |
175 # Check that the file ends in one and only one newline character. | 175 # Check that the file ends in one and only one newline character. |
176 if len(contents) > 1 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"): | 176 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'): |
177 eof_files.append(f.LocalPath()) | 177 eof_files.append(f.LocalPath()) |
178 | 178 |
179 if eof_files: | 179 if eof_files: |
180 return [output_api.PresubmitPromptWarning( | 180 return [output_api.PresubmitPromptWarning( |
181 'These files should end in one (and only one) newline character:', | 181 'These files should end in one (and only one) newline character:', |
182 items=eof_files)] | 182 items=eof_files)] |
183 return [] | 183 return [] |
184 | 184 |
185 | 185 |
186 def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api, | 186 def CheckChangeHasNoCrAndHasOnlyOneEol(input_api, output_api, |
187 source_file_filter=None): | 187 source_file_filter=None): |
188 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass. | 188 """Runs both CheckChangeHasNoCR and CheckChangeHasOnlyOneEOL in one pass. |
189 | 189 |
190 It is faster because it is reading the file only once. | 190 It is faster because it is reading the file only once. |
191 """ | 191 """ |
192 cr_files = [] | 192 cr_files = [] |
193 eof_files = [] | 193 eof_files = [] |
194 for f in input_api.AffectedSourceFiles(source_file_filter): | 194 for f in input_api.AffectedSourceFiles(source_file_filter): |
195 contents = input_api.ReadFile(f, 'rb') | 195 contents = input_api.ReadFile(f, 'rb') |
196 if '\r' in contents: | 196 if '\r' in contents: |
197 cr_files.append(f.LocalPath()) | 197 cr_files.append(f.LocalPath()) |
198 # Check that the file ends in one and only one newline character. | 198 # Check that the file ends in one and only one newline character. |
199 if len(contents) > 1 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"): | 199 if len(contents) > 1 and (contents[-1:] != '\n' or contents[-2:-1] == '\n'): |
200 eof_files.append(f.LocalPath()) | 200 eof_files.append(f.LocalPath()) |
201 outputs = [] | 201 outputs = [] |
202 if cr_files: | 202 if cr_files: |
203 outputs.append(output_api.PresubmitPromptWarning( | 203 outputs.append(output_api.PresubmitPromptWarning( |
204 "Found a CR character in these files:", items=cr_files)) | 204 'Found a CR character in these files:', items=cr_files)) |
205 if eof_files: | 205 if eof_files: |
206 outputs.append(output_api.PresubmitPromptWarning( | 206 outputs.append(output_api.PresubmitPromptWarning( |
207 'These files should end in one (and only one) newline character:', | 207 'These files should end in one (and only one) newline character:', |
208 items=eof_files)) | 208 items=eof_files)) |
209 return outputs | 209 return outputs |
210 | 210 |
211 | 211 |
212 def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None): | 212 def CheckChangeHasNoTabs(input_api, output_api, source_file_filter=None): |
213 """Checks that there are no tab characters in any of the text files to be | 213 """Checks that there are no tab characters in any of the text files to be |
214 submitted. | 214 submitted. |
215 """ | 215 """ |
216 tabs = [] | 216 tabs = [] |
217 for f, line_num, line in input_api.RightHandSideLines(source_file_filter): | 217 for f, line_num, line in input_api.RightHandSideLines(source_file_filter): |
218 if '\t' in line: | 218 if '\t' in line: |
219 tabs.append("%s, line %s" % (f.LocalPath(), line_num)) | 219 tabs.append('%s, line %s' % (f.LocalPath(), line_num)) |
220 if tabs: | 220 if tabs: |
221 return [output_api.PresubmitPromptWarning("Found a tab character in:", | 221 return [output_api.PresubmitPromptWarning('Found a tab character in:', |
222 long_text="\n".join(tabs))] | 222 long_text='\n'.join(tabs))] |
223 return [] | 223 return [] |
224 | 224 |
225 | 225 |
226 def CheckChangeHasNoStrayWhitespace(input_api, output_api, | 226 def CheckChangeHasNoStrayWhitespace(input_api, output_api, |
227 source_file_filter=None): | 227 source_file_filter=None): |
228 """Checks that there is no stray whitespace at source lines end.""" | 228 """Checks that there is no stray whitespace at source lines end.""" |
229 errors = [] | 229 errors = [] |
230 for f, line_num, line in input_api.RightHandSideLines(source_file_filter): | 230 for f, line_num, line in input_api.RightHandSideLines(source_file_filter): |
231 if line.rstrip() != line: | 231 if line.rstrip() != line: |
232 errors.append("%s, line %s" % (f.LocalPath(), line_num)) | 232 errors.append('%s, line %s' % (f.LocalPath(), line_num)) |
233 if errors: | 233 if errors: |
234 return [output_api.PresubmitPromptWarning( | 234 return [output_api.PresubmitPromptWarning( |
235 "Found line ending with white spaces in:", | 235 'Found line ending with white spaces in:', |
236 long_text="\n".join(errors))] | 236 long_text='\n'.join(errors))] |
237 return [] | 237 return [] |
238 | 238 |
239 | 239 |
240 def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None): | 240 def CheckLongLines(input_api, output_api, maxlen=80, source_file_filter=None): |
241 """Checks that there aren't any lines longer than maxlen characters in any of | 241 """Checks that there aren't any lines longer than maxlen characters in any of |
242 the text files to be submitted. | 242 the text files to be submitted. |
243 """ | 243 """ |
244 bad = [] | 244 bad = [] |
245 for f, line_num, line in input_api.RightHandSideLines(source_file_filter): | 245 for f, line_num, line in input_api.RightHandSideLines(source_file_filter): |
246 # Allow lines with http://, https:// and #define/#pragma/#include/#if/#endif | 246 # Allow lines with http://, https:// and #define/#pragma/#include/#if/#endif |
247 # to exceed the maxlen rule. | 247 # to exceed the maxlen rule. |
248 if (len(line) > maxlen and | 248 if (len(line) > maxlen and |
249 not 'http://' in line and | 249 not 'http://' in line and |
250 not 'https://' in line and | 250 not 'https://' in line and |
251 not line.startswith('#define') and | 251 not line.startswith('#define') and |
252 not line.startswith('#include') and | 252 not line.startswith('#include') and |
253 not line.startswith('#pragma') and | 253 not line.startswith('#pragma') and |
254 not line.startswith('#if') and | 254 not line.startswith('#if') and |
255 not line.startswith('#endif')): | 255 not line.startswith('#endif')): |
256 bad.append( | 256 bad.append( |
257 '%s, line %s, %s chars' % | 257 '%s, line %s, %s chars' % |
258 (f.LocalPath(), line_num, len(line))) | 258 (f.LocalPath(), line_num, len(line))) |
259 if len(bad) == 5: # Just show the first 5 errors. | 259 if len(bad) == 5: # Just show the first 5 errors. |
260 break | 260 break |
261 | 261 |
262 if bad: | 262 if bad: |
263 msg = "Found lines longer than %s characters (first 5 shown)." % maxlen | 263 msg = 'Found lines longer than %s characters (first 5 shown).' % maxlen |
264 return [output_api.PresubmitPromptWarning(msg, items=bad)] | 264 return [output_api.PresubmitPromptWarning(msg, items=bad)] |
265 else: | 265 else: |
266 return [] | 266 return [] |
267 | 267 |
268 | 268 |
269 def CheckLicense(input_api, output_api, license, source_file_filter=None): | 269 def CheckLicense(input_api, output_api, license, source_file_filter=None): |
270 """Verifies the license header. | 270 """Verifies the license header. |
271 """ | 271 """ |
272 license_re = input_api.re.compile(license, input_api.re.MULTILINE) | 272 license_re = input_api.re.compile(license, input_api.re.MULTILINE) |
273 bad_files = [] | 273 bad_files = [] |
274 for f in input_api.AffectedSourceFiles(source_file_filter): | 274 for f in input_api.AffectedSourceFiles(source_file_filter): |
275 contents = input_api.ReadFile(f, 'rb') | 275 contents = input_api.ReadFile(f, 'rb') |
276 if not license_re.search(contents): | 276 if not license_re.search(contents): |
277 bad_files.append(f.LocalPath()) | 277 bad_files.append(f.LocalPath()) |
278 if bad_files: | 278 if bad_files: |
279 if input_api.is_committing: | 279 if input_api.is_committing: |
280 res_type = output_api.PresubmitPromptWarning | 280 res_type = output_api.PresubmitPromptWarning |
281 else: | 281 else: |
282 res_type = output_api.PresubmitNotifyResult | 282 res_type = output_api.PresubmitNotifyResult |
283 return [res_type( | 283 return [res_type( |
284 "Found a bad license header in these files:", items=bad_files)] | 284 'Found a bad license header in these files:', items=bad_files)] |
285 return [] | 285 return [] |
286 | 286 |
287 | 287 |
288 def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None): | 288 def CheckChangeSvnEolStyle(input_api, output_api, source_file_filter=None): |
289 """Checks that the source files have svn:eol-style=LF.""" | 289 """Checks that the source files have svn:eol-style=LF.""" |
290 return CheckSvnProperty(input_api, output_api, | 290 return CheckSvnProperty(input_api, output_api, |
291 'svn:eol-style', 'LF', | 291 'svn:eol-style', 'LF', |
292 input_api.AffectedSourceFiles(source_file_filter)) | 292 input_api.AffectedSourceFiles(source_file_filter)) |
293 | 293 |
294 | 294 |
(...skipping 25 matching lines...) Expand all Loading... |
320 """Checks that affected_files files have prop=expected.""" | 320 """Checks that affected_files files have prop=expected.""" |
321 if input_api.change.scm != 'svn': | 321 if input_api.change.scm != 'svn': |
322 return [] | 322 return [] |
323 | 323 |
324 bad = filter(lambda f: f.Property(prop) != expected, affected_files) | 324 bad = filter(lambda f: f.Property(prop) != expected, affected_files) |
325 if bad: | 325 if bad: |
326 if input_api.is_committing: | 326 if input_api.is_committing: |
327 res_type = output_api.PresubmitError | 327 res_type = output_api.PresubmitError |
328 else: | 328 else: |
329 res_type = output_api.PresubmitNotifyResult | 329 res_type = output_api.PresubmitNotifyResult |
330 message = "Run the command: svn pset %s %s \\" % (prop, expected) | 330 message = 'Run the command: svn pset %s %s \\' % (prop, expected) |
331 return [res_type(message, items=bad)] | 331 return [res_type(message, items=bad)] |
332 return [] | 332 return [] |
333 | 333 |
334 | 334 |
335 ### Other checks | 335 ### Other checks |
336 | 336 |
337 def CheckDoNotSubmit(input_api, output_api): | 337 def CheckDoNotSubmit(input_api, output_api): |
338 return ( | 338 return ( |
339 CheckDoNotSubmitInDescription(input_api, output_api) + | 339 CheckDoNotSubmitInDescription(input_api, output_api) + |
340 CheckDoNotSubmitInFiles(input_api, output_api) | 340 CheckDoNotSubmitInFiles(input_api, output_api) |
341 ) | 341 ) |
342 | 342 |
343 | 343 |
344 def CheckTreeIsOpen(input_api, output_api, url, closed): | 344 def CheckTreeIsOpen(input_api, output_api, url, closed): |
345 """Checks that an url's content doesn't match a regexp that would mean that | 345 """Checks that an url's content doesn't match a regexp that would mean that |
346 the tree is closed.""" | 346 the tree is closed.""" |
347 assert(input_api.is_committing) | 347 if not input_api.is_committing: |
| 348 return [] |
348 try: | 349 try: |
349 connection = input_api.urllib2.urlopen(url) | 350 connection = input_api.urllib2.urlopen(url) |
350 status = connection.read() | 351 status = connection.read() |
351 connection.close() | 352 connection.close() |
352 if input_api.re.match(closed, status): | 353 if input_api.re.match(closed, status): |
353 long_text = status + '\n' + url | 354 long_text = status + '\n' + url |
354 return [output_api.PresubmitPromptWarning("The tree is closed.", | 355 return [output_api.PresubmitPromptWarning('The tree is closed.', |
355 long_text=long_text)] | 356 long_text=long_text)] |
356 except IOError: | 357 except IOError: |
357 pass | 358 pass |
358 return [] | 359 return [] |
359 | 360 |
360 | 361 |
361 def RunPythonUnitTests(input_api, output_api, unit_tests): | 362 def RunPythonUnitTests(input_api, output_api, unit_tests): |
362 """Run the unit tests out of process, capture the output and use the result | 363 """Run the unit tests out of process, capture the output and use the result |
363 code to determine success. | 364 code to determine success. |
364 """ | 365 """ |
365 # We don't want to hinder users from uploading incomplete patches. | 366 # We don't want to hinder users from uploading incomplete patches. |
366 if input_api.is_committing: | 367 if input_api.is_committing: |
367 message_type = output_api.PresubmitError | 368 message_type = output_api.PresubmitError |
368 else: | 369 else: |
369 message_type = output_api.PresubmitNotifyResult | 370 message_type = output_api.PresubmitNotifyResult |
370 outputs = [] | 371 outputs = [] |
371 for unit_test in unit_tests: | 372 for unit_test in unit_tests: |
372 # Run the unit tests out of process. This is because some unit tests | 373 # Run the unit tests out of process. This is because some unit tests |
373 # stub out base libraries and don't clean up their mess. It's too easy to | 374 # stub out base libraries and don't clean up their mess. It's too easy to |
374 # get subtle bugs. | 375 # get subtle bugs. |
375 cwd = None | 376 cwd = None |
376 env = None | 377 env = None |
377 unit_test_name = unit_test | 378 unit_test_name = unit_test |
378 # "python -m test.unit_test" doesn't work. We need to change to the right | 379 # 'python -m test.unit_test' doesn't work. We need to change to the right |
379 # directory instead. | 380 # directory instead. |
380 if '.' in unit_test: | 381 if '.' in unit_test: |
381 # Tests imported in submodules (subdirectories) assume that the current | 382 # Tests imported in submodules (subdirectories) assume that the current |
382 # directory is in the PYTHONPATH. Manually fix that. | 383 # directory is in the PYTHONPATH. Manually fix that. |
383 unit_test = unit_test.replace('.', '/') | 384 unit_test = unit_test.replace('.', '/') |
384 cwd = input_api.os_path.dirname(unit_test) | 385 cwd = input_api.os_path.dirname(unit_test) |
385 unit_test = input_api.os_path.basename(unit_test) | 386 unit_test = input_api.os_path.basename(unit_test) |
386 env = input_api.environ.copy() | 387 env = input_api.environ.copy() |
387 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH | 388 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH |
388 backpath = [ | 389 backpath = [ |
389 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1)) | 390 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1)) |
390 ] | 391 ] |
391 if env.get('PYTHONPATH'): | 392 if env.get('PYTHONPATH'): |
392 backpath.append(env.get('PYTHONPATH')) | 393 backpath.append(env.get('PYTHONPATH')) |
393 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath)) | 394 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath)) |
394 subproc = input_api.subprocess.Popen( | 395 subproc = input_api.subprocess.Popen( |
395 [ | 396 [ |
396 input_api.python_executable, | 397 input_api.python_executable, |
397 "-m", | 398 '-m', |
398 "%s" % unit_test | 399 '%s' % unit_test |
399 ], | 400 ], |
400 cwd=cwd, | 401 cwd=cwd, |
401 env=env, | 402 env=env, |
402 stdin=input_api.subprocess.PIPE, | 403 stdin=input_api.subprocess.PIPE, |
403 stdout=input_api.subprocess.PIPE, | 404 stdout=input_api.subprocess.PIPE, |
404 stderr=input_api.subprocess.PIPE) | 405 stderr=input_api.subprocess.PIPE) |
405 stdoutdata, stderrdata = subproc.communicate() | 406 stdoutdata, stderrdata = subproc.communicate() |
406 # Discard the output if returncode == 0 | 407 # Discard the output if returncode == 0 |
407 if subproc.returncode: | 408 if subproc.returncode: |
408 outputs.append("Test '%s' failed with code %d\n%s\n%s\n" % ( | 409 outputs.append('Test \'%s\' failed with code %d\n%s\n%s\n' % ( |
409 unit_test_name, subproc.returncode, stdoutdata, stderrdata)) | 410 unit_test_name, subproc.returncode, stdoutdata, stderrdata)) |
410 if outputs: | 411 if outputs: |
411 return [message_type("%d unit tests failed." % len(outputs), | 412 return [message_type('%d unit tests failed.' % len(outputs), |
412 long_text='\n'.join(outputs))] | 413 long_text='\n'.join(outputs))] |
413 return [] | 414 return [] |
| 415 |
| 416 |
| 417 def CheckRietveldTryJobExecution(input_api, output_api, host_url, platforms, |
| 418 owner): |
| 419 if not input_api.is_committing: |
| 420 return [] |
| 421 if not input_api.change.issue or not input_api.change.patchset: |
| 422 return [] |
| 423 url = '%s/%d/get_build_results/%d' % ( |
| 424 host_url, input_api.change.issue, input_api.change.patchset) |
| 425 try: |
| 426 connection = input_api.urllib2.urlopen(url) |
| 427 # platform|status|url |
| 428 values = [item.split('|', 2) for item in connection.read().splitlines()] |
| 429 connection.close() |
| 430 except input_api.urllib2.HTTPError, e: |
| 431 if e.code == 404: |
| 432 # Fallback to no try job. |
| 433 return [output_api.PresubmitPromptWarning( |
| 434 'You should try the patch first.')] |
| 435 else: |
| 436 # Another HTTP error happened, warn the user. |
| 437 return [output_api.PresubmitPromptWarning( |
| 438 'Got %s while looking for try job status.' % str(e))] |
| 439 |
| 440 if not values: |
| 441 # It returned an empty list. Probably a private review. |
| 442 return [] |
| 443 # Reformat as an dict of platform: [status, url] |
| 444 values = dict([[v[0], [v[1], v[2]]] for v in values if len(v) == 3]) |
| 445 if not values: |
| 446 # It returned useless data. |
| 447 return [output_api.PresubmitNotifyResult('Failed to parse try job results')] |
| 448 |
| 449 for platform in platforms: |
| 450 values.setdefault(platform, ['not started', '']) |
| 451 message = None |
| 452 non_success = [k.upper() for k,v in values.iteritems() if v[0] != 'success'] |
| 453 if 'failure' in [v[0] for v in values.itervalues()]: |
| 454 message = 'Try job failures on %s!\n' % ', '.join(non_success) |
| 455 elif non_success: |
| 456 message = ('Unfinished (or not even started) try jobs on ' |
| 457 '%s.\n') % ', '.join(non_success) |
| 458 if message: |
| 459 message += ( |
| 460 'Is try server wrong or broken? Please notify %s. ' |
| 461 'Thanks.\n' % owner) |
| 462 return [output_api.PresubmitPromptWarning(message=message)] |
| 463 return [] |
| 464 |
| 465 |
| 466 def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings, |
| 467 ignored): |
| 468 if not input_api.json: |
| 469 return [output_api.PresubmitPromptWarning( |
| 470 'Please install simplejson or upgrade to python 2.6+')] |
| 471 try: |
| 472 connection = input_api.urllib2.urlopen(url) |
| 473 raw_data = connection.read() |
| 474 connection.close() |
| 475 except IOError: |
| 476 return [output_api.PresubmitNotifyResult('%s is not accessible' % url)] |
| 477 |
| 478 try: |
| 479 data = input_api.json.loads(raw_data) |
| 480 except ValueError: |
| 481 return [output_api.PresubmitNotifyResult('Received malformed json while ' |
| 482 'looking up buildbot status')] |
| 483 |
| 484 out = [] |
| 485 for (builder_name, builder) in data.iteritems(): |
| 486 if builder_name in ignored: |
| 487 continue |
| 488 pending_builds_len = len(builder.get('pending_builds', [])) |
| 489 if pending_builds_len > max_pendings: |
| 490 out.append('%s has %d build(s) pending' % |
| 491 (builder_name, pending_builds_len)) |
| 492 if out: |
| 493 return [output_api.PresubmitPromptWarning( |
| 494 'Build(s) pending. It is suggested to wait that no more than %d ' |
| 495 'builds are pending.' % max_pendings, |
| 496 long_text='\n'.join(out))] |
| 497 return [] |
OLD | NEW |