chromium/third_party/blink/tools/blinkpy/w3c/export_notifier_unittest.py

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

from blinkpy.common.host_mock import MockHost
from blinkpy.common.system.log_testing import LoggingTestCase
from blinkpy.w3c.gerrit_mock import MockGerritAPI, MockGerritCL
from blinkpy.w3c.export_notifier import ExportNotifier, PRStatusInfo
from blinkpy.w3c.wpt_github import PullRequest
from blinkpy.w3c.wpt_github_mock import MockWPTGitHub


class ExportNotifierTest(LoggingTestCase):
    def setUp(self):
        super(ExportNotifierTest, self).setUp()
        self.host = MockHost()
        self.git = self.host.git()
        self.gerrit = MockGerritAPI()
        self.notifier = ExportNotifier(self.host, self.git, self.gerrit)

    def test_get_gerrit_sha_from_comment_success(self):
        gerrit_comment = self.generate_notifier_comment(123, {}, 'SHA', None)

        actual = PRStatusInfo.get_gerrit_sha_from_comment(gerrit_comment)

        self.assertEqual(actual, 'SHA')

    def test_get_gerrit_sha_from_comment_fail(self):
        gerrit_comment = 'ABC'

        actual = PRStatusInfo.get_gerrit_sha_from_comment(gerrit_comment)

        self.assertIsNone(actual)

    def test_to_gerrit_comment(self):
        checks_results = {'key1': 'val1', 'key2': 'val2'}
        pr_status_info = PRStatusInfo(checks_results, 123, 'SHA')
        expected = self.generate_notifier_comment(123, checks_results, 'SHA',
                                                  None)

        actual = pr_status_info.to_gerrit_comment()

        self.assertEqual(expected, actual)

    def test_to_gerrit_comment_latest(self):
        checks_results = {'key1': 'val1', 'key2': 'val2'}
        pr_status_info = PRStatusInfo(checks_results, 123, None)
        expected = self.generate_notifier_comment(123, checks_results,
                                                  'Latest', None)

        actual = pr_status_info.to_gerrit_comment()

        self.assertEqual(expected, actual)

    def test_to_gerrit_comment_with_patchset(self):
        checks_results = {'key1': 'val1', 'key2': 'val2'}
        pr_status_info = PRStatusInfo(checks_results, 123, 'SHA')
        expected = self.generate_notifier_comment(123, checks_results, 'SHA',
                                                  3)

        actual = pr_status_info.to_gerrit_comment(3)

        self.assertEqual(expected, actual)

    def test_get_relevant_failed_taskcluster_checks_success(self):
        check_runs = [
            {
                "id": "1",
                "conclusion": "failure",
                "name": "wpt-chrome-dev-stability"
            },
            {
                "id": "2",
                "conclusion": "failure",
                "name": "wpt-firefox-nightly-stability"
            },
            {
                "id": "3",
                "conclusion": "failure",
                "name": "lint"
            },
            {
                "id": "4",
                "conclusion": "failure",
                "name": "infrastructure/ tests"
            },
        ]
        expected = {
            'wpt-chrome-dev-stability':
            'https://github.com/web-platform-tests/wpt/runs/1',
            'wpt-firefox-nightly-stability':
            'https://github.com/web-platform-tests/wpt/runs/2',
            'lint':
            'https://github.com/web-platform-tests/wpt/runs/3',
            'infrastructure/ tests':
            'https://github.com/web-platform-tests/wpt/runs/4',
        }

        self.assertEqual(
            self.notifier.get_relevant_failed_taskcluster_checks(
                check_runs), expected)

    def test_get_relevant_failed_taskcluster_checks_empty(self):
        check_runs = [
            {
                "id": "1",
                "conclusion": "success",
                "name": "wpt-chrome-dev-stability"
            },
            {
                "id": "2",
                "conclusion": "failure",
                "name": "infra"
            },
        ]

        self.assertEqual(
            self.notifier.get_relevant_failed_taskcluster_checks(
                check_runs), {})

    def test_has_latest_taskcluster_status_commented_false(self):
        pr_status_info = PRStatusInfo('bar', 123, 'SHA')
        messages = [{
            "date": "2019-08-20 17:42:05.000000000",
            "message": "Uploaded patch set 1.\nInitial upload",
            "_revision_number": 1
        }]

        actual = self.notifier.has_latest_taskcluster_status_commented(
            messages, pr_status_info)

        self.assertFalse(actual)

    def test_has_latest_taskcluster_status_commented_true(self):
        pr_status_info = PRStatusInfo({'a': 'b'}, 123, 'SHA')
        messages = [
            {
                "date": "2019-08-20 17:42:05.000000000",
                "message": "Uploaded patch set 1.\nInitial upload",
                "_revision_number": 1
            },
            {
                "date": "2019-08-21 17:41:05.000000000",
                "message": self.generate_notifier_comment(123, {}, 'SHA', 3),
                "_revision_number": 2
            },
        ]

        actual = self.notifier.has_latest_taskcluster_status_commented(
            messages, pr_status_info)

        self.assertTrue(actual)

    def test_get_check_runs_success(self):
        self.notifier.wpt_github = MockWPTGitHub(pull_requests=[])
        self.notifier.wpt_github.check_runs = [
            {
                "id": "123",
                "conclusion": "failure",
                "name": "wpt-chrome-dev-stability"
            },
            {
                "id": "456",
                "conclusion": "success",
                "name": "firefox"
            },
        ]
        actual = self.notifier.get_check_runs(123)

        self.assertEqual(len(actual), 2)
        self.assertEqual(self.notifier.wpt_github.calls, [
            'get_pr_branch',
            'get_branch_check_runs',
        ])

    def test_process_failing_prs_success(self):
        checks_results = {'key1': 'val1', 'key2': 'val2'}
        self.notifier.dry_run = False
        self.notifier.gerrit = MockGerritAPI()
        self.notifier.gerrit.cl = MockGerritCL(data={
            'change_id':
            'abc',
            'messages': [
                {
                    "date": "2019-08-20 17:42:05.000000000",
                    "message": "Uploaded patch set 1.\nInitial upload",
                    "_revision_number": 1
                },
                {
                    "date": "2019-08-21 17:41:05.000000000",
                    "message":
                    self.generate_notifier_comment(123, {}, 'notnum', 3),
                    "_revision_number": 2
                },
            ],
            'revisions': {
                'SHA': {
                    '_number': 1
                }
            }
        }, api=self.notifier.gerrit)
        gerrit_dict = {'abc': PRStatusInfo(checks_results, 123, 'SHA')}
        expected = self.generate_notifier_comment(123, checks_results, 'SHA',
                                                  1)

        self.notifier.process_failing_prs(gerrit_dict)

        self.assertEqual(self.notifier.gerrit.cls_queried, ['abc'])
        self.assertEqual(
            self.notifier.gerrit.request_posted,
            [('/a/changes/chromium%2Fsrc~main~abc/revisions/current/review', {
                'message': expected
            })])

    def test_process_failing_prs_has_commented(self):
        self.notifier.dry_run = False
        self.notifier.gerrit = MockGerritAPI()
        self.notifier.gerrit.cl = MockGerritCL(data={
            'change_id':
            'abc',
            'messages': [
                {
                    "date": "2019-08-20 17:42:05.000000000",
                    "message": "Uploaded patch set 1.\nInitial upload",
                    "_revision_number": 1
                },
                {
                    "date": "2019-08-21 17:41:05.000000000",
                    "message":
                    self.generate_notifier_comment(123, {}, 'SHA', 3),
                    "_revision_number": 2
                },
            ],
            'revisions': {
                'SHA': {
                    '_number': 1
                }
            }
        }, api=self.notifier.gerrit)
        gerrit_dict = {'abc': PRStatusInfo('bar', 123, 'SHA')}

        self.notifier.process_failing_prs(gerrit_dict)

        self.assertEqual(self.notifier.gerrit.cls_queried, ['abc'])
        self.assertEqual(self.notifier.gerrit.request_posted, [])

    def test_process_failing_prs_with_latest_sha(self):
        self.notifier.dry_run = False
        self.notifier.gerrit = MockGerritAPI()
        self.notifier.gerrit.cl = MockGerritCL(data={
            'change_id':
            'abc',
            'messages': [
                {
                    "date": "2019-08-20 17:42:05.000000000",
                    "message": "Uploaded patch set 1.\nInitial upload",
                    "_revision_number": 1
                },
                {
                    "date": "2019-08-21 17:41:05.000000000",
                    "message":
                    self.generate_notifier_comment(123, {}, 'notnum', 3),
                    "_revision_number": 2
                },
            ],
            'revisions': {
                'SHA': {
                    '_number': 1
                }
            }
        }, api=self.notifier.gerrit)
        checks_results = {'key1': 'val1', 'key2': 'val2'}
        expected = self.generate_notifier_comment(123, checks_results,
                                                  'Latest')
        gerrit_dict = {'abc': PRStatusInfo(checks_results, 123, None)}

        self.notifier.process_failing_prs(gerrit_dict)

        self.assertEqual(self.notifier.gerrit.cls_queried, ['abc'])
        self.assertEqual(
            self.notifier.gerrit.request_posted,
            [('/a/changes/chromium%2Fsrc~main~abc/revisions/current/review', {
                'message': expected
            })])

    def test_process_failing_prs_raise_gerrit_error(self):
        self.notifier.dry_run = False
        self.notifier.gerrit = MockGerritAPI(raise_error=True)
        gerrit_dict = {'abc': PRStatusInfo('bar', 'SHA')}

        self.notifier.process_failing_prs(gerrit_dict)

        self.assertEqual(self.notifier.gerrit.cls_queried, ['abc'])
        self.assertEqual(self.notifier.gerrit.request_posted, [])
        self.assertLog([
            'INFO: Processing 1 CLs with failed Taskcluster checks.\n',
            'INFO: Change-Id: abc\n',
            'ERROR: Could not process Gerrit CL abc: Error from query_cl\n',
        ])

    def test_export_notifier_success(self):
        self.notifier.wpt_github = MockWPTGitHub(pull_requests=[])
        self.notifier.wpt_github.recent_failing_pull_requests = [
            PullRequest(
                title='title1',
                number=1234,
                body=
                'description\nWPT-Export-Revision: hash\nChange-Id: decafbad',
                state='open',
                node_id='PR_1',
                labels=[''])
        ]
        self.notifier.wpt_github.check_runs = [
            {
                "id": "123",
                "conclusion": "failure",
                "name": "wpt-chrome-dev-stability"
            },
            {
                "id": "456",
                "conclusion": "success",
                "name": "firefox"
            },
        ]
        checks_results = {
            'wpt-chrome-dev-stability':
            'https://github.com/web-platform-tests/wpt/runs/123'
        }

        self.notifier.dry_run = False
        self.notifier.gerrit = MockGerritAPI()
        self.notifier.gerrit.cl = MockGerritCL(data={
            'change_id':
            'decafbad',
            'messages': [
                {
                    "date": "2019-08-20 17:42:05.000000000",
                    "message": "Uploaded patch set 1.\nInitial upload",
                    "_revision_number": 1
                },
                {
                    "date":
                    "2019-08-21 17:41:05.000000000",
                    "message":
                    self.generate_notifier_comment(1234, {}, 'notnum', 3),
                    "_revision_number":
                    2
                },
            ],
            'revisions': {
                'hash': {
                    '_number': 2
                }
            }
        }, api=self.notifier.gerrit)
        expected = self.generate_notifier_comment(1234, checks_results, 'hash',
                                                  2)

        pr_by_change_id = self.notifier.main()
        self.assertEqual(set(pr_by_change_id), {'decafbad'})
        self.assertEqual(pr_by_change_id['decafbad'].pr_number, 1234)
        self.assertEqual(self.notifier.wpt_github.calls, [
            'recent_failing_chromium_exports',
            'get_pr_branch',
            'get_branch_check_runs',
        ])
        self.assertEqual(self.notifier.gerrit.cls_queried, ['decafbad'])
        self.assertEqual(self.notifier.gerrit.request_posted, [(
            '/a/changes/chromium%2Fsrc~main~decafbad/revisions/current/review',
            {
                'message': expected
            })])

    def generate_notifier_comment(self,
                                  pr_number,
                                  checks_results,
                                  sha,
                                  patchset=None):
        checks_results_comment = ''
        for check, url in checks_results.items():
            checks_results_comment += '\n%s (%s)' % (check, url)

        if patchset:
            comment = (
                'The exported PR, https://github.com/web-platform-tests/wpt/pull/{}, '
                'has failed the following check(s) on GitHub:\n{}'
                '\n\nThese failures will block the export. '
                'They may represent new or existing problems; please take '
                'a look at the output and see if it can be fixed. '
                'Unresolved failures will be looked at by the Ecosystem-Infra '
                'sheriff after this CL has been landed in Chromium; if you '
                'need earlier help please contact [email protected].\n\n'
                'Any suggestions to improve this service are welcome; '
                'crbug.com/1027618.\n\n'
                'Gerrit CL SHA: {}\n'
                'Patchset Number: {}').format(pr_number,
                                              checks_results_comment, sha,
                                              patchset)
        else:
            comment = (
                'The exported PR, https://github.com/web-platform-tests/wpt/pull/{}, '
                'has failed the following check(s) on GitHub:\n{}'
                '\n\nThese failures will block the export. '
                'They may represent new or existing problems; please take '
                'a look at the output and see if it can be fixed. '
                'Unresolved failures will be looked at by the Ecosystem-Infra '
                'sheriff after this CL has been landed in Chromium; if you '
                'need earlier help please contact [email protected].\n\n'
                'Any suggestions to improve this service are welcome; '
                'crbug.com/1027618.\n\n'
                'Gerrit CL SHA: {}').format(pr_number, checks_results_comment,
                                            sha)
        return comment