Merge "Make file matchers overridable"

This commit is contained in:
Zuul 2018-06-11 14:46:53 +00:00 committed by Gerrit Code Review
commit 8dea8a32fd
6 changed files with 169 additions and 41 deletions

View File

@ -967,24 +967,21 @@ Here is an example of two job definitions:
it will remain set for all child jobs and variants (it can not be it will remain set for all child jobs and variants (it can not be
set to ``false``). set to ``false``).
.. _matchers:
The following job attributes are considered "matchers". They are
not inherited in the usual manner, instead, these attributes are
used to determine whether a specific variant is used when
running a job.
.. attr:: branches .. attr:: branches
A regular expression (or list of regular expressions) which A regular expression (or list of regular expressions) which
describe on what branches a job should run (or in the case of describe on what branches a job should run (or in the case of
variants: to alter the behavior of a job for a certain branch). variants, to alter the behavior of a job for a certain branch).
This attribute is not inherited in the usual manner. Instead,
it is used to determine whether each variant on which it appears
will be used when running the job.
If there is no job definition for a given job which matches the If there is no job definition for a given job which matches the
branch of an item, then that job is not run for the item. branch of an item, then that job is not run for the item.
Otherwise, all of the job variants which match that branch (and Otherwise, all of the job variants which match that branch are
any other selection criteria) are used when freezing the job. used when freezing the job. However, if
However, if :attr:`job.override-checkout` or :attr:`job.override-checkout` or
:attr:`job.required-projects.override-checkout` are set for a :attr:`job.required-projects.override-checkout` are set for a
project, Zuul will attempt to use the job variants which match project, Zuul will attempt to use the job variants which match
the values supplied in ``override-checkout`` for jobs defined in the values supplied in ``override-checkout`` for jobs defined in
@ -1053,18 +1050,20 @@ Here is an example of two job definitions:
.. attr:: files .. attr:: files
This matcher indicates that the job should only run on changes This indicates that the job should only run on changes where the
where the specified files are modified. This is a regular specified files are modified. Unlike **branches**, this value
expression or list of regular expressions. is subject to inheritance and overriding, so only the final
value is used to determine if the job should run. This is a
regular expression or list of regular expressions.
.. attr:: irrelevant-files .. attr:: irrelevant-files
This matcher is a negative complement of **files**. It This is a negative complement of **files**. It indicates that
indicates that the job should run unless *all* of the files the job should run unless *all* of the files changed match this
changed match this list. In other words, if the regular list. In other words, if the regular expression ``docs/.*`` is
expression ``docs/.*`` is supplied, then this job will not run supplied, then this job will not run if the only files changed
if the only files changed are in the docs directory. A regular are in the docs directory. A regular expression or list of
expression or list of regular expressions. regular expressions.
.. _project: .. _project:

View File

@ -0,0 +1,8 @@
features:
- |
Files (and irrelevant-files) matchers are now overridable. Zuul
now uses only branch matchers to collect job variants. Once those
variants are collected, they are combined, and the files and
irrelevant-files attributes are inherited and overridden as any
other job attribute. The final values are used to determine
whether the job should ultimately run.

View File

@ -0,0 +1,63 @@
- pipeline:
name: check
manager: independent
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- job:
name: base
parent: null
run: playbooks/base.yaml
- job:
name: parent-override-job
files: parent1.txt
- job:
name: parent-override-job
files: parent2.txt
- job:
name: child-job
parent: parent-override-job
- job:
name: child-override-job
parent: parent-override-job
files: child.txt
- job:
name: project-override-job
parent: parent-override-job
- job:
name: irr-job
files:
- tests/.*
irrelevant-files:
- README
- job:
name: irr-override-job
parent: irr-job
irrelevant-files:
- README
- tests/docs/.*
- project:
name: org/project
check:
jobs:
- child-job # parent2
- child-override-job # child
- project-override-job: # project
files: project.txt
- irr-job # tests/docs/foo tests/foo
- irr-override-job # tests/foo

View File

