diff options
Diffstat (limited to 'tests/unit')
-rw-r--r-- | tests/unit/test_circular_dependencies.py | 64 | ||||
-rw-r--r-- | tests/unit/test_client.py | 136 | ||||
-rw-r--r-- | tests/unit/test_gerrit.py | 99 | ||||
-rw-r--r-- | tests/unit/test_github_driver.py | 7 | ||||
-rw-r--r-- | tests/unit/test_github_requirements.py | 178 | ||||
-rw-r--r-- | tests/unit/test_requirements.py | 221 | ||||
-rw-r--r-- | tests/unit/test_scheduler.py | 79 |
7 files changed, 763 insertions, 21 deletions
diff --git a/tests/unit/test_circular_dependencies.py b/tests/unit/test_circular_dependencies.py index a3f9dda33..28ca528b5 100644 --- a/tests/unit/test_circular_dependencies.py +++ b/tests/unit/test_circular_dependencies.py @@ -2246,6 +2246,70 @@ class TestGerritCircularDependencies(ZuulTestCase): self.assertEqual(B.data["status"], "MERGED") @simple_layout('layouts/deps-by-topic.yaml') + def test_deps_by_topic_git_needs(self): + A = self.fake_gerrit.addFakeChange('org/project1', "master", "A", + topic='test-topic') + B = self.fake_gerrit.addFakeChange('org/project2', "master", "B", + topic='test-topic') + C = self.fake_gerrit.addFakeChange('org/project2', "master", "C", + topic='other-topic') + D = self.fake_gerrit.addFakeChange('org/project1', "master", "D", + topic='other-topic') + + # Git level dependency between B and C + B.setDependsOn(C, 1) + + self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1)) + self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1)) + self.fake_gerrit.addEvent(C.getPatchsetCreatedEvent(1)) + self.fake_gerrit.addEvent(D.getPatchsetCreatedEvent(1)) + self.waitUntilSettled() + + self.assertEqual(len(A.patchsets[-1]["approvals"]), 1) + self.assertEqual(A.patchsets[-1]["approvals"][0]["type"], "Verified") + self.assertEqual(A.patchsets[-1]["approvals"][0]["value"], "1") + + self.assertEqual(len(B.patchsets[-1]["approvals"]), 1) + self.assertEqual(B.patchsets[-1]["approvals"][0]["type"], "Verified") + self.assertEqual(B.patchsets[-1]["approvals"][0]["value"], "1") + + self.assertEqual(len(C.patchsets[-1]["approvals"]), 1) + self.assertEqual(C.patchsets[-1]["approvals"][0]["type"], "Verified") + self.assertEqual(C.patchsets[-1]["approvals"][0]["value"], "1") + + self.assertEqual(len(D.patchsets[-1]["approvals"]), 1) + self.assertEqual(D.patchsets[-1]["approvals"][0]["type"], "Verified") + self.assertEqual(D.patchsets[-1]["approvals"][0]["value"], "1") + + # We're about to add approvals to changes without adding the + # triggering events to Zuul, so that we can be sure that it is + # enqueing the changes based on dependencies, not because of + # triggering events. Since it will have the changes cached + # already (without approvals), we need to clear the cache + # first. + for connection in self.scheds.first.connections.connections.values(): + connection.maintainCache([], max_age=0) + + A.addApproval("Code-Review", 2) + B.addApproval("Code-Review", 2) + C.addApproval("Code-Review", 2) + D.addApproval("Code-Review", 2) + A.addApproval("Approved", 1) + C.addApproval("Approved", 1) + D.addApproval("Approved", 1) + self.fake_gerrit.addEvent(B.addApproval("Approved", 1)) + self.waitUntilSettled() + + self.assertEqual(A.reported, 3) + self.assertEqual(B.reported, 3) + self.assertEqual(C.reported, 3) + self.assertEqual(D.reported, 3) + self.assertEqual(A.data["status"], "MERGED") + self.assertEqual(B.data["status"], "MERGED") + self.assertEqual(C.data["status"], "MERGED") + self.assertEqual(D.data["status"], "MERGED") + + @simple_layout('layouts/deps-by-topic.yaml') def test_deps_by_topic_new_patchset(self): # Make sure that we correctly update the change cache on new # patchsets. diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index f241147eb..2e90d3fb4 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -21,10 +21,12 @@ import time import configparser import datetime import dateutil.tz +import uuid import fixtures import jwt import testtools +import sqlalchemy from zuul.zk import ZooKeeperClient from zuul.zk.locks import SessionAwareLock @@ -499,27 +501,107 @@ class TestDBPruneParse(BaseTestCase): class DBPruneTestCase(ZuulTestCase): tenant_config_file = 'config/single-tenant/main.yaml' + # This should be larger than the limit size in sqlconnection + num_buildsets = 55 + + def _createBuildset(self, update_time): + connection = self.scheds.first.sched.sql.connection + buildset_uuid = uuid.uuid4().hex + event_id = uuid.uuid4().hex + with connection.getSession() as db: + start_time = update_time - datetime.timedelta(seconds=1) + end_time = update_time + db_buildset = db.createBuildSet( + uuid=buildset_uuid, + tenant='tenant-one', + pipeline='check', + project='org/project', + change='1', + patchset='1', + ref='refs/changes/1', + oldrev='', + newrev='', + branch='master', + zuul_ref='Zref', + ref_url='http://gerrit.example.com/1', + event_id=event_id, + event_timestamp=update_time, + updated=update_time, + first_build_start_time=start_time, + last_build_end_time=end_time, + result='SUCCESS', + ) + for build_num in range(2): + build_uuid = uuid.uuid4().hex + db_build = db_buildset.createBuild( + uuid=build_uuid, + job_name=f'job{build_num}', + start_time=start_time, + end_time=end_time, + result='SUCCESS', + voting=True, + ) + for art_num in range(2): + db_build.createArtifact( + name=f'artifact{art_num}', + url='http://example.com', + ) + for provides_num in range(2): + db_build.createProvides( + name=f'item{provides_num}', + ) + for event_num in range(2): + db_build.createBuildEvent( + event_type=f'event{event_num}', + event_time=start_time, + ) + + def _query(self, db, model): + table = model.__table__ + q = db.session().query(model).order_by(table.c.id.desc()) + try: + return q.all() + except sqlalchemy.orm.exc.NoResultFound: + return [] + + def _getBuildsets(self, db): + return self._query(db, db.connection.buildSetModel) + + def _getBuilds(self, db): + return self._query(db, db.connection.buildModel) + + def _getProvides(self, db): + return self._query(db, db.connection.providesModel) + + def _getArtifacts(self, db): + return self._query(db, db.connection.artifactModel) + + def _getBuildEvents(self, db): + return self._query(db, db.connection.buildEventModel) def _setup(self): config_file = os.path.join(self.test_root, 'zuul.conf') with open(config_file, 'w') as f: self.config.write(f) - A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A') - self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1)) - self.waitUntilSettled() - - time.sleep(1) - - B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B') - self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1)) - self.waitUntilSettled() + update_time = (datetime.datetime.utcnow() - + datetime.timedelta(minutes=self.num_buildsets)) + for x in range(self.num_buildsets): + update_time = update_time + datetime.timedelta(minutes=1) + self._createBuildset(update_time) connection = self.scheds.first.sched.sql.connection - buildsets = connection.getBuildsets() - builds = connection.getBuilds() - self.assertEqual(len(buildsets), 2) - self.assertEqual(len(builds), 6) + with connection.getSession() as db: + buildsets = self._getBuildsets(db) + builds = self._getBuilds(db) + artifacts = self._getArtifacts(db) + provides = self._getProvides(db) + events = self._getBuildEvents(db) + self.assertEqual(len(buildsets), self.num_buildsets) + self.assertEqual(len(builds), 2 * self.num_buildsets) + self.assertEqual(len(artifacts), 4 * self.num_buildsets) + self.assertEqual(len(provides), 4 * self.num_buildsets) + self.assertEqual(len(events), 4 * self.num_buildsets) for build in builds: self.log.debug("Build %s %s %s", build, build.start_time, build.end_time) @@ -535,6 +617,7 @@ class DBPruneTestCase(ZuulTestCase): start_time = buildsets[0].first_build_start_time self.log.debug("Cutoff %s", start_time) + # Use the default batch size (omit --batch-size arg) p = subprocess.Popen( [os.path.join(sys.prefix, 'bin/zuul-admin'), '-c', config_file, @@ -545,13 +628,20 @@ class DBPruneTestCase(ZuulTestCase): out, _ = p.communicate() self.log.debug(out.decode('utf8')) - buildsets = connection.getBuildsets() - builds = connection.getBuilds() - self.assertEqual(len(buildsets), 1) - self.assertEqual(len(builds), 3) + with connection.getSession() as db: + buildsets = self._getBuildsets(db) + builds = self._getBuilds(db) + artifacts = self._getArtifacts(db) + provides = self._getProvides(db) + events = self._getBuildEvents(db) for build in builds: self.log.debug("Build %s %s %s", build, build.start_time, build.end_time) + self.assertEqual(len(buildsets), 1) + self.assertEqual(len(builds), 2) + self.assertEqual(len(artifacts), 4) + self.assertEqual(len(provides), 4) + self.assertEqual(len(events), 4) def test_db_prune_older_than(self): # Test pruning buildsets older than a relative time @@ -567,15 +657,23 @@ class DBPruneTestCase(ZuulTestCase): '-c', config_file, 'prune-database', '--older-than', '0d', + '--batch-size', '5', ], stdout=subprocess.PIPE) out, _ = p.communicate() self.log.debug(out.decode('utf8')) - buildsets = connection.getBuildsets() - builds = connection.getBuilds() + with connection.getSession() as db: + buildsets = self._getBuildsets(db) + builds = self._getBuilds(db) + artifacts = self._getArtifacts(db) + provides = self._getProvides(db) + events = self._getBuildEvents(db) self.assertEqual(len(buildsets), 0) self.assertEqual(len(builds), 0) + self.assertEqual(len(artifacts), 0) + self.assertEqual(len(provides), 0) + self.assertEqual(len(events), 0) class TestDBPruneMysql(DBPruneTestCase): diff --git a/tests/unit/test_gerrit.py b/tests/unit/test_gerrit.py index 2a63d5ef8..4085e8b1b 100644 --- a/tests/unit/test_gerrit.py +++ b/tests/unit/test_gerrit.py @@ -827,6 +827,14 @@ class TestGerritFake(ZuulTestCase): config_file = "zuul-gerrit-github.conf" tenant_config_file = "config/circular-dependencies/main.yaml" + def _make_tuple(self, data): + ret = [] + for c in data: + dep_change = c['number'] + dep_ps = c['currentPatchSet']['number'] + ret.append((int(dep_change), int(dep_ps))) + return sorted(ret) + def _get_tuple(self, change_number): ret = [] data = self.fake_gerrit.get( @@ -903,6 +911,11 @@ class TestGerritFake(ZuulTestCase): ret = self.fake_gerrit._getSubmittedTogether(C1, None) self.assertEqual(ret, [(4, 1)]) + # Test also the query used by the GerritConnection: + ret = self.fake_gerrit._simpleQuery('status:open topic:test-topic') + ret = self._make_tuple(ret) + self.assertEqual(ret, [(3, 1), (4, 1)]) + class TestGerritConnection(ZuulTestCase): config_file = 'zuul-gerrit-web.conf' @@ -958,6 +971,92 @@ class TestGerritConnection(ZuulTestCase): self.assertEqual(A.data['status'], 'MERGED') self.assertEqual(B.data['status'], 'MERGED') + def test_submit_requirements(self): + A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A') + A.addApproval('Code-Review', 2) + # Set an unsatisfied submit requirement + A.setSubmitRequirements([ + { + "name": "Code-Review", + "description": "Disallow self-review", + "status": "UNSATISFIED", + "is_legacy": False, + "submittability_expression_result": { + "expression": "label:Code-Review=MAX,user=non_uploader " + "AND -label:Code-Review=MIN", + "fulfilled": False, + "passing_atoms": [], + "failing_atoms": [ + "label:Code-Review=MAX,user=non_uploader", + "label:Code-Review=MIN" + ] + } + }, + { + "name": "Verified", + "status": "UNSATISFIED", + "is_legacy": True, + "submittability_expression_result": { + "expression": "label:Verified=MAX -label:Verified=MIN", + "fulfilled": False, + "passing_atoms": [], + "failing_atoms": [ + "label:Verified=MAX", + "-label:Verified=MIN" + ] + } + }, + ]) + self.fake_gerrit.addEvent(A.addApproval('Approved', 1)) + self.waitUntilSettled() + self.assertHistory([]) + self.assertEqual(A.queried, 1) + self.assertEqual(A.data['status'], 'NEW') + + # Mark the requirement satisfied + A.setSubmitRequirements([ + { + "name": "Code-Review", + "description": "Disallow self-review", + "status": "SATISFIED", + "is_legacy": False, + "submittability_expression_result": { + "expression": "label:Code-Review=MAX,user=non_uploader " + "AND -label:Code-Review=MIN", + "fulfilled": False, + "passing_atoms": [ + "label:Code-Review=MAX,user=non_uploader", + ], + "failing_atoms": [ + "label:Code-Review=MIN" + ] + } + }, + { + "name": "Verified", + "status": "UNSATISFIED", + "is_legacy": True, + "submittability_expression_result": { + "expression": "label:Verified=MAX -label:Verified=MIN", + "fulfilled": False, + "passing_atoms": [], + "failing_atoms": [ + "label:Verified=MAX", + "-label:Verified=MIN" + ] + } + }, + ]) + self.fake_gerrit.addEvent(A.addApproval('Approved', 1)) + self.waitUntilSettled() + self.assertHistory([ + dict(name="project-merge", result="SUCCESS", changes="1,1"), + dict(name="project-test1", result="SUCCESS", changes="1,1"), + dict(name="project-test2", result="SUCCESS", changes="1,1"), + ], ordered=False) + self.assertEqual(A.queried, 3) + self.assertEqual(A.data['status'], 'MERGED') + class TestGerritUnicodeRefs(ZuulTestCase): config_file = 'zuul-gerrit-web.conf' diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py index 47e84ca7f..3060f5673 100644 --- a/tests/unit/test_github_driver.py +++ b/tests/unit/test_github_driver.py @@ -1430,7 +1430,9 @@ class TestGithubDriver(ZuulTestCase): repo._set_branch_protection( 'master', contexts=['tenant-one/check', 'tenant-one/gate']) - A = self.fake_github.openFakePullRequest('org/project', 'master', 'A') + pr_description = "PR description" + A = self.fake_github.openFakePullRequest('org/project', 'master', 'A', + body_text=pr_description) self.fake_github.emitEvent(A.getPullRequestOpenedEvent()) self.waitUntilSettled() @@ -1448,6 +1450,9 @@ class TestGithubDriver(ZuulTestCase): 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 that we won't duplicate the PR title in the merge + # message description. + self.assertEqual(A.merge_message, pr_description) @simple_layout('layouts/basic-github.yaml', driver='github') def test_invalid_event(self): diff --git a/tests/unit/test_github_requirements.py b/tests/unit/test_github_requirements.py index ef1f75944..f3021d41d 100644 --- a/tests/unit/test_github_requirements.py +++ b/tests/unit/test_github_requirements.py @@ -678,3 +678,181 @@ class TestGithubAppRequirements(ZuulGithubAppTestCase): self.fake_github.emitEvent(comment) self.waitUntilSettled() self.assertEqual(len(self.history), 1) + + +class TestGithubTriggerRequirements(ZuulTestCase): + """Test pipeline and trigger requirements""" + config_file = 'zuul-github-driver.conf' + scheduler_count = 1 + + @simple_layout('layouts/github-trigger-requirements.yaml', driver='github') + def test_require_status(self): + # Test trigger require-status + jobname = 'require-status' + project = 'org/project' + A = self.fake_github.openFakePullRequest(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent(f'test {jobname}') + + # No status from zuul so should not be enqueued + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # An error status should not cause it to be enqueued + self.fake_github.setCommitStatus(project, A.head_sha, 'error', + context='tenant-one/check') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A success status goes in + self.fake_github.setCommitStatus(project, A.head_sha, 'success', + context='tenant-one/check') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/github-trigger-requirements.yaml', driver='github') + def test_reject_status(self): + # Test trigger reject-status + jobname = 'reject-status' + project = 'org/project' + A = self.fake_github.openFakePullRequest(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent(f'test {jobname}') + + # No status from zuul so should be enqueued + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # A failure status should not cause it to be enqueued + self.fake_github.setCommitStatus(project, A.head_sha, 'failure', + context='tenant-one/check') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + + # A success status goes in + self.fake_github.setCommitStatus(project, A.head_sha, 'success', + context='tenant-one/check') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 2) + self.assertEqual(self.history[1].name, jobname) + + @simple_layout('layouts/github-trigger-requirements.yaml', driver='github') + def test_require_review(self): + # Test trigger require-review + jobname = 'require-review' + project = 'org/project' + A = self.fake_github.openFakePullRequest(project, 'master', 'A') + A.writers.extend(('maintainer',)) + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent(f'test {jobname}') + + # No review so should not be enqueued + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # An changes requested review should not cause it to be enqueued + A.addReview('maintainer', 'CHANGES_REQUESTED') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A positive review goes in + A.addReview('maintainer', 'APPROVED') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/github-trigger-requirements.yaml', driver='github') + def test_reject_review(self): + # Test trigger reject-review + jobname = 'reject-review' + project = 'org/project' + A = self.fake_github.openFakePullRequest(project, 'master', 'A') + A.writers.extend(('maintainer',)) + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent(f'test {jobname}') + + # No review so should be enqueued + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # An changes requested review should not cause it to be enqueued + A.addReview('maintainer', 'CHANGES_REQUESTED') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + + # A positive review goes in + A.addReview('maintainer', 'APPROVED') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 2) + self.assertEqual(self.history[1].name, jobname) + + @simple_layout('layouts/github-trigger-requirements.yaml', driver='github') + def test_require_label(self): + # Test trigger require-label + jobname = 'require-label' + project = 'org/project' + A = self.fake_github.openFakePullRequest(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent(f'test {jobname}') + + # No label so should not be enqueued + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A random should not cause it to be enqueued + A.addLabel('foobar') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # An approved label goes in + A.addLabel('approved') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/github-trigger-requirements.yaml', driver='github') + def test_reject_label(self): + # Test trigger reject-label + jobname = 'reject-label' + project = 'org/project' + A = self.fake_github.openFakePullRequest(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getCommentAddedEvent(f'test {jobname}') + + # No label so should be enqueued + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # A rejected label should not cause it to be enqueued + A.addLabel('rejected') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + + # Any other label, it goes in + A.removeLabel('rejected') + A.addLabel('okay') + self.fake_github.emitEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 2) + self.assertEqual(self.history[1].name, jobname) diff --git a/tests/unit/test_requirements.py b/tests/unit/test_requirements.py index 9f3b87187..c5dca56cd 100644 --- a/tests/unit/test_requirements.py +++ b/tests/unit/test_requirements.py @@ -14,7 +14,7 @@ import time -from tests.base import ZuulTestCase +from tests.base import ZuulTestCase, simple_layout class TestRequirementsApprovalNewerThan(ZuulTestCase): @@ -490,3 +490,222 @@ class TestRequirementsTrustedCheck(ZuulTestCase): self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1)) self.waitUntilSettled() self.assertHistory([]) + + +class TestGerritTriggerRequirements(ZuulTestCase): + scheduler_count = 1 + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_require_open(self): + # Test trigger require-open + jobname = 'require-open' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's open, so it should be enqueued + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # Not open, so should be ignored + A.setMerged() + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_reject_open(self): + # Test trigger reject-open + jobname = 'reject-open' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's open, so it should not be enqueued + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # Not open, so should be enqueued + A.setMerged() + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_require_wip(self): + # Test trigger require-wip + jobname = 'require-wip' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's not WIP, so it should be ignored + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # WIP, so should be enqueued + A.setWorkInProgress(True) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_reject_wip(self): + # Test trigger reject-wip + jobname = 'reject-wip' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's not WIP, so it should be enqueued + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # WIP, so should be ignored + A.setWorkInProgress(True) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_require_current_patchset(self): + # Test trigger require-current_patchset + jobname = 'require-current-patchset' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's current, so it should be enqueued + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # Not current, so should be ignored + A.addPatchset() + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_reject_current_patchset(self): + # Test trigger reject-current_patchset + jobname = 'reject-current-patchset' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's current, so it should be ignored + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # Not current, so should be enqueued + A.addPatchset() + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_require_status(self): + # Test trigger require-status + jobname = 'require-status' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's not merged, so it should be ignored + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # Merged, so should be enqueued + A.setMerged() + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_reject_status(self): + # Test trigger reject-status + jobname = 'reject-status' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # It's not merged, so it should be enqueued + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # Merged, so should be ignored + A.setMerged() + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_require_approval(self): + # Test trigger require-approval + jobname = 'require-approval' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # Missing approval, so it should be ignored + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # Has approval, so it should be enqueued + A.addApproval('Verified', 1, username='zuul') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + @simple_layout('layouts/gerrit-trigger-requirements.yaml') + def test_reject_approval(self): + # Test trigger reject-approval + jobname = 'reject-approval' + project = 'org/project' + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.getChangeCommentEvent(1, f'test {jobname}') + + # Missing approval, so it should be enqueued + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) + + # Has approval, so it should be ignored + A.addApproval('Verified', 1, username='zuul') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, jobname) diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py index 172ed34dc..131034f17 100644 --- a/tests/unit/test_scheduler.py +++ b/tests/unit/test_scheduler.py @@ -7440,6 +7440,85 @@ class TestSchedulerMerges(ZuulTestCase): result = self._test_project_merge_mode('cherry-pick') self.assertEqual(result, expected_messages) + def test_project_merge_mode_cherrypick_redundant(self): + # A redundant commit (that is, one that has already been applied to the + # working tree) should be skipped + self.executor_server.keep_jobdir = False + project = 'org/project-cherry-pick' + files = { + "foo.txt": "ABC", + } + A = self.fake_gerrit.addFakeChange(project, 'master', 'A', files=files) + A.addApproval('Code-Review', 2) + self.fake_gerrit.addEvent(A.addApproval('Approved', 1)) + self.waitUntilSettled() + + self.executor_server.hold_jobs_in_build = True + B = self.fake_gerrit.addFakeChange(project, 'master', 'B', files=files) + B.addApproval('Code-Review', 2) + self.fake_gerrit.addEvent(B.addApproval('Approved', 1)) + self.waitUntilSettled() + + build = self.builds[-1] + path = os.path.join(build.jobdir.src_root, 'review.example.com', + project) + repo = git.Repo(path) + repo_messages = [c.message.strip() for c in repo.iter_commits()] + repo_messages.reverse() + + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + + expected_messages = [ + 'initial commit', + 'add content from fixture', + 'A-1', + ] + self.assertHistory([ + dict(name='project-test1', result='SUCCESS', changes='1,1'), + dict(name='project-test1', result='SUCCESS', changes='2,1'), + ]) + self.assertEqual(A.data['status'], 'MERGED') + self.assertEqual(B.data['status'], 'MERGED') + self.assertEqual(repo_messages, expected_messages) + + def test_project_merge_mode_cherrypick_empty(self): + # An empty commit (that is, one that doesn't modify any files) should + # be preserved + self.executor_server.keep_jobdir = False + project = 'org/project-cherry-pick' + self.executor_server.hold_jobs_in_build = True + A = self.fake_gerrit.addFakeChange(project, 'master', 'A', empty=True) + A.addApproval('Code-Review', 2) + self.fake_gerrit.addEvent(A.addApproval('Approved', 1)) + self.waitUntilSettled() + + build = self.builds[-1] + path = os.path.join(build.jobdir.src_root, 'review.example.com', + project) + repo = git.Repo(path) + repo_messages = [c.message.strip() for c in repo.iter_commits()] + repo_messages.reverse() + + changed_files = list(repo.commit("HEAD").diff(repo.commit("HEAD~1"))) + self.assertEqual(changed_files, []) + + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + + expected_messages = [ + 'initial commit', + 'add content from fixture', + 'A-1', + ] + self.assertHistory([ + dict(name='project-test1', result='SUCCESS', changes='1,1'), + ]) + self.assertEqual(A.data['status'], 'MERGED') + self.assertEqual(repo_messages, expected_messages) + def test_project_merge_mode_cherrypick_branch_merge(self): "Test that branches can be merged together in cherry-pick mode" self.create_branch('org/project-merge-branches', 'mp') |