[webkitbugspy] Support radar as issue tracker type
https://bugs.webkit.org/show_bug.cgi?id=234993
<rdar://problem/87276178>
Reviewed by Stephanie Lewis.
* Tools/Scripts/libraries/webkitbugspy/setup.py: Bump version.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: Ditto.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py:
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py:
(Base): Radar mock should not inherit from mocks.Requests.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py:
(Bugzilla): Inherit from both Base and mocks.Requests.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py:
(GitHub): Inherit from both Base and mocks.Requests.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py: Added.
(AppleDirectoryUserEntry): Mock AppleDirectoryUserEntry from radarclient.
(AppleDirectoryQuery): Mock AppleDirectoryQuery from radarclient.
(RadarModel): Mock various Model objects from radarclient.
(RadarClient): Mock RadarClient from radarclient.
(Radar): Mock radarclient library.
(NoRadar): Mock unavailable radarclient library.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py: Added.
(Tracker):
(Tracker.radarclient): Optionally import radarclient.
(Tracker.__init__):
(Tracker.authentication): Determine which authentication strategy to use.
(Tracker.from_string): Construct issue given Radar string
(Tracker.user): Find user given name, DSID or email.
(Tracker.issue): Construct issue given Radar ID.
(Tracker.populate): Populate Issue arguments.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py: Added.
(TestGitHub):
(TestGitHub.test_no_radar):
(TestGitHub.test_users):
(TestGitHub.test_link):
(TestGitHub.test_title):
(TestGitHub.test_timestamp):
(TestGitHub.test_creator):
(TestGitHub.test_description):
(TestGitHub.test_assignee):
(TestGitHub.test_comments):
(TestGitHub.test_watchers):
(TestGitHub.test_references):
(TestGitHub.test_reference_parse):
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py:
(Tracker): Match single integer issue URLs.
Canonical link: https://commits.webkit.org/246064@main
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288038 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 515e954..d61c34e 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,53 @@
+2022-01-07 Jonathan Bedard <jbedard@apple.com>
+
+ [webkitbugspy] Support radar as issue tracker type
+ https://bugs.webkit.org/show_bug.cgi?id=234993
+ <rdar://problem/87276178>
+
+ Reviewed by Stephanie Lewis.
+
+ * Scripts/libraries/webkitbugspy/setup.py: Bump version.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: Ditto.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py:
+ * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py:
+ (Base): Radar mock should not inherit from mocks.Requests.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py:
+ (Bugzilla): Inherit from both Base and mocks.Requests.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py:
+ (GitHub): Inherit from both Base and mocks.Requests.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py: Added.
+ (AppleDirectoryUserEntry): Mock AppleDirectoryUserEntry from radarclient.
+ (AppleDirectoryQuery): Mock AppleDirectoryQuery from radarclient.
+ (RadarModel): Mock various Model objects from radarclient.
+ (RadarClient): Mock RadarClient from radarclient.
+ (Radar): Mock radarclient library.
+ (NoRadar): Mock unavailable radarclient library.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/radar.py: Added.
+ (Tracker):
+ (Tracker.radarclient): Optionally import radarclient.
+ (Tracker.__init__):
+ (Tracker.authentication): Determine which authentication strategy to use.
+ (Tracker.from_string): Construct issue given Radar string
+ (Tracker.user): Find user given name, DSID or email.
+ (Tracker.issue): Construct issue given Radar ID.
+ (Tracker.populate): Populate Issue arguments.
+ * Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py: Added.
+ (TestGitHub):
+ (TestGitHub.test_no_radar):
+ (TestGitHub.test_users):
+ (TestGitHub.test_link):
+ (TestGitHub.test_title):
+ (TestGitHub.test_timestamp):
+ (TestGitHub.test_creator):
+ (TestGitHub.test_description):
+ (TestGitHub.test_assignee):
+ (TestGitHub.test_comments):
+ (TestGitHub.test_watchers):
+ (TestGitHub.test_references):
+ (TestGitHub.test_reference_parse):
+ * Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py:
+ (Tracker): Match single integer issue URLs.
+
2022-01-14 Wenson Hsieh <wenson_hsieh@apple.com>
Avoid redundant text analysis requests when long pressing inside an image that contains Live Text
diff --git a/Tools/Scripts/libraries/webkitbugspy/setup.py b/Tools/Scripts/libraries/webkitbugspy/setup.py
index 6218bae..9ab9765 100644
--- a/Tools/Scripts/libraries/webkitbugspy/setup.py
+++ b/Tools/Scripts/libraries/webkitbugspy/setup.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -30,7 +30,7 @@
setup(
name='webkitbugspy',
- version='0.0.1',
+ version='0.0.2',
description='Library containing a shared API for various bug trackers.',
long_description=readme(),
classifiers=[
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
index c2222b1..550f293 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -46,7 +46,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(0, 0, 1)
+version = Version(0, 0, 2)
from .user import User
from .issue import Issue
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py
index a4af2a9..2d65b5c 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -23,3 +23,4 @@
from .bugzilla import Bugzilla
from .data import ISSUES, USERS
from .github import GitHub
+from .radar import Radar, NoRadar
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py
index 714c6cd..4b60ee8 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -21,10 +21,9 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from webkitbugspy import User
-from webkitcorepy import mocks
-class Base(mocks.Requests):
+class Base(object):
@classmethod
def transform_user(cls, user):
return User(
@@ -33,12 +32,7 @@
emails=user.emails,
)
- def __init__(self, *hosts, **kwargs):
- super(Base, self).__init__(*hosts)
-
- users = kwargs.get('users', None)
- issues = kwargs.get('issues', None)
-
+ def __init__(self, users=None, issues=None):
self.users = User.Mapping()
for user in users or []:
self.users.add(type(self).transform_user(user))
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py
index 29442b9..327200a 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/bugzilla.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -29,7 +29,7 @@
from webkitcorepy import mocks
-class Bugzilla(Base):
+class Bugzilla(Base, mocks.Requests):
top = None
@classmethod
@@ -46,7 +46,8 @@
)
def __init__(self, hostname='bugs.example.com', users=None, issues=None):
- super(Bugzilla, self).__init__(hostname, users=users, issues=issues)
+ Base.__init__(self, users=users, issues=issues)
+ mocks.Requests.__init__(self, hostname)
def _user(self, url, username):
user = self.users[username]
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py
index 86da254..e22b518 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/github.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -30,7 +30,7 @@
from webkitcorepy import mocks
-class GitHub(Base):
+class GitHub(Base, mocks.Requests):
top = None
@classmethod
@@ -50,7 +50,8 @@
hostname, repo = hostname.split('/', 1)
self.api_host = 'api.{hostname}/repos/{repo}'.format(hostname=hostname, repo=repo)
- super(GitHub, self).__init__(hostname, 'api.{}'.format(hostname), users=users, issues=issues)
+ Base.__init__(self, users=users, issues=issues)
+ mocks.Requests.__init__(self, hostname, 'api.{}'.format(hostname))
self._environment = None
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py
new file mode 100644
index 0000000..06d2c6b
--- /dev/null
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/mocks/radar.py
@@ -0,0 +1,184 @@
+# Copyright (C) 2022 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from .base import Base
+
+from webkitbugspy import User
+from webkitcorepy import string_utils
+from webkitcorepy.mocks import ContextStack
+
+
+class AppleDirectoryUserEntry(object):
+ def __init__(self, user):
+ self.first_name = lambda: user.name.split(' ', 1)[0]
+ self.last_name = lambda: user.name.split(' ', 1)[1]
+ self.email = lambda: user.email
+ self.dsid = lambda: user.username
+
+
+class AppleDirectoryQuery(object):
+ def __init__(self, parent):
+ self.parent = parent
+
+ def user_entry_for_dsid(self, dsid):
+ return self.user_entry_for_attribute_value('dsid', dsid)
+
+ def user_entry_for_attribute_value(self, name, value):
+ if name not in ('cn', 'dsid', 'mail'):
+ raise ValueError("'{}' is not a valid user attribute value".format(name))
+ found = self.parent.users.get(value)
+ if not found:
+ return None
+ if name == 'cn' and value != found.name:
+ return None
+ if name == 'dsid' and value != found.username:
+ return None
+ if name == 'mail' and value not in found.emails:
+ return None
+ return AppleDirectoryUserEntry(found)
+
+
+class RadarModel(object):
+ class Person(object):
+ def __init__(self, user):
+ self.firstName = user.name.split(' ', 1)[0]
+ self.lastName = user.name.split(' ', 1)[1]
+ self.email = user.email
+ self.dsid = user.username
+
+ class CCMembership(object):
+ def __init__(self, user):
+ self.person = RadarModel.Person(user)
+
+ class CollectionProperty(object):
+ def __init__(self, *properties):
+ self._properties = properties
+
+ def items(self, type=None):
+ for property in self._properties:
+ yield property
+
+ class DescriptionEntry(object):
+ def __init__(self, text):
+ self.text = text
+
+ class CommentAuthor(object):
+ def __init__(self, user):
+ self.name = user.name
+ self.email = user.email
+
+ class DiagnosisEntry(object):
+ def __init__(self, text, addedAt, addedBy):
+ self.text = text
+ self.addedAt = addedAt
+ self.addedBy = addedBy
+
+ def __init__(self, client, issue):
+ from datetime import datetime, timedelta
+
+ self.client = client
+ self._issue = issue
+ self.title = issue['title']
+ self.id = issue['id']
+ self.createdAt = datetime.utcfromtimestamp(issue['timestamp'] - timedelta(hours=7).seconds)
+ self.assignee = self.Person(Radar.transform_user(issue['assignee']))
+ self.description = self.CollectionProperty(self.DescriptionEntry(issue['description']))
+ self.state = 'Investigate' if issue['opened'] else 'Verify'
+ self.originator = self.Person(Radar.transform_user(issue['creator']))
+ self.diagnosis = self.CollectionProperty(*[
+ self.DiagnosisEntry(
+ text=comment.content,
+ addedAt=datetime.utcfromtimestamp(comment.timestamp - timedelta(hours=7).seconds),
+ addedBy=self.CommentAuthor(Radar.transform_user(comment.user)),
+ ) for comment in issue.get('comments', [])
+ ])
+ self.cc_memberships = self.CollectionProperty(*[
+ self.CCMembership(Radar.transform_user(watcher)) for watcher in issue.get('watchers', [])
+ ])
+
+ def related_radars(self):
+ for reference in self._issue.get('references', []):
+ ref = self.client.radar_for_id(reference)
+ if ref:
+ yield ref
+
+
+class RadarClient(object):
+ def __init__(self, parent):
+ self.parent = parent
+
+ def radar_for_id(self, problem_id):
+ found = self.parent.issues.get(problem_id)
+ if not found:
+ return None
+ return RadarModel(self, found)
+
+
+class Radar(Base, ContextStack):
+ top = None
+
+ class AuthenticationStrategySystemAccount(object):
+ def __init__(self, _, __, ___, ____):
+ pass
+
+ class AuthenticationStrategySPNego(object):
+ pass
+
+ class ClientSystemIdentifier(object):
+ def __init__(self, name, version):
+ pass
+
+ @classmethod
+ def transform_user(cls, user):
+ return User(
+ name=user.name,
+ username=sum(bytearray(string_utils.encode(user.email))) % 1000,
+ emails=user.emails,
+ )
+
+ def __init__(self, users=None, issues=None):
+ Base.__init__(self, users=users, issues=issues)
+ ContextStack.__init__(self, Radar)
+
+ self.users = User.Mapping()
+ for name in sorted([user.name for user in users or []]):
+ self.users.add(self.transform_user(users[name]))
+
+ self.issues = {}
+ for issue in issues or []:
+ self.add(issue)
+
+ self.AppleDirectoryQuery = AppleDirectoryQuery(self)
+ self.RadarClient = lambda authentication_strategy, client_system_identifier: RadarClient(self)
+
+ from mock import patch
+ self.patches.append(patch('webkitbugspy.radar.Tracker.radarclient', new=lambda s=None: self))
+
+
+class NoRadar(ContextStack):
+ top = None
+
+ def __init__(self):
+ super(NoRadar, self).__init__(NoRadar)
+
+ from mock import patch
+ self.patches.append(patch('webkitbugspy.radar.Tracker.radarclient', new=lambda s=None: None))
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py
new file mode 100644
index 0000000..462d87a
--- /dev/null
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/radar.py
@@ -0,0 +1,176 @@
+# Copyright (C) 2022 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import calendar
+import re
+import sys
+
+from webkitcorepy import Environment
+from webkitbugspy import Issue, Tracker as GenericTracker, User, name as library_name, version as library_version
+
+
+class Tracker(GenericTracker):
+ RES = [
+ re.compile(r'<?rdar://problem/(?P<id>\d+)>?'),
+ re.compile(r'<?radar://problem/(?P<id>\d+)>?'),
+ re.compile(r'<?rdar:\/\/(?P<id>\d+)>?'),
+ re.compile(r'<?radar:\/\/(?P<id>\d+)>?'),
+ ]
+
+ @staticmethod
+ def radarclient():
+ try:
+ import radarclient
+ return radarclient
+ except ImportError:
+ return None
+
+ def __init__(self, users=None, authentication=None):
+ super(Tracker, self).__init__(users=users)
+
+ self.library = self.radarclient()
+ if self.library:
+ self.client = self.library.RadarClient(
+ authentication or self.authentication(),
+ self.library.ClientSystemIdentifier(library_name, str(library_version)),
+ )
+ else:
+ self.client = None
+
+ def authentication(self):
+ username = Environment.instance().get('RADAR_USERNAME')
+ password = Environment.instance().get('RADAR_PASSWORD')
+ totp_secret = Environment.instance().get('RADAR_TOTP_SECRET')
+ totp_id = Environment.instance().get('RADAR_TOTP_ID') or 1
+
+ if username and password and totp_secret and totp_id:
+ return self.library.AuthenticationStrategySystemAccount(
+ username, password, totp_secret, totp_id,
+ )
+ return self.library.AuthenticationStrategySPNego()
+
+ def from_string(self, string):
+ for regex in self.RES:
+ match = regex.match(string)
+ if match:
+ return self.issue(int(match.group('id')))
+ return None
+
+ def user(self, name=None, username=None, email=None):
+ user = super(Tracker, self).user(name=name, username=username, email=email)
+ if user:
+ return user
+ if not name or not username or not email:
+ found = None
+ if username:
+ found = self.library.AppleDirectoryQuery.user_entry_for_dsid(int(username))
+ elif email:
+ found = self.library.AppleDirectoryQuery.user_entry_for_attribute_value('mail', email)
+ elif name:
+ found = self.library.AppleDirectoryQuery.user_entry_for_attribute_value('cn', name)
+ if not found:
+ raise RuntimeError("Failed to find '{}'".format(User(
+ name, username, [email],
+ )))
+ name = '{} {}'.format(found.first_name(), found.last_name())
+ username = found.dsid()
+ email = found.email()
+ return self.users.create(
+ name=name,
+ username=username,
+ emails=[email],
+ )
+
+ def issue(self, id):
+ return Issue(id=int(id), tracker=self)
+
+ def populate(self, issue, member=None):
+ issue._link = '<rdar://{}>'.format(issue.id)
+ if (not self.client or not self.library) and member:
+ sys.stderr.write('radarclient inaccessible on this machine\n')
+ return issue
+
+ if not member:
+ return issue
+
+ radar = self.client.radar_for_id(issue.id)
+ if not radar:
+ sys.stderr.write("Failed to fetch '{}'\n".format(issue.link))
+ return issue
+
+ issue._title = radar.title
+ issue._timestamp = int(calendar.timegm(radar.createdAt.timetuple()))
+ issue._assignee = self.user(
+ name='{} {}'.format(radar.assignee.firstName, radar.assignee.lastName),
+ username=radar.assignee.dsid,
+ email=radar.assignee.email,
+ )
+ issue._description = '\n'.join([desc.text for desc in radar.description.items()])
+ issue._opened = False if radar.state in ('Verify', 'Closed') else True
+ issue._creator = self.user(
+ name='{} {}'.format(radar.originator.firstName, radar.originator.lastName),
+ username=radar.originator.dsid,
+ email=radar.originator.email,
+ )
+
+ if member == 'watchers':
+ issue._watchers = []
+ for member in radar.cc_memberships.items():
+ if member.person.dsid == radar.originator.dsid:
+ continue
+ issue._watchers.append(self.user(
+ name='{} {}'.format(member.person.firstName, member.person.lastName),
+ username=member.person.dsid,
+ email=member.person.email,
+ ))
+
+ if member == 'comments':
+ issue._comments = []
+ for item in radar.diagnosis.items(type='user'):
+ issue._comments.append(Issue.Comment(
+ user=self.user(
+ name=item.addedBy.name,
+ email=item.addedBy.email,
+ ), timestamp=int(calendar.timegm(item.addedAt.timetuple())),
+ content=item.text,
+ ))
+
+ if member == 'references':
+ issue._references = []
+ refs = set()
+
+ for text in [issue.description] + [comment.content for comment in issue.comments]:
+ for match in self.REFERENCE_RE.findall(text):
+ candidate = GenericTracker.from_string(match[0]) or self.from_string(match[0])
+ if not candidate or candidate.link in refs or candidate.id == issue.id:
+ continue
+ issue._references.append(candidate)
+ refs.add(candidate.link)
+
+ for r in radar.related_radars():
+ candidate = self.issue(r.id)
+ if candidate.link in refs or candidate.id == issue.id:
+ continue
+ issue._references.append(candidate)
+ refs.add(candidate.link)
+
+ return issue
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py
new file mode 100644
index 0000000..30048a2
--- /dev/null
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/radar_unittest.py
@@ -0,0 +1,136 @@
+# Copyright (C) 2022 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+from webkitbugspy import Issue, User, radar, mocks
+
+
+class TestGitHub(unittest.TestCase):
+ def test_no_radar(self):
+ with mocks.NoRadar():
+ tracker = radar.Tracker()
+ self.assertIsNone(tracker.library)
+ self.assertIsNone(tracker.client)
+
+ def test_users(self):
+ with mocks.Radar(users=mocks.USERS):
+ tracker = radar.Tracker()
+ self.assertEqual(
+ User.Encoder().default(tracker.user(username=504)),
+ dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']),
+ )
+ self.assertEqual(
+ User.Encoder().default(tracker.user(email='tcontributor@example.com')),
+ dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']),
+ )
+ self.assertEqual(
+ User.Encoder().default(tracker.user(name='Felix Filer')),
+ dict(name='Felix Filer', username=809, emails=['ffiler@example.com']),
+ )
+
+ def test_link(self):
+ with mocks.Radar(users=mocks.USERS):
+ tracker = radar.Tracker()
+ self.assertEqual(tracker.issue(1234).link, '<rdar://1234>')
+ self.assertEqual(
+ tracker.from_string('<rdar://problem/1234>').link,
+ '<rdar://1234>',
+ )
+ self.assertEqual(
+ tracker.from_string('<radar://1234>').link,
+ '<rdar://1234>',
+ )
+ self.assertEqual(
+ tracker.from_string('<radar://problem/1234>').link,
+ '<rdar://1234>',
+ )
+
+ def test_title(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ tracker = radar.Tracker()
+ self.assertEqual(tracker.issue(1).title, 'Example issue 1')
+ self.assertEqual(str(tracker.issue(1)), '<rdar://1> Example issue 1')
+
+ def test_timestamp(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ self.assertEqual(radar.Tracker().issue(1).timestamp, 1639510960)
+
+ def test_creator(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ self.assertEqual(
+ User.Encoder().default(radar.Tracker().issue(1).creator),
+ dict(name='Felix Filer', username=809, emails=['ffiler@example.com']),
+ )
+
+ def test_description(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ self.assertEqual(
+ radar.Tracker().issue(1).description,
+ 'An example issue for testing',
+ )
+
+ def test_assignee(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ self.assertEqual(
+ User.Encoder().default(radar.Tracker().issue(1).assignee),
+ dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']),
+ )
+
+ def test_comments(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ comments = radar.Tracker().issue(1).comments
+ self.assertEqual(len(comments), 2)
+ self.assertEqual(comments[0].timestamp, 1639511020)
+ self.assertEqual(comments[0].content, 'Was able to reproduce on version 1.2.3')
+ self.assertEqual(
+ User.Encoder().default(comments[0].user),
+ dict(name='Felix Filer', username=809, emails=['ffiler@example.com']),
+ )
+
+ def test_watchers(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ self.assertEqual(
+ User.Encoder().default(radar.Tracker().issue(1).watchers), [
+ dict(name='Tim Contributor', username=504, emails=['tcontributor@example.com']),
+ dict(name='Wilma Watcher', username=46, emails=['wwatcher@example.com']),
+ ],
+ )
+
+ def test_references(self):
+ with mocks.Radar(issues=mocks.ISSUES):
+ tracker = radar.Tracker()
+ self.assertEqual(tracker.issue(1).references, [])
+ self.assertEqual(tracker.issue(2).references, [tracker.issue(3)])
+ self.assertEqual(tracker.issue(3).references, [tracker.issue(2)])
+
+ def test_reference_parse(self):
+ with mocks.Radar(issues=mocks.ISSUES) as mock:
+ tracker = radar.Tracker()
+ mock.issues[1]['comments'].append(
+ Issue.Comment(
+ user=mocks.USERS['Wilma Watcher'],
+ timestamp=1639539630,
+ content='Is this related to <rdar://2> ?',
+ ),
+ )
+ self.assertEqual(tracker.issue(1).references, [tracker.issue(2)])
diff --git a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
index c7fc0a2..2fca2f7 100644
--- a/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
+++ b/Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tracker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 Apple Inc. All rights reserved.
+# Copyright (C) 2021-2022 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -28,7 +28,7 @@
class Tracker(object):
- REFERENCE_RE = re.compile(r'((https|http|rdar|radar)://[^\s><,\'\"}{\]\[)(]+[^\s><,\'\"}{\]\[)(\.\?])')
+ REFERENCE_RE = re.compile(r'((https|http|rdar|radar)://[^\s><,\'\"}{\]\[)(]*[^\s><,\'\"}{\]\[)(\.\?])')
_trackers = []