chromium/android_webview/tools/system_webview_shell/test/data/webexposed/validate_not_webview_exposed.py

#!/usr/bin/env vpython3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import re

SRC = os.path.abspath(
    os.path.join(os.path.dirname(__file__), *([os.pardir] * 6)))
BLINK_WEB_TESTS = os.path.join(SRC, 'third_party', 'blink', 'web_tests')
BLINK_WEB_EXPOSED_TESTS = os.path.join(BLINK_WEB_TESTS, 'webexposed')
BLINK_INTERFACES_PATH = os.path.join(BLINK_WEB_EXPOSED_TESTS,
                                     'global-interface-listing-expected.txt')
NOT_WEBVIEW_EXPOSED_PATH = os.path.join(os.path.dirname(__file__),
                                        'not-webview-exposed.txt')
NOT_WEBVIEW_EXPOSED_REPO_RELPATH = (
    '//' + os.path.relpath(NOT_WEBVIEW_EXPOSED_PATH, SRC))

# Keywords used to find interfaces
INTERFACE_KEYWORDS = ['interface', '[GLOBAL OBJECT]']
# Keywords to find properties
PROPERTIES_KEYWORDS = ['getter', 'setter', 'method', 'attribute']


def build_interfaces_map(file_obj, interfaces_map=None):
  """Build a dictionary mapping interfaces to their set of properties

  Args:
    file_obj: File object for file containing interfaces and properties.
    interfaces_map: Existing dictionary to merge interfaces and
      properties into.

  Returns:
    A dictionary mapping interfaces to their properties.
  """
  interfaces_map = interfaces_map or {}
  props = None

  for line in file_obj.read().splitlines():
    line = re.sub(r'#.*$', '', line).strip()
    if any(line.startswith(keyword) for keyword in INTERFACE_KEYWORDS):
      props = interfaces_map.setdefault(line, set())
    elif any(line.startswith(keyword) for keyword in PROPERTIES_KEYWORDS):
      assert props is not None, ('Property %r has no parent interface in %s' %
                                 (line, file_obj.name))
      props.add(line)

  return interfaces_map


def construct_error_message(missing_interfaces, missing_properties):
  """Constructs an error message from missing interfaces and properties

  Args:
    missing_interfaces: List of missing interfaces.
    missing_properties: Dictionary mapping missing properties to an interface.

  Returns:
    A string containing an error message describing missing interfaces and
      properties.
  """
  error_msg = ''
  if missing_interfaces:
    error_msg += (
        ('\nThe following interfaces are missing in blink '
         'expectations files.\nIn order to suppress this message '
         'remove them and their properties from\n%s.\n%s') %
        (NOT_WEBVIEW_EXPOSED_REPO_RELPATH, '\n'.join(
            ['\t- %s' % interface for interface in missing_interfaces])))
    error_msg += '\n'

  if missing_properties:
    error_msg += (('\nThe following interface properties are missing in blink '
                   'expectations files.\nIn order to suppress this message '
                   'remove them from \n%s.\n') %
                  NOT_WEBVIEW_EXPOSED_REPO_RELPATH)

    for interface, properties in missing_properties.items():
      error_msg += (
          'Interface %r is missing the following properties\n%s\n' %
          (interface, '\n'.join(['\t- %s' % prop for prop in properties])))

    error_msg += '\n'

  return error_msg


def main():
  missing_blink_interfaces = []
  missing_blink_properties = {}
  error_msg = ''

  with open(NOT_WEBVIEW_EXPOSED_PATH) as excluded_interfaces_obj, \
      open(BLINK_INTERFACES_PATH) as blink_interfaces_obj:

    excluded_interfaces = build_interfaces_map(excluded_interfaces_obj)
    blink_interfaces = build_interfaces_map(blink_interfaces_obj)

    # Check that all not WebView exposed interfaces and properties are in the
    # set of blink interfaces and properties.
    for interface in excluded_interfaces:
      if interface not in blink_interfaces:
        missing_blink_interfaces.append(interface)
      else:
        non_blink_props = (excluded_interfaces.get(interface, set()) -
                           blink_interfaces.get(interface, set()))
        if non_blink_props:
          missing_blink_properties.update({interface: non_blink_props})

    error_msg = construct_error_message(missing_blink_interfaces,
                                        missing_blink_properties)
    assert not (missing_blink_interfaces or missing_blink_properties), error_msg


if __name__ == '__main__':
  main()