| 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 |