chromium/tools/metrics/histograms/PRESUBMIT.py

# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into depot_tools.
"""

PRESUBMIT_VERSION = '2.0.0'

import os
from pathlib import Path
import sys


def GetPrettyPrintErrors(input_api, output_api, cwd, rel_path, results):
  """Runs pretty-print command for specified file."""
  args = [
      input_api.python3_executable, 'pretty_print.py', rel_path, '--presubmit',
      '--non-interactive'
  ]
  exit_code = input_api.subprocess.call(args, cwd=cwd)

  if exit_code != 0:
    error_msg = ('%s is not formatted correctly; run `git cl format` to fix.' %
                 rel_path)
    results.append(output_api.PresubmitError(error_msg))


def GetTokenErrors(input_api, output_api, cwd, rel_path, results):
  """Validates histogram tokens in specified file."""
  exit_code = input_api.subprocess.call(
      [input_api.python3_executable, 'validate_token.py', rel_path], cwd=cwd)

  if exit_code != 0:
    error_msg = (
        '%s contains histogram(s) using <variants> not defined in the file, '
        'please run validate_token.py %s to fix.' % (rel_path, rel_path))
    results.append(output_api.PresubmitError(error_msg))


def GetValidateHistogramsError(input_api, output_api, cwd, results):
  """Validates histograms format and index file."""
  exit_code = input_api.subprocess.call(
      [input_api.python3_executable, 'validate_format.py'], cwd=cwd)

  if exit_code != 0:
    error_msg = (
        'Histograms are not well-formatted; please run %s/validate_format.py '
        'and fix the reported errors.' % cwd)
    results.append(output_api.PresubmitError(error_msg))

  exit_code = input_api.subprocess.call(
      [input_api.python3_executable, 'validate_histograms_index.py'], cwd=cwd)

  if exit_code != 0:
    error_msg = (
        'Histograms index file is not up-to-date. Please run '
        '%s/histogram_paths.py to update it' % cwd)
    results.append(output_api.PresubmitError(error_msg))


def ValidateSingleFile(input_api, output_api, file_obj, cwd, results):
  """Does corresponding validations if histograms.xml or enums.xml is changed.

  Args:
    input_api: An input_api instance that contains information about changes.
    output_api: An output_api instance to create results of the PRESUBMIT check.
    file_obj: A file object of one of the changed files.
    cwd: Path to current working directory.
    results: The returned variable which is a list of output_api results.

  Returns:
    A boolean that True if a histograms.xml or enums.xml file is changed.
  """
  p = file_obj.AbsoluteLocalPath()
  # Only do PRESUBMIT checks when |p| is under |cwd|.
  if input_api.os_path.commonprefix([p, cwd]) != cwd:
    return False
  filepath = input_api.os_path.relpath(p, cwd)

  if 'test_data' in filepath:
    return False

  # If the changed file is histograms.xml or histogram_suffixes_list.xml,
  # pretty-print it.
  elif ('histograms.xml' in filepath
        or 'histogram_suffixes_list.xml' in filepath):
    GetPrettyPrintErrors(input_api, output_api, cwd, filepath, results)
    GetTokenErrors(input_api, output_api, cwd, filepath, results)
    return True

  # If the changed file is enums.xml, pretty-print it.
  elif 'enums.xml' in filepath:
    GetPrettyPrintErrors(input_api, output_api, cwd, filepath, results)
    return True

  return False


def CheckHistogramFormatting(input_api, output_api):
  """Checks that histograms.xml is pretty-printed and well-formatted."""
  results = []
  cwd = input_api.PresubmitLocalPath()
  xml_changed = False

  # Only for changed files, do corresponding checks if the file is
  # histograms.xml or enums.xml.
  for file_obj in input_api.AffectedTextFiles():
    is_changed = ValidateSingleFile(
        input_api, output_api, file_obj, cwd, results)
    xml_changed = xml_changed or is_changed

  # Run validate_format.py and validate_histograms_index.py, if changed files
  # contain histograms.xml or enums.xml.
  if xml_changed:
    GetValidateHistogramsError(input_api, output_api, cwd, results)

  return results


def CheckWebViewHistogramsAllowlistOnUpload(input_api, output_api):
  """Checks that histograms_allowlist.txt contains valid histograms."""
  xml_filter = lambda f: Path(f.LocalPath()).suffix == '.xml'
  xml_files = input_api.AffectedFiles(file_filter=xml_filter)
  if not xml_files:
    return []

  # src_path should point to chromium/src
  src_path = os.path.join(input_api.PresubmitLocalPath(), '..', '..', '..')
  histograms_allowlist_check_path = os.path.join(src_path, 'android_webview',
                                                 'java', 'res', 'raw')
  sys.path.append(histograms_allowlist_check_path)
  from histograms_allowlist_check import CheckWebViewHistogramsAllowlist
  return CheckWebViewHistogramsAllowlist(src_path, output_api)


def CheckBooleansAreEnums(input_api, output_api):
  """Checks that histograms that use Booleans do not use units."""
  results = []
  cwd = input_api.PresubmitLocalPath()
  inclusion_pattern = input_api.re.compile(r'units="[Bb]oolean')
  units_warning = """
  You are using 'units' for a boolean histogram, but you should be using
  'enum' instead."""

  # Only for changed files, do corresponding checks if the file is
  # histograms.xml or enums.xml.
  for affected_file in input_api.AffectedTextFiles():
    filepath = input_api.os_path.relpath(affected_file.AbsoluteLocalPath(), cwd)
    if 'histograms.xml' in filepath:
      for line_number, line in affected_file.ChangedContents():
        if inclusion_pattern.search(line):
          results.append('%s:%s\n\t%s' % (filepath, line_number, line.strip()))

  # If a histograms.xml file was changed, check for units="[Bb]oolean".
  if results:
    return [output_api.PresubmitPromptOrNotify(units_warning, results)]
  return results