@ -82,22 +82,22 @@ class TestJob(BaseTestCase):
def test_change_matches_returns_false_for_matched_skip_if(self): def test_change_matches_returns_false_for_matched_skip_if(self):
change = model.Change('project') change = model.Change('project')
change.files = ['/COMMIT_MSG', 'docs/foo'] change.files = ['/COMMIT_MSG', 'docs/foo']
self.assertFalse(self.job.changeMatches(change)) self.assertFalse(self.job.changeMatchesFiles(change))
def test_change_matches_returns_false_for_single_matched_skip_if(self): def test_change_matches_returns_false_for_single_matched_skip_if(self):
change = model.Change('project') change = model.Change('project')
change.files = ['docs/foo'] change.files = ['docs/foo']
self.assertFalse(self.job.changeMatches(change)) self.assertFalse(self.job.changeMatchesFiles(change))
def test_change_matches_returns_true_for_unmatched_skip_if(self): def test_change_matches_returns_true_for_unmatched_skip_if(self):
change = model.Change('project') change = model.Change('project')
change.files = ['/COMMIT_MSG', 'foo'] change.files = ['/COMMIT_MSG', 'foo']
self.assertTrue(self.job.changeMatches(change)) self.assertTrue(self.job.changeMatchesFiles(change))
def test_change_matches_returns_true_for_single_unmatched_skip_if(self): def test_change_matches_returns_true_for_single_unmatched_skip_if(self):
change = model.Change('project') change = model.Change('project')
change.files = ['foo'] change.files = ['foo']
self.assertTrue(self.job.changeMatches(change)) self.assertTrue(self.job.changeMatchesFiles(change))
def test_job_sets_defaults_for_boolean_attributes(self): def test_job_sets_defaults_for_boolean_attributes(self):
self.assertIsNotNone(self.job.voting) self.assertIsNotNone(self.job.voting)
@ -203,9 +203,9 @@ class TestJob(BaseTestCase):
item = queue.enqueueChange(change) item = queue.enqueueChange(change)
item.layout = self.layout item.layout = self.layout
self.assertTrue(base.changeMatches(change)) self.assertTrue(base.changeMatchesBranch(change))
self.assertTrue(python27.changeMatches(change)) self.assertTrue(python27.changeMatchesBranch(change))
self.assertFalse(python27diablo.changeMatches(change)) self.assertFalse(python27diablo.changeMatchesBranch(change))
item.freezeJobGraph() item.freezeJobGraph()
self.assertEqual(len(item.getJobs()), 1) self.assertEqual(len(item.getJobs()), 1)
@ -217,9 +217,9 @@ class TestJob(BaseTestCase):
item = queue.enqueueChange(change) item = queue.enqueueChange(change)
item.layout = self.layout item.layout = self.layout
self.assertTrue(base.changeMatches(change)) self.assertTrue(base.changeMatchesBranch(change))
self.assertTrue(python27.changeMatches(change)) self.assertTrue(python27.changeMatchesBranch(change))
self.assertTrue(python27diablo.changeMatches(change)) self.assertTrue(python27diablo.changeMatchesBranch(change))
item.freezeJobGraph() item.freezeJobGraph()
self.assertEqual(len(item.getJobs()), 1) self.assertEqual(len(item.getJobs()), 1)
@ -269,8 +269,8 @@ class TestJob(BaseTestCase):
item = queue.enqueueChange(change) item = queue.enqueueChange(change)
item.layout = self.layout item.layout = self.layout
self.assertTrue(base.changeMatches(change)) self.assertTrue(base.changeMatchesFiles(change))
self.assertFalse(python27.changeMatches(change)) self.assertFalse(python27.changeMatchesFiles(change))
item.freezeJobGraph() item.freezeJobGraph()
self.assertEqual([], item.getJobs()) self.assertEqual([], item.getJobs())

View File

