blob: b6b9ff76ff57d3eed6346e243fbf79541f305729 [file] [log] [blame]
#!/usr/bin/env vpython
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import re
import sys
import urllib2
_BUILD_REGEX = r'builds/(\d+)'
_REVISION_REGEX = r'\nCr-Commit-Position: refs/heads/master@{#(\d+)}'
class _Color(object):
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def _PrintWithColor(text, *colors):
print ''.join(colors) + text + _Color.ENDC
def _ExtractBuildRevisionRange(build_page_content, build_url, test_name):
if not test_name in build_page_content:
raise Exception(
'Cannot find %s in %s. If you think this is an exception step, please, '
'restart the process with build# = last test build - 1'
% (test_name, build_url))
if not ('failed ' + test_name) in build_page_content:
return None
revisions = re.findall(_REVISION_REGEX, build_page_content)
revisions = list(int(r) for r in revisions)
return revisions
def _ShouldSkipBuild(build_page_content, build_url):
_GLOBAL_FAILURE = [
'failed sharded perf tests',
'exception sharded perf tests',
]
for failure in _GLOBAL_FAILURE:
if failure in build_page_content:
print
_PrintWithColor(
"Warning: %s has '%s'."
' Skipping this build' % (build_url, failure), _Color.WARNING)
return True
return False
def FindFirstFailureRange(build_url, test_name):
build_number = re.findall(_BUILD_REGEX, build_url)
assert len(build_number) == 1, (
'Must put in a valid build url with build number')
build_number = int(build_number[0])
initial_build_url = build_url[:build_url.find('builds/')] + 'builds/'
while True:
current_build_url = initial_build_url + str(build_number)
build_number -= 1
print '\rProcess %s' % current_build_url,
sys.stdout.flush()
build_page_content = urllib2.urlopen(current_build_url).read()
if _ShouldSkipBuild(build_page_content, build_url):
continue
failure_revisions = _ExtractBuildRevisionRange(
build_page_content, current_build_url, test_name)
if failure_revisions == None:
return first_failure_revisions, first_failed_build
else:
first_failure_revisions = failure_revisions
first_failed_build = current_build_url
def Main(args):
parser = argparse.ArgumentParser(
'Find first failed revision range for a given failed test. Notes that '
'this tool cannot handle flaky test failures.')
parser.add_argument('test_name')
parser.add_argument('build_url',
help='A build url which |test_name| is failing')
options = parser.parse_args(args)
first_failure_revisions, first_failed_build = FindFirstFailureRange(
options.build_url, options.test_name)
print
print
_PrintWithColor(
'First failure range: %s - %s CLs' % (
(min(first_failure_revisions), max(first_failure_revisions)),
len(set(first_failure_revisions))),
_Color.BOLD, _Color.OKGREEN)
_PrintWithColor('First failed build: %s' % first_failed_build,
_Color.BOLD, _Color.OKGREEN)
return 0
if __name__ == '__main__':
sys.exit(Main(sys.argv[1:]))