chromium/third_party/blink/PRESUBMIT_test.py

#!/usr/bin/env vpython3
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Note: running this test requires installing the package python-mock.
# pylint: disable=C0103
# pylint: disable=F0401
import PRESUBMIT

import os.path
import subprocess
import sys
import unittest

sys.path.append(
    os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))

import mock
from PRESUBMIT_test_mocks import MockInputApi
from PRESUBMIT_test_mocks import MockOutputApi
from PRESUBMIT_test_mocks import MockAffectedFile


class Capture(object):
    """Class to capture a call argument that can be tested later on."""

    def __init__(self):
        self.value = None

    def __eq__(self, other):
        self.value = other
        return True


class PresubmitTest(unittest.TestCase):
    @mock.patch('subprocess.Popen')
    @mock.patch('PRESUBMIT_test_mocks.MockInputApi.RunTests', create=True)
    @mock.patch('PRESUBMIT_test_mocks.MockCannedChecks.GetPylint', create=True)
    def testCheckChangeOnUploadWithBlinkAndChromiumFiles(
            self, _, _run_tests, _get_pylint):
        """This verifies that CheckChangeOnUpload will only call
        check_blink_style.py on non-test files.
        """
        diff_file_blink_h = ['some diff']
        diff_file_chromium_h = ['another diff']
        diff_file_test_expectations = ['more diff']
        mock_input_api = MockInputApi()
        mock_python_file = MockAffectedFile('file_blink.py', ['lint me'])
        mock_input_api.files = [
            MockAffectedFile('file_blink.h', diff_file_blink_h),
            MockAffectedFile('file_chromium.h', diff_file_chromium_h),
            MockAffectedFile(
                mock_input_api.os_path.join('web_tests', 'TestExpectations'),
                diff_file_test_expectations),
            mock_python_file,
        ]
        mock_input_api.presubmit_local_path = mock_input_api.os_path.join(
            'path', 'to', 'third_party', 'blink')
        with mock.patch.object(mock_python_file,
                               'AbsoluteLocalPath',
                               return_value=mock_input_api.os_path.join(
                                   'path', 'to', 'third_party', 'blink',
                                   'file_blink.py')):
            # Access to a protected member _CheckStyle
            # pylint: disable=W0212
            PRESUBMIT._CheckStyle(mock_input_api, MockOutputApi())
            mock_input_api.canned_checks.GetPylint.assert_called_once_with(
                mock.ANY,
                mock.ANY,
                files_to_check=[r'file_blink\.py'],
                pylintrc=mock_input_api.os_path.join('tools', 'blinkpy',
                                                     'pylintrc'))

        capture = Capture()
        # pylint: disable=E1101
        subprocess.Popen.assert_called_with(capture, stderr=-1)
        self.assertEqual(6, len(capture.value))
        self.assertEqual('file_blink.h', capture.value[3])

    @mock.patch('subprocess.Popen')
    def testCheckChangeOnUploadWithEmptyAffectedFileList(self, _):
        """This verifies that CheckChangeOnUpload will skip calling
        check_blink_style.py if the affected file list is empty.
        """
        diff_file_chromium1_h = ['some diff']
        diff_file_chromium2_h = ['another diff']
        diff_file_layout_test_html = ['more diff']
        mock_input_api = MockInputApi()
        mock_input_api.files = []
        # Access to a protected member _CheckStyle
        # pylint: disable=W0212
        PRESUBMIT._CheckStyle(mock_input_api, MockOutputApi())
        self.assertEqual(0, subprocess.Popen.call_count)

    def test_FilterPaths(self):
        """This verifies that _FilterPaths removes expected paths."""
        diff_file_chromium1_h = ['some diff']
        diff_web_tests_html = ['more diff']
        diff_presubmit = ['morer diff']
        diff_test_expectations = ['morest diff']
        mock_input_api = MockInputApi()
        mock_input_api.files = [
            MockAffectedFile('file_chromium1.h', diff_file_chromium1_h),
            MockAffectedFile(
                mock_input_api.os_path.join('web_tests', 'some_tests.html'),
                diff_web_tests_html),
            MockAffectedFile(
                mock_input_api.os_path.join('web_tests', 'TestExpectations'),
                diff_test_expectations),
            # Note that this path must have a slash, whereas most other paths
            # must have os-standard path separators.
            MockAffectedFile('blink/PRESUBMIT', diff_presubmit),
        ]
        # Access to a protected member _FilterPaths
        # pylint: disable=W0212
        filtered = PRESUBMIT._FilterPaths(mock_input_api)
        self.assertEqual(['file_chromium1.h'], filtered)

    def testCheckPublicHeaderWithBlinkMojo(self):
        """This verifies that _CheckForWrongMojomIncludes detects -blink mojo
        headers in public files.
        """

        mock_input_api = MockInputApi()
        potentially_bad_content = \
            '#include "public/platform/modules/cache_storage.mojom-blink.h"'
        mock_input_api.files = [
            MockAffectedFile(
                mock_input_api.os_path.join('third_party', 'blink', 'public',
                                            'a_header.h'),
                [potentially_bad_content], None)
        ]
        # Access to a protected member _CheckForWrongMojomIncludes
        # pylint: disable=W0212
        errors = PRESUBMIT._CheckForWrongMojomIncludes(mock_input_api,
                                                       MockOutputApi())
        self.assertEqual(
            'Public blink headers using Blink variant mojoms found. ' +
            'You must include .mojom-forward.h or .mojom-shared.h instead:',
            errors[0].message)

    def testCheckInternalHeaderWithBlinkMojo(self):
        """This verifies that _CheckForWrongMojomIncludes accepts -blink mojo
        headers in blink internal files.
        """

        mock_input_api = MockInputApi()
        potentially_bad_content = """
        #include "public/platform/modules/cache_storage.mojom-blink.h"
        #include "public/platform/modules/cache_storage.mojom-blink-forward.h"
        #include "public/platform/modules/cache_storage.mojom-blink-test-utils.h"
        """
        mock_input_api.files = [
            MockAffectedFile(
                mock_input_api.os_path.join('third_party', 'blink', 'renderer',
                                            'core', 'a_header.h'),
                [potentially_bad_content], None)
        ]
        # Access to a protected member _CheckForWrongMojomIncludes
        # pylint: disable=W0212
        errors = PRESUBMIT._CheckForWrongMojomIncludes(mock_input_api,
                                                       MockOutputApi())
        self.assertEqual([], errors)


