diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst index 4c5a6243a8..e3ad2e8cf0 100644 --- a/doc/source/zuul.rst +++ b/doc/source/zuul.rst @@ -423,13 +423,11 @@ explanation of each of the parameters:: containing 'retrigger' somewhere in the comment text are added to a change. - *require-approval* (deprecated) + *require-approval* This may be used for any event. It requires that a certain kind of approval be present for the current patchset of the change (the approval could be added by the event in question). It follows the - same syntax as the "approval" pipeline requirement below. This - form should be considered deprecated and the pipeline requirement - used instead. + same syntax as the "approval" pipeline requirement below. **timer** This trigger will run based on a cron-style time specification. @@ -475,7 +473,9 @@ explanation of each of the parameters:: Any other field is interpreted as a review category and value pair. For example ``verified: 1`` would require that the approval - be for a +1 vote in the "Verified" column. + be for a +1 vote in the "Verified" column. The value may either + be a single value or a list: ``verified: [1, 2]`` would match + either a +1 or +2 vote. **open** A boolean value (``true`` or ``false``) that indicates whether the change diff --git a/tests/fixtures/layout-pipeline-requirements.yaml b/tests/fixtures/layout-pipeline-requirements.yaml deleted file mode 100644 index 1826d88ef2..0000000000 --- a/tests/fixtures/layout-pipeline-requirements.yaml +++ /dev/null @@ -1,59 +0,0 @@ -includes: - - python-file: custom_functions.py - -pipelines: - - name: check - manager: IndependentPipelineManager - require: - approval: - - email-filter: jenkins@example.com - older-than: 48h - open: True - trigger: - gerrit: - - event: patchset-created - - event: comment-added - 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 - require: - status: - - NEW - approval: - - verified: 1 - username: jenkins - newer-than: 48h - trigger: - gerrit: - - event: comment-added - approval: - - approved: 1 - - event: comment-added - 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/layout-require-approval.yaml b/tests/fixtures/layout-require-approval.yaml deleted file mode 100644 index 18eee99a84..0000000000 --- a/tests/fixtures/layout-require-approval.yaml +++ /dev/null @@ -1,58 +0,0 @@ -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/layout-current-patchset.yaml b/tests/fixtures/layout-requirement-current-patchset.yaml similarity index 82% rename from tests/fixtures/layout-current-patchset.yaml rename to tests/fixtures/layout-requirement-current-patchset.yaml index dc8f768be0..405077e569 100644 --- a/tests/fixtures/layout-current-patchset.yaml +++ b/tests/fixtures/layout-requirement-current-patchset.yaml @@ -1,6 +1,3 @@ -includes: - - python-file: custom_functions.py - pipelines: - name: check manager: IndependentPipelineManager @@ -19,6 +16,5 @@ pipelines: projects: - name: org/project - merge-mode: cherry-pick check: - project-check diff --git a/tests/fixtures/layout-requirement-email.yaml b/tests/fixtures/layout-requirement-email.yaml new file mode 100644 index 0000000000..6ed7603f51 --- /dev/null +++ b/tests/fixtures/layout-requirement-email.yaml @@ -0,0 +1,37 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - email-filter: jenkins@example.com + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - email-filter: jenkins@example.com + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/fixtures/layout-requirement-newer-than.yaml b/tests/fixtures/layout-requirement-newer-than.yaml new file mode 100644 index 0000000000..b6beb35be8 --- /dev/null +++ b/tests/fixtures/layout-requirement-newer-than.yaml @@ -0,0 +1,39 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - username: jenkins + newer-than: 48h + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - username: jenkins + newer-than: 48h + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/fixtures/layout-requirement-older-than.yaml b/tests/fixtures/layout-requirement-older-than.yaml new file mode 100644 index 0000000000..2edf9df1ac --- /dev/null +++ b/tests/fixtures/layout-requirement-older-than.yaml @@ -0,0 +1,39 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - username: jenkins + older-than: 48h + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - username: jenkins + older-than: 48h + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/fixtures/layout-requirement-open.yaml b/tests/fixtures/layout-requirement-open.yaml new file mode 100644 index 0000000000..e62719d963 --- /dev/null +++ b/tests/fixtures/layout-requirement-open.yaml @@ -0,0 +1,20 @@ +pipelines: + - name: check + manager: IndependentPipelineManager + require: + open: True + trigger: + gerrit: + - event: patchset-created + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project + check: + - project-check diff --git a/tests/fixtures/layout-requirement-status.yaml b/tests/fixtures/layout-requirement-status.yaml new file mode 100644 index 0000000000..af33468288 --- /dev/null +++ b/tests/fixtures/layout-requirement-status.yaml @@ -0,0 +1,20 @@ +pipelines: + - name: check + manager: IndependentPipelineManager + require: + status: NEW + trigger: + gerrit: + - event: patchset-created + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project + check: + - project-check diff --git a/tests/fixtures/layout-requirement-username.yaml b/tests/fixtures/layout-requirement-username.yaml new file mode 100644 index 0000000000..7a549f04b6 --- /dev/null +++ b/tests/fixtures/layout-requirement-username.yaml @@ -0,0 +1,37 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - username: jenkins + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - username: jenkins + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/fixtures/layout-requirement-vote.yaml b/tests/fixtures/layout-requirement-vote.yaml new file mode 100644 index 0000000000..7ccadffa68 --- /dev/null +++ b/tests/fixtures/layout-requirement-vote.yaml @@ -0,0 +1,39 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - username: jenkins + verified: 1 + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - username: jenkins + verified: 1 + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/fixtures/layout-requirement-vote1.yaml b/tests/fixtures/layout-requirement-vote1.yaml new file mode 100644 index 0000000000..7ccadffa68 --- /dev/null +++ b/tests/fixtures/layout-requirement-vote1.yaml @@ -0,0 +1,39 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - username: jenkins + verified: 1 + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - username: jenkins + verified: 1 + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/fixtures/layout-requirement-vote2.yaml b/tests/fixtures/layout-requirement-vote2.yaml new file mode 100644 index 0000000000..33d84d1d72 --- /dev/null +++ b/tests/fixtures/layout-requirement-vote2.yaml @@ -0,0 +1,39 @@ +pipelines: + - name: pipeline + manager: IndependentPipelineManager + require: + approval: + - username: jenkins + verified: [1, 2] + trigger: + gerrit: + - event: comment-added + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + + - name: trigger + manager: IndependentPipelineManager + trigger: + gerrit: + - event: comment-added + require-approval: + - username: jenkins + verified: [1, 2] + success: + gerrit: + verified: 1 + failure: + gerrit: + verified: -1 + +projects: + - name: org/project1 + pipeline: + - project1-pipeline + - name: org/project2 + trigger: + - project2-trigger diff --git a/tests/test_requirements.py b/tests/test_requirements.py new file mode 100644 index 0000000000..120e37e0f8 --- /dev/null +++ b/tests/test_requirements.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python + +# Copyright 2012-2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import time + +from tests.base import ZuulTestCase + +logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(name)-32s ' + '%(levelname)-8s %(message)s') + + +class TestRequirements(ZuulTestCase): + """Test pipeline and trigger requirements""" + + def test_pipeline_require_approval_newer_than(self): + "Test pipeline requirement: approval newer than" + return self._test_require_approval_newer_than('org/project1', + 'project1-pipeline') + + def test_trigger_require_approval_newer_than(self): + "Test trigger requirement: approval newer than" + return self._test_require_approval_newer_than('org/project2', + 'project2-trigger') + + def _test_require_approval_newer_than(self, project, job): + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-newer-than.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + # No +1 from Jenkins so should not be enqueued + self.assertEqual(len(self.history), 0) + + # Add a too-old +1, should not be enqueued + A.addApproval('VRFY', 1, granted_on=time.time() - 72 * 60 * 60) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # Add a recent +1 + self.fake_gerrit.addEvent(A.addApproval('VRFY', 1)) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, job) + + def test_pipeline_require_approval_older_than(self): + "Test pipeline requirement: approval older than" + return self._test_require_approval_older_than('org/project1', + 'project1-pipeline') + + def test_trigger_require_approval_older_than(self): + "Test trigger requirement: approval older than" + return self._test_require_approval_older_than('org/project2', + 'project2-trigger') + + def _test_require_approval_older_than(self, project, job): + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-older-than.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + # No +1 from Jenkins so should not be enqueued + self.assertEqual(len(self.history), 0) + + # Add a recent +1 which should not be enqueued + A.addApproval('VRFY', 1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # Add an old +1 which should be enqueued + A.addApproval('VRFY', 1, granted_on=time.time() - 72 * 60 * 60) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, job) + + def test_pipeline_require_approval_username(self): + "Test pipeline requirement: approval username" + return self._test_require_approval_username('org/project1', + 'project1-pipeline') + + def test_trigger_require_approval_username(self): + "Test trigger requirement: approval username" + return self._test_require_approval_username('org/project2', + 'project2-trigger') + + def _test_require_approval_username(self, project, job): + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-username.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + # No approval from Jenkins so should not be enqueued + self.assertEqual(len(self.history), 0) + + # Add an approval from Jenkins + A.addApproval('VRFY', 1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, job) + + def test_pipeline_require_approval_email(self): + "Test pipeline requirement: approval email" + return self._test_require_approval_email('org/project1', + 'project1-pipeline') + + def test_trigger_require_approval_email(self): + "Test trigger requirement: approval email" + return self._test_require_approval_email('org/project2', + 'project2-trigger') + + def _test_require_approval_email(self, project, job): + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-email.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + # No approval from Jenkins so should not be enqueued + self.assertEqual(len(self.history), 0) + + # Add an approval from Jenkins + A.addApproval('VRFY', 1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, job) + + def test_pipeline_require_approval_vote1(self): + "Test pipeline requirement: approval vote with one value" + return self._test_require_approval_vote1('org/project1', + 'project1-pipeline') + + def test_trigger_require_approval_vote1(self): + "Test trigger requirement: approval vote with one value" + return self._test_require_approval_vote1('org/project2', + 'project2-trigger') + + def _test_require_approval_vote1(self, project, job): + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-vote1.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + # No approval from Jenkins so should not be enqueued + self.assertEqual(len(self.history), 0) + + # A -1 from jenkins should not cause it to be enqueued + A.addApproval('VRFY', -1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A +1 should allow it to be enqueued + A.addApproval('VRFY', 1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, job) + + def test_pipeline_require_approval_vote2(self): + "Test pipeline requirement: approval vote with two values" + return self._test_require_approval_vote2('org/project1', + 'project1-pipeline') + + def test_trigger_require_approval_vote2(self): + "Test trigger requirement: approval vote with two values" + return self._test_require_approval_vote2('org/project2', + 'project2-trigger') + + def _test_require_approval_vote2(self, project, job): + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-vote2.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange(project, 'master', 'A') + # A comment event that we will keep submitting to trigger + comment = A.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + # No approval from Jenkins so should not be enqueued + self.assertEqual(len(self.history), 0) + + # A -1 from jenkins should not cause it to be enqueued + A.addApproval('VRFY', -1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A -2 from jenkins should not cause it to be enqueued + A.addApproval('VRFY', -2) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + # A +1 should allow it to be enqueued + A.addApproval('VRFY', 1) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + self.assertEqual(self.history[0].name, job) + + # A +2 should allow it to be enqueued + B = self.fake_gerrit.addFakeChange(project, 'master', 'B') + # A comment event that we will keep submitting to trigger + comment = B.addApproval('CRVW', 2, username='nobody') + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + + B.addApproval('VRFY', 2) + self.fake_gerrit.addEvent(comment) + self.waitUntilSettled() + self.assertEqual(len(self.history), 2) + self.assertEqual(self.history[1].name, job) + + def test_pipeline_require_current_patchset(self): + "Test pipeline requirement: current-patchset" + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-' + 'current-patchset.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + # Create two patchsets and let their tests settle out. Then + # comment on first patchset and check that no additional + # jobs are run. + A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A') + self.fake_gerrit.addEvent(A.addApproval('CRVW', 1)) + self.waitUntilSettled() + A.addPatchset() + self.fake_gerrit.addEvent(A.addApproval('CRVW', 1)) + self.waitUntilSettled() + + self.assertEqual(len(self.history), 2) # one job for each ps + self.fake_gerrit.addEvent(A.getChangeCommentEvent(1)) + self.waitUntilSettled() + + # Assert no new jobs ran after event for old patchset. + self.assertEqual(len(self.history), 2) + + # Make sure the same event on a new PS will trigger + self.fake_gerrit.addEvent(A.getChangeCommentEvent(2)) + self.waitUntilSettled() + self.assertEqual(len(self.history), 3) + + def test_pipeline_require_open(self): + "Test pipeline requirement: open" + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-open.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A', + status='MERGED') + self.fake_gerrit.addEvent(A.addApproval('CRVW', 2)) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B') + self.fake_gerrit.addEvent(B.addApproval('CRVW', 2)) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) + + def test_pipeline_require_status(self): + "Test pipeline requirement: status" + self.config.set('zuul', 'layout_config', + 'tests/fixtures/layout-requirement-status.yaml') + self.sched.reconfigure(self.config) + self.registerJobs() + + A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A', + status='MERGED') + self.fake_gerrit.addEvent(A.addApproval('CRVW', 2)) + self.waitUntilSettled() + self.assertEqual(len(self.history), 0) + + B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B') + self.fake_gerrit.addEvent(B.addApproval('CRVW', 2)) + self.waitUntilSettled() + self.assertEqual(len(self.history), 1) diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index 6e65774a94..70a9ec1886 100755 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -664,31 +664,6 @@ class TestScheduler(ZuulTestCase): self.assertTrue(trigger.canMerge(a, mgr.getSubmitAllowNeeds())) trigger.maintainCache([]) - def test_pipeline_requirements_closed_change(self): - "Test that pipeline requirements for closed changes are effective" - self.config.set('zuul', 'layout_config', - 'tests/fixtures/layout-pipeline-requirements.yaml') - self.sched.reconfigure(self.config) - - A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A', - status='MERGED') - self.fake_gerrit.addEvent(A.addApproval('CRVW', 2)) - self.waitUntilSettled() - self.assertEqual(len(self.history), 0) - self.assertEqual(len(self.builds), 0) - - B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B', - status='MERGED') - B.addApproval('CRVW', 2) - B.addApproval('VRFY', 1) - self.fake_gerrit.addEvent(B.addApproval('APRV', 1)) - self.waitUntilSettled() - self.assertEqual(len(self.history), 0) - self.assertEqual(len(self.builds), 0) - - for pipeline in self.sched.layout.pipelines.values(): - pipeline.trigger.maintainCache([]) - def test_build_configuration(self): "Test that zuul merges the right commits for testing" @@ -1651,135 +1626,6 @@ class TestScheduler(ZuulTestCase): self.assertEqual(D.data['status'], 'MERGED') self.assertEqual(D.reported, 2) - def test_pipeline_requirements_approval_check_and_gate(self): - "Test pipeline requirements triggers both check and gate" - self.config.set('zuul', 'layout_config', - 'tests/fixtures/layout-pipeline-requirements.yaml') - self.sched.reconfigure(self.config) - self.registerJobs() - self._test_required_approval_check_and_gate() - - 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() - self._test_required_approval_check_and_gate() - - def _test_required_approval_check_and_gate(self): - 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_pipeline_requirements_approval_newer(self): - "Test pipeline requirements newer trigger parameter" - self.config.set('zuul', 'layout_config', - 'tests/fixtures/layout-pipeline-requirements.yaml') - self.sched.reconfigure(self.config) - self.registerJobs() - self._test_required_approval_newer() - - 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() - self._test_required_approval_newer() - - def _test_required_approval_newer(self): - 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_pipeline_requirements_approval_older(self): - "Test pipeline requirements older trigger parameter" - self.config.set('zuul', 'layout_config', - 'tests/fixtures/layout-pipeline-requirements.yaml') - self.sched.reconfigure(self.config) - self.registerJobs() - self._test_required_approval_older() - - 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() - self._test_required_approval_older() - - def _test_required_approval_older(self): - 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 @@ -2921,35 +2767,3 @@ For CI problems and help debugging, contact ci@example.org""" self.getJobFromHistory('experimental-project-test').result, 'SUCCESS') self.assertEqual(A.reported, 1) - - def test_old_patchset_doesnt_trigger(self): - "Test that jobs never run against old patchsets" - self.config.set('zuul', 'layout_config', - 'tests/fixtures/layout-current-patchset.yaml') - self.sched.reconfigure(self.config) - self.registerJobs() - # Create two patchsets and let their tests settle out. Then - # comment on first patchset and check that no additional - # jobs are run. - A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A') - # Added because the layout file really wants an approval but this - # doesn't match anyways. - self.fake_gerrit.addEvent(A.addApproval('CRVW', 1)) - self.waitUntilSettled() - A.addPatchset() - self.fake_gerrit.addEvent(A.addApproval('CRVW', 1)) - self.waitUntilSettled() - - old_history_count = len(self.history) - self.assertEqual(old_history_count, 2) # one job for each ps - self.fake_gerrit.addEvent(A.getChangeCommentEvent(1)) - self.waitUntilSettled() - - # Assert no new jobs ran after event for old patchset. - self.assertEqual(len(self.history), old_history_count) - - # The last thing we did was add an event for 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([]) diff --git a/zuul/model.py b/zuul/model.py index a97c541557..4a9a9f654e 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -979,10 +979,66 @@ class TriggerEvent(object): return change -class EventFilter(object): +class BaseFilter(object): + def __init__(self, required_approvals=[]): + self.required_approvals = required_approvals + + for a in self.required_approvals: + for k, v in a.items(): + if k == 'username': + pass + elif k == 'email-filter': + a[k] = re.compile(v) + elif k == 'newer-than': + a[k] = time_to_seconds(v) + elif k == 'older-than': + a[k] = time_to_seconds(v) + else: + if not isinstance(v, list): + a[k] = [v] + + def matchesRequiredApprovals(self, change): + now = time.time() + for rapproval in self.required_approvals: + matches_approval = False + for approval in change.approvals: + if 'description' not in approval: + continue + found_approval = True + by = approval.get('by', {}) + for k, v in rapproval.items(): + if k == 'username': + if (by.get('username', '') != v): + found_approval = False + elif k == 'email-filter': + if (not v.search(by.get('email', ''))): + found_approval = False + elif k == 'newer-than': + t = now - v + if (approval['grantedOn'] < t): + found_approval = False + elif k == 'older-than': + t = now - v + if (approval['grantedOn'] >= t): + found_approval = False + else: + if (normalizeCategory(approval['description']) != k or + int(approval['value']) not in v): + found_approval = False + if found_approval: + matches_approval = True + break + if not matches_approval: + return False + return True + + +class EventFilter(BaseFilter): def __init__(self, types=[], branches=[], refs=[], event_approvals={}, comment_filters=[], email_filters=[], username_filters=[], - timespecs=[], require_approvals=[]): + timespecs=[], required_approvals=[]): + super(EventFilter, self).__init__( + required_approvals=required_approvals) self._types = types self._branches = branches self._refs = refs @@ -996,17 +1052,8 @@ class EventFilter(object): self.email_filters = [re.compile(x) for x in email_filters] self.username_filters = [re.compile(x) for x in username_filters] self.event_approvals = event_approvals - self.require_approvals = require_approvals self.timespecs = timespecs - for a in self.require_approvals: - if 'older-than' in a: - a['older-than'] = time_to_seconds(a['older-than']) - if 'newer-than' in a: - a['newer-than'] = time_to_seconds(a['newer-than']) - if 'email-filter' in a: - a['email-filter'] = re.compile(a['email-filter']) - def __repr__(self): ret = '= t): - found_approval = False - else: - if (normalizeCategory(approval['description']) != k or - int(approval['value']) != v): - found_approval = False - if found_approval: - matches_approval = True - break - if not matches_approval: - return False + # required approvals are ANDed + if not self.matchesRequiredApprovals(change): + return False # timespecs are ORed matches_timespec = False @@ -1149,21 +1167,14 @@ class EventFilter(object): return True -class ChangeishFilter(object): +class ChangeishFilter(BaseFilter): def __init__(self, open=None, current_patchset=None, - statuses=[], approvals=[]): + statuses=[], required_approvals=[]): + super(ChangeishFilter, self).__init__( + required_approvals=required_approvals) self.open = open self.current_patchset = current_patchset self.statuses = statuses - self.approvals = approvals - - for a in self.approvals: - if 'older-than' in a: - a['older-than'] = time_to_seconds(a['older-than']) - if 'newer-than' in a: - a['newer-than'] = time_to_seconds(a['newer-than']) - if 'email-filter' in a: - a['email-filter'] = re.compile(a['email-filter']) def __repr__(self): ret = '= t): - found_approval = False - else: - if (normalizeCategory(approval['description']) != k or - int(approval['value']) != v): - found_approval = False - if found_approval: - matches_approval = True - break - if not matches_approval: - return False + # required approvals are ANDed + if not self.matchesRequiredApprovals(change): + return False return True diff --git a/zuul/scheduler.py b/zuul/scheduler.py index 09fda725d5..e919b0c573 100644 --- a/zuul/scheduler.py +++ b/zuul/scheduler.py @@ -280,7 +280,7 @@ class Scheduler(threading.Thread): open=require.get('open'), current_patchset=require.get('current-patchset'), statuses=toList(require.get('status')), - approvals=toList(require.get('approval'))) + required_approvals=toList(require.get('approval'))) manager.changeish_filters.append(f) # TODO: move this into triggers (may require pluggable @@ -302,7 +302,7 @@ class Scheduler(threading.Thread): toList(trigger.get('email_filter')), username_filters= toList(trigger.get('username_filter')), - require_approvals= + required_approvals= toList(trigger.get('require-approval'))) manager.event_filters.append(f) elif 'timer' in conf_pipeline['trigger']: