| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Performance Test Bisect Tool | 6 """Performance Test Bisect Tool |
| 7 | 7 |
| 8 This script bisects a series of changelists using binary search. It starts at | 8 This script bisects a series of changelists using binary search. It starts at |
| 9 a bad revision where a performance metric has regressed, and asks for a last | 9 a bad revision where a performance metric has regressed, and asks for a last |
| 10 known-good revision. It will then binary search across this revision range by | 10 known-good revision. It will then binary search across this revision range by |
| (...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 244 command: A list containing the args to git. | 244 command: A list containing the args to git. |
| 245 | 245 |
| 246 Returns: | 246 Returns: |
| 247 A tuple of the output and return code. | 247 A tuple of the output and return code. |
| 248 """ | 248 """ |
| 249 command = ['git'] + command | 249 command = ['git'] + command |
| 250 | 250 |
| 251 return RunProcess(command) | 251 return RunProcess(command) |
| 252 | 252 |
| 253 | 253 |
| 254 def CheckRunGit(command): |
| 255 """Run a git subcommand, returning its output and return code. Asserts if |
| 256 the return code of the call is non-zero. |
| 257 |
| 258 Args: |
| 259 command: A list containing the args to git. |
| 260 |
| 261 Returns: |
| 262 A tuple of the output and return code. |
| 263 """ |
| 264 (output, return_code) = RunGit(command) |
| 265 |
| 266 assert not return_code, 'An error occurred while running'\ |
| 267 ' "git %s"' % ' '.join(command) |
| 268 return output |
| 269 |
| 270 |
| 254 def BuildWithMake(threads, targets, print_output): | 271 def BuildWithMake(threads, targets, print_output): |
| 255 cmd = ['make', 'BUILDTYPE=Release', '-j%d' % threads] + targets | 272 cmd = ['make', 'BUILDTYPE=Release', '-j%d' % threads] + targets |
| 256 | 273 |
| 257 (output, return_code) = RunProcess(cmd, print_output) | 274 (output, return_code) = RunProcess(cmd, print_output) |
| 258 | 275 |
| 259 return not return_code | 276 return not return_code |
| 260 | 277 |
| 261 | 278 |
| 262 def BuildWithNinja(threads, targets, print_output): | 279 def BuildWithNinja(threads, targets, print_output): |
| 263 cmd = ['ninja', '-C', os.path.join('out', 'Release'), | 280 cmd = ['ninja', '-C', os.path.join('out', 'Release'), |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 319 Args: | 336 Args: |
| 320 revision_range_end: The SHA1 for the end of the range. | 337 revision_range_end: The SHA1 for the end of the range. |
| 321 revision_range_start: The SHA1 for the beginning of the range. | 338 revision_range_start: The SHA1 for the beginning of the range. |
| 322 | 339 |
| 323 Returns: | 340 Returns: |
| 324 A list of the revisions between |revision_range_start| and | 341 A list of the revisions between |revision_range_start| and |
| 325 |revision_range_end| (inclusive). | 342 |revision_range_end| (inclusive). |
| 326 """ | 343 """ |
| 327 revision_range = '%s..%s' % (revision_range_start, revision_range_end) | 344 revision_range = '%s..%s' % (revision_range_start, revision_range_end) |
| 328 cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range] | 345 cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range] |
| 329 (log_output, return_code) = RunGit(cmd) | 346 log_output = CheckRunGit(cmd) |
| 330 | |
| 331 assert not return_code, 'An error occurred while running'\ | |
| 332 ' "git %s"' % ' '.join(cmd) | |
| 333 | 347 |
| 334 revision_hash_list = log_output.split() | 348 revision_hash_list = log_output.split() |
| 335 revision_hash_list.append(revision_range_start) | 349 revision_hash_list.append(revision_range_start) |
| 336 | 350 |
| 337 return revision_hash_list | 351 return revision_hash_list |
| 338 | 352 |
| 339 def SyncToRevision(self, revision, use_gclient=True): | 353 def SyncToRevision(self, revision, use_gclient=True): |
| 340 """Syncs to the specified revision. | 354 """Syncs to the specified revision. |
| 341 | 355 |
| 342 Args: | 356 Args: |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 382 | 396 |
| 383 if search > 0: | 397 if search > 0: |
| 384 search_range = xrange(svn_revision, svn_revision + search, 1) | 398 search_range = xrange(svn_revision, svn_revision + search, 1) |
| 385 else: | 399 else: |
| 386 search_range = xrange(svn_revision, svn_revision + search, -1) | 400 search_range = xrange(svn_revision, svn_revision + search, -1) |
| 387 | 401 |
| 388 for i in search_range: | 402 for i in search_range: |
| 389 svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i) | 403 svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i) |
| 390 cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern, 'origin/master'] | 404 cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern, 'origin/master'] |
| 391 | 405 |
| 392 (log_output, return_code) = RunGit(cmd) | 406 log_output = CheckRunGit(cmd) |
| 407 log_output = log_output.strip() |
| 393 | 408 |
| 394 assert not return_code, 'An error occurred while running'\ | 409 if log_output: |
| 395 ' "git %s"' % ' '.join(cmd) | 410 git_revision = log_output |
| 396 | 411 |
| 397 if not return_code: | 412 break |
| 398 log_output = log_output.strip() | |
| 399 | |
| 400 if log_output: | |
| 401 git_revision = log_output | |
| 402 | |
| 403 break | |
| 404 | 413 |
| 405 return git_revision | 414 return git_revision |
| 406 | 415 |
| 407 def IsInProperBranch(self): | 416 def IsInProperBranch(self): |
| 408 """Confirms they're in the master branch for performing the bisection. | 417 """Confirms they're in the master branch for performing the bisection. |
| 409 This is needed or gclient will fail to sync properly. | 418 This is needed or gclient will fail to sync properly. |
| 410 | 419 |
| 411 Returns: | 420 Returns: |
| 412 True if the current branch on src is 'master' | 421 True if the current branch on src is 'master' |
| 413 """ | 422 """ |
| 414 cmd = ['rev-parse', '--abbrev-ref', 'HEAD'] | 423 cmd = ['rev-parse', '--abbrev-ref', 'HEAD'] |
| 415 (log_output, return_code) = RunGit(cmd) | 424 log_output = CheckRunGit(cmd) |
| 416 | |
| 417 assert not return_code, 'An error occurred while running'\ | |
| 418 ' "git %s"' % ' '.join(cmd) | |
| 419 | |
| 420 log_output = log_output.strip() | 425 log_output = log_output.strip() |
| 421 | 426 |
| 422 return log_output == "master" | 427 return log_output == "master" |
| 423 | 428 |
| 424 def SVNFindRev(self, revision): | 429 def SVNFindRev(self, revision): |
| 425 """Maps directly to the 'git svn find-rev' command. | 430 """Maps directly to the 'git svn find-rev' command. |
| 426 | 431 |
| 427 Args: | 432 Args: |
| 428 revision: The git SHA1 to use. | 433 revision: The git SHA1 to use. |
| 429 | 434 |
| 430 Returns: | 435 Returns: |
| 431 An integer changelist #, otherwise None. | 436 An integer changelist #, otherwise None. |
| 432 """ | 437 """ |
| 433 | 438 |
| 434 cmd = ['svn', 'find-rev', revision] | 439 cmd = ['svn', 'find-rev', revision] |
| 435 | 440 |
| 436 (output, return_code) = RunGit(cmd) | 441 output = CheckRunGit(cmd) |
| 437 | |
| 438 assert not return_code, 'An error occurred while running'\ | |
| 439 ' "git %s"' % ' '.join(cmd) | |
| 440 | |
| 441 svn_revision = output.strip() | 442 svn_revision = output.strip() |
| 442 | 443 |
| 443 if IsStringInt(svn_revision): | 444 if IsStringInt(svn_revision): |
| 444 return int(svn_revision) | 445 return int(svn_revision) |
| 445 | 446 |
| 446 return None | 447 return None |
| 447 | 448 |
| 448 def QueryRevisionInfo(self, revision): | 449 def QueryRevisionInfo(self, revision): |
| 449 """Gathers information on a particular revision, such as author's name, | 450 """Gathers information on a particular revision, such as author's name, |
| 450 email, subject, and date. | 451 email, subject, and date. |
| 451 | 452 |
| 452 Args: | 453 Args: |
| 453 revision: Revision you want to gather information on. | 454 revision: Revision you want to gather information on. |
| 454 Returns: | 455 Returns: |
| 455 A dict in the following format: | 456 A dict in the following format: |
| 456 { | 457 { |
| 457 'author': %s, | 458 'author': %s, |
| 458 'email': %s, | 459 'email': %s, |
| 459 'date': %s, | 460 'date': %s, |
| 460 'subject': %s, | 461 'subject': %s, |
| 461 } | 462 } |
| 462 """ | 463 """ |
| 463 commit_info = {} | 464 commit_info = {} |
| 464 | 465 |
| 465 formats = ['%cN', '%cE', '%s', '%cD'] | 466 formats = ['%cN', '%cE', '%s', '%cD'] |
| 466 targets = ['author', 'email', 'subject', 'date'] | 467 targets = ['author', 'email', 'subject', 'date'] |
| 467 | 468 |
| 468 for i in xrange(len(formats)): | 469 for i in xrange(len(formats)): |
| 469 cmd = ['log', '--format=%s' % formats[i], '-1', revision] | 470 cmd = ['log', '--format=%s' % formats[i], '-1', revision] |
| 470 (output, return_code) = RunGit(cmd) | 471 output = CheckRunGit(cmd) |
| 471 commit_info[targets[i]] = output.rstrip() | 472 commit_info[targets[i]] = output.rstrip() |
| 472 | 473 |
| 473 assert not return_code, 'An error occurred while running'\ | |
| 474 ' "git %s"' % ' '.join(cmd) | |
| 475 | |
| 476 return commit_info | 474 return commit_info |
| 477 | 475 |
| 478 def CheckoutFileAtRevision(self, file_name, revision): | 476 def CheckoutFileAtRevision(self, file_name, revision): |
| 479 """Performs a checkout on a file at the given revision. | 477 """Performs a checkout on a file at the given revision. |
| 480 | 478 |
| 481 Returns: | 479 Returns: |
| 482 True if successful. | 480 True if successful. |
| 483 """ | 481 """ |
| 484 return not RunGit(['checkout', revision, file_name])[1] | 482 return not RunGit(['checkout', revision, file_name])[1] |
| 485 | 483 |
| 486 def RevertFileToHead(self, file_name): | 484 def RevertFileToHead(self, file_name): |
| 487 """Unstages a file and returns it to HEAD. | 485 """Unstages a file and returns it to HEAD. |
| 488 | 486 |
| 489 Returns: | 487 Returns: |
| 490 True if successful. | 488 True if successful. |
| 491 """ | 489 """ |
| 492 # Reset doesn't seem to return 0 on success. | 490 # Reset doesn't seem to return 0 on success. |
| 493 RunGit(['reset', 'HEAD', bisect_utils.FILE_DEPS_GIT]) | 491 RunGit(['reset', 'HEAD', bisect_utils.FILE_DEPS_GIT]) |
| 494 | 492 |
| 495 return not RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1] | 493 return not RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1] |
| 496 | 494 |
| 495 def QueryFileRevisionHistory(self, filename, revision_start, revision_end): |
| 496 """Returns a list of commits that modified this file. |
| 497 |
| 498 Args: |
| 499 filename: Name of file. |
| 500 revision_start: Start of revision range. |
| 501 revision_end: End of revision range. |
| 502 |
| 503 Returns: |
| 504 Returns a list of commits that touched this file. |
| 505 """ |
| 506 cmd = ['log', '--format=%H', '%s^1..%s' % (revision_start, revision_end), |
| 507 filename] |
| 508 output = CheckRunGit(cmd) |
| 509 |
| 510 return [o for o in output.split('\n') if o] |
| 511 |
| 497 class BisectPerformanceMetrics(object): | 512 class BisectPerformanceMetrics(object): |
| 498 """BisectPerformanceMetrics performs a bisection against a list of range | 513 """BisectPerformanceMetrics performs a bisection against a list of range |
| 499 of revisions to narrow down where performance regressions may have | 514 of revisions to narrow down where performance regressions may have |
| 500 occurred.""" | 515 occurred.""" |
| 501 | 516 |
| 502 def __init__(self, source_control, opts): | 517 def __init__(self, source_control, opts): |
| 503 super(BisectPerformanceMetrics, self).__init__() | 518 super(BisectPerformanceMetrics, self).__init__() |
| 504 | 519 |
| 505 self.opts = opts | 520 self.opts = opts |
| 506 self.source_control = source_control | 521 self.source_control = source_control |
| 507 self.src_cwd = os.getcwd() | 522 self.src_cwd = os.getcwd() |
| 508 self.depot_cwd = {} | 523 self.depot_cwd = {} |
| 509 self.cleanup_commands = [] | 524 self.cleanup_commands = [] |
| 525 self.warnings = [] |
| 510 | 526 |
| 511 # This always starts true since the script grabs latest first. | 527 # This always starts true since the script grabs latest first. |
| 512 self.was_blink = True | 528 self.was_blink = True |
| 513 | 529 |
| 514 for d in DEPOT_NAMES: | 530 for d in DEPOT_NAMES: |
| 515 # The working directory of each depot is just the path to the depot, but | 531 # The working directory of each depot is just the path to the depot, but |
| 516 # since we're already in 'src', we can skip that part. | 532 # since we're already in 'src', we can skip that part. |
| 517 | 533 |
| 518 self.depot_cwd[d] = self.src_cwd + DEPOT_DEPS_NAME[d]['src'][3:] | 534 self.depot_cwd[d] = self.src_cwd + DEPOT_DEPS_NAME[d]['src'][3:] |
| 519 | 535 |
| (...skipping 517 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1037 | 1053 |
| 1038 print | 1054 print |
| 1039 print 'Revisions to bisect on [%s]:' % depot | 1055 print 'Revisions to bisect on [%s]:' % depot |
| 1040 for revision_id in revision_list: | 1056 for revision_id in revision_list: |
| 1041 print ' -> %s' % (revision_id, ) | 1057 print ' -> %s' % (revision_id, ) |
| 1042 print | 1058 print |
| 1043 | 1059 |
| 1044 if self.opts.output_buildbot_annotations: | 1060 if self.opts.output_buildbot_annotations: |
| 1045 bisect_utils.OutputAnnotationStepClosed() | 1061 bisect_utils.OutputAnnotationStepClosed() |
| 1046 | 1062 |
| 1063 def NudgeRevisionsIfDEPSChange(self, bad_revision, good_revision): |
| 1064 """Checks to see if changes to DEPS file occurred, and that the revision |
| 1065 range also includes the change to .DEPS.git. If it doesn't, attempts to |
| 1066 expand the revision range to include it. |
| 1067 |
| 1068 Args: |
| 1069 bad_rev: First known bad revision. |
| 1070 good_revision: Last known good revision. |
| 1071 |
| 1072 Returns: |
| 1073 A tuple with the new bad and good revisions. |
| 1074 """ |
| 1075 if self.source_control.IsGit(): |
| 1076 changes_to_deps = self.source_control.QueryFileRevisionHistory( |
| 1077 'DEPS', good_revision, bad_revision) |
| 1078 |
| 1079 if changes_to_deps: |
| 1080 # DEPS file was changed, search from the oldest change to DEPS file to |
| 1081 # bad_revision to see if there are matching .DEPS.git changes. |
| 1082 oldest_deps_change = changes_to_deps[-1] |
| 1083 changes_to_gitdeps = self.source_control.QueryFileRevisionHistory( |
| 1084 bisect_utils.FILE_DEPS_GIT, oldest_deps_change, bad_revision) |
| 1085 |
| 1086 if len(changes_to_deps) != len(changes_to_gitdeps): |
| 1087 # Grab the timestamp of the last DEPS change |
| 1088 cmd = ['log', '--format=%ct', '-1', changes_to_deps[0]] |
| 1089 output = CheckRunGit(cmd) |
| 1090 commit_time = int(output) |
| 1091 |
| 1092 # Try looking for a commit that touches the .DEPS.git file in the |
| 1093 # next 15 minutes after the DEPS file change. |
| 1094 cmd = ['log', '--format=%H', '-1', |
| 1095 '--before=%d' % (commit_time + 900), '--after=%d' % commit_time, |
| 1096 'origin/master', bisect_utils.FILE_DEPS_GIT] |
| 1097 output = CheckRunGit(cmd) |
| 1098 output = output.strip() |
| 1099 if output: |
| 1100 self.warnings.append('Detected change to DEPS and modified ' |
| 1101 'revision range to include change to .DEPS.git') |
| 1102 return (output, good_revision) |
| 1103 else: |
| 1104 self.warnings.append('Detected change to DEPS but couldn\'t find ' |
| 1105 'matching change to .DEPS.git') |
| 1106 return (bad_revision, good_revision) |
| 1107 |
| 1047 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): | 1108 def Run(self, command_to_run, bad_revision_in, good_revision_in, metric): |
| 1048 """Given known good and bad revisions, run a binary search on all | 1109 """Given known good and bad revisions, run a binary search on all |
| 1049 intermediate revisions to determine the CL where the performance regression | 1110 intermediate revisions to determine the CL where the performance regression |
| 1050 occurred. | 1111 occurred. |
| 1051 | 1112 |
| 1052 Args: | 1113 Args: |
| 1053 command_to_run: Specify the command to execute the performance test. | 1114 command_to_run: Specify the command to execute the performance test. |
| 1054 good_revision: Number/tag of the known good revision. | 1115 good_revision: Number/tag of the known good revision. |
| 1055 bad_revision: Number/tag of the known bad revision. | 1116 bad_revision: Number/tag of the known bad revision. |
| 1056 metric: The performance metric to monitor. | 1117 metric: The performance metric to monitor. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1098 'src', -100) | 1159 'src', -100) |
| 1099 | 1160 |
| 1100 if bad_revision is None: | 1161 if bad_revision is None: |
| 1101 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) | 1162 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (bad_revision_in,) |
| 1102 return results | 1163 return results |
| 1103 | 1164 |
| 1104 if good_revision is None: | 1165 if good_revision is None: |
| 1105 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) | 1166 results['error'] = 'Could\'t resolve [%s] to SHA1.' % (good_revision_in,) |
| 1106 return results | 1167 return results |
| 1107 | 1168 |
| 1169 (bad_revision, good_revision) = self.NudgeRevisionsIfDEPSChange( |
| 1170 bad_revision, good_revision) |
| 1171 |
| 1108 if self.opts.output_buildbot_annotations: | 1172 if self.opts.output_buildbot_annotations: |
| 1109 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') | 1173 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') |
| 1110 | 1174 |
| 1111 print 'Gathering revision range for bisection.' | 1175 print 'Gathering revision range for bisection.' |
| 1112 | 1176 |
| 1113 # Retrieve a list of revisions to do bisection on. | 1177 # Retrieve a list of revisions to do bisection on. |
| 1114 src_revision_list = self.GetRevisionList(bad_revision, good_revision) | 1178 src_revision_list = self.GetRevisionList(bad_revision, good_revision) |
| 1115 | 1179 |
| 1116 if self.opts.output_buildbot_annotations: | 1180 if self.opts.output_buildbot_annotations: |
| 1117 bisect_utils.OutputAnnotationStepClosed() | 1181 bisect_utils.OutputAnnotationStepClosed() |
| (...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1373 good_std_dev = revision_data[first_working_revision]['value']['std_dev'] | 1437 good_std_dev = revision_data[first_working_revision]['value']['std_dev'] |
| 1374 good_mean = revision_data[first_working_revision]['value']['mean'] | 1438 good_mean = revision_data[first_working_revision]['value']['mean'] |
| 1375 bad_mean = revision_data[last_broken_revision]['value']['mean'] | 1439 bad_mean = revision_data[last_broken_revision]['value']['mean'] |
| 1376 | 1440 |
| 1377 # A standard deviation of 0 could indicate either insufficient runs | 1441 # A standard deviation of 0 could indicate either insufficient runs |
| 1378 # or a test that consistently returns the same value. | 1442 # or a test that consistently returns the same value. |
| 1379 if good_std_dev > 0: | 1443 if good_std_dev > 0: |
| 1380 deviations = math.fabs(bad_mean - good_mean) / good_std_dev | 1444 deviations = math.fabs(bad_mean - good_mean) / good_std_dev |
| 1381 | 1445 |
| 1382 if deviations < 1.5: | 1446 if deviations < 1.5: |
| 1383 print 'Warning: Regression was less than 1.5 standard deviations '\ | 1447 self.warnings.append('Regression was less than 1.5 standard ' |
| 1384 'from "good" value. Results may not be accurate.' | 1448 'deviations from "good" value. Results may not be accurate.') |
| 1385 print | |
| 1386 elif self.opts.repeat_test_count == 1: | 1449 elif self.opts.repeat_test_count == 1: |
| 1387 print 'Warning: Tests were only set to run once. This may be '\ | 1450 self.warnings.append('Tests were only set to run once. This ' |
| 1388 'insufficient to get meaningful results.' | 1451 'may be insufficient to get meaningful results.') |
| 1389 print | |
| 1390 | 1452 |
| 1391 # Check for any other possible regression ranges | 1453 # Check for any other possible regression ranges |
| 1392 prev_revision_data = revision_data_sorted[0][1] | 1454 prev_revision_data = revision_data_sorted[0][1] |
| 1393 prev_revision_id = revision_data_sorted[0][0] | 1455 prev_revision_id = revision_data_sorted[0][0] |
| 1394 possible_regressions = [] | 1456 possible_regressions = [] |
| 1395 for current_id, current_data in revision_data_sorted: | 1457 for current_id, current_data in revision_data_sorted: |
| 1396 if current_data['value']: | 1458 if current_data['value']: |
| 1397 prev_mean = prev_revision_data['value']['mean'] | 1459 prev_mean = prev_revision_data['value']['mean'] |
| 1398 cur_mean = current_data['value']['mean'] | 1460 cur_mean = current_data['value']['mean'] |
| 1399 | 1461 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1440 if percent_change is None: | 1502 if percent_change is None: |
| 1441 percent_change = 0 | 1503 percent_change = 0 |
| 1442 | 1504 |
| 1443 print ' %8s %s [%.2f%%, %s x std.dev]' % ( | 1505 print ' %8s %s [%.2f%%, %s x std.dev]' % ( |
| 1444 previous_data['depot'], previous_id, 100 * percent_change, | 1506 previous_data['depot'], previous_id, 100 * percent_change, |
| 1445 deviations) | 1507 deviations) |
| 1446 print ' %8s %s' % ( | 1508 print ' %8s %s' % ( |
| 1447 current_data['depot'], current_id) | 1509 current_data['depot'], current_id) |
| 1448 print | 1510 print |
| 1449 | 1511 |
| 1512 if self.warnings: |
| 1513 print |
| 1514 print 'The following warnings were generated:' |
| 1515 print |
| 1516 for w in self.warnings: |
| 1517 print ' - %s' % w |
| 1518 print |
| 1519 |
| 1450 if self.opts.output_buildbot_annotations: | 1520 if self.opts.output_buildbot_annotations: |
| 1451 bisect_utils.OutputAnnotationStepClosed() | 1521 bisect_utils.OutputAnnotationStepClosed() |
| 1452 | 1522 |
| 1453 | 1523 |
| 1454 def DetermineAndCreateSourceControl(): | 1524 def DetermineAndCreateSourceControl(): |
| 1455 """Attempts to determine the underlying source control workflow and returns | 1525 """Attempts to determine the underlying source control workflow and returns |
| 1456 a SourceControl object. | 1526 a SourceControl object. |
| 1457 | 1527 |
| 1458 Returns: | 1528 Returns: |
| 1459 An instance of a SourceControl object, or None if the current workflow | 1529 An instance of a SourceControl object, or None if the current workflow |
| (...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1729 | 1799 |
| 1730 if not(bisect_results['error']): | 1800 if not(bisect_results['error']): |
| 1731 return 0 | 1801 return 0 |
| 1732 else: | 1802 else: |
| 1733 print 'Error: ' + bisect_results['error'] | 1803 print 'Error: ' + bisect_results['error'] |
| 1734 print | 1804 print |
| 1735 return 1 | 1805 return 1 |
| 1736 | 1806 |
| 1737 if __name__ == '__main__': | 1807 if __name__ == '__main__': |
| 1738 sys.exit(main()) | 1808 sys.exit(main()) |
| OLD | NEW |