Implement pipeline requirement on github reviews

Github reviews are a new pipeline requirement that is driver specific.
Reviews can be approved, changes_requested, or comment. They can come
from people with read, write, or admin access. Access is hierarchical,
admin level includes write and read, and write access includes read.

Review requirements model loosely the gerrit approvals, allowing
filtering on username, email, newer-than, older-than, type, and
permission.

Brings in an unreleased Github3.py code. Further extends that code to
determine if a user has push rights to a repository.

Documentation is not included with this change, as the docs need
restructuring for driver specific require / reject.

Change-Id: I3ab2139c2b11b7dc8aa896a03047615bcf42adba
Signed-off-by: Jesse Keating <omgjlk@us.ibm.com>
diff --git a/tests/base.py b/tests/base.py
index 1d36694..9232d52 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -542,7 +542,8 @@
 class FakeGithubPullRequest(object):
 
     def __init__(self, github, number, project, branch,
-                 subject, upstream_root, files=[], number_of_commits=1):
+                 subject, upstream_root, files=[], number_of_commits=1,
+                 writers=[]):
         """Creates a new PR with several commits.
         Sends an event about opened PR."""
         self.github = github
@@ -557,6 +558,8 @@
         self.comments = []
         self.labels = []
         self.statuses = {}
+        self.reviews = []
+        self.writers = []
         self.updated_at = None
         self.head_sha = None
         self.is_merged = False
@@ -773,6 +776,26 @@
             }
         }))
 
+    def addReview(self, user, state, granted_on=None):
+        # Each user will only have one review at a time, so replace
+        # any existing reviews
+        # FIXME(jlk): this isn't quite right, reviews stack, we only
+        # consider the latest for a user. Thanks GitHub!!
+        for review in self.reviews:
+            if review['user']['login'] == user:
+                self.reviews.remove(review)
+
+        if not granted_on:
+            granted_on = time.time()
+        self.reviews.append({
+            'state': state,
+            'user': {
+                'login': user,
+                'email': user + "@derp.com",
+            },
+            'provided': int(granted_on),
+        })
+
     def _getPRReference(self):
         return '%s/head' % self.number
 
@@ -903,6 +926,10 @@
         pr = self.pull_requests[number - 1]
         return pr.files
 
+    def _getPullReviews(self, owner, project, number):
+        pr = self.pull_requests[number - 1]
+        return pr.reviews
+
     def getUser(self, login):
         data = {
             'username': login,
@@ -911,6 +938,16 @@
         }
         return data
 
+    def getRepoPermission(self, project, login):
+        owner, proj = project.split('/')
+        for pr in self.pull_requests:
+            pr_owner, pr_project = pr.project.split('/')
+            if (pr_owner == owner and proj == pr_project):
+                if login in pr.writers:
+                    return 'write'
+                else:
+                    return 'read'
+
     def getGitUrl(self, project):
         return os.path.join(self.upstream_root, str(project))