diff options
Diffstat (limited to 'tests/unit/test_github_driver.py')
-rw-r--r-- | tests/unit/test_github_driver.py | 240 |
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) |