@ -4893,10 +4893,61 @@ For CI problems and help debugging, contact ci@example.org"""
self.waitUntilSettled() self.waitUntilSettled()
self.assertHistory([ self.assertHistory([
dict(name='child-job', result='SUCCESS', changes='2,1'),
dict(name='child-job', result='SUCCESS', changes='3,1'), dict(name='child-job', result='SUCCESS', changes='3,1'),
dict(name='child-job', result='SUCCESS', changes='4,1'), dict(name='child-job', result='SUCCESS', changes='4,1'),
]) ], ordered=False)
@simple_layout('layouts/file-matchers.yaml')
def test_file_matchers(self):
"Test several file matchers"
files = {'parent1.txt': ''}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=files)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
files = {'parent2.txt': ''}
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B',
files=files)
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
files = {'child.txt': ''}
C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C',
files=files)
self.fake_gerrit.addEvent(C.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
files = {'project.txt': ''}
D = self.fake_gerrit.addFakeChange('org/project', 'master', 'D',
files=files)
self.fake_gerrit.addEvent(D.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
files = {'tests/foo': ''}
E = self.fake_gerrit.addFakeChange('org/project', 'master', 'E',
files=files)
self.fake_gerrit.addEvent(E.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
files = {'tests/docs/foo': ''}
F = self.fake_gerrit.addFakeChange('org/project', 'master', 'F',
files=files)
self.fake_gerrit.addEvent(F.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertHistory([
dict(name='child-job', result='SUCCESS', changes='2,1'),
dict(name='child-override-job', result='SUCCESS', changes='3,1'),
dict(name='project-override-job', result='SUCCESS', changes='4,1'),
dict(name='irr-job', result='SUCCESS', changes='5,1'),
dict(name='irr-override-job', result='SUCCESS', changes='5,1'),
dict(name='irr-job', result='SUCCESS', changes='6,1'),
], ordered=False)
def test_trusted_project_dep_on_non_live_untrusted_project(self): def test_trusted_project_dep_on_non_live_untrusted_project(self):
# Test we get a layout for trusted projects when they depend on # Test we get a layout for trusted projects when they depend on

View File

@ -934,8 +934,6 @@ class Job(ConfigObject):
success_message=None, success_message=None,
failure_url=None, failure_url=None,
success_url=None, success_url=None,
# Matchers. These are separate so they can be individually
# overidden.
branch_matcher=None, branch_matcher=None,
file_matcher=None, file_matcher=None,
irrelevant_file_matcher=None, # skip-if irrelevant_file_matcher=None, # skip-if
@ -1294,7 +1292,7 @@ class Job(ConfigObject):
self.inheritance_path = self.inheritance_path + (repr(other),) self.inheritance_path = self.inheritance_path + (repr(other),)
def changeMatches(self, change, override_branch=None): def changeMatchesBranch(self, change, override_branch=None):
if override_branch is None: if override_branch is None:
branch_change = change branch_change = change
else: else:
@ -1307,7 +1305,9 @@ class Job(ConfigObject):
if self.branch_matcher and not self.branch_matcher.matches( if self.branch_matcher and not self.branch_matcher.matches(
branch_change): branch_change):
return False return False
return True
def changeMatchesFiles(self, change):
if self.file_matcher and not self.file_matcher.matches(change): if self.file_matcher and not self.file_matcher.matches(change):
return False return False
@ -3057,8 +3057,9 @@ class Layout(object):
branches = self.tenant.getProjectBranches(project) branches = self.tenant.getProjectBranches(project)
if override_branch not in branches: if override_branch not in branches:
override_branch = None override_branch = None
if not variant.changeMatches(change, if not variant.changeMatchesBranch(
override_branch=override_branch): change,
override_branch=override_branch):
self.log.debug("Variant %s did not match %s", repr(variant), self.log.debug("Variant %s did not match %s", repr(variant),
change) change)
item.debug("Variant {variant} did not match".format( item.debug("Variant {variant} did not match".format(
@ -3140,7 +3141,7 @@ class Layout(object):
# jobs which match. # jobs which match.
override_checkouts = {} override_checkouts = {}
for variant in job_list.jobs[jobname]: for variant in job_list.jobs[jobname]:
if variant.changeMatches(change): if variant.changeMatchesBranch(change):
self._updateOverrideCheckouts(override_checkouts, variant) self._updateOverrideCheckouts(override_checkouts, variant)
try: try:
variants = self.collectJobs( variants = self.collectJobs(
@ -3167,7 +3168,7 @@ class Layout(object):
# variants # variants
matched = False matched = False
for variant in job_list.jobs[jobname]: for variant in job_list.jobs[jobname]:
if variant.changeMatches(change): if variant.changeMatchesBranch(change):
frozen_job.applyVariant(variant, item.layout) frozen_job.applyVariant(variant, item.layout)
matched = True matched = True
self.log.debug("Pipeline variant %s matched %s", self.log.debug("Pipeline variant %s matched %s",
@ -3185,6 +3186,12 @@ class Layout(object):
item.debug("No matching pipeline variants for {jobname}". item.debug("No matching pipeline variants for {jobname}".
format(jobname=jobname), indent=2) format(jobname=jobname), indent=2)
continue continue
if not frozen_job.changeMatchesFiles(change):
self.log.debug("Job %s did not match files in %s",
repr(frozen_job), change)
item.debug("Job {jobname} did not match files".
format(jobname=jobname), indent=2)
continue
if frozen_job.abstract: if frozen_job.abstract:
raise Exception("Job %s is abstract and may not be " raise Exception("Job %s is abstract and may not be "
"directly run" % "directly run" %