Cleanup approval requirement testing

On further inspection, trigger requirements will still be required
for parts of the kind of pipeline construction that we are using in
OpenStack; the idea that they could be completely replaced by
pipeline requirements is incorrect.  To that end, un-deprecate
trigger requirements and cleanup the testing of both of them so that
each requirement is tested (relatively) independently.  A small
amount of requirement composition is also tested, though all possible
combinations (a lot!) are not.

Move this testing into a new file for organizational purposes.

Also, support multiple vote values (eg, "+1" or "+2") when requiring
an approval.

Change-Id: I683c9a574ced0e27ced59e62b8059fef2dfd8b20
changes/29/102029/3
James E. Blair 8 years ago
parent 2c9effbfa0
commit 9c17dbf763
  1. 10
      doc/source/zuul.rst
  2. 59
      tests/fixtures/layout-pipeline-requirements.yaml
  3. 58
      tests/fixtures/layout-require-approval.yaml
  4. 4
      tests/fixtures/layout-requirement-current-patchset.yaml
  5. 37
      tests/fixtures/layout-requirement-email.yaml
  6. 39
      tests/fixtures/layout-requirement-newer-than.yaml
  7. 39
      tests/fixtures/layout-requirement-older-than.yaml
  8. 20
      tests/fixtures/layout-requirement-open.yaml
  9. 20
      tests/fixtures/layout-requirement-status.yaml
  10. 37
      tests/fixtures/layout-requirement-username.yaml
  11. 39
      tests/fixtures/layout-requirement-vote.yaml
  12. 39
      tests/fixtures/layout-requirement-vote1.yaml
  13. 39
      tests/fixtures/layout-requirement-vote2.yaml
  14. 323
      tests/test_requirements.py
  15. 186
      tests/test_scheduler.py
  16. 168
      zuul/model.py
  17. 4
      zuul/scheduler.py

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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([])

@ -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 = '<EventFilter'
@ -1019,9 +1066,9 @@ class EventFilter(object):
if self.event_approvals:
ret += ' event_approvals: %s' % ', '.join(
['%s:%s' % a for a in self.event_approvals.items()])
if self.require_approvals:
ret += ' require_approvals: %s' % ', '.join(
['%s' % a for a in self.require_approvals])
if self.required_approvals:
ret += ' required_approvals: %s' % ', '.join(
['%s' % a for a in self.required_approvals])
if self._comment_filters:
ret += ' comment_filters: %s' % ', '.join(self._comment_filters)
if self._email_filters:
@ -1101,42 +1148,13 @@ class EventFilter(object):
if not matches_approval:
return False
if self.require_approvals and not change.approvals:
if self.required_approvals and not change.approvals:
# A change with no approvals can not match
return False
now = time.time()
for rapproval in self.require_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']) != 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 = '<ChangeishFilter'
@ -1174,8 +1185,8 @@ class ChangeishFilter(object):
ret += ' current-patchset: %s' % self.current_patchset
if self.statuses:
ret += ' statuses: %s' % ', '.join(self.statuses)
if self.approvals:
ret += ' approvals: %s' % str(self.approvals)
if self.required_approvals:
ret += ' required_approvals: %s' % str(self.required_approvals)
ret += '>'
return ret
@ -1193,42 +1204,13 @@ class ChangeishFilter(object):
if change.status not in self.statuses:
return False
if self.approvals and not change.approvals:
if self.required_approvals and not change.approvals:
# A change with no approvals can not match
return False
now = time.time()
for rapproval in self.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']) != 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

@ -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']:

Loading…
Cancel
Save