summaryrefslogtreecommitdiff
path: root/tests/unit/test_github_driver.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/test_github_driver.py')
-rw-r--r--tests/unit/test_github_driver.py240
1 files changed, 236 insertions, 4 deletions
diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py
index f8c693e0e..49dae0ccb 100644
--- a/tests/unit/test_github_driver.py
+++ b/tests/unit/test_github_driver.py
@@ -23,12 +23,14 @@ import textwrap
from unittest import mock, skip
import git
+import gitdb
import github3.exceptions
from tests.fakegithub import FakeFile, FakeGithubEnterpriseClient
from zuul.driver.github.githubconnection import GithubShaCache
from zuul.zk.layout import LayoutState
from zuul.lib import strings
+from zuul.merger.merger import Repo
from zuul.model import MergeRequest, EnqueueEvent, DequeueEvent
from tests.base import (AnsibleZuulTestCase, BaseTestCase,
@@ -36,7 +38,7 @@ from tests.base import (AnsibleZuulTestCase, BaseTestCase,
simple_layout, random_sha1)
from tests.base import ZuulWebFixture
-EMPTY_LAYOUT_STATE = LayoutState("", "", 0, None, {})
+EMPTY_LAYOUT_STATE = LayoutState("", "", 0, None, {}, -1)
class TestGithubDriver(ZuulTestCase):
@@ -172,6 +174,40 @@ class TestGithubDriver(ZuulTestCase):
])
@simple_layout('layouts/files-github.yaml', driver='github')
+ def test_changed_file_match_filter(self):
+ path = os.path.join(self.upstream_root, 'org/project')
+ base_sha = git.Repo(path).head.object.hexsha
+
+ files = {'{:03d}.txt'.format(n): 'test' for n in range(300)}
+ files["foobar-requires"] = "test"
+ files["to-be-removed"] = "test"
+ A = self.fake_github.openFakePullRequest(
+ 'org/project', 'master', 'A', files=files, base_sha=base_sha)
+
+ self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
+ self.waitUntilSettled()
+ # project-test1 and project-test2 should be run
+ self.assertEqual(2, len(self.history))
+
+ @simple_layout('layouts/files-github.yaml', driver='github')
+ def test_changed_and_reverted_file_not_match_filter(self):
+ path = os.path.join(self.upstream_root, 'org/project')
+ base_sha = git.Repo(path).head.object.hexsha
+
+ files = {'{:03d}.txt'.format(n): 'test' for n in range(300)}
+ files["foobar-requires"] = "test"
+ files["to-be-removed"] = "test"
+ A = self.fake_github.openFakePullRequest(
+ 'org/project', 'master', 'A', files=files, base_sha=base_sha)
+ A.addCommit(delete_files=['to-be-removed'])
+
+ self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
+ self.waitUntilSettled()
+ # Only project-test1 should be run, because the file to-be-removed
+ # is reverted and not in changed files to trigger project-test2
+ self.assertEqual(1, len(self.history))
+
+ @simple_layout('layouts/files-github.yaml', driver='github')
def test_pull_file_rename(self):
A = self.fake_github.openFakePullRequest(
'org/project', 'master', 'A', files={
@@ -1120,6 +1156,55 @@ class TestGithubDriver(ZuulTestCase):
# the change should have entered the gate
self.assertEqual(2, len(self.history))
+ @simple_layout('layouts/gate-github.yaml', driver='github')
+ def test_status_checks_removal(self):
+ github = self.fake_github.getGithubClient()
+ repo = github.repo_from_project('org/project')
+ repo._set_branch_protection(
+ 'master', contexts=['something/check', 'tenant-one/gate'])
+
+ A = self.fake_github.openFakePullRequest('org/project', 'master', 'A')
+ self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
+ self.waitUntilSettled()
+
+ self.executor_server.hold_jobs_in_build = True
+ # Since the required status 'something/check' is not fulfilled,
+ # no job is expected
+ self.assertEqual(0, len(self.builds))
+ self.assertEqual(0, len(self.history))
+
+ # Set the required status 'something/check'
+ repo.create_status(A.head_sha, 'success', 'example.com', 'description',
+ 'something/check')
+
+ self.fake_github.emitEvent(A.getPullRequestOpenedEvent())
+ self.waitUntilSettled()
+ self.assertEqual(2, len(self.builds))
+ self.assertEqual(0, len(self.history))
+
+ # Remove it and verify the change is dequeued.
+ repo.create_status(A.head_sha, 'failed', 'example.com', 'description',
+ 'something/check')
+ self.fake_github.emitEvent(A.getCommitStatusEvent('something/check',
+ state='failed',
+ user='foo'))
+ self.waitUntilSettled()
+
+ self.executor_server.hold_jobs_in_build = False
+ self.executor_server.release()
+ self.waitUntilSettled()
+
+ # The change should be dequeued.
+ self.assertHistory([
+ dict(name='project-test1', result='ABORTED'),
+ dict(name='project-test2', result='ABORTED'),
+ ], ordered=False)
+ self.assertEqual(1, len(A.comments))
+ self.assertFalse(A.is_merged)
+ self.assertIn('This change is unable to merge '
+ 'due to a missing merge requirement.',
+ A.comments[0])
+
# This test case verifies that no reconfiguration happens if a branch was
# deleted that didn't contain configuration.
@simple_layout('layouts/basic-github.yaml', driver='github')
@@ -1299,7 +1384,7 @@ class TestGithubDriver(ZuulTestCase):
# now check if the merge was done via rebase
merges = [report for report in self.fake_github.github_data.reports
if report[2] == 'merge']
- assert(len(merges) == 1 and merges[0][3] == 'squash')
+ assert (len(merges) == 1 and merges[0][3] == 'squash')
@simple_layout('layouts/basic-github.yaml', driver='github')
def test_invalid_event(self):
@@ -1656,6 +1741,53 @@ class TestGithubUnprotectedBranches(ZuulTestCase):
new_sha='0' * 40,
removed_files=['zuul.yaml'])
+ def test_branch_protection_rule_update(self):
+ """Test the branch_protection_rule event"""
+ github = self.fake_github.getGithubClient()
+ repo = github.repo_from_project('org/project2')
+
+ github_connection = self.scheds.first.connections.connections['github']
+ tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
+ project = github_connection.source.getProject('org/project2')
+
+ # The repo starts without branch protection, and Zuul is
+ # configured to exclude unprotected branches, so we should see
+ # no branches.
+ branches = github_connection.getProjectBranches(project, tenant)
+ self.assertEqual(branches, [])
+ tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
+ prev_layout = tenant.layout.uuid
+
+ # Add a rule to the master branch
+ repo._set_branch_protection('master', True)
+ self.fake_github.emitEvent(
+ self.fake_github.getBranchProtectionRuleEvent(
+ 'org/project2', 'created'))
+ self.waitUntilSettled()
+
+ # Verify that it shows up
+ branches = github_connection.getProjectBranches(project, tenant)
+ self.assertEqual(branches, ['master'])
+ tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
+ new_layout = tenant.layout.uuid
+ self.assertNotEqual(new_layout, prev_layout)
+ prev_layout = new_layout
+
+ # Remove the rule
+ repo._set_branch_protection('master', False)
+ self.fake_github.emitEvent(
+ self.fake_github.getBranchProtectionRuleEvent(
+ 'org/project2', 'deleted'))
+ self.waitUntilSettled()
+
+ # Verify it's gone again
+ branches = github_connection.getProjectBranches(project, tenant)
+ self.assertEqual(branches, [])
+ tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
+ new_layout = tenant.layout.uuid
+ self.assertNotEqual(new_layout, prev_layout)
+ prev_layout = new_layout
+
class TestGithubWebhook(ZuulTestCase):
config_file = 'zuul-github-driver.conf'
@@ -2277,7 +2409,7 @@ class TestCheckRunAnnotations(ZuulGithubAppTestCase, AnsibleZuulTestCase):
})
-class TestGithubDriverEnterise(ZuulGithubAppTestCase):
+class TestGithubDriverEnterprise(ZuulGithubAppTestCase):
config_file = 'zuul-github-driver-enterprise.conf'
scheduler_count = 1
@@ -2315,7 +2447,7 @@ class TestGithubDriverEnterise(ZuulGithubAppTestCase):
self.assertEqual(len(A.comments), 0)
-class TestGithubDriverEnteriseLegacy(ZuulGithubAppTestCase):
+class TestGithubDriverEnterpriseLegacy(ZuulGithubAppTestCase):
config_file = 'zuul-github-driver-enterprise.conf'
scheduler_count = 1
@@ -2355,3 +2487,103 @@ class TestGithubDriverEnteriseLegacy(ZuulGithubAppTestCase):
r'.*I shouldnt be seen.*',
re.DOTALL)))
self.assertEqual(len(A.comments), 0)
+
+
+class TestGithubDriverEnterpriseCache(ZuulGithubAppTestCase):
+ config_file = 'zuul-github-driver-enterprise.conf'
+ scheduler_count = 1
+
+ def setup_config(self, config_file):
+ self.upstream_cache_root = self.upstream_root + '-cache'
+ config = super().setup_config(config_file)
+ # This adds the GHE repository cache feature
+ config.set('connection github', 'repo_cache', self.upstream_cache_root)
+ config.set('connection github', 'repo_retry_timeout', '30')
+ # Synchronize the upstream repos to the upstream repo cache
+ self.synchronize_repo('org/common-config')
+ self.synchronize_repo('org/project')
+ return config
+
+ def init_repo(self, project, tag=None):
+ super().init_repo(project, tag)
+ # After creating the upstream repo, also create the empty
+ # cache repo (but unsynchronized for now)
+ parts = project.split('/')
+ path = os.path.join(self.upstream_cache_root, *parts[:-1])
+ if not os.path.exists(path):
+ os.makedirs(path)
+ path = os.path.join(self.upstream_cache_root, project)
+ repo = git.Repo.init(path)
+
+ with repo.config_writer() as config_writer:
+ config_writer.set_value('user', 'email', 'user@example.com')
+ config_writer.set_value('user', 'name', 'User Name')
+
+ def synchronize_repo(self, project):
+ # Synchronize the upstream repo to the cache
+ upstream_path = os.path.join(self.upstream_root, project)
+ upstream = git.Repo(upstream_path)
+
+ cache_path = os.path.join(self.upstream_cache_root, project)
+ cache = git.Repo(cache_path)
+
+ refs = upstream.git.for_each_ref(
+ '--format=%(objectname) %(refname)'
+ )
+ for ref in refs.splitlines():
+ parts = ref.split(" ")
+ if len(parts) == 2:
+ commit, ref = parts
+ else:
+ continue
+
+ self.log.debug("Synchronize ref %s: %s", ref, commit)
+ cache.git.fetch(upstream_path, ref)
+ binsha = gitdb.util.to_bin_sha(commit)
+ obj = git.objects.Object.new_from_sha(cache, binsha)
+ git.refs.Reference.create(cache, ref, obj, force=True)
+
+ @simple_layout('layouts/merging-github.yaml', driver='github')
+ def test_github_repo_cache(self):
+ # Test that we fetch and configure retries correctly when
+ # using a github enterprise repo cache (the cache can be
+ # slightly out of sync).
+ github = self.fake_github.getGithubClient()
+ repo = github.repo_from_project('org/project')
+ repo._set_branch_protection('master', require_review=True)
+
+ # Make sure we have correctly overridden the retry attempts
+ merger = self.executor_server.merger
+ repo = merger.getRepo('github', 'org/project')
+ self.assertEqual(repo.retry_attempts, 1)
+
+ # Our initial attempt should fail; make it happen quickly
+ self.patch(Repo, 'retry_interval', 1)
+
+ # pipeline merges the pull request on success
+ A = self.fake_github.openFakePullRequest('org/project', 'master',
+ 'PR title',
+ body='I shouldnt be seen',
+ body_text='PR body')
+
+ A.addReview('user', 'APPROVED')
+ self.fake_github.emitEvent(A.getCommentAddedEvent('merge me'))
+ self.waitUntilSettled('initial failed attempt')
+
+ self.assertFalse(A.is_merged)
+
+ # Now synchronize the upstream repo to the cache and try again
+ self.synchronize_repo('org/project')
+
+ self.fake_github.emitEvent(A.getCommentAddedEvent('merge me'))
+ self.waitUntilSettled('second successful attempt')
+
+ self.assertTrue(A.is_merged)
+
+ self.assertThat(A.merge_message,
+ MatchesRegex(r'.*PR title\n\nPR body.*', re.DOTALL))
+ self.assertThat(A.merge_message,
+ Not(MatchesRegex(
+ r'.*I shouldnt be seen.*',
+ re.DOTALL)))
+ self.assertEqual(len(A.comments), 0)