Diff
Modified: trunk/Tools/ChangeLog (286575 => 286576)
--- trunk/Tools/ChangeLog 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/ChangeLog 2021-12-07 00:18:20 UTC (rev 286576)
@@ -1,3 +1,48 @@
+2021-12-04 Jonathan Bedard <[email protected]>
+
+ [reporelaypy] Add implementation of commits.webkit.org
+ https://bugs.webkit.org/show_bug.cgi?id=233734
+ <rdar://problem/85945101>
+
+ Reviewed by Dewei Zhu.
+
+ As commits.webkit.org becomes more important to WebKit development, we need to apply
+ our normal robust testing and review to the code backing it.
+
+ * Scripts/libraries/reporelaypy/reporelaypy/__init__.py: Add entrypoint to library.
+ * Scripts/libraries/reporelaypy/reporelaypy/checkout.py: Added.
+ (Checkout): Abstraction managing local repository checkout.
+ * Scripts/libraries/reporelaypy/reporelaypy/checkoutroute.py: Added.
+ (Redirector): Generates redirects for certain SCM services.
+ (CheckoutRoute): http routes returning information based on a checkout.
+ * Scripts/libraries/reporelaypy/reporelaypy/database.py: Added.
+ (Database): A connection to a true Redis or TinyRedis database.
+ * Scripts/libraries/reporelaypy/reporelaypy/tests/checkout_unittest.py:
+ (CheckoutUnittest):
+ * Scripts/libraries/reporelaypy/reporelaypy/tests/checkoutroute_unittest.py: Added.
+ (RedirectorUnittest):
+ (CheckoutRouteUnittest):
+ * Scripts/libraries/reporelaypy/reporelaypy/tests/database_unittest.py: Added.
+ (DatabaseUnittest):
+ * Scripts/libraries/reporelaypy/reporelaypy/webserver.py: Copied from Tools/Scripts/libraries/webkitflaskpy/setup.py.
+ (health):
+ * Scripts/libraries/reporelaypy/run: Added.
+ * Scripts/libraries/reporelaypy/setup.py: Added.
+ * Scripts/libraries/resultsdbpy/resultsdbpy/__init__.py: Bump version.
+ * Scripts/libraries/resultsdbpy/resultsdbpy/flask_support/flask_testcase.py:
+ (FlaskTestCase.run_with_mock_webserver.decorator.real_method):
+ (FlaskRequestsResponse): Moved to webkitflaskpy.
+ * Scripts/libraries/webkitflaskpy/setup.py: Bump version, update library description.
+ * Scripts/libraries/webkitflaskpy/webkitflaskpy/__init__.py: Bump version.
+ * Scripts/libraries/webkitflaskpy/webkitflaskpy/mock_app.py: Added.
+ (mock_app): Setup a flask app for testing.
+ * Scripts/libraries/webkitflaskpy/webkitflaskpy/response.py: Added.
+ (Response): Move FlaskRequestsResponse to more generic location.
+ * Scripts/webkitpy/__init__.py: Include reporelaypy for testing.
+ * Scripts/webkitpy/test/main.py:
+ (main): Include reporelaypy for testing.
+ (Tester._run_tests): Ditto.
+
2021-12-06 Ryan Haddad <[email protected]>
REGRESSION (r286507): [macOS] Many file system access layout tests became flaky failures
Copied: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/__init__.py (from rev 286574, trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/__init__.py) (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/__init__.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/__init__.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,59 @@
+# Copyright (C) 2021 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 os
+import sys
+
+
+def _maybe_add_webkit_python_library_paths():
+ # Hopefully we're beside webkit*py libraries, otherwise webkit*py will need to be installed.
+ libraries_path = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
+ for library in ['webkitcorepy', 'webkitscmpy', 'webkitflaskpy']:
+ library_path = os.path.join(libraries_path, library)
+ if os.path.isdir(library_path) and os.path.isdir(os.path.join(library_path, library)) and library_path not in sys.path:
+ sys.path.insert(0, library_path)
+
+
+_maybe_add_webkit_python_library_paths()
+
+try:
+ from webkitcorepy import AutoInstall, Package, Version
+except ImportError:
+ raise ImportError(
+ "'webkitcorepy' could not be found on your Python path.\n" +
+ "You are not running from a WebKit checkout.\n" +
+ "Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
+ )
+
+version = Version(0, 1, 0)
+
+import webkitflaskpy
+
+from reporelaypy.checkout import Checkout
+from reporelaypy.database import Database
+from reporelaypy.checkoutroute import CheckoutRoute, Redirector
+
+AutoInstall.register(Package('fakeredis', Version(1, 5, 2)))
+AutoInstall.register(Package('hiredis', Version(1, 1, 0)))
+AutoInstall.register(Package('redis', Version(3, 5, 3)))
+
+name = 'reporelaypy'
Added: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/checkout.py (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/checkout.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/checkout.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,204 @@
+# Copyright (C) 2021 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 json
+import multiprocessing
+import os
+import shutil
+import sys
+
+from webkitcorepy import run
+from webkitscmpy import local
+
+
+class Checkout(object):
+ class Exception(RuntimeError):
+ pass
+
+ class Encoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, dict):
+ return {key: self.default(value) for key, value in obj.items()}
+ if isinstance(obj, list):
+ return [self.default(value) for value in obj]
+ if not isinstance(obj, Checkout):
+ return super(Checkout.Encoder, self).default(obj)
+
+ return dict(
+ path=obj.path,
+ url=""
+ sentinal=obj.sentinal,
+ )
+
+ @classmethod
+ def from_json(cls, data):
+ data = "" if isinstance(data, dict) else json.loads(data)
+ if isinstance(data, list):
+ return [cls(**node, primary=False) for node in data]
+ return cls(**data, primary=False)
+
+ @staticmethod
+ def clone(url, path, sentinal_file=None):
+ run([local.Git.executable(), 'clone', url, path], cwd=os.path.dirname(path))
+ run([local.Git.executable(), 'config', 'pull.ff', 'only'], cwd=path)
+
+ if sentinal_file:
+ with open(sentinal_file, 'w') as cloned:
+ cloned.write('yes\n')
+ return 0
+
+ def __init__(self, path, url="" http_proxy=None, sentinal=True, primary=True):
+ self.sentinal = sentinal
+ self.path = path
+ self.url = ""
+ self._repository = None
+ self._child_process = None
+
+ containing_path = os.path.dirname(path)
+ if not os.path.isdir(containing_path):
+ raise self.Exception("Containing path '{}' does not exist".format(containing_path))
+
+ if http_proxy and run([
+ local.Git.executable(), 'config', '--global', 'http.proxy', 'http://{}'.format(http_proxy),
+ ]).returncode:
+ split_proxy = http_proxy.split('@')
+ raise self.Exception("Failed to set https proxy to '{}'".format(
+ '{}@{}'.format('*' * len(split_proxy[0]), split_proxy[1]) if '@' in http_proxy else http_proxy,
+ ))
+
+ try:
+ if self.repository:
+ if not self.url:
+ self.url = ""
+ if self.url and self.repository.url(name='origin') != self.url:
+ sys.stderr.write("Specified '{}' as the URL, but the specified path is from '{}'\n".format(
+ self.url, self.repository.url(name='origin'),
+ ))
+ return
+ except FileNotFoundError:
+ pass
+
+ if not primary:
+ return
+
+ if not self.url:
+ raise self.Exception('No url provided to clone from')
+
+ if os.path.isfile(self.sentinal_file):
+ os.remove(self.sentinal_file)
+ if os.path.isdir(path):
+ shutil.rmtree(path, ignore_errors=True)
+
+ if self.sentinal:
+ self._child_process = multiprocessing.Process(
+ target=self.clone,
+ args=(self.url, path, self.sentinal_file),
+ )
+ self._child_process.start()
+ else:
+ self.clone(self.url, path)
+
+ @property
+ def sentinal_file(self):
+ return os.path.join(os.path.dirname(self.path), 'cloned')
+
+ @property
+ def repository(self):
+ if self._repository:
+ return self._repository
+ if os.path.isfile(self.sentinal_file) or not self.sentinal:
+ if self._child_process:
+ self._child_process.join()
+ self._child_process = None
+ self._repository = local.Git(self.path)
+ return self._repository
+ return None
+
+ def is_updated(self, branch, remote='origin'):
+ if not self.repository:
+ sys.stderr.write('Cannot query checkout, clone still pending...\n')
+ return None
+
+ result = run(
+ [self.repository.executable(), 'show-ref', branch],
+ cwd=self.repository.root_path,
+ capture_output=True,
+ encoding='utf-8',
+ )
+ if result.returncode:
+ return False
+ ref = None
+ for line in result.stdout.splitlines():
+ if line.split()[-1].startswith('refs/heads'):
+ ref = line.split()[0]
+ break
+ if not ref:
+ return False
+ for line in result.stdout.splitlines():
+ if line.split()[-1].startswith('refs/remotes/{}'.format(remote)):
+ return ref == line.split()[0]
+ return False
+
+ def update_for(self, branch=None, remote='origin'):
+ if not self.repository:
+ sys.stderr.write("Cannot update '{}', clone still pending...\n".format(branch))
+ return None
+
+ branch = branch or self.repository.default_branch
+ if branch == self.repository.default_branch:
+ self.repository.pull(remote=remote)
+ self.repository.cache.populate(branch=branch)
+ return True
+ if not self.repository.prod_branches.match(branch):
+ return False
+ if self.is_updated(branch, remote=remote):
+ return True
+
+ run(
+ [self.repository.executable(), 'fetch', remote, '{}:{}'.format(branch, branch)],
+ cwd=self.repository.root_path,
+ )
+ self.repository.cache.populate(branch=branch)
+ return True
+
+ def update_all(self, remote='origin'):
+ if not self.repository:
+ sys.stderr.write("Cannot update checkout, clone still pending...\n")
+ return None
+
+ self.repository.pull(remote=remote)
+ self.repository.cache.populate(branch=self.repository.default_branch)
+
+ # First, update all branches we're already tracking
+ all_branches = set(self.repository.branches_for(remote=remote))
+ for branch in self.repository.branches_for(remote=False):
+ if branch in all_branches:
+ all_branches.remove(branch)
+ self.update_for(branch=branch, remote=remote)
+
+ # Then, track all untracked branches
+ for branch in all_branches:
+ run(
+ [self.repository.executable(), 'branch', '--track', branch, 'remotes/{}/{}'.format(remote, branch)],
+ cwd=self.repository.root_path,
+ )
+ self.repository.cache.populate(branch=branch)
Added: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/checkoutroute.py (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/checkoutroute.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/checkoutroute.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,189 @@
+# Copyright (C) 2021 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 json
+import re
+
+from flask import abort, current_app, json as fjson, redirect, Flask, Response
+from reporelaypy import Database
+from webkitflaskpy import AuthedBlueprint
+from webkitscmpy import Commit, remote
+
+
+class Redirector(object):
+ class Encoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, dict):
+ return {key: self.default(value) for key, value in obj.items()}
+ if isinstance(obj, list):
+ return [self.default(value) for value in obj]
+ if not isinstance(obj, Redirector):
+ return super(Checkout.Encoder, self).default(obj)
+
+ return dict(
+ name=obj.name,
+ url=""
+ )
+
+ @classmethod
+ def bitbucket_generator(cls, base):
+ def redirector(commit):
+ if commit and commit.hash:
+ return redirect('{}/commits/{}'.format(base, commit.hash))
+ return redirect('{}/commits'.format(base))
+
+ return redirector
+
+ @classmethod
+ def trac_generator(cls, base):
+ def redirector(commit):
+ if commit and commit.revision:
+ return redirect('{}/changeset/{}/webkit'.format(base, commit.revision))
+ return redirect(base)
+
+ return redirector
+
+ @classmethod
+ def github_generator(cls, base):
+ def redirector(commit):
+ if commit and commit.hash:
+ return redirect('{}/commit/{}'.format(base, commit.hash))
+ return redirect('{}/commits'.format(base))
+
+ return redirector
+
+ @classmethod
+ def from_json(cls, data):
+ data = "" if isinstance(data, dict) else json.loads(data)
+ if isinstance(data, list):
+ return [cls(**node) for node in data]
+ return cls(**data)
+
+ def __init__(self, url, name=None):
+ self.url = ""
+ self.type = None
+ self._redirect = None
+
+ for key, params in dict(
+ bitbucket=(remote.BitBucket.URL_RE, self.bitbucket_generator),
+ trac=(re.compile(r'\Ahttps?://trac.(?P<domain>\S+)\Z'), self.trac_generator),
+ github=(remote.GitHub.URL_RE, self.github_generator),
+ ).items():
+ regex, generator = params
+ if regex.match(url):
+ self.type = key
+ self._redirect = generator(url)
+ break
+ if not self.type:
+ raise TypeError("'{}' is not a recognized redirect base")
+ self.name = name or self.type
+
+ def __call__(self, commit):
+ if not self._redirect:
+ abort(Response("No valid redirect for '{}'".format(self.url), status=500))
+ return self._redirect(commit)
+
+
+class CheckoutRoute(AuthedBlueprint):
+ def __init__(self, checkout, redirectors=None, import_name=__name__, auth_decorator=None, database=None):
+ super(CheckoutRoute, self).__init__('checkout', import_name, url_prefix=None, auth_decorator=auth_decorator)
+
+ self.checkout = checkout
+ self.database = database or Database()
+
+ if not redirectors:
+ redirectors = [Redirector(self.checkout.url)]
+ self.redirectors = redirectors
+
+ self.add_url_rule('/', 'landing', lambda: self.redirectors[0](None), methods=('GET',))
+ self.add_url_rule(
+ '/<path:ref>', 'redirect',
+ lambda ref: self.redirectors[0](self.commit(ref)),
+ methods=('GET',),
+ )
+ for redirector in self.redirectors:
+ self.add_url_rule(
+ '/{}'.format(redirector.name), 'landing-{}'.format(redirector.name),
+ lambda redirector=redirector: redirector(None),
+ methods=('GET',),
+ )
+ self.add_url_rule(
+ '/<path:ref>/{}'.format(redirector.name), 'redirect-{}'.format(redirector.name),
+ lambda ref, redirector=redirector: redirector(self.commit(ref)), methods=('GET',),
+ )
+
+ self.add_url_rule('/<path:ref>/json', 'api', self.api, methods=('GET',))
+ self.add_url_rule('/changeset/<path:revision>/webkit', 'trac', self.trac, methods=('GET',))
+
+ def commit(self, ref=None):
+ try:
+ retrieved = self.database.get(ref)
+ if retrieved:
+ return Commit.from_json(retrieved)
+ except (redis.exceptions.ResponseError, TypeError, ValueError) as e:
+ sys.stderr.write('{}\n'.format(e))
+
+ if not self.checkout.repository:
+ return None
+
+ commit = self.checkout.repository.find(ref) if ref else self.checkout.repository.commit()
+ if not commit:
+ return commit
+
+ encoded = json.dumps(commit, cls=Commit.Encoder)
+ self.database.set(commit.hash, encoded)
+ self.database.set('r{}'.format(commit.revision), encoded)
+ self.database.set(str(commit), encoded)
+
+ return commit
+
+ def api(self, ref):
+ try:
+ return current_app.response_class(
+ fjson.dumps(Commit.Encoder().default(self.commit(ref)), indent=4),
+ mimetype='application/json',
+ )
+
+ except BaseException as e:
+ return current_app.response_class(
+ fjson.dumps(dict(
+ status='Not Found',
+ message='No commit with the specified reference could be found',
+ error=str(e),
+ ), indent=4),
+ mimetype='application/json',
+ status=404,
+ )
+
+ def trac(self, revision):
+ try:
+ return self.redirectors[0](self.commit('r{}'.format(int(revision))))
+ except BaseException as e:
+ return current_app.response_class(
+ fjson.dumps(dict(
+ status='Server Error',
+ message='Failed to digest trac link',
+ error=str(e),
+ ), indent=4),
+ mimetype='application/json',
+ status=500,
+ )
Added: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/database.py (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/database.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/database.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,76 @@
+# Copyright (C) 2021 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 webkitcorepy import Environment
+
+
+class Database(object):
+ _instance = None
+ HOST_ENV = 'REDIS_HOST'
+ PASSWORD_ENV = 'REDIS_PASSWORD'
+ EXPIRATION_ENV = 'REDIS_DEFAULT_EXPIRATION'
+ DEFAULT_EXPIRATION = 60 * 60 * 24
+
+ @classmethod
+ def instance(cls, **kwargs):
+ if not cls._instance:
+ cls._instance = cls(**kwargs)
+ return cls._instance
+
+ def __init__(self, host=None, password=None, partition=None, default_expiration=None):
+ self.partition = partition or ''
+ if default_expiration is None:
+ self.default_expiration = int(Environment.instance().get(self.EXPIRATION_ENV) or self.DEFAULT_EXPIRATION)
+ else:
+ self.default_expiration = int(default_expiration)
+
+ self.host = host or Environment.instance().get(self.HOST_ENV)
+ self.password = password or Environment.instance().get(self.PASSWORD_ENV)
+
+ if host:
+ import redis
+ self._redis = redis.Redis(host=host, password=password)
+ else:
+ import fakeredis
+ self._redis = fakeredis.FakeStrictRedis()
+
+ def ping(self):
+ return self._redis.ping()
+
+ def get(self, name):
+ return self._redis.get(self.partition + name)
+
+ def set(self, name, value, **kwargs):
+ if 'ex' not in kwargs and self.default_expiration:
+ kwargs['ex'] = self.default_expiration
+ return self._redis.set(self.partition + name, value, **kwargs)
+
+ def lock(self, name, **kwargs):
+ return self._redis.lock(self.partition + name, **kwargs)
+
+ def delete(self, *names):
+ names_to_delete = [self.partition + name for name in names]
+ return self._redis.delete(*names_to_delete)
+
+ def scan_iter(self, match=None, **kwargs):
+ for key in self._redis.scan_iter(match=self.partition + match, **kwargs):
+ yield key[len(self.partition):]
Copied: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/checkout_unittest.py (from rev 286574, trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py) (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/checkout_unittest.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/checkout_unittest.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,49 @@
+# Copyright (C) 2021 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 os
+import unittest
+
+from reporelaypy import Checkout
+from webkitcorepy import testing, OutputCapture
+from webkitscmpy import mocks
+
+
+class CheckoutUnittest(testing.PathTestCase):
+ basepath = 'mock/repository'
+
+ def setUp(self):
+ super(CheckoutUnittest, self).setUp()
+ os.mkdir(os.path.join(self.path, '.git'))
+
+ def test_constructor_sentinal(self):
+ with mocks.local.Git(self.path) as repo, OutputCapture():
+ with open(os.path.join(os.path.dirname(self.path), 'cloned'), 'w') as sentinal:
+ sentinal.write('yes\n')
+ Checkout(path=self.path, url="" sentinal=True)
+
+ def test_constructor_no_sentinal(self):
+ with mocks.local.Git(self.path) as repo:
+ Checkout(path=self.path, url="" sentinal=False)
+
+ with self.assertRaises(Checkout.Exception):
+ Checkout(path=self.path, url="" sentinal=True)
Added: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/checkoutroute_unittest.py (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/checkoutroute_unittest.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/checkoutroute_unittest.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,176 @@
+# Copyright (C) 2021 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 os
+import json
+import unittest
+
+from reporelaypy import Checkout, CheckoutRoute, Redirector
+from webkitcorepy import testing, OutputCapture
+from webkitflaskpy import mock_app
+from webkitscmpy import mocks, Commit
+
+
+class RedirectorUnittest(unittest.TestCase):
+ def test_empty(self):
+ self.assertEqual(
+ [],
+ Redirector.from_json(json.dumps([], cls=Redirector.Encoder)),
+ )
+
+ def test_bitbucket(self):
+ redir = Redirector('https://bitbucket.webkit.org/projects/WEBKIT/repos/webkit')
+ self.assertEqual(redir.type, 'bitbucket')
+ self.assertEqual(
+ redir(None).headers.get('location'),
+ 'https://bitbucket.webkit.org/projects/WEBKIT/repos/webkit/commits',
+ )
+ self.assertEqual(
+ redir(Commit(hash='deadbeef')).headers.get('location'),
+ 'https://bitbucket.webkit.org/projects/WEBKIT/repos/webkit/commits/deadbeef',
+ )
+
+ transfered = Redirector.from_json(json.dumps([redir], cls=Redirector.Encoder))[0]
+ self.assertEqual(redir.type, transfered.type)
+ self.assertEqual(redir(None).headers.get('location'), transfered(None).headers.get('location'))
+ self.assertEqual(
+ redir(Commit(hash='deadbeef')).headers.get('location'),
+ transfered(Commit(hash='deadbeef')).headers.get('location'),
+ )
+
+ def test_trac(self):
+ redir = Redirector('https://trac.webkit.org')
+ self.assertEqual(redir.type, 'trac')
+ self.assertEqual(
+ redir(None).headers.get('location'),
+ 'https://trac.webkit.org',
+ )
+ self.assertEqual(
+ redir(Commit(revision=12345)).headers.get('location'),
+ 'https://trac.webkit.org/changeset/12345/webkit',
+ )
+
+ transfered = Redirector.from_json(json.dumps([redir], cls=Redirector.Encoder))[0]
+ self.assertEqual(redir.type, transfered.type)
+ self.assertEqual(redir(None).headers.get('location'), transfered(None).headers.get('location'))
+ self.assertEqual(
+ redir(Commit(revision=12345)).headers.get('location'),
+ transfered(Commit(revision=12345)).headers.get('location'),
+ )
+
+ def test_github(self):
+ redir = Redirector('https://github.com/WebKit/WebKit')
+ self.assertEqual(redir.type, 'github')
+ self.assertEqual(
+ redir(None).headers.get('location'),
+ 'https://github.com/WebKit/WebKit/commits',
+ )
+ self.assertEqual(
+ redir(Commit(hash='deadbeef')).headers.get('location'),
+ 'https://github.com/WebKit/WebKit/commit/deadbeef',
+ )
+
+ transfered = Redirector.from_json(json.dumps([redir], cls=Redirector.Encoder))[0]
+ self.assertEqual(redir.type, transfered.type)
+ self.assertEqual(redir(None).headers.get('location'), transfered(None).headers.get('location'))
+ self.assertEqual(
+ redir(Commit(hash='deadbeef')).headers.get('location'),
+ transfered(Commit(hash='deadbeef')).headers.get('location'),
+ )
+
+
+class CheckoutRouteUnittest(testing.PathTestCase):
+ basepath = 'mock/repository'
+
+ def setUp(self):
+ super(CheckoutRouteUnittest, self).setUp()
+ os.mkdir(os.path.join(self.path, '.git'))
+
+ @mock_app
+ def test_landing(self, app=None, client=None):
+ with mocks.local.Git(self.path) as repo:
+ app.register_blueprint(CheckoutRoute(
+ Checkout(path=self.path, url="" sentinal=False),
+ redirectors=[Redirector('https://trac.webkit.org')],
+ ))
+ response = client.get('/')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.headers.get('location'), 'https://trac.webkit.org')
+
+ @mock_app
+ def test_json(self, app=None, client=None):
+ with mocks.local.Git(self.path) as repo:
+ app.register_blueprint(CheckoutRoute(
+ Checkout(path=self.path, url="" sentinal=False),
+ redirectors=[Redirector('https://trac.webkit.org')],
+ ))
+ reference = Commit.Encoder().default(repo.commits['main'][3])
+ reference['message'] = reference['message'].rstrip()
+
+ response = client.get('4@main/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), reference)
+
+ @mock_app
+ def test_redirect(self, app=None, client=None):
+ with mocks.local.Git(self.path) as repo:
+ app.register_blueprint(CheckoutRoute(
+ Checkout(path=self.path, url="" sentinal=False),
+ redirectors=[Redirector('https://github.com/WebKit/WebKit')],
+ ))
+
+ response = client.get('1@branch-a')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(
+ response.headers.get('location'),
+ 'https://github.com/WebKit/WebKit/commit/a30ce8494bf1ac2807a69844f726be4a9843ca55',
+ )
+
+ @mock_app
+ def test_invoked_redirect(self, app=None, client=None):
+ with mocks.local.Git(self.path, git_svn=True) as repo:
+ app.register_blueprint(CheckoutRoute(
+ Checkout(path=self.path, url="" sentinal=False),
+ redirectors=[Redirector('https://github.com/WebKit/WebKit'), Redirector('https://trac.webkit.org')],
+ ))
+
+ response = client.get('a30ce849/trac')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(
+ response.headers.get('location'),
+ 'https://trac.webkit.org/changeset/3/webkit',
+ )
+
+ @mock_app
+ def test_trac(self, app=None, client=None):
+ with mocks.local.Git(self.path, git_svn=True) as repo:
+ app.register_blueprint(CheckoutRoute(
+ Checkout(path=self.path, url="" sentinal=False),
+ redirectors=[Redirector('https://github.com/WebKit/WebKit')],
+ ))
+
+ response = client.get('/changeset/6/webkit')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(
+ response.headers.get('location'),
+ 'https://github.com/WebKit/WebKit/commit/621652add7fc416099bd2063366cc38ff61afe36',
+ )
Added: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/database_unittest.py (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/database_unittest.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/tests/database_unittest.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,99 @@
+# Copyright (C) 2021 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 reporelaypy import Database
+
+
+class DatabaseUnittest(unittest.TestCase):
+ PARTITION_1 = 'partition_1:'
+ PARTITION_2 = 'partition_2;'
+ EMPTY_PARTITION = None
+
+ def test_get(self):
+ redis_1 = Database(partition=self.PARTITION_1)
+ redis_2 = Database(partition=self.PARTITION_2)
+ redis_3 = Database(partition=self.EMPTY_PARTITION)
+ redis_1.set('key', 'value')
+ redis_2.set('key', 'other')
+ redis_3.set('key', 'empty')
+
+ self.assertEqual(redis_1.get('key').decode('utf-8'), 'value')
+ self.assertEqual(redis_2.get('key').decode('utf-8'), 'other')
+ self.assertEqual(redis_3.get('key').decode('utf-8'), 'empty')
+
+ def test_delete(self):
+ redis_1 = Database(partition=self.PARTITION_1)
+ redis_2 = Database(partition=self.PARTITION_2)
+ redis_3 = Database(partition=self.EMPTY_PARTITION)
+
+ redis_1.set('key-a', 'value-a')
+ redis_1.set('key-b', 'value-b')
+ redis_2.set('key-a', 'other-a')
+ redis_2.set('key-b', 'other-b')
+ redis_3.set('key-a', 'empty-a')
+ redis_3.set('key-b', 'empty-b')
+
+ redis_1.delete('key-a', 'key-b')
+ self.assertEqual(redis_1.get('key-a'), None)
+ self.assertEqual(redis_1.get('key-b'), None)
+
+ self.assertEqual(redis_2.get('key-a').decode('utf-8'), 'other-a')
+ self.assertEqual(redis_2.get('key-b').decode('utf-8'), 'other-b')
+ self.assertEqual(redis_3.get('key-a').decode('utf-8'), 'empty-a')
+ self.assertEqual(redis_3.get('key-b').decode('utf-8'), 'empty-b')
+
+ redis_2.delete('key-a')
+ self.assertEqual(redis_2.get('key-a'), None)
+ self.assertEqual(redis_2.get('key-b').decode('utf-8'), 'other-b')
+
+ redis_3.delete('key-a')
+ self.assertEqual(redis_3.get('key-a'), None)
+ self.assertEqual(redis_3.get('key-b').decode('utf-8'), 'empty-b')
+
+ def test_lock(self):
+ redis_1 = Database(partition=self.PARTITION_1)
+ redis_2 = Database(partition=self.PARTITION_2)
+ redis_3 = Database(partition=self.EMPTY_PARTITION)
+ with redis_1.lock(name='lock', blocking_timeout=.5):
+ with redis_2.lock(name='lock', blocking_timeout=.5):
+ with redis_3.lock(name='lock', blocking_timeout=.5):
+ pass
+
+ def test_scan(self):
+ redis_1 = Database(partition=self.PARTITION_1)
+ redis_2 = Database(partition=self.PARTITION_2)
+ redis_3 = Database(partition=self.EMPTY_PARTITION)
+ redis_1.set('iter-a', 'value-a')
+ redis_1.set('iter-b', 'value-b')
+ redis_2.set('iter-c', 'value-c')
+ redis_2.set('iter-d', 'value-d')
+ redis_3.set('iter-e', 'value-e')
+ redis_3.set('iter-f', 'value-f')
+
+ for key in redis_1.scan_iter('iter*'):
+ self.assertIn(key.decode('utf-8'), ['iter-a', 'iter-b'])
+ for key in redis_2.scan_iter('iter*'):
+ self.assertIn(key.decode('utf-8'), ['iter-c', 'iter-d'])
+ for key in redis_3.scan_iter('iter*'):
+ self.assertIn(key.decode('utf-8'), ['iter-e', 'iter-f'])
Copied: trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/webserver.py (from rev 286574, trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py) (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/webserver.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/reporelaypy/webserver.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,53 @@
+# Copyright (C) 2021 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 os
+
+autoinstall_path = os.environ.get('AUTOINSTALL_PATH')
+if autoinstall_path:
+ from webkitcorepy import AutoInstall
+ AutoInstall.set_directory(autoinstall_path)
+
+from flask import Flask, current_app, json as fjson
+from reporelaypy import Checkout, CheckoutRoute, Database, Redirector
+
+app = Flask(__name__)
+
+database = Database()
+checkout = Checkout.from_json(os.environ.get('CHECKOUT', '{}'))
+checkout_routes = CheckoutRoute(
+ checkout, redirectors=Redirector.from_json(os.environ.get('REDIRECTORS', '[]')),
+ import_name=__name__, database=database,
+)
+
+
[email protected]('/__health')
+def health():
+ return 'ready' if checkout.repository else 'cloning'
+
+
+app.register_blueprint(checkout_routes)
+
+
+if __name__ == '__main__':
+ port = int(os.environ.get('PORT', 5000))
+ app.run(host='0.0.0.0', port=port)
Added: trunk/Tools/Scripts/libraries/reporelaypy/run (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/run (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/run 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2020 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 argparse
+import json
+import math
+import os
+import subprocess
+import sys
+import time
+
+import reporelaypy
+import webkitcorepy
+
+scripts = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+if os.path.isdir(os.path.join(scripts, 'webkitpy')):
+ sys.path.insert(0, scripts)
+ import webkitpy
+
+from reporelaypy import Checkout, Database, Redirector
+from webkitcorepy import arguments, AutoInstall
+from whichcraft import which
+
+
+def main(args=None):
+ parser = argparse.ArgumentParser(description='Run a git relay server for testing')
+
+ group = parser.add_argument_group('Repository')
+ group.add_argument(
+ '--path', '-p', '-C',
+ dest='path', default=os.getcwd(), action='',
+ help='Set the repository path to be used',
+ )
+ group.add_argument(
+ '--http-proxy', dest='http_proxy', default=None, action='',
+ help='Set an http proxy',
+ )
+ group.add_argument(
+ '--url', dest='url', default=None, action='',
+ help='Set the repository path to be used',
+ )
+ parser.add_argument(
+ '--sentinal', '--no-sentinal', action="" dest='sentinal', default=False,
+ help='Lay down sentinal file on disk to determine checkout state.',
+ )
+ group.add_argument(
+ '--update', dest='update_interval', default=0, action='', type=int,
+ help='Set the number of seconds to wait between polling git checkout',
+ )
+
+ group = parser.add_argument_group('Database')
+ group.add_argument(
+ '--redis-host', dest='redis_host', default=None, action='',
+ help="Set the hostname of the Redis database to be used. (Environment variable '{}' used by default)".format(Database.HOST_ENV),
+ )
+ group.add_argument(
+ '--redis-password', dest='redis_password', default=None, action='',
+ help="Set the password of the Redis database to be used. (Environment variable '{}' used by default)".format(Database.PASSWORD_ENV),
+ )
+ group.add_argument(
+ '--redis-expiration', dest='redis_expiration', default=Database.DEFAULT_EXPIRATION, action='', type=int,
+ help="Set the default Redis expiration time in seconds",
+ )
+
+ group = parser.add_argument_group('Webserver')
+ group.add_argument(
+ '--port', dest='port', default=5000, action='',
+ help='Port to expose webserver on.',
+ )
+ group.add_argument(
+ '--poll', dest='poll', default=10, action='',
+ help='Number of seconds between polls of the webserver process.',
+ )
+ group.add_argument(
+ '--redirector', '-r', dest='redirector', action='',
+ help='Base URL to redirect user to for commit information.',
+ )
+
+ args = parser.parse_args(args=args)
+
+ database = Database(host=args.redis_host, password=args.redis_password)
+ print('Constructed database with parameters:')
+ print(' Host: {}'.format(database.host or 'None (in-memory fake database)'))
+ if database.password:
+ print(' Password: {}'.format(len(database.password) * '*'))
+ if database.default_expiration:
+ print(' Expiration: {} hours'.format(database.default_expiration / (60.0 * 60.0)))
+ print('Connecting to database...')
+ database.ping()
+ print('Connected to database!')
+
+ print('Finding checkout...')
+ checkout = Checkout(path=args.path, url="" http_proxy=args.http_proxy, sentinal=args.sentinal)
+ print('Git checkout:')
+ print(' Path: {}'.format(checkout.path))
+ print(' URL: {}'.format(checkout.url))
+ if not checkout.repository:
+ print(' Cloning repository...')
+
+ if args.update_interval:
+ print(' Polling checkout with interval of {} seconds, doing first poll now...'.format(args.update_interval))
+ checkout.update_all()
+
+ if args.redirector:
+ print('User specified the following redirect urls:')
+ for url in args.redirector:
+ redirector = Redirector(url)
+ print(' {}: {}'.format(redirector.name, redirector.url))
+
+ env = dict(
+ PYTHONPATH=':'.join(sys.path),
+ CHECKOUT=json.dumps(checkout, cls=Checkout.Encoder),
+ REDIRECTORS=json.dumps([Redirector(url) for url in args.redirector or []], cls=Redirector.Encoder)
+ )
+
+ if AutoInstall.directory:
+ env['AUTOINSTALL_PATH'] = AutoInstall.directory
+ if database.host:
+ env[database.HOST_ENV] = database.host
+ if database.password:
+ env[database.PASSWORD_ENV] = database.password
+ if database.default_expiration:
+ env[database.EXPIRATION_ENV] = str(database.default_expiration)
+
+ with subprocess.Popen(
+ [which('gunicorn'), 'reporelaypy.webserver:app'],
+ cwd=os.path.dirname(os.path.dirname(reporelaypy.__file__)),
+ env=env,
+ ) as webserver:
+ last_poll = time.time()
+ last_pull = time.time()
+
+ while True:
+ if last_poll + args.poll < time.time():
+ if webserver.poll() is None:
+ break
+ last_poll = time.time()
+ if last_pull + args.update_interval < time.time():
+ checkout.update_all()
+ last_pull = time.time()
+ time.sleep(math.gcd(args.poll, args.update_interval))
+
+ return 0
+
+
+if '__main__' == __name__:
+ sys.exit(main(args=sys.argv[1:]))
Property changes on: trunk/Tools/Scripts/libraries/reporelaypy/run
___________________________________________________________________
Added: svn:executable
+*
\ No newline at end of property
Copied: trunk/Tools/Scripts/libraries/reporelaypy/setup.py (from rev 286574, trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py) (0 => 286576)
--- trunk/Tools/Scripts/libraries/reporelaypy/setup.py (rev 0)
+++ trunk/Tools/Scripts/libraries/reporelaypy/setup.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,66 @@
+# Copyright (C) 2021 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 setuptools import setup
+
+
+def readme():
+ with open('README.md') as f:
+ return f.read()
+
+
+setup(
+ name='reporelaypy',
+ version='0.1.0',
+ description='Library for visualizing, processing and storing test results.',
+ long_description=readme(),
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Framework :: Flask',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: MacOS',
+ 'Natural Language :: English',
+ 'Programming Language :: Python :: 3 :: Only',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: Software Development :: Version Control :: Git',
+ ],
+ keywords='git hook mirror webkit',
+ url='',
+ author='Jonathan Bedard',
+ author_email='[email protected]',
+ license='Modified BSD',
+ packages=[
+ 'reporelaypy',
+ 'reporelaypy.test',
+ ],
+ install_requires=[
+ 'fakeredis',
+ 'redis',
+ 'xmltodict',
+ 'webkitcorepy',
+ 'webkitscmpy',
+ 'webkitflaskpy',
+ ],
+ include_package_data=True,
+ zip_safe=False,
+)
Modified: trunk/Tools/Scripts/libraries/resultsdbpy/resultsdbpy/__init__.py (286575 => 286576)
--- trunk/Tools/Scripts/libraries/resultsdbpy/resultsdbpy/__init__.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/libraries/resultsdbpy/resultsdbpy/__init__.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -44,7 +44,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(3, 1, 5)
+version = Version(3, 1, 6)
import webkitflaskpy
Modified: trunk/Tools/Scripts/libraries/resultsdbpy/resultsdbpy/flask_support/flask_testcase.py (286575 => 286576)
--- trunk/Tools/Scripts/libraries/resultsdbpy/resultsdbpy/flask_support/flask_testcase.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/libraries/resultsdbpy/resultsdbpy/flask_support/flask_testcase.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -21,31 +21,17 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import atexit
-import json
import os
import requests
import unittest
from flask import Flask
-from flask.wrappers import Response
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from resultsdbpy.flask_support.flask_test_context import FlaskTestContext
+from webkitflaskpy import Response
-class FlaskRequestsResponse(Response):
- @property
- def text(self):
- return self.data.decode('utf-8')
-
- @property
- def content(self):
- return self.data
-
- def json(self):
- return json.loads(self.text)
-
-
class FlaskTestCase(unittest.TestCase):
URL = ""
@@ -104,7 +90,7 @@
def decorator(method):
def real_method(val, method=method, **kwargs):
app = Flask('testing')
- app.response_class = FlaskRequestsResponse
+ app.response_class = Response
app.config['TESTING'] = True
val.setup_webserver(app, **kwargs)
app.add_url_rule('/__health', 'health', lambda: 'ok', methods=('GET',))
Modified: trunk/Tools/Scripts/libraries/resultsdbpy/setup.py (286575 => 286576)
--- trunk/Tools/Scripts/libraries/resultsdbpy/setup.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/libraries/resultsdbpy/setup.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -30,7 +30,7 @@
setup(
name='resultsdbpy',
- version='3.1.5',
+ version='3.1.6',
description='Library for visualizing, processing and storing test results.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py (286575 => 286576)
--- trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -30,8 +30,8 @@
setup(
name='webkitflaskpy',
- version='0.2.0',
- description='Library for visualizing, processing and storing test results.',
+ version='0.3.0',
+ description="Library supporting the WebKit Team's flask based web services.",
long_description=readme(),
classifiers=[
'Development Status :: 4 - Beta',
Modified: trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/__init__.py (286575 => 286576)
--- trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/__init__.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/__init__.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -43,7 +43,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(0, 2, 0)
+version = Version(0, 3, 0)
AutoInstall.register(Package('click', Version(7, 1, 2)))
AutoInstall.register(Package('flask', Version(1, 1, 2)))
@@ -53,5 +53,7 @@
AutoInstall.register(Package('werkzeug', Version(1, 0, 1)))
from webkitflaskpy.authed_blueprint import AuthedBlueprint # noqa: E402
+from webkitflaskpy.response import Response
+from webkitflaskpy.mock_app import mock_app
name = 'webkitflaskpy'
Copied: trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/mock_app.py (from rev 286574, trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py) (0 => 286576)
--- trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/mock_app.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/mock_app.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,37 @@
+# Copyright (C) 2021 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 flask import Flask
+
+from .response import Response
+
+
+def mock_app(method):
+ def real_method(val, method=method, **kwargs):
+ app = Flask('testing')
+ app.response_class = Response
+ app.config['TESTING'] = True
+ app.add_url_rule('/__health', 'health', lambda: 'ok', methods=('GET',))
+ return method(val, app=app, client=app.test_client(), **kwargs)
+
+ real_method.__name__ = method.__name__
+ return real_method
Copied: trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/response.py (from rev 286574, trunk/Tools/Scripts/libraries/webkitflaskpy/setup.py) (0 => 286576)
--- trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/response.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitflaskpy/webkitflaskpy/response.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -0,0 +1,38 @@
+# Copyright (C) 2021 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 json
+
+from flask.wrappers import Response as FlaskResponse
+
+
+class Response(FlaskResponse):
+ @property
+ def text(self):
+ return self.data.decode('utf-8')
+
+ @property
+ def content(self):
+ return self.data
+
+ def json(self):
+ return json.loads(self.text)
Modified: trunk/Tools/Scripts/webkitpy/__init__.py (286575 => 286576)
--- trunk/Tools/Scripts/webkitpy/__init__.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/webkitpy/__init__.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -80,6 +80,10 @@
AutoInstall.register(Package('zipp', Version(1, 2, 0)))
AutoInstall.register(Package('zope.interface', Version(5, 1, 0), aliases=['zope'], pypi_name='zope-interface'))
+if sys.version_info > (3, 0):
+ AutoInstall.register(Package('reporelaypy', Version(0, 1, 0)), local=True)
+
+AutoInstall.register(Package('webkitflaskpy', Version(0, 1, 1)), local=True)
AutoInstall.register(Package('webkitscmpy', Version(0, 12, 5)), local=True)
import webkitscmpy
Modified: trunk/Tools/Scripts/webkitpy/test/main.py (286575 => 286576)
--- trunk/Tools/Scripts/webkitpy/test/main.py 2021-12-07 00:06:10 UTC (rev 286575)
+++ trunk/Tools/Scripts/webkitpy/test/main.py 2021-12-07 00:18:20 UTC (rev 286576)
@@ -65,6 +65,8 @@
tester.add_tree(os.path.join(_webkit_root, 'Tools', 'Scripts', 'libraries', 'webkitcorepy'), 'webkitcorepy')
tester.add_tree(os.path.join(_webkit_root, 'Tools', 'Scripts', 'libraries', 'webkitscmpy'), 'webkitscmpy')
tester.add_tree(os.path.join(_webkit_root, 'Tools', 'Scripts', 'libraries', 'webkitflaskpy'), 'webkitflaskpy')
+ if sys.version_info > (3, 0):
+ tester.add_tree(os.path.join(_webkit_root, 'Tools', 'Scripts', 'libraries', 'reporelaypy'), 'reporelaypy')
# AppleWin is the only platform that does not support Modern WebKit
# FIXME: Find a better way to detect this currently assuming cygwin means AppleWin
@@ -188,6 +190,12 @@
sys.path = self.finder.additional_paths(sys.path) + sys.path
from webkitcorepy import AutoInstall
+
+ # Force registration of all autoinstalled packages.
+ if sys.version_info > (3, 0):
+ import reporelaypy
+ import webkitflaskpy
+
AutoInstall.install_everything()
start_time = time.time()