Commit 1a028f30 authored by Kyle Ju's avatar Kyle Ju Committed by Commit Bot

Surface cross-browsers failures from a chromium export to its corresponding CL.

Design doc:
https://docs.google.com/document/d/1MtdbUcWBDZyvmV0FOdsTWw_Jv16YtE6KW5BnnCVYX4c/edit#heading=h.7nki9mck5t64

Running Command:
python third_party/blink/tools/wpt_export.py --dry-run --credentials <your credentials> --surface-failures-to-gerrit

Tested on a mock CL
https://chromium-review.googlesource.com/c/chromium/src/+/1784803

Change-Id: I368d6fc5b0f7f3e5364fca1fe26cef3514585f96
Bug: 996383
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1749720
Commit-Queue: Kyle Ju <kyleju@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#715167}
parent 0c1376c3
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Sends notifications after automatic exports.
Automatically comments on a Gerrit CL when its corresponding PR fails the Taskcluster check. In
other words, surfaces cross-browser WPT regressions from Github to Gerrit.
Design doc: https://docs.google.com/document/d/1MtdbUcWBDZyvmV0FOdsTWw_Jv16YtE6KW5BnnCVYX4c
"""
import logging
from blinkpy.w3c.common import WPT_REVISION_FOOTER
from blinkpy.w3c.gerrit import GerritError
from blinkpy.w3c.wpt_github import GitHubError
_log = logging.getLogger(__name__)
class ExportNotifier(object):
def __init__(self, host, wpt_github, gerrit, dry_run=True):
self.host = host
self.wpt_github = wpt_github
self.gerrit = gerrit
self.dry_run = dry_run
def main(self):
"""Surfaces stability check failures to Gerrit through comments."""
gerrit_dict = {}
try:
_log.info('Searching for recent failiing chromium exports.')
prs = self.wpt_github.recent_failing_chromium_exports()
except GitHubError as e:
_log.info(
'Surfacing Taskcluster failures cannot be completed due to the following error:')
_log.error(str(e))
return True
if len(prs) > 100:
_log.error(
'Too many open failing PRs: %s; abort.', len(prs))
return True
_log.info('Found %d failing PRs.', len(prs))
for pr in prs:
statuses = self.get_taskcluster_statuses(pr.number)
if not statuses:
continue
taskcluster_status = self.get_failure_taskcluster_status(
statuses, pr.number)
if not taskcluster_status:
continue
gerrit_id = self.wpt_github.extract_metadata(
'Change-Id: ', pr.body)
if not gerrit_id:
_log.error(
'Can not retrieve Change-Id for %s.', pr.number)
continue
gerrit_sha = self.wpt_github.extract_metadata(
WPT_REVISION_FOOTER + ' ', pr.body)
gerrit_dict[gerrit_id] = PRStatusInfo(
taskcluster_status['node_id'],
taskcluster_status['target_url'],
gerrit_sha)
self.process_failing_prs(gerrit_dict)
return False
def get_taskcluster_statuses(self, number):
"""Retrieves Taskcluster status through PR number.
Returns:
A JSON object representing all Taskcluster statuses for this PR.
"""
try:
branch = self.wpt_github.get_pr_branch(number)
statuses = self.wpt_github.get_branch_statuses(branch)
except GitHubError as e:
_log.error(str(e))
return None
return statuses
def process_failing_prs(self, gerrit_dict):
"""Processes and comments on CLs with failed TackCluster status."""
_log.info('Processing %d CLs with failed Taskcluster status.',
len(gerrit_dict))
for change_id, pr_status_info in gerrit_dict.items():
try:
cl = self.gerrit.query_cl_comments_and_revisions(change_id)
has_commented = self.has_latest_taskcluster_status_commented(
cl.messages, pr_status_info)
if has_commented:
continue
revision = cl.revisions.get(pr_status_info.gerrit_sha)
if revision:
cl_comment = pr_status_info.to_gerrit_comment(
revision['_number'])
else:
cl_comment = pr_status_info.to_gerrit_comment()
if self.dry_run:
_log.info(
'[dry_run] Would have commented on CL %s\n', change_id)
_log.debug(
'Comments are:\n %s\n', cl_comment)
else:
_log.info('Commenting on CL %s\n', change_id)
cl.post_comment(cl_comment)
except GerritError as e:
_log.error(
'Could not process Gerrit CL %s: %s', change_id, str(e))
continue
def has_latest_taskcluster_status_commented(self, messages, pr_status_info):
"""Determines if the Taskcluster status has already been commented on the messages of a CL.
Args:
messages: messagese of a CL in JSON Array format, in chronological order.
pr_status_info: PRStatusInfo object.
"""
for message in reversed(messages):
existing_status = PRStatusInfo.from_gerrit_comment(
message['message'])
if existing_status:
return existing_status.node_id == pr_status_info.node_id
return False
def get_failure_taskcluster_status(self, taskcluster_status, pr_number):
"""Parses Taskcluster status from Taskcluster statuses description field.
Args:
taskcluster_status: array; is of following format:
[
{
"url": "https://b",
"avatar_url": "https://a",
"id": 1,
"node_id": "A",
"state": "failure",
"description": "TaskGroup: failure",
"target_url": "https://tools.taskcluster.net/task-group-inspector/#/abc",
"context": "Community-TC (pull_request)",
"created_at": "2019-08-05T22:52:08Z",
"updated_at": "2019-08-05T22:52:08Z"
}
]
e.g. https://api.github.com/repos/web-platform-tests/wpt/commits/chromium-export-cl-1407433/status
pr_number: The PR number.
Returns:
Taskcluster status dictionary if it has a failure status; None otherwise.
"""
status = None
for status_dict in taskcluster_status:
if status_dict['context'] == 'Community-TC (pull_request)':
status = status_dict
break
if status and status['state'] == 'failure':
return status
if status is None:
return None
assert status['state'] == 'success'
_log.debug('Taskcluster status for PR %s is %s', pr_number, status)
return None
class PRStatusInfo(object):
NODE_ID_TAG = 'Taskcluster Node ID: '
LINK_TAG = 'Taskcluster Link: '
CL_SHA_TAG = 'Gerrit CL SHA: '
PATCHSET_TAG = 'Patchset Number: '
def __init__(self, node_id, link, gerrit_sha=None):
self._node_id = node_id
self._link = link
if gerrit_sha:
self._gerrit_sha = gerrit_sha
else:
self._gerrit_sha = 'Latest'
@property
def node_id(self):
return self._node_id
@property
def link(self):
return self._link
@property
def gerrit_sha(self):
return self._gerrit_sha
@staticmethod
def from_gerrit_comment(comment):
tags = [PRStatusInfo.NODE_ID_TAG,
PRStatusInfo.LINK_TAG,
PRStatusInfo.CL_SHA_TAG]
values = ['', '', '']
for line in comment.splitlines():
for index, tag in enumerate(tags):
if line.startswith(tag):
values[index] = line[len(tag):]
for val in values:
if not val:
return None
return PRStatusInfo(*values)
def to_gerrit_comment(self, patchset=None):
status_line = ('The exported PR for the current patch failed Taskcluster check(s) '
'on GitHub, which could indict cross-broswer failures on the '
'exportable changes. Please contact ecosystem-infra@ team for '
'more information.')
node_id_line = ('\n\n{}{}').format(
PRStatusInfo.NODE_ID_TAG, self.node_id)
link_line = ('\n{}{}').format(PRStatusInfo.LINK_TAG, self.link)
sha_line = ('\n{}{}').format(PRStatusInfo.CL_SHA_TAG, self.gerrit_sha)
comment = status_line + node_id_line + link_line + sha_line
if patchset is not None:
comment += ('\n{}{}').format(PRStatusInfo.PATCHSET_TAG, patchset)
return comment
......@@ -58,9 +58,13 @@ class GerritAPI(object):
}
return self.host.web.request('POST', url, data=json.dumps(data), headers=headers)
def query_cl(self, change_id):
"""Quries a commit information from Gerrit."""
path = '/changes/chromium%2Fsrc~master~{}?{}'.format(change_id, QUERY_OPTIONS)
def query_cl_comments_and_revisions(self, change_id):
"""Queries a CL with comments and revisions information."""
return self.query_cl(change_id, 'o=MESSAGES&o=ALL_REVISIONS')
def query_cl(self, change_id, query_options=QUERY_OPTIONS):
"""Queries a commit information from Gerrit."""
path = '/changes/chromium%2Fsrc~master~{}?{}'.format(change_id, query_options)
try:
cl_data = self.get(path)
except NetworkTimeout:
......@@ -134,6 +138,14 @@ class GerritCL(object):
def status(self):
return self._data['status']
@property
def messages(self):
return self._data['messages']
@property
def revisions(self):
return self._data['revisions']
def post_comment(self, message):
"""Posts a comment to the CL."""
path = '/a/changes/{change_id}/revisions/current/review'.format(
......
......@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from blinkpy.w3c.gerrit import GerritCL, GerritError
from blinkpy.w3c.gerrit import GerritCL, GerritError, QUERY_OPTIONS
# Some unused arguments may be included to match the real class's API.
# pylint: disable=unused-argument
......@@ -20,7 +20,10 @@ class MockGerritAPI(object):
def query_exportable_open_cls(self):
return self.exportable_open_cls
def query_cl(self, change_id):
def query_cl_comments_and_revisions(self, change_id):
return self.query_cl(change_id, 'o=MESSAGES&o=ALL_REVISIONS')
def query_cl(self, change_id, query_options=QUERY_OPTIONS):
self.cls_queried.append(change_id)
if self.raise_error:
raise GerritError("Error from query_cl")
......
......@@ -19,6 +19,7 @@ from blinkpy.w3c.common import (
)
from blinkpy.w3c.gerrit import GerritAPI, GerritCL, GerritError
from blinkpy.w3c.wpt_github import WPTGitHub, MergeError
from blinkpy.w3c.export_notifier import ExportNotifier
_log = logging.getLogger(__name__)
......@@ -84,7 +85,19 @@ class TestExporter(object):
for error in git_errors:
_log.error(error)
return not (gerrit_error or git_errors)
export_error = gerrit_error or git_errors
if export_error:
return not export_error
_log.info('Automatic export process has finished successfully.')
export_notifier_failure = False
if options.surface_failures_to_gerrit:
_log.info('Starting surfacing cross-browser failures to Gerrit.')
export_notifier_failure = ExportNotifier(
self.host, self.wpt_github, self.gerrit, self.dry_run).main()
return not export_notifier_failure
def parse_args(self, argv):
parser = argparse.ArgumentParser(description=__doc__)
......@@ -99,6 +112,10 @@ class TestExporter(object):
'--credentials-json', required=True,
help='A JSON file with an object containing zero or more of the '
'following keys: GH_USER, GH_TOKEN, GERRIT_USER, GERRIT_TOKEN')
parser.add_argument(
'--surface-failures-to-gerrit', action='store_true',
help='Indicates whether to run the service that surfaces GitHub '
'faliures to Gerrit through comments.')
return parser.parse_args(argv)
def process_gerrit_cls(self, gerrit_cls):
......
......@@ -32,7 +32,8 @@ class TestExporterTest(LoggingTestCase):
def test_dry_run_stops_before_creating_pr(self):
test_exporter = TestExporter(self.host)
test_exporter.wpt_github = MockWPTGitHub(pull_requests=[
PullRequest(title='title1', number=1234, body='', state='open', labels=[]),
PullRequest(title='title1', number=1234,
body='', state='open', labels=[]),
])
test_exporter.gerrit = MockGerritAPI()
test_exporter.gerrit.exportable_open_cls = [MockGerritCL(
......@@ -52,10 +53,13 @@ class TestExporterTest(LoggingTestCase):
body='fake body', change_id='I001')
)]
test_exporter.get_exportable_commits = lambda: ([
MockChromiumCommit(self.host, position='refs/heads/master@{#458475}'),
MockChromiumCommit(self.host, position='refs/heads/master@{#458476}'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458475}'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458476}'),
], [])
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json', '--dry-run'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json', '--dry-run'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [
......@@ -67,14 +71,19 @@ class TestExporterTest(LoggingTestCase):
def test_creates_pull_request_for_all_exportable_commits(self):
test_exporter = TestExporter(self.host)
test_exporter.wpt_github = MockWPTGitHub(pull_requests=[], create_pr_fail_index=1)
test_exporter.wpt_github = MockWPTGitHub(
pull_requests=[], create_pr_fail_index=1)
test_exporter.gerrit = MockGerritAPI()
test_exporter.get_exportable_commits = lambda: ([
MockChromiumCommit(self.host, position='refs/heads/master@{#1}', change_id='I001', subject='subject 1', body='body 1'),
MockChromiumCommit(self.host, position='refs/heads/master@{#2}', change_id='I002', subject='subject 2', body='body 2'),
MockChromiumCommit(self.host, position='refs/heads/master@{#3}', change_id='I003', subject='subject 3', body='body 3'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#1}', change_id='I001', subject='subject 1', body='body 1'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#2}', change_id='I002', subject='subject 2', body='body 2'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#3}', change_id='I003', subject='subject 3', body='body 3'),
], [])
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [
......@@ -92,8 +101,10 @@ class TestExporterTest(LoggingTestCase):
'add_label "chromium-export"',
])
self.assertEqual(test_exporter.wpt_github.pull_requests_created, [
('chromium-export-96862edfc1', 'subject 1', 'body 1\n\nChange-Id: I001\n'),
('chromium-export-ce0e78bf18', 'subject 3', 'body 3\n\nChange-Id: I003\n'),
('chromium-export-96862edfc1', 'subject 1',
'body 1\n\nChange-Id: I001\n'),
('chromium-export-ce0e78bf18', 'subject 3',
'body 3\n\nChange-Id: I003\n'),
])
def test_creates_and_merges_pull_requests(self):
......@@ -136,13 +147,19 @@ class TestExporterTest(LoggingTestCase):
], unsuccessful_merge_index=3) # Mark the last PR as unmergable.
test_exporter.gerrit = MockGerritAPI()
test_exporter.get_exportable_commits = lambda: ([
MockChromiumCommit(self.host, position='refs/heads/master@{#458475}', change_id='I0005'),
MockChromiumCommit(self.host, position='refs/heads/master@{#458476}', change_id='I0476'),
MockChromiumCommit(self.host, position='refs/heads/master@{#458477}', change_id='Idead'),
MockChromiumCommit(self.host, position='refs/heads/master@{#458478}', change_id='I0118'),
MockChromiumCommit(self.host, position='refs/heads/master@{#458479}', change_id='I0147'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458475}', change_id='I0005'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458476}', change_id='I0476'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458477}', change_id='Idead'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458478}', change_id='I0118'),
MockChromiumCommit(
self.host, position='refs/heads/master@{#458479}', change_id='I0147'),
], [])
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [
......@@ -171,7 +188,8 @@ class TestExporterTest(LoggingTestCase):
'merge_pr',
])
self.assertEqual(test_exporter.wpt_github.pull_requests_created, [
('chromium-export-52c3178508', 'Fake subject', 'Fake body\n\nChange-Id: I0476\n'),
('chromium-export-52c3178508', 'Fake subject',
'Fake body\n\nChange-Id: I0476\n'),
])
self.assertEqual(test_exporter.wpt_github.pull_requests_merged, [3456])
......@@ -236,7 +254,8 @@ class TestExporterTest(LoggingTestCase):
api=test_exporter.gerrit,
chromium_commit=MockChromiumCommit(self.host)
)]
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [
......@@ -297,7 +316,8 @@ class TestExporterTest(LoggingTestCase):
MockChromiumCommit(self.host, change_id='decafbad'),
], [])
test_exporter.gerrit = MockGerritAPI()
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [
......@@ -320,7 +340,8 @@ class TestExporterTest(LoggingTestCase):
MockChromiumCommit(self.host, change_id='decafbad'),
], [])
test_exporter.gerrit = MockGerritAPI()
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [
......@@ -359,7 +380,8 @@ class TestExporterTest(LoggingTestCase):
api=test_exporter.gerrit,
chromium_commit=MockChromiumCommit(self.host)
)]
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertTrue(success)
self.assertEqual(test_exporter.wpt_github.calls, [])
......@@ -375,7 +397,8 @@ class TestExporterTest(LoggingTestCase):
test_exporter.get_exportable_commits = lambda: ([], [])
test_exporter.gerrit = MockGerritAPI()
test_exporter.gerrit.query_exportable_open_cls = raise_gerrit_error
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertFalse(success)
self.assertLog(['INFO: Cloning GitHub web-platform-tests/wpt into /tmp/wpt\n',
......@@ -388,9 +411,11 @@ class TestExporterTest(LoggingTestCase):
def test_run_returns_false_on_patch_failure(self):
test_exporter = TestExporter(self.host)
test_exporter.wpt_github = MockWPTGitHub(pull_requests=[])
test_exporter.get_exportable_commits = lambda: ([], ['There was an error with the rutabaga.'])
test_exporter.get_exportable_commits = lambda: (
[], ['There was an error with the rutabaga.'])
test_exporter.gerrit = MockGerritAPI()
success = test_exporter.main(['--credentials-json', '/tmp/credentials.json'])
success = test_exporter.main(
['--credentials-json', '/tmp/credentials.json'])
self.assertFalse(success)
self.assertLog(['INFO: Cloning GitHub web-platform-tests/wpt into /tmp/wpt\n',
......
......@@ -3,6 +3,7 @@
# found in the LICENSE file.
import base64
import datetime
import json
import logging
import re
......@@ -208,6 +209,48 @@ class WPTGitHub(object):
state=item['state'],
labels=labels)
def recent_failing_chromium_exports(self):
"""Fetches open PRs with an export label, failing status, and updated
within the last month.
API doc: https://developer.github.com/v3/search/#search-issues-and-pull-requests
Returns:
A list of PullRequest namedtuples.
"""
one_month_ago = datetime.date.today() - datetime.timedelta(days=31)
path = (
'/search/issues'
'?q=repo:{}/{}%20type:pr+is:open%20label:{}%20status:failure%20updated:>{}'
'&sort=updated'
'&page=1'
'&per_page={}'
).format(
WPT_GH_ORG,
WPT_GH_REPO_NAME,
EXPORT_PR_LABEL,
one_month_ago.isoformat(),
MAX_PER_PAGE
)
failing_prs = []
while path is not None:
response = self.request(path, method='GET')
if response.status_code == 200:
if response.data['incomplete_results']:
raise GitHubError('complete results', 'incomplete results',
'fetch failing open chromium exports', path)
prs = [self.make_pr_from_item(item) for item in response.data['items']]
failing_prs += prs
else:
raise GitHubError(200, response.status_code,
'fetch failing open chromium exports', path)
path = self.extract_link_next(response.getheader('Link'))
_log.info('Fetched %d PRs from GitHub.', len(failing_prs))
return failing_prs
@memoized
def all_pull_requests(self):
"""Fetches the most recent (open and closed) PRs with the export label.
......@@ -218,7 +261,7 @@ class WPTGitHub(object):
can't really find *all* of them; we fetch the most recently updated ones
because we only check whether recent commits have been exported.
API doc: https://developer.github.com/v3/search/#search-issues
API doc: https://developer.github.com/v3/search/#search-issues-and-pull-requests
Returns:
A list of PullRequest namedtuples.
......@@ -258,6 +301,26 @@ class WPTGitHub(object):
_log.info('Fetched %d PRs from GitHub.', len(all_prs))
return all_prs
def get_branch_statuses(self, branch_name):
"""Gets the status of a PR.
API doc: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
Returns:
The list of check statuses of the PR.
"""
path = '/repos/{}/{}/commits/{}/status'.format(
WPT_GH_ORG,
WPT_GH_REPO_NAME,
branch_name
)
response = self.request(path, method='GET')
if response.status_code != 200:
raise GitHubError(200, response.status_code, 'get the statuses of PR %d' % branch_name)
return response.data['statuses']
def get_pr_branch(self, pr_number):
"""Gets the remote branch name of a PR.
......
......@@ -23,6 +23,7 @@ class MockWPTGitHub(object):
merged. (-1 means none is merged.)
"""
self.pull_requests = pull_requests
self.recent_failing_pull_requests = []
self.calls = []
self.pull_requests_created = []
self.pull_requests_merged = []
......@@ -30,11 +31,16 @@ class MockWPTGitHub(object):
self.create_pr_index = 0
self.create_pr_fail_index = create_pr_fail_index
self.merged_index = merged_index
self.status = ''
def all_pull_requests(self, limit=30):
self.calls.append('all_pull_requests')
return self.pull_requests
def recent_failing_chromium_exports(self):
self.calls.append("recent_failing_chromium_exports")
return self.recent_failing_pull_requests
def is_pr_merged(self, number):
for index, pr in enumerate(self.pull_requests):
if pr.number == number:
......@@ -54,7 +60,8 @@ class MockWPTGitHub(object):
self.calls.append('create_pr')
if self.create_pr_fail_index != self.create_pr_index:
self.pull_requests_created.append((remote_branch_name, desc_title, body))
self.pull_requests_created.append(
(remote_branch_name, desc_title, body))
self.create_pr_index += 1
return 5678
......@@ -79,6 +86,10 @@ class MockWPTGitHub(object):
self.calls.append('get_pr_branch')
return 'fake_branch_PR_%d' % number
def get_branch_statuses(self, branch_name):
self.calls.append('get_branch_statuses')
return self.status
def pr_for_chromium_commit(self, commit):
self.calls.append('pr_for_chromium_commit')
for pr in self.pull_requests:
......
......@@ -50,6 +50,35 @@ class WPTGitHubTest(unittest.TestCase):
def test_extract_link_next_not_found(self):
self.assertIsNone(self.wpt_github.extract_link_next(''))
def test_recent_failing_chromium_exports_single_page(self):
self.wpt_github = WPTGitHub(MockHost(), user='rutabaga', token='decafbad', pr_history_window=1)
self.wpt_github.host.web.responses = [
{'status_code': 200,
'headers': {'Link': ''},
'body': json.dumps({'incomplete_results': False, 'items': [self.generate_pr_item(1)]})},
]
self.assertEqual(len(self.wpt_github.recent_failing_chromium_exports()), 1)
def test_recent_failing_chromium_exports_all_pages(self):
self.wpt_github = WPTGitHub(MockHost(), user='rutabaga', token='decafbad', pr_history_window=1)
self.wpt_github.host.web.responses = [
{'status_code': 200,
'headers': {'Link': '<https://api.github.com/resources?page=2>; rel="next"'},
'body': json.dumps({'incomplete_results': False, 'items': [self.generate_pr_item(1)]})},
{'status_code': 200,
'headers': {'Link': ''},
'body': json.dumps({'incomplete_results': False, 'items': [self.generate_pr_item(2)]})},
]
self.assertEqual(len(self.wpt_github.recent_failing_chromium_exports()), 2)
def test_recent_failing_chromium_exports_throws_github_error(self):
self.wpt_github.host.web.responses = [
{'status_code': 204},
]
with self.assertRaises(GitHubError):
self.wpt_github.recent_failing_chromium_exports()
def test_all_pull_requests_single_page(self):
self.wpt_github = WPTGitHub(MockHost(), user='rutabaga', token='decafbad', pr_history_window=1)
self.wpt_github.host.web.responses = [
......@@ -122,6 +151,13 @@ class WPTGitHubTest(unittest.TestCase):
with self.assertRaises(GitHubError):
self.wpt_github.create_pr('branch', 'title', 'body')
def test_get_branch_statuses(self):
self.wpt_github.host.web.responses = [
{'status_code': 200,
'body': json.dumps({'statuses': [{'description': 'abc'}]})},
]
self.assertEqual(self.wpt_github.get_branch_statuses('1234')[0]['description'], 'abc')
def test_get_pr_branch(self):
self.wpt_github.host.web.responses = [
{'status_code': 200,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment