Diff
Modified: trunk/Tools/ChangeLog (284891 => 284892)
--- trunk/Tools/ChangeLog 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/ChangeLog 2021-10-26 20:10:33 UTC (rev 284892)
@@ -1,3 +1,40 @@
+2021-10-26 Jonathan Bedard <jbed...@apple.com>
+
+ [webkitscmpy] Comment and close pull-requests
+ https://bugs.webkit.org/show_bug.cgi?id=232095
+ <rdar://problem/84515738>
+
+ Reviewed by Dewei Zhu.
+
+ * Scripts/libraries/webkitscmpy/setup.py: Bump version.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py:
+ (BitBucket.request): Add support for activities, opening and closing pull-request.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py:
+ (GitHub.__init__): Add issues.
+ (GitHub.request): Access issue underlying pull-request (which includes global comments).
+ * Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py:
+ (PullRequest.Exception): Added.
+ (PullRequest.Comment): Added.
+ (PullRequest.__init__): Add list of pull-request comments, metadata used by generator.
+ (PullRequest.open): Re-open the pull-request.
+ (PullRequest.close): Close the pull-request.
+ (PullRequest.comment): Make a comment on the pull-request.
+ (PullRequest.comments): List all comments on a pull-request.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py:
+ (BitBucket.PRGenerator.update): Handle closing and opening of the pull-request.
+ (BitBucket.PRGenerator.comment): Make a comment on a pull-request.
+ (BitBucket.PRGenerator.comments): List all comments on a pull-request.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py:
+ (GitHub.PRGenerator.update): Handle closing and opening of the pull-request.
+ (GitHub.PRGenerator.comment): Make a comment on a issue underpinning a pull-request.
+ (GitHub.PRGenerator.comments): List all comments on the pull-request.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py:
+ (Scm.PRGenerator.update): Add support for opening and closing a pull-request.
+ (Scm.PRGenerator.comment): Make a comment on a pull-request.
+ (Scm.PRGenerator.comments): List all comments for a pull-request
+ * Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py:
+
2021-10-26 Simon Fraser <simon.fra...@apple.com>
The script should decide when an image diff cases, not ImageDiff
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/setup.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -29,7 +29,7 @@
setup(
name='webkitscmpy',
- version='2.2.15',
+ version='2.2.16',
description='Library designed to interact with git and svn repositories.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -46,7 +46,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(2, 2, 15)
+version = Version(2, 2, 16)
AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
AutoInstall.register(Package('jinja2', Version(2, 11, 3)))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -22,6 +22,7 @@
import os
import json
+import time
from webkitcorepy import mocks
from webkitscmpy import Commit, remote as scmremote
@@ -207,7 +208,7 @@
at = (params or {}).get('at', None)
if at and candidate.get('fromRef', {}).get('id') != at:
continue
- prs.append(candidate)
+ prs.append({key: value for key, value in candidate.items() if key != 'activities'})
return mocks.Response.fromJson(dict(
size=len(prs),
@@ -223,12 +224,14 @@
json['fromRef']['displayId'] = json['fromRef']['id'].split('/')[-2:]
json['toRef']['displayId'] = json['toRef']['id'].split('/')[-2:]
json['state'] = 'OPEN'
+ json['activities'] = []
self.pull_requests.append(json)
return mocks.Response.fromJson(json)
# Update or access pull-request
if stripped_url.startswith(pr_base):
- number = int(stripped_url.split('/')[-1])
+ split_url = stripped_url.split('/')
+ number = int(split_url[9])
existing = None
for i in range(len(self.pull_requests)):
if self.pull_requests[i].get('id') == number:
@@ -237,6 +240,33 @@
return mocks.Response.create404(url)
if method == 'PUT':
self.pull_requests[existing].update(json)
- return mocks.Response.fromJson(self.pull_requests[existing])
+ if len(split_url) < 11:
+ return mocks.Response.fromJson({key: value for key, value in self.pull_requests[existing].items() if key != 'activities'})
+ if method == 'GET' and split_url[-1] == 'activities':
+ return mocks.Response.fromJson(dict(
+ size=len(self.pull_requests[existing].get('activities', [])),
+ isLastPage=True,
+ values=self.pull_requests[existing].get('activities', []),
+ ))
+ if method == 'POST' and split_url[-1] == 'comments':
+ self.pull_requests[existing]['activities'].append(dict(comment=dict(
+ author=dict(displayName='Tim Committer', emailAddress='commit...@webkit.org'),
+ createdDate=int(time.time() * 1000),
+ updatedDate=int(time.time() * 1000),
+ text=json.get('text', ''),
+ )))
+ return mocks.Response.fromJson({})
+ if method == 'POST' and split_url[-1] == 'decline':
+ self.pull_requests[existing]['open'] = False
+ self.pull_requests[existing]['closed'] = True
+ self.pull_requests[existing]['state'] = 'DECLINED'
+ return mocks.Response.fromJson({})
+ if method == 'POST' and split_url[-1] == 'reopen':
+ self.pull_requests[existing]['open'] = True
+ self.pull_requests[existing]['closed'] = False
+ self.pull_requests[existing]['state'] = 'OPEN'
+ return mocks.Response.fromJson({})
+ return mocks.Response.create404(url)
+
return mocks.Response.create404(url)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -22,6 +22,7 @@
import os
import json
+import time
from webkitcorepy import mocks
from webkitscmpy import Commit, remote as scmremote
@@ -59,6 +60,7 @@
self.head = self.commits[self.default_branch][-1]
self.tags = {}
self.pull_requests = []
+ self.issues = dict()
self.users = dict()
self._environment = None
@@ -292,6 +294,8 @@
), url=""
def request(self, method, url, data="" params=None, auth=None, json=None, **kwargs):
+ from datetime import datetime, timedelta
+
if not url.startswith('http://') and not url.startswith('https://'):
return mocks.Response.create404(url)
@@ -398,11 +402,14 @@
ref=json['base'],
user=dict(login=self.remote.split('/')[-2]),
)
+ if json.get('state'):
+ pr['state'] = json.get('state')
# Create specifically
if method == 'POST' and auth and stripped_url == pr_base:
pr['number'] = 1 + max([0] + [pr.get('number', 0) for pr in self.pull_requests])
pr['user'] = dict(login=auth.username)
+ pr['_links'] = dict(issue=dict(href=''.format(self.api_remote, pr['number'])))
self.pull_requests.append(pr)
return mocks.Response.fromJson(pr, url=""
@@ -422,4 +429,21 @@
if method == 'GET' and stripped_url.startswith('{}/users'.format(self.api_remote.split('/')[0])):
return self._users(url, stripped_url.split('/')[-1])
+ # Access underlying issue
+ if stripped_url.startswith('{}/issues/'.format(self.api_remote)):
+ number = int(stripped_url.split('/')[5])
+ issue = self.issues.get(number, dict(comments=[]))
+ if method == 'GET' and stripped_url.split('/')[6] == 'comments':
+ return mocks.Response.fromJson(issue['comments'], url=""
+ if method == 'POST' and stripped_url.split('/')[6] == 'comments':
+ self.issues[number] = issue
+ now = datetime.utcfromtimestamp(int(time.time()) - timedelta(hours=7).seconds).strftime('%Y-%m-%dT%H:%M:%SZ')
+ self.issues[number]['comments'].append(dict(
+ user=dict(login=auth.username),
+ created_at=now, updated_at=now,
+ body=json.get('body', ''),
+ ))
+ return mocks.Response.fromJson(issue['comments'], url=""
+ return mocks.Response.create404(url)
+
return mocks.Response.create404(url)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -21,11 +21,46 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
+import six
from .commit import Commit
+from datetime import datetime
+from webkitscmpy import Contributor
class PullRequest(object):
+ class Exception(RuntimeError):
+ pass
+
+ class Comment(object):
+ def __init__(self, author, timestamp, content):
+ if author and isinstance(author, dict) and author.get('name'):
+ self.author = Contributor(author.get('name'), author.get('emails'))
+ elif author and isinstance(author, six.string_types) and '@' in author:
+ self.author = Contributor(author, [author])
+ elif author and not isinstance(author, Contributor):
+ raise TypeError("Expected 'author' to be of type {}, got '{}'".format(Contributor, author))
+ else:
+ self.author = author
+
+ if isinstance(timestamp, six.string_types) and timestamp.isdigit():
+ timestamp = int(timestamp)
+ if timestamp and not isinstance(timestamp, int):
+ raise TypeError("Expected 'timestamp' to be of type int, got '{}'".format(timestamp))
+ self.timestamp = timestamp
+
+ if content and not isinstance(content, six.string_types):
+ raise ValueError("Expected 'content' to be a string, got '{}'".format(content))
+ self.content = content
+
+ def __repr__(self):
+ return '({} @ {}) {}'.format(
+ self.author,
+ datetime.utcfromtimestamp(self.timestamp) if self.timestamp else '-',
+ self.content,
+ )
+
+
COMMIT_BODY_RES = [
dict(
re=re.compile(r'\A#### (?P<hash>[0-9a-f]+)\n```\n(?P<message>.+)\n```\n?\Z', flags=re.DOTALL),
@@ -99,7 +134,12 @@
body = part.rstrip().lstrip()
return body or None, commits
- def __init__(self, number, title=None, body=None, author=None, head=None, base=None, opened=None, generator=None):
+ def __init__(
+ self, number, title=None,
+ body=None, author=None,
+ head=None, base=None,
+ opened=None, generator=None, metadata=None,
+ ):
self.number = number
self.title = title
self.body, self.commits = self.parse_body(body)
@@ -110,6 +150,8 @@
self._reviewers = None
self._approvers = None
self._blockers = None
+ self._metadata = metadata
+ self._comments = None
self.generator = generator
@property
@@ -136,5 +178,32 @@
return '?'
return self._opened
+ def open(self):
+ if self.opened is True:
+ return self
+ if not self.generator:
+ raise self.Exception('No associated pull-request generator')
+ return self.generator.update(self, opened=True)
+
+ def close(self):
+ if self.opened is False:
+ return self
+ if not self.generator:
+ raise self.Exception('No associated pull-request generator')
+ return self.generator.update(self, opened=False)
+
+ def comment(self, content):
+ if not self.generator:
+ raise self.Exception('No associated pull-request generator')
+ self.generator.comment(self, content)
+ self._comments = None
+ return self
+
+ @property
+ def comments(self):
+ if self._comments is None and self.generator:
+ self._comments = list(self.generator.comments(self))
+ return self._comments
+
def __repr__(self):
return 'PR {}{}'.format(self.number, ' | {}'.format(self.title) if self.title else '')
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -142,10 +142,32 @@
return None
return self.PullRequest(response.json())
- def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None):
+ def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None, opened=None):
if not isinstance(pull_request, PullRequest):
- raise ValueError(
- "Expected 'pull_request' to be of type '{}' not '{}'".format(PullRequest, type(pull_request)))
+ raise ValueError("Expected 'pull_request' to be of type '{}' not '{}'".format(PullRequest, type(pull_request)))
+
+ pr_url = 'https://{domain}/rest/api/1.0/projects/{project}/repos/{name}/pull-requests/{id}'.format(
+ domain=self.repository.domain,
+ project=self.repository.project,
+ name=self.repository.name,
+ id=pull_request.number,
+ )
+
+ if opened is not None:
+ response = requests.get(pr_url)
+ if response.status_code // 100 != 2:
+ return None
+ response = requests.post(
+ '{}/{}'.format(pr_url, 'reopen' if opened else 'decline'),
+ json=dict(version=response.json().get('version', 0)),
+ )
+ if response.status_code // 100 != 2:
+ return None
+
+ pull_request._opened = opened
+ if not any((head, title, body, commits, base)):
+ return pull_request
+
if not any((head, title, body, commits, base)):
raise ValueError('No arguments to update pull-request provided')
@@ -176,12 +198,6 @@
),
)
- pr_url = 'https://{domain}/rest/api/1.0/projects/{project}/repos/{name}/pull-requests/{id}'.format(
- domain=self.repository.domain,
- project=self.repository.project,
- name=self.repository.name,
- id=pull_request.number,
- )
response = requests.get(pr_url)
if response.status_code // 100 != 2:
return None
@@ -213,6 +229,32 @@
pull_request._approvers = got._approvers if got else []
return pull_request
+ def comment(self, pull_request, content):
+ response = requests.post(
+ 'https://{domain}/rest/api/1.0/projects/{project}/repos/{name}/pull-requests/{id}/comments'.format(
+ domain=self.repository.domain,
+ project=self.repository.project,
+ name=self.repository.name,
+ id=pull_request.number,
+ ), json=dict(text=content),
+ )
+ if response.status_code // 100 != 2:
+ sys.stderr.write("Failed to add comment to '{}'\n".format(pull_request))
+
+ def comments(self, pull_request):
+ for action in reversed(self.repository.request('pull-requests/{}/activities'.format(pull_request.number)) or []):
+ comment = action.get('comment', {})
+ user = comment.get('author', {})
+ if not comment or not user or not comment.get('text'):
+ continue
+
+ yield PullRequest.Comment(
+ author=self.repository.contributors.create(user['displayName'], user['emailAddress']),
+ timestamp=comment.get('updatedDate', comment.get('createdDate')) // 1000,
+ content=comment.get('text'),
+ )
+
+
@classmethod
def is_webserver(cls, url):
return True if cls.URL_RE.match(url) else False
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -54,6 +54,9 @@
closed=False,
).get(data.get('state'), None),
generator=self,
+ metadata=dict(
+ issue=data.get('_links', {}).get('issue', {}).get('href'),
+ ),
)
def get(self, number):
@@ -103,10 +106,10 @@
return None
return self.PullRequest(response.json())
- def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None):
+ def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None, opened=None):
if not isinstance(pull_request, PullRequest):
raise ValueError("Expected 'pull_request' to be of type '{}' not '{}'".format(PullRequest, type(pull_request)))
- if not any((head, title, body, commits, base)):
+ if not any((head, title, body, commits, base)) and opened is None:
raise ValueError('No arguments to update pull-request provided')
user, _ = self.repository.credentials(required=True)
@@ -117,6 +120,8 @@
)
if body or commits:
updates['body'] = PullRequest.create_body(body, commits)
+ if opened is not None:
+ updates['state'] = 'open' if opened else 'closed'
response = requests.post(
'{api_url}/repos/{owner}/{name}/pulls/{number}'.format(
api_url=self.repository.api_url,
@@ -127,6 +132,9 @@
headers=dict(Accept='application/vnd.github.v3+json'),
json=updates,
)
+ if response.status_code == 422:
+ pull_request._opened = False
+ return pull_request
if response.status_code // 100 != 2:
return None
data = ""
@@ -141,8 +149,11 @@
pull_request._opened = dict(
open=True,
closed=False,
- ).get(data.get('state'), None),
+ ).get(data.get('state'), None)
pull_request.generator = self
+ pull_request._metadata = dict(
+ issue=data.get('_links', {}).get('issue', {}).get('href'),
+ )
return pull_request
@@ -184,7 +195,57 @@
pull_request._reviewers = sorted(pull_request._reviewers)
return pull_request
+ def comment(self, pull_request, content):
+ issue = pull_request._metadata.get('issue')
+ if not issue:
+ old = pull_request
+ pull_request = self.get(old.number)
+ pull_request._reviewers = old._reviewers
+ pull_request._approvers = old._approvers
+ pull_request._blockers = old._blockers
+ issue = pull_request._metadata.get('issue')
+ if not issue:
+ raise self.repository.Exception('Failed to find issue underlying pull-request')
+ response = requests.post(
+ '{}/comments'.format(issue),
+ auth=HTTPBasicAuth(*self.repository.credentials(required=True)),
+ headers=dict(Accept='application/vnd.github.v3+json'),
+ json=dict(body=content),
+ )
+ if response.status_code // 100 != 2:
+ sys.stderr.write("Failed to add comment to '{}'\n".format(pull_request))
+ def comments(self, pull_request):
+ issue = pull_request._metadata.get('issue')
+ if not issue:
+ old = pull_request
+ pull_request = self.get(old.number)
+ pull_request._reviewers = old._reviewers
+ pull_request._approvers = old._approvers
+ pull_request._blockers = old._blockers
+ issue = pull_request._metadata.get('issue')
+ if not issue:
+ raise self.repository.Exception('Failed to find issue underlying pull-request')
+ response = requests.get(
+ '{}/comments'.format(issue),
+ auth=HTTPBasicAuth(*self.repository.credentials()),
+ headers=dict(Accept='application/vnd.github.v3+json'),
+ )
+ for node in response.json() if response.status_code // 100 == 2 else []:
+ user = node.get('user', {}).get('login')
+ if not user:
+ continue
+ tm = node.get('updated_at', node.get('created_at'))
+ if tm:
+ tm = int(calendar.timegm(datetime.strptime(tm, '%Y-%m-%dT%H:%M:%SZ').timetuple()))
+
+ yield PullRequest.Comment(
+ author=self._contributor(user),
+ timestamp=tm,
+ content=node.get('body'),
+ )
+
+
@classmethod
def is_webserver(cls, url):
return True if cls.URL_RE.match(url) else False
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -39,13 +39,19 @@
def create(self, head, title, body=None, commits=None, base=None):
raise NotImplementedError()
- def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None):
+ def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None, opened=None):
raise NotImplementedError()
def reviewers(self, pull_request):
raise NotImplementedError()
+ def comment(self, pull_request, content):
+ raise NotImplementedError()
+ def comments(self, pull_request):
+ raise NotImplementedError()
+
+
@classmethod
def from_url(cls, url, contributors=None):
from webkitscmpy import remote
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py (284891 => 284892)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py 2021-10-26 19:51:18 UTC (rev 284891)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py 2021-10-26 20:10:33 UTC (rev 284892)
@@ -460,7 +460,9 @@
reviews=[
dict(user=dict(login='ereviewer'), state='APPROVED'),
dict(user=dict(login='sreviewer'), state='CHANGES_REQUESTED'),
- ],
+ ], _links=dict(
+ issue=dict(href=''.format(result.api_remote)),
+ ),
)]
return result
@@ -509,7 +511,25 @@
self.assertEqual(pr.approvers, [])
self.assertEqual(pr.blockers, [Contributor('Suspicious Reviewer', ['srevie...@webkit.org'])])
+ def test_comments(self):
+ with self.webserver():
+ repo = remote.GitHub(self.remote)
+ pr = repo.pull_requests.get(1)
+ self.assertEqual(pr.comments, [])
+ pr.comment('Commenting!')
+ self.assertEqual([c.content for c in pr.comments], ['Commenting!'])
+ def test_open_close(self):
+ with self.webserver():
+ repo = remote.GitHub(self.remote)
+ pr = repo.pull_requests.get(1)
+ self.assertTrue(pr.opened)
+ pr.close()
+ self.assertFalse(pr.opened)
+ pr.open()
+ self.assertTrue(pr.opened)
+
+
class TestNetworkPullRequestBitBucket(unittest.TestCase):
remote = 'https://bitbucket.example.com/projects/WEBKIT/repos/webkit'
@@ -521,6 +541,7 @@
state='OPEN',
open=True,
closed=False,
+ activities=[],
title='Example Change',
author=dict(
user=dict(
@@ -606,3 +627,21 @@
])
self.assertEqual(pr.approvers, [])
self.assertEqual(pr.blockers, [Contributor('Suspicious Reviewer', ['srevie...@webkit.org'])])
+
+ def test_comments(self):
+ with self.webserver():
+ repo = remote.BitBucket(self.remote)
+ pr = repo.pull_requests.get(1)
+ self.assertEqual(pr.comments, [])
+ pr.comment('Commenting!')
+ self.assertEqual([c.content for c in pr.comments], ['Commenting!'])
+
+ def test_open_close(self):
+ with self.webserver():
+ repo = remote.BitBucket(self.remote)
+ pr = repo.pull_requests.get(1)
+ self.assertTrue(pr.opened)
+ pr.close()
+ self.assertFalse(pr.opened)
+ pr.open()
+ self.assertTrue(pr.opened)