chromium/android_webview/javatests/PRESUBMIT.py

# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Presubmit tests for android_webview/javatests/

Runs various style checks before upload.
"""

def CheckChangeOnUpload(input_api, output_api):
  results = []
  results.extend(_CheckAwJUnitTestRunner(input_api, output_api))
  results.extend(_CheckNoSkipCommandLineAnnotation(input_api, output_api))
  results.extend(_CheckNoSandboxedRendererSwitch(input_api, output_api))
  results.extend(_CheckNoDomUtils(input_api, output_api))
  return results


def _CheckAwJUnitTestRunner(input_api, output_api):
  """Checks that new tests use the AwJUnit4ClassRunner instead of some other
  test runner. This is because WebView has special logic in the
  AwJUnit4ClassRunner.
  """

  run_with_pattern = input_api.re.compile(
      r'^@RunWith\((.*)\)$')
  aw_runner = 'AwJUnit4ClassRunner.class'
  parameterized_runner = 'Parameterized.class'
  runners_factory_pattern = input_api.re.compile(
      r'^@UseParametersRunnerFactory\((.*)\)$')
  correct_factory = 'AwJUnit4ClassRunnerWithParameters.Factory.class'

  errors = []

  def _FilterFile(affected_file):
    return input_api.FilterSourceFile(
        affected_file,
        files_to_skip=input_api.DEFAULT_FILES_TO_SKIP,
        files_to_check=[r'.*Test\.java$'])

  for f in input_api.AffectedSourceFiles(_FilterFile):
    run_with_matches = []
    prev_line_standard_runner = False
    prev_line_parameterized_runner = False
    for line in f.NewContents():
      if prev_line_parameterized_runner:
        prev_line_parameterized_runner = False
        match = runners_factory_pattern.search(line)
        if match:
          if match.group(1) != correct_factory:
            errors.append("%s - %s" % (f.LocalPath(), line))
        else:
          # Note: we require specific order and adjacent lines here
          # to simplify the check. This is not required by Java.
          errors.append("%s - %s" % (
            f.LocalPath(),
            "Missing @UseParametersRunnerFactory annotation"))
      elif prev_line_standard_runner:
        prev_line_standard_runner = False
        match = runners_factory_pattern.search(line)
        if match:
          errors.append("%s - %s" % (
            f.LocalPath(),
            "@UseParametersRunnerFactory annotation present but runner is not"
            " Parameterized"))
      else:
        match = run_with_pattern.search(line)
        if match:
          run_with_matches.append(line)
          if match.group(1) == parameterized_runner:
            prev_line_parameterized_runner = True
          elif match.group(1) == aw_runner:
            prev_line_standard_runner = True
          else:
            errors.append("%s - %s" % (f.LocalPath(), line))
    if not run_with_matches:
      errors.append("%s - %s" % (f.LocalPath(), "Missing @RunWith annotation"))

  results = []

  if errors:
    results.append(output_api.PresubmitPromptWarning("""
android_webview/javatests/ should use either
@RunWith(AwJUnit4ClassRunner.class),
or @RunWith(Parameterized.class) together with
@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
- in that order and on adjacent lines (this is to simplify the check) -
not any other test runner (e.g., BaseJUnit4ClassRunner). We assume this is
supposed to be a test class because the filename ends with 'Test.java' (if this
is actually a helper/utility class, please rename).
""", errors))

  return results


def _CheckNoSkipCommandLineAnnotation(input_api, output_api):
  """Checks that tests do not add @SkipCommandLineParameterization annotation.
  This was previously used to run the test in single-process-mode only (or,
  multi-process-mode only if used with
  @CommandLineFlags.Add(AwSwitches.WEBVIEW_SANDBOXED_RENDERER)). This is
  obsolete because we have dedicated annotations (@OnlyRunInSingleProcessMode
  and @OnlyRunInMultiProcessMode).
  """

  skip_command_line_annotation = input_api.re.compile(
      r'^\s*@SkipCommandLineParameterization.*$')

  errors = []

  def _FilterFile(affected_file):
    return input_api.FilterSourceFile(
        affected_file,
        files_to_skip=input_api.DEFAULT_FILES_TO_SKIP,
        files_to_check=[r'.*\.java$'])

  for f in input_api.AffectedSourceFiles(_FilterFile):
    for line_num, line in f.ChangedContents():
      match = skip_command_line_annotation.search(line)
      if match:
        errors.append("%s:%d" % (f.LocalPath(), line_num))

  results = []

  if errors:
    results.append(output_api.PresubmitPromptWarning("""
android_webview/javatests/ should not use @SkipCommandLineParameterization to
run in either multi-process or single-process only. Instead, use @OnlyRunIn.
""", errors))

  return results


def _CheckNoSandboxedRendererSwitch(input_api, output_api):
  """Checks that tests do not add the AwSwitches.WEBVIEW_SANDBOXED_RENDERER
  command line flag. Tests should instead use @OnlyRunIn(MULTI_PROCESS).
  """

  # This will not catch multi-line annotations (which are valid if adding
  # multiple switches), but is better than nothing (and avoids false positives).
  sandboxed_renderer_pattern = input_api.re.compile(
      r'^\s*@CommandLineFlags\.Add\(.*'
      r'\bAwSwitches\.WEBVIEW_SANDBOXED_RENDERER\b.*\)$')

  errors = []

  def _FilterFile(affected_file):
    return input_api.FilterSourceFile(
        affected_file,
        files_to_skip=input_api.DEFAULT_FILES_TO_SKIP,
        files_to_check=[r'.*\.java$'])

  for f in input_api.AffectedSourceFiles(_FilterFile):
    for line_num, line in f.ChangedContents():
      match = sandboxed_renderer_pattern.search(line)
      if match:
        errors.append("%s:%d" % (f.LocalPath(), line_num))

  results = []

  if errors:
    results.append(output_api.PresubmitPromptWarning("""
android_webview/javatests/ should not use AwSwitches.WEBVIEW_SANDBOXED_RENDERER
to run in multi-process only. Instead, use @OnlyRunIn(MULTI_PROCESS).
""", errors))

  return results


def _CheckNoDomUtils(input_api, output_api):
  """Checks that tests prefer JSUtils.clickNodeWithUserGesture() over
  DOMUtils.clickNode().
  """

  dom_utils_pattern = input_api.re.compile(r'DOMUtils\.clickNode\(')
  errors = []
  def _FilterFile(affected_file):
    return input_api.FilterSourceFile(
        affected_file,
        files_to_skip=input_api.DEFAULT_FILES_TO_SKIP,
        files_to_check=[r'.*\.java$'])

  for f in input_api.AffectedSourceFiles(_FilterFile):
    for line_num, line in f.ChangedContents():
      m = dom_utils_pattern.search(line)
      if m:
        errors.append("%s:%d" % (f.LocalPath(), line_num))

  results = []
  if errors:
    results.append(output_api.PresubmitPromptWarning("""
DOMUtils.clickNode() has been observed to cause flakiness in WebView tests.
Prefer using JSUtils.clickNodeWithUserGesture() as a more reliable replacement
where possible. This is a "soft" warning, so you can bypass this if
DOMUtils.clickNode() is the only way.
""", errors))

  return results