Add require-approval to Gerrit trigger
This feature allows Zuul to consider existing (or new) approval
votes associated with a change when determining whether an event
matches. For example, it can be used to require that a Verified
vote of a certain age be present before a change is enqueued in
a pipeline.
Change-Id: I81344713d71b345b08576334568b9c49c810c7e9
diff --git a/tests/fixtures/layout-require-approval.yaml b/tests/fixtures/layout-require-approval.yaml
new file mode 100644
index 0000000..18eee99
--- /dev/null
+++ b/tests/fixtures/layout-require-approval.yaml
@@ -0,0 +1,58 @@
+includes:
+ - python-file: custom_functions.py
+
+pipelines:
+ - name: check
+ manager: IndependentPipelineManager
+ trigger:
+ gerrit:
+ - event: patchset-created
+ - event: comment-added
+ require-approval:
+ - email-filter: jenkins@example.com
+ older-than: 48h
+ success:
+ gerrit:
+ verified: 1
+ failure:
+ gerrit:
+ verified: -1
+
+ - name: gate
+ manager: DependentPipelineManager
+ failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
+ trigger:
+ gerrit:
+ - event: comment-added
+ require-approval:
+ - verified: 1
+ username: jenkins
+ newer-than: 48h
+ approval:
+ - approved: 1
+ - event: comment-added
+ require-approval:
+ - verified: 1
+ username: jenkins
+ newer-than: 48h
+ approval:
+ - verified: 1
+ success:
+ gerrit:
+ verified: 2
+ submit: true
+ failure:
+ gerrit:
+ verified: -2
+ start:
+ gerrit:
+ verified: 0
+ precedence: high
+
+projects:
+ - name: org/project
+ merge-mode: cherry-pick
+ check:
+ - project-check
+ gate:
+ - project-gate
diff --git a/tests/fixtures/layouts/good_require_approvals.yaml b/tests/fixtures/layouts/good_require_approvals.yaml
new file mode 100644
index 0000000..b7993b0
--- /dev/null
+++ b/tests/fixtures/layouts/good_require_approvals.yaml
@@ -0,0 +1,36 @@
+includes:
+ - python-file: custom_functions.py
+
+pipelines:
+ - name: check
+ manager: IndependentPipelineManager
+ trigger:
+ gerrit:
+ - event: comment-added
+ require-approval:
+ - username: jenkins
+ older-than: 48h
+ - event: comment-added
+ require-approval:
+ - email-filter: jenkins@example.com
+ newer-than: 48h
+ - event: comment-added
+ require-approval:
+ - approved: 1
+ - event: comment-added
+ require-approval:
+ - approved: 1
+ username: jenkins
+ email-filter: jenkins@example.com
+ success:
+ gerrit:
+ verified: 1
+ failure:
+ gerrit:
+ verified: -1
+
+projects:
+ - name: org/project
+ merge-mode: cherry-pick
+ check:
+ - project-check
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index 0ce0f88..3b7b9f0 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -214,10 +214,19 @@
"reason": ""}
return event
- def addApproval(self, category, value):
+ def addApproval(self, category, value, username='jenkins',
+ granted_on=None):
+ if not granted_on:
+ granted_on = time.time()
approval = {'description': self.categories[category][0],
'type': category,
- 'value': str(value)}
+ 'value': str(value),
+ 'username': username,
+ 'email': username + '@example.com',
+ 'grantedOn': int(granted_on)}
+ for i, x in enumerate(self.patchsets[-1]['approvals'][:]):
+ if x['username'] == username and x['type'] == category:
+ del self.patchsets[-1]['approvals'][i]
self.patchsets[-1]['approvals'].append(approval)
event = {'approvals': [approval],
'author': {'email': 'user@example.com',
@@ -2658,6 +2667,95 @@
self.assertEqual(D.data['status'], 'MERGED')
self.assertEqual(D.reported, 2)
+ def test_required_approval_check_and_gate(self):
+ "Test required-approval triggers both check and gate"
+ self.config.set('zuul', 'layout_config',
+ 'tests/fixtures/layout-require-approval.yaml')
+ self.sched.reconfigure(self.config)
+ self.registerJobs()
+
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ A.addApproval('CRVW', 2)
+ # Add a too-old +1
+ A.addApproval('VRFY', 1, granted_on=time.time() - 72 * 60 * 60)
+
+ aprv = A.addApproval('APRV', 1)
+ self.fake_gerrit.addEvent(aprv)
+ self.waitUntilSettled()
+ # Should have run a check job
+ self.assertEqual(len(self.history), 1)
+ self.assertEqual(self.history[0].name, 'project-check')
+
+ # Report the result of that check job (overrides previous vrfy)
+ # Skynet alert: this should trigger a gate job now that
+ # all reqs are met
+ self.fake_gerrit.addEvent(A.addApproval('VRFY', 1))
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 2)
+ self.assertEqual(self.history[1].name, 'project-gate')
+
+ def test_required_approval_newer(self):
+ "Test required-approval newer trigger parameter"
+ self.config.set('zuul', 'layout_config',
+ 'tests/fixtures/layout-require-approval.yaml')
+ self.sched.reconfigure(self.config)
+ self.registerJobs()
+
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ A.addApproval('CRVW', 2)
+ aprv = A.addApproval('APRV', 1)
+ self.fake_gerrit.addEvent(aprv)
+ self.waitUntilSettled()
+ # No +1 from Jenkins so should not be enqueued
+ self.assertEqual(len(self.history), 0)
+
+ # Add a too-old +1, should trigger check but not gate
+ A.addApproval('VRFY', 1, granted_on=time.time() - 72 * 60 * 60)
+ self.fake_gerrit.addEvent(aprv)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 1)
+ self.assertEqual(self.history[0].name, 'project-check')
+
+ # Add a recent +1
+ self.fake_gerrit.addEvent(A.addApproval('VRFY', 1))
+ self.fake_gerrit.addEvent(aprv)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 2)
+ self.assertEqual(self.history[1].name, 'project-gate')
+
+ def test_required_approval_older(self):
+ "Test required-approval older trigger parameter"
+ self.config.set('zuul', 'layout_config',
+ 'tests/fixtures/layout-require-approval.yaml')
+ self.sched.reconfigure(self.config)
+ self.registerJobs()
+
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ crvw = A.addApproval('CRVW', 2)
+ self.fake_gerrit.addEvent(crvw)
+ self.waitUntilSettled()
+ # No +1 from Jenkins so should not be enqueued
+ self.assertEqual(len(self.history), 0)
+
+ # Add an old +1 and trigger check with a comment
+ A.addApproval('VRFY', 1, granted_on=time.time() - 72 * 60 * 60)
+ self.fake_gerrit.addEvent(crvw)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 1)
+ self.assertEqual(self.history[0].name, 'project-check')
+
+ # Add a recent +1 and make sure nothing changes
+ A.addApproval('VRFY', 1)
+ self.fake_gerrit.addEvent(crvw)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 1)
+
+ # The last thing we did was query a change then do nothing
+ # with a pipeline, so it will be in the cache; clean it up so
+ # it does not fail the test.
+ for pipeline in self.sched.layout.pipelines.values():
+ pipeline.trigger.maintainCache([])
+
def test_rerun_on_error(self):
"Test that if a worker fails to run a job, it is run again"
self.worker.hold_jobs_in_build = True