class CxxDependencyTest(unittest.TestCase):
    allow_list = [
        'base::OnceCallback<void()>',
        'base::RepeatingCallback<void()>',
        'gfx::ColorSpace',
        'gfx::CubicBezier',
        'gfx::ICCProfile',
        'gfx::Point',
        'gfx::Rect',
        'scoped_refptr<base::SingleThreadTaskRunner>',
    ]
    disallow_list = [
        'content::RenderFrame',
        'gfx::Canvas',
        'net::IPEndPoint',
        'ui::Clipboard',
    ]
    disallow_message = []

    def runCheck(self, filename, file_contents):
        mock_input_api = MockInputApi()
        mock_input_api.files = [
            MockAffectedFile(filename, file_contents),
        ]
        # Access to a protected member
        # pylint: disable=W0212
        return PRESUBMIT._CheckForForbiddenChromiumCode(
            mock_input_api, MockOutputApi())

    # References in comments should never be checked.
    def testCheckCommentsIgnored(self):
        filename = 'third_party/blink/renderer/core/frame/frame.cc'
        for item in self.allow_list:
            errors = self.runCheck(filename, ['// %s' % item])
            self.assertEqual([], errors)

        for item in self.disallow_list:
            errors = self.runCheck(filename, ['// %s' % item])
            self.assertEqual([], errors)

    # References in Test files should never be checked.
    def testCheckTestsIgnored(self):
        filename = 'third_party/blink/rendere/core/frame/frame_test.cc'
        for item in self.allow_list:
            errors = self.runCheck(filename, ['// %s' % item])
            self.assertEqual([], errors)

        for item in self.disallow_list:
            errors = self.runCheck(filename, ['// %s' % item])
            self.assertEqual([], errors)

    # core, modules, public, et cetera should all have dependency enforcement.
    def testCheckCoreEnforcement(self):
        filename = 'third_party/blink/renderer/core/frame/frame.cc'
        for item in self.allow_list:
            errors = self.runCheck(filename, ['%s' % item])
            self.assertEqual([], errors)

        for item in self.disallow_list:
            errors = self.runCheck(filename, ['%s' % item])
            self.assertEqual(1, len(errors))
            self.assertRegex(errors[0].message,
                             r'^[^:]+:\d+ uses disallowed identifier .+$')

    def testCheckModulesEnforcement(self):
        filename = 'third_party/blink/renderer/modules/modules_initializer.cc'
        for item in self.allow_list:
            errors = self.runCheck(filename, ['%s' % item])
            self.assertEqual([], errors)

        for item in self.disallow_list:
            errors = self.runCheck(filename, ['%s' % item])
            self.assertEqual(1, len(errors))
            self.assertRegex(errors[0].message,
                             r'^[^:]+:\d+ uses disallowed identifier .+$')

    def testCheckPublicEnforcement(self):
        filename = 'third_party/blink/renderer/public/platform/web_thread.h'
        for item in self.allow_list:
            errors = self.runCheck(filename, ['%s' % item])
            self.assertEqual([], errors)

        for item in self.disallow_list:
            errors = self.runCheck(filename, ['%s' % item])
            self.assertEqual(1, len(errors))
            self.assertRegex(errors[0].message,
                             r'^[^:]+:\d+ uses disallowed identifier .+$')

    # platform and controller should be opted out of enforcement, but aren't
    # currently checked because the PRESUBMIT test mocks are missing too
    # much functionality...

    # External module checks should not affect CSS files.
    def testCheckCSSIgnored(self):
        filename = 'third_party/blink/renderer/someFile.css'
        errors = self.runCheck(filename,
                               ['.toolbar::after { color: pink; }\n'])
        self.assertEqual([], errors)


if __name__ == '__main__':
    unittest.main()