blob: 705befa5c8d55312e8a3b1a92adc16224bf112a0 [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import argparse
import re
import sys
import urllib2
_BUILD_REGEX = r'builds/(\d+)'
_REVISION_REGEX = r'\nCr-Commit-Position: refs/heads/(?:master|main)@{#(\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:]))