chromium/tools/perf/diagnose_test_failure

#!/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:]))