Share a fake pull request database across connections
Because connections can be recreated, ensure that the fake pull
request database (really a dictionary) is shared across instances
of connections and fake github classes.
Also, move the fake github3 classes to their own file -- they were
getting larger and unruly.
Change-Id: I471c1487039c8b25a0bab95d918f31b92b9cd32b
diff --git a/tests/base.py b/tests/base.py
index 9a8878e..59c0d2a 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -40,7 +40,6 @@
import uuid
import urllib
-
import git
import gear
import fixtures
@@ -53,6 +52,7 @@
from git.exc import NoSuchPathError
import yaml
+import tests.fakegithub
import zuul.driver.gerrit.gerritsource as gerritsource
import zuul.driver.gerrit.gerritconnection as gerritconnection
import zuul.driver.github.githubconnection as githubconnection
@@ -601,194 +601,6 @@
_points_to_commits_only = True
-class FakeGithub(object):
-
- class FakeUser(object):
- def __init__(self, login):
- self.login = login
- self.name = "Github User"
- self.email = "github.user@example.com"
-
- class FakeBranch(object):
- def __init__(self, branch='master'):
- self.name = branch
-
- class FakeStatus(object):
- def __init__(self, state, url, description, context, user):
- self._state = state
- self._url = url
- self._description = description
- self._context = context
- self._user = user
-
- def as_dict(self):
- return {
- 'state': self._state,
- 'url': self._url,
- 'description': self._description,
- 'context': self._context,
- 'creator': {
- 'login': self._user
- }
- }
-
- class FakeCommit(object):
- def __init__(self):
- self._statuses = []
-
- def set_status(self, state, url, description, context, user):
- status = FakeGithub.FakeStatus(
- state, url, description, context, user)
- # always insert a status to the front of the list, to represent
- # the last status provided for a commit.
- self._statuses.insert(0, status)
-
- def statuses(self):
- return self._statuses
-
- class FakeRepository(object):
- def __init__(self):
- self._branches = [FakeGithub.FakeBranch()]
- self._commits = {}
-
- def branches(self, protected=False):
- if protected:
- # simulate there is no protected branch
- return []
- return self._branches
-
- def create_status(self, sha, state, url, description, context,
- user='zuul'):
- # Since we're bypassing github API, which would require a user, we
- # default the user as 'zuul' here.
- commit = self._commits.get(sha, None)
- if commit is None:
- commit = FakeGithub.FakeCommit()
- self._commits[sha] = commit
- commit.set_status(state, url, description, context, user)
-
- def commit(self, sha):
- commit = self._commits.get(sha, None)
- if commit is None:
- commit = FakeGithub.FakeCommit()
- self._commits[sha] = commit
- return commit
-
- class FakeLabel(object):
- def __init__(self, name):
- self.name = name
-
- class FakeIssue(object):
- def __init__(self, fake_pull_request):
- self._fake_pull_request = fake_pull_request
-
- def pull_request(self):
- return FakeGithub.FakePull(self._fake_pull_request)
-
- def labels(self):
- return [FakeGithub.FakeLabel(l)
- for l in self._fake_pull_request.labels]
-
- class FakeFile(object):
- def __init__(self, filename):
- self.filename = filename
-
- class FakePull(object):
- def __init__(self, fake_pull_request):
- self._fake_pull_request = fake_pull_request
-
- def issue(self):
- return FakeGithub.FakeIssue(self._fake_pull_request)
-
- def files(self):
- return [FakeGithub.FakeFile(fn)
- for fn in self._fake_pull_request.files]
-
- def as_dict(self):
- pr = self._fake_pull_request
- connection = pr.github
- data = {
- 'number': pr.number,
- 'title': pr.subject,
- 'url': 'https://%s/%s/pull/%s' % (
- connection.server, pr.project, pr.number
- ),
- 'updated_at': pr.updated_at,
- 'base': {
- 'repo': {
- 'full_name': pr.project
- },
- 'ref': pr.branch,
- },
- 'mergeable': True,
- 'state': pr.state,
- 'head': {
- 'sha': pr.head_sha,
- 'repo': {
- 'full_name': pr.project
- }
- },
- 'merged': pr.is_merged,
- 'body': pr.body
- }
- return data
-
- class FakeIssueSearchResult(object):
- def __init__(self, issue):
- self.issue = issue
-
- def __init__(self, connection):
- self._fake_github_connection = connection
- self._repos = {}
-
- def user(self, login):
- return self.FakeUser(login)
-
- def repository(self, owner, proj):
- return self._repos.get((owner, proj), None)
-
- def repo_from_project(self, project):
- # This is a convenience method for the tests.
- owner, proj = project.split('/')
- return self.repository(owner, proj)
-
- def addProject(self, project):
- owner, proj = project.name.split('/')
- self._repos[(owner, proj)] = self.FakeRepository()
-
- def pull_request(self, owner, project, number):
- fake_pr = self._fake_github_connection.pull_requests[number - 1]
- return self.FakePull(fake_pr)
-
- def search_issues(self, query):
- def tokenize(s):
- return re.findall(r'[\w]+', s)
-
- parts = tokenize(query)
- terms = set()
- results = []
- for part in parts:
- kv = part.split(':', 1)
- if len(kv) == 2:
- if kv[0] in set('type', 'is', 'in'):
- # We only perform one search now and these aren't
- # important; we can honor these terms later if
- # necessary.
- continue
- terms.add(part)
-
- for pr in self._fake_github_connection.pull_requests:
- if not pr.body:
- body = set()
- else:
- body = set(tokenize(pr.body))
- if terms.intersection(body):
- issue = FakeGithub.FakeIssue(pr)
- results.append(FakeGithub.FakeIssueSearchResult(issue))
-
- return results
-
-
class FakeGithubPullRequest(object):
def __init__(self, github, number, project, branch,
@@ -1114,18 +926,18 @@
log = logging.getLogger("zuul.test.FakeGithubConnection")
def __init__(self, driver, connection_name, connection_config,
- upstream_root=None):
+ changes_db=None, upstream_root=None):
super(FakeGithubConnection, self).__init__(driver, connection_name,
connection_config)
self.connection_name = connection_name
self.pr_number = 0
- self.pull_requests = []
+ self.pull_requests = changes_db
self.statuses = {}
self.upstream_root = upstream_root
self.merge_failure = False
self.merge_not_allowed_count = 0
self.reports = []
- self.github_client = FakeGithub(self)
+ self.github_client = tests.fakegithub.FakeGithub(changes_db)
def getGithubClient(self,
project=None,
@@ -1138,7 +950,7 @@
pull_request = FakeGithubPullRequest(
self, self.pr_number, project, branch, subject, self.upstream_root,
files=files, body=body)
- self.pull_requests.append(pull_request)
+ self.pull_requests[self.pr_number] = pull_request
return pull_request
def getPushEvent(self, project, ref, old_rev=None, new_rev=None,
@@ -1186,7 +998,7 @@
self.getGithubClient(project).addProject(project)
def getPullBySha(self, sha, project):
- prs = list(set([p for p in self.pull_requests if
+ prs = list(set([p for p in self.pull_requests.values() if
sha == p.head_sha and project == p.project]))
if len(prs) > 1:
raise Exception('Multiple pulls found with head sha: %s' % sha)
@@ -1194,12 +1006,12 @@
return self.getPull(pr.project, pr.number)
def _getPullReviews(self, owner, project, number):
- pr = self.pull_requests[number - 1]
+ pr = self.pull_requests[number]
return pr.reviews
def getRepoPermission(self, project, login):
owner, proj = project.split('/')
- for pr in self.pull_requests:
+ for pr in self.pull_requests.values():
pr_owner, pr_project = pr.project.split('/')
if (pr_owner == owner and proj == pr_project):
if login in pr.writers:
@@ -1216,13 +1028,13 @@
def commentPull(self, project, pr_number, message):
# record that this got reported
self.reports.append((project, pr_number, 'comment'))
- pull_request = self.pull_requests[pr_number - 1]
+ pull_request = self.pull_requests[pr_number]
pull_request.addComment(message)
def mergePull(self, project, pr_number, commit_message='', sha=None):
# record that this got reported
self.reports.append((project, pr_number, 'merge'))
- pull_request = self.pull_requests[pr_number - 1]
+ pull_request = self.pull_requests[pr_number]
if self.merge_failure:
raise Exception('Pull request was not merged')
if self.merge_not_allowed_count > 0:
@@ -1242,13 +1054,13 @@
def labelPull(self, project, pr_number, label):
# record that this got reported
self.reports.append((project, pr_number, 'label', label))
- pull_request = self.pull_requests[pr_number - 1]
+ pull_request = self.pull_requests[pr_number]
pull_request.addLabel(label)
def unlabelPull(self, project, pr_number, label):
# record that this got reported
self.reports.append((project, pr_number, 'unlabel', label))
- pull_request = self.pull_requests[pr_number - 1]
+ pull_request = self.pull_requests[pr_number]
pull_request.removeLabel(label)
@@ -2218,6 +2030,7 @@
# Set a changes database so multiple FakeGerrit's can report back to
# a virtual canonical database given by the configured hostname
self.gerrit_changes_dbs = {}
+ self.github_changes_dbs = {}
def getGerritConnection(driver, name, config):
db = self.gerrit_changes_dbs.setdefault(config['server'], {})
@@ -2233,7 +2046,10 @@
getGerritConnection))
def getGithubConnection(driver, name, config):
+ server = config.get('server', 'github.com')
+ db = self.github_changes_dbs.setdefault(server, {})
con = FakeGithubConnection(driver, name, config,
+ changes_db=db,
upstream_root=self.upstream_root)
self.event_queues.append(con.event_queue)
setattr(self, 'fake_' + name, con)
diff --git a/tests/fakegithub.py b/tests/fakegithub.py
new file mode 100644
index 0000000..6fb2d66
--- /dev/null
+++ b/tests/fakegithub.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+
+# Copyright 2018 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+
+
+class FakeUser(object):
+ def __init__(self, login):
+ self.login = login
+ self.name = "Github User"
+ self.email = "github.user@example.com"
+
+
+class FakeBranch(object):
+ def __init__(self, branch='master'):
+ self.name = branch
+
+
+class FakeStatus(object):
+ def __init__(self, state, url, description, context, user):
+ self._state = state
+ self._url = url
+ self._description = description
+ self._context = context
+ self._user = user
+
+ def as_dict(self):
+ return {
+ 'state': self._state,
+ 'url': self._url,
+ 'description': self._description,
+ 'context': self._context,
+ 'creator': {
+ 'login': self._user
+ }
+ }
+
+
+class FakeCommit(object):
+ def __init__(self):
+ self._statuses = []
+
+ def set_status(self, state, url, description, context, user):
+ status = FakeStatus(
+ state, url, description, context, user)
+ # always insert a status to the front of the list, to represent
+ # the last status provided for a commit.
+ self._statuses.insert(0, status)
+
+ def statuses(self):
+ return self._statuses
+
+
+class FakeRepository(object):
+ def __init__(self):
+ self._branches = [FakeBranch()]
+ self._commits = {}
+
+ def branches(self, protected=False):
+ if protected:
+ # simulate there is no protected branch
+ return []
+ return self._branches
+
+ def create_status(self, sha, state, url, description, context,
+ user='zuul'):
+ # Since we're bypassing github API, which would require a user, we
+ # default the user as 'zuul' here.
+ commit = self._commits.get(sha, None)
+ if commit is None:
+ commit = FakeCommit()
+ self._commits[sha] = commit
+ commit.set_status(state, url, description, context, user)
+
+ def commit(self, sha):
+ commit = self._commits.get(sha, None)
+ if commit is None:
+ commit = FakeCommit()
+ self._commits[sha] = commit
+ return commit
+
+
+class FakeLabel(object):
+ def __init__(self, name):
+ self.name = name
+
+
+class FakeIssue(object):
+ def __init__(self, fake_pull_request):
+ self._fake_pull_request = fake_pull_request
+
+ def pull_request(self):
+ return FakePull(self._fake_pull_request)
+
+ def labels(self):
+ return [FakeLabel(l)
+ for l in self._fake_pull_request.labels]
+
+
+class FakeFile(object):
+ def __init__(self, filename):
+ self.filename = filename
+
+
+class FakePull(object):
+ def __init__(self, fake_pull_request):
+ self._fake_pull_request = fake_pull_request
+
+ def issue(self):
+ return FakeIssue(self._fake_pull_request)
+
+ def files(self):
+ return [FakeFile(fn)
+ for fn in self._fake_pull_request.files]
+
+ def as_dict(self):
+ pr = self._fake_pull_request
+ connection = pr.github
+ data = {
+ 'number': pr.number,
+ 'title': pr.subject,
+ 'url': 'https://%s/%s/pull/%s' % (
+ connection.server, pr.project, pr.number
+ ),
+ 'updated_at': pr.updated_at,
+ 'base': {
+ 'repo': {
+ 'full_name': pr.project
+ },
+ 'ref': pr.branch,
+ },
+ 'mergeable': True,
+ 'state': pr.state,
+ 'head': {
+ 'sha': pr.head_sha,
+ 'repo': {
+ 'full_name': pr.project
+ }
+ },
+ 'merged': pr.is_merged,
+ 'body': pr.body
+ }
+ return data
+
+
+class FakeIssueSearchResult(object):
+ def __init__(self, issue):
+ self.issue = issue
+
+
+class FakeGithub(object):
+ def __init__(self, pull_requests):
+ self._pull_requests = pull_requests
+ self._repos = {}
+
+ def user(self, login):
+ return FakeUser(login)
+
+ def repository(self, owner, proj):
+ return self._repos.get((owner, proj), None)
+
+ def repo_from_project(self, project):
+ # This is a convenience method for the tests.
+ owner, proj = project.split('/')
+ return self.repository(owner, proj)
+
+ def addProject(self, project):
+ owner, proj = project.name.split('/')
+ self._repos[(owner, proj)] = FakeRepository()
+
+ def pull_request(self, owner, project, number):
+ fake_pr = self._pull_requests[number]
+ return FakePull(fake_pr)
+
+ def search_issues(self, query):
+ def tokenize(s):
+ return re.findall(r'[\w]+', s)
+
+ parts = tokenize(query)
+ terms = set()
+ results = []
+ for part in parts:
+ kv = part.split(':', 1)
+ if len(kv) == 2:
+ if kv[0] in set('type', 'is', 'in'):
+ # We only perform one search now and these aren't
+ # important; we can honor these terms later if
+ # necessary.
+ continue
+ terms.add(part)
+
+ for pr in self._pull_requests.values():
+ if not pr.body:
+ body = set()
+ else:
+ body = set(tokenize(pr.body))
+ if terms.intersection(body):
+ issue = FakeIssue(pr)
+ results.append(FakeIssueSearchResult(issue))
+
+ return results