Merge "Use timeout fixture (30 seconds) for tests"
diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst
index afe4cf6..0ec9f88 100644
--- a/doc/source/zuul.rst
+++ b/doc/source/zuul.rst
@@ -634,8 +634,35 @@
- name: plugin-triggering
jobprefix: plugin-foobar
-You can pass several parameters to a template. A ``parameter`` value will be
-used for expansion of ``{parameter}`` in the template strings.
+You can pass several parameters to a template. A ``parameter`` value
+will be used for expansion of ``{parameter}`` in the template
+strings. The parameter ``name`` will be automatically provided and
+will contain the short name of the project, that is the portion of the
+project name after the last ``/`` character.
+
+Multiple templates can be combined in a project, and the jobs from all
+of those templates will be added to the project. Individual jobs may
+also be added::
+
+ projects:
+ - name: plugin/foobar
+ template:
+ - name: plugin-triggering
+ jobprefix: plugin-foobar
+ - name: plugin-extras
+ jobprefix: plugin-foobar
+ check:
+ - foobar-extra-special-job
+
+The order of the jobs listed in the project (which only affects the
+order of jobs listed on the report) will be the jobs from each
+template in the order listed, followed by any jobs individually listed
+for the project.
+
+Note that if multiple templates are used for a project and one
+template specifies a job that is also specified in another template,
+or specified in the project itself, those jobs will be duplicated in
+the resulting project configuration.
logging.conf
~~~~~~~~~~~~
diff --git a/tests/fixtures/layout.yaml b/tests/fixtures/layout.yaml
index dc659fb..98dfe86 100644
--- a/tests/fixtures/layout.yaml
+++ b/tests/fixtures/layout.yaml
@@ -110,6 +110,16 @@
check:
- '{projectname}-test1'
- '{projectname}-test2'
+ - name: test-three-and-four
+ check:
+ - '{name}-test3'
+ - '{name}-test4'
+ - name: test-five
+ check:
+ - '{name}-{something}-test5'
+ - name: test-five-also
+ check:
+ - '{name}-{something}-test5'
projects:
- name: org/project
@@ -195,8 +205,20 @@
- name: org/templated-project
template:
- - name: test-one-and-two
- projectname: project
+ - name: test-one-and-two
+ projectname: project
+
+ - name: org/layered-project
+ template:
+ - name: test-one-and-two
+ projectname: project
+ - name: test-three-and-four
+ - name: test-five
+ something: foo
+ - name: test-five-also
+ something: foo
+ check:
+ - project-test6
- name: org/node-project
gate:
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index c15d70c..48f2281 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -766,6 +766,7 @@
self.init_repo("org/one-job-project")
self.init_repo("org/nonvoting-project")
self.init_repo("org/templated-project")
+ self.init_repo("org/layered-project")
self.init_repo("org/node-project")
self.init_repo("org/conflict-project")
@@ -1958,6 +1959,32 @@
self.assertEqual(self.getJobFromHistory('project-test2').result,
'SUCCESS')
+ def test_layered_templates(self):
+ "Test whether a job generated via a template can be launched"
+
+ A = self.fake_gerrit.addFakeChange(
+ 'org/layered-project', 'master', 'A')
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertEqual(self.getJobFromHistory('project-test1').result,
+ 'SUCCESS')
+ self.assertEqual(self.getJobFromHistory('project-test2').result,
+ 'SUCCESS')
+ self.assertEqual(self.getJobFromHistory('layered-project-test3'
+ ).result, 'SUCCESS')
+ self.assertEqual(self.getJobFromHistory('layered-project-test4'
+ ).result, 'SUCCESS')
+ # test5 should run twice because two templates define it
+ test5_count = 0
+ for job in self.worker.build_history:
+ if job.name == 'layered-project-foo-test5':
+ test5_count += 1
+ self.assertEqual(job.result, 'SUCCESS')
+ self.assertEqual(test5_count, 2)
+ self.assertEqual(self.getJobFromHistory('project-test6').result,
+ 'SUCCESS')
+
def test_dependent_changes_dequeue(self):
"Test that dependent patches are not needlessly tested"
diff --git a/zuul/layoutvalidator.py b/zuul/layoutvalidator.py
index 70c7101..bc82501 100644
--- a/zuul/layoutvalidator.py
+++ b/zuul/layoutvalidator.py
@@ -156,6 +156,9 @@
# Craft the templates schemas
schema = {v.Required('name'): v.Any(*template_names)}
for required_param in template_parameters:
+ # special case 'name' which will be automatically provided
+ if required_param == 'name':
+ continue
# add this template parameters as requirements:
schema.update({v.Required(required_param): str})
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index f8dd6e1..96bd624 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -286,16 +286,33 @@
for config_project in data.get('projects', []):
project = Project(config_project['name'])
+ shortname = config_project['name'].split('/')[-1]
- for requested_template in config_project.get('template', []):
+ # This is reversed due to the prepend operation below, so
+ # the ultimate order is templates (in order) followed by
+ # statically defined jobs.
+ for requested_template in reversed(
+ config_project.get('template', [])):
# Fetch the template from 'project-templates'
tpl = project_templates.get(
requested_template.get('name'))
# Expand it with the project context
+ requested_template['name'] = shortname
expanded = deep_format(tpl, requested_template)
- # Finally merge the expansion with whatever has been already
- # defined for this project
- config_project.update(expanded)
+ # Finally merge the expansion with whatever has been
+ # already defined for this project. Prepend our new
+ # jobs to existing ones (which may have been
+ # statically defined or defined by other templates).
+ for pipeline in layout.pipelines.values():
+ if pipeline.name in expanded:
+ config_project.update(
+ {pipeline.name: expanded[pipeline.name] +
+ config_project.get(pipeline.name, [])})
+ # TODO: future enhancement -- add an option to the
+ # template block to indicate that duplicate jobs should be
+ # merged (especially to handle the case where they have
+ # children and you want all of the children to run after a
+ # single run of the parent).
layout.projects[config_project['name']] = project
mode = config_project.get('merge-mode', 'merge-resolve')