Allow merge failures to have unique reporters.
For example, we would like to be able to count (with the MySQL reporter) what jobs have failed because they can't be merged vs. real failures. Change-Id: I98eb8b8817bbda57efc2ef5bfcc2a5076fe8f4fd
This commit is contained in:
parent
8f752c3c82
commit
b7179777f4
|
@ -238,6 +238,12 @@ explanation of each of the parameters::
|
||||||
reported back to Gerrit when at least one voting build fails.
|
reported back to Gerrit when at least one voting build fails.
|
||||||
Defaults to "Build failed."
|
Defaults to "Build failed."
|
||||||
|
|
||||||
|
**merge-failure-message**
|
||||||
|
An optional field that supplies the introductory text in message
|
||||||
|
reported back to Gerrit when a change fails to merge with the
|
||||||
|
current state of the repository.
|
||||||
|
Defaults to "Merge failed."
|
||||||
|
|
||||||
**footer-message**
|
**footer-message**
|
||||||
An optional field to supply additional information after test results.
|
An optional field to supply additional information after test results.
|
||||||
Useful for adding information about the CI system such as debugging
|
Useful for adding information about the CI system such as debugging
|
||||||
|
@ -413,6 +419,12 @@ explanation of each of the parameters::
|
||||||
Uses the same syntax as **success**, but describes what Zuul should
|
Uses the same syntax as **success**, but describes what Zuul should
|
||||||
do if at least one job fails.
|
do if at least one job fails.
|
||||||
|
|
||||||
|
**merge-failure**
|
||||||
|
Uses the same syntax as **success**, but describes what Zuul should
|
||||||
|
do if it is unable to merge in the patchset. If no merge-failure
|
||||||
|
reporters are listed then the ``failure`` reporters will be used to
|
||||||
|
notify of unsuccessful merges.
|
||||||
|
|
||||||
**start**
|
**start**
|
||||||
Uses the same syntax as **success**, but describes what Zuul should
|
Uses the same syntax as **success**, but describes what Zuul should
|
||||||
do when a change is added to the pipeline manager. This can be used,
|
do when a change is added to the pipeline manager. This can be used,
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
pipelines:
|
||||||
|
- name: check
|
||||||
|
manager: IndependentPipelineManager
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: patchset-created
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
verified: 1
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -1
|
||||||
|
|
||||||
|
- name: post
|
||||||
|
manager: IndependentPipelineManager
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: ref-updated
|
||||||
|
ref: ^(?!refs/).*$
|
||||||
|
|
||||||
|
- name: gate
|
||||||
|
manager: DependentPipelineManager
|
||||||
|
failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
|
||||||
|
merge-failure-message: "The merge failed! For more information..."
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: comment-added
|
||||||
|
approval:
|
||||||
|
- approved: 1
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
verified: 2
|
||||||
|
submit: true
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -2
|
||||||
|
merge-failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -1
|
||||||
|
smtp:
|
||||||
|
to: you@example.com
|
||||||
|
start:
|
||||||
|
gerrit:
|
||||||
|
verified: 0
|
||||||
|
precedence: high
|
||||||
|
|
||||||
|
projects:
|
||||||
|
- name: org/project
|
||||||
|
check:
|
||||||
|
- project-merge:
|
||||||
|
- project-test1
|
||||||
|
- project-test2
|
||||||
|
gate:
|
||||||
|
- project-merge:
|
||||||
|
- project-test1
|
||||||
|
- project-test2
|
|
@ -0,0 +1,39 @@
|
||||||
|
pipelines:
|
||||||
|
- name: check
|
||||||
|
manager: IndependentPipelineManager
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: patchset-created
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
verified: 1
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -1
|
||||||
|
merge-failure-message:
|
||||||
|
|
||||||
|
- 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
|
||||||
|
approval:
|
||||||
|
- approved: 1
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
verified: 2
|
||||||
|
submit: true
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -2
|
||||||
|
merge-failure:
|
||||||
|
start:
|
||||||
|
gerrit:
|
||||||
|
verified: 0
|
||||||
|
precedence: high
|
||||||
|
|
||||||
|
projects:
|
||||||
|
- name: org/project
|
||||||
|
check:
|
||||||
|
- project-check
|
|
@ -0,0 +1,53 @@
|
||||||
|
pipelines:
|
||||||
|
- name: check
|
||||||
|
manager: IndependentPipelineManager
|
||||||
|
merge-failure-message: "Could not merge the change. Please rebase..."
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: patchset-created
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
verified: 1
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -1
|
||||||
|
|
||||||
|
- name: post
|
||||||
|
manager: IndependentPipelineManager
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: ref-updated
|
||||||
|
ref: ^(?!refs/).*$
|
||||||
|
merge-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
|
||||||
|
approval:
|
||||||
|
- approved: 1
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
verified: 2
|
||||||
|
submit: true
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -2
|
||||||
|
merge-failure:
|
||||||
|
gerrit:
|
||||||
|
verified: -1
|
||||||
|
smtp:
|
||||||
|
to: you@example.com
|
||||||
|
start:
|
||||||
|
gerrit:
|
||||||
|
verified: 0
|
||||||
|
precedence: high
|
||||||
|
|
||||||
|
projects:
|
||||||
|
- name: org/project
|
||||||
|
check:
|
||||||
|
- project-check
|
|
@ -3712,3 +3712,82 @@ For CI problems and help debugging, contact ci@example.org"""
|
||||||
|
|
||||||
self.assertEqual(failure_body, self.smtp_messages[0]['body'])
|
self.assertEqual(failure_body, self.smtp_messages[0]['body'])
|
||||||
self.assertEqual(success_body, self.smtp_messages[1]['body'])
|
self.assertEqual(success_body, self.smtp_messages[1]['body'])
|
||||||
|
|
||||||
|
def test_merge_failure_reporters(self):
|
||||||
|
"""Check that the config is set up correctly"""
|
||||||
|
|
||||||
|
self.config.set('zuul', 'layout_config',
|
||||||
|
'tests/fixtures/layout-merge-failure.yaml')
|
||||||
|
self.sched.reconfigure(self.config)
|
||||||
|
self.registerJobs()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
"Merge Failed.\n\nThis change was unable to be automatically "
|
||||||
|
"merged with the current state of the repository. Please rebase "
|
||||||
|
"your change and upload a new patchset.",
|
||||||
|
self.sched.layout.pipelines['check'].merge_failure_message)
|
||||||
|
self.assertEqual(
|
||||||
|
"The merge failed! For more information...",
|
||||||
|
self.sched.layout.pipelines['gate'].merge_failure_message)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
len(self.sched.layout.pipelines['check'].merge_failure_actions), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
len(self.sched.layout.pipelines['gate'].merge_failure_actions), 2)
|
||||||
|
|
||||||
|
self.assertTrue(isinstance(
|
||||||
|
self.sched.layout.pipelines['check'].merge_failure_actions[0].
|
||||||
|
reporter, zuul.reporter.gerrit.Reporter))
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
(
|
||||||
|
isinstance(self.sched.layout.pipelines['gate'].
|
||||||
|
merge_failure_actions[0].reporter,
|
||||||
|
zuul.reporter.smtp.Reporter) and
|
||||||
|
isinstance(self.sched.layout.pipelines['gate'].
|
||||||
|
merge_failure_actions[1].reporter,
|
||||||
|
zuul.reporter.gerrit.Reporter)
|
||||||
|
) or (
|
||||||
|
isinstance(self.sched.layout.pipelines['gate'].
|
||||||
|
merge_failure_actions[0].reporter,
|
||||||
|
zuul.reporter.gerrit.Reporter) and
|
||||||
|
isinstance(self.sched.layout.pipelines['gate'].
|
||||||
|
merge_failure_actions[1].reporter,
|
||||||
|
zuul.reporter.smtp.Reporter)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_merge_failure_reports(self):
|
||||||
|
"""Check that when a change fails to merge the correct message is sent
|
||||||
|
to the correct reporter"""
|
||||||
|
self.config.set('zuul', 'layout_config',
|
||||||
|
'tests/fixtures/layout-merge-failure.yaml')
|
||||||
|
self.sched.reconfigure(self.config)
|
||||||
|
self.registerJobs()
|
||||||
|
|
||||||
|
# Check a test failure isn't reported to SMTP
|
||||||
|
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||||
|
A.addApproval('CRVW', 2)
|
||||||
|
self.worker.addFailTest('project-test1', A)
|
||||||
|
self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
|
||||||
|
self.waitUntilSettled()
|
||||||
|
|
||||||
|
self.assertEqual(3, len(self.history)) # 3 jobs
|
||||||
|
self.assertEqual(0, len(self.smtp_messages))
|
||||||
|
|
||||||
|
# Check a merge failure is reported to SMTP
|
||||||
|
# B should be merged, but C will conflict with B
|
||||||
|
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
|
||||||
|
B.addPatchset(['conflict'])
|
||||||
|
C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
|
||||||
|
C.addPatchset(['conflict'])
|
||||||
|
B.addApproval('CRVW', 2)
|
||||||
|
C.addApproval('CRVW', 2)
|
||||||
|
self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
|
||||||
|
self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
|
||||||
|
self.waitUntilSettled()
|
||||||
|
|
||||||
|
self.assertEqual(6, len(self.history)) # A and B jobs
|
||||||
|
self.assertEqual(1, len(self.smtp_messages))
|
||||||
|
self.assertEqual('The merge failed! For more information...',
|
||||||
|
self.smtp_messages[0]['body'])
|
||||||
|
|
|
@ -79,11 +79,13 @@ class LayoutSchema(object):
|
||||||
'description': str,
|
'description': str,
|
||||||
'success-message': str,
|
'success-message': str,
|
||||||
'failure-message': str,
|
'failure-message': str,
|
||||||
|
'merge-failure-message': str,
|
||||||
'footer-message': str,
|
'footer-message': str,
|
||||||
'dequeue-on-new-patchset': bool,
|
'dequeue-on-new-patchset': bool,
|
||||||
'trigger': trigger,
|
'trigger': trigger,
|
||||||
'success': report_actions,
|
'success': report_actions,
|
||||||
'failure': report_actions,
|
'failure': report_actions,
|
||||||
|
'merge-failure': report_actions,
|
||||||
'start': report_actions,
|
'start': report_actions,
|
||||||
'window': window,
|
'window': window,
|
||||||
'window-floor': window_floor,
|
'window-floor': window_floor,
|
||||||
|
|
|
@ -63,6 +63,7 @@ class Pipeline(object):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = None
|
self.description = None
|
||||||
self.failure_message = None
|
self.failure_message = None
|
||||||
|
self.merge_failure_message = None
|
||||||
self.success_message = None
|
self.success_message = None
|
||||||
self.footer_message = None
|
self.footer_message = None
|
||||||
self.dequeue_on_new_patchset = True
|
self.dequeue_on_new_patchset = True
|
||||||
|
@ -171,6 +172,11 @@ class Pipeline(object):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def didMergerSucceed(self, item):
|
||||||
|
if item.current_build_set.unable_to_merge:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def didAnyJobFail(self, item):
|
def didAnyJobFail(self, item):
|
||||||
for job in self.getJobs(item.change):
|
for job in self.getJobs(item.change):
|
||||||
if not job.voting:
|
if not job.voting:
|
||||||
|
@ -206,9 +212,8 @@ class Pipeline(object):
|
||||||
fakebuild.result = 'SKIPPED'
|
fakebuild.result = 'SKIPPED'
|
||||||
item.addBuild(fakebuild)
|
item.addBuild(fakebuild)
|
||||||
|
|
||||||
def setUnableToMerge(self, item, msg):
|
def setUnableToMerge(self, item):
|
||||||
item.current_build_set.unable_to_merge = True
|
item.current_build_set.unable_to_merge = True
|
||||||
item.current_build_set.unable_to_merge_message = msg
|
|
||||||
root = self.getJobTree(item.change.project)
|
root = self.getJobTree(item.change.project)
|
||||||
for job in root.getJobs():
|
for job in root.getJobs():
|
||||||
fakebuild = Build(job, None)
|
fakebuild = Build(job, None)
|
||||||
|
@ -677,7 +682,6 @@ class BuildSet(object):
|
||||||
self.commit = None
|
self.commit = None
|
||||||
self.zuul_url = None
|
self.zuul_url = None
|
||||||
self.unable_to_merge = False
|
self.unable_to_merge = False
|
||||||
self.unable_to_merge_message = None
|
|
||||||
self.failing_reasons = []
|
self.failing_reasons = []
|
||||||
self.merge_state = self.NEW
|
self.merge_state = self.NEW
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,11 @@ class Scheduler(threading.Thread):
|
||||||
pipeline.precedence = precedence
|
pipeline.precedence = precedence
|
||||||
pipeline.failure_message = conf_pipeline.get('failure-message',
|
pipeline.failure_message = conf_pipeline.get('failure-message',
|
||||||
"Build failed.")
|
"Build failed.")
|
||||||
|
pipeline.merge_failure_message = conf_pipeline.get(
|
||||||
|
'merge-failure-message', "Merge Failed.\n\nThis change was "
|
||||||
|
"unable to be automatically merged with the current state of "
|
||||||
|
"the repository. Please rebase your change and upload a new "
|
||||||
|
"patchset.")
|
||||||
pipeline.success_message = conf_pipeline.get('success-message',
|
pipeline.success_message = conf_pipeline.get('success-message',
|
||||||
"Build succeeded.")
|
"Build succeeded.")
|
||||||
pipeline.footer_message = conf_pipeline.get('footer-message', "")
|
pipeline.footer_message = conf_pipeline.get('footer-message', "")
|
||||||
|
@ -233,7 +238,7 @@ class Scheduler(threading.Thread):
|
||||||
'dequeue-on-new-patchset', True)
|
'dequeue-on-new-patchset', True)
|
||||||
|
|
||||||
action_reporters = {}
|
action_reporters = {}
|
||||||
for action in ['start', 'success', 'failure']:
|
for action in ['start', 'success', 'failure', 'merge-failure']:
|
||||||
action_reporters[action] = []
|
action_reporters[action] = []
|
||||||
if conf_pipeline.get(action):
|
if conf_pipeline.get(action):
|
||||||
for reporter_name, params \
|
for reporter_name, params \
|
||||||
|
@ -247,6 +252,11 @@ class Scheduler(threading.Thread):
|
||||||
pipeline.start_actions = action_reporters['start']
|
pipeline.start_actions = action_reporters['start']
|
||||||
pipeline.success_actions = action_reporters['success']
|
pipeline.success_actions = action_reporters['success']
|
||||||
pipeline.failure_actions = action_reporters['failure']
|
pipeline.failure_actions = action_reporters['failure']
|
||||||
|
if len(action_reporters['merge-failure']) > 0:
|
||||||
|
pipeline.merge_failure_actions = \
|
||||||
|
action_reporters['merge-failure']
|
||||||
|
else:
|
||||||
|
pipeline.merge_failure_actions = action_reporters['failure']
|
||||||
|
|
||||||
pipeline.window = conf_pipeline.get('window', 20)
|
pipeline.window = conf_pipeline.get('window', 20)
|
||||||
pipeline.window_floor = conf_pipeline.get('window-floor', 3)
|
pipeline.window_floor = conf_pipeline.get('window-floor', 3)
|
||||||
|
@ -936,6 +946,8 @@ class BasePipelineManager(object):
|
||||||
self.log.info(" %s" % self.pipeline.success_actions)
|
self.log.info(" %s" % self.pipeline.success_actions)
|
||||||
self.log.info(" On failure:")
|
self.log.info(" On failure:")
|
||||||
self.log.info(" %s" % self.pipeline.failure_actions)
|
self.log.info(" %s" % self.pipeline.failure_actions)
|
||||||
|
self.log.info(" On merge-failure:")
|
||||||
|
self.log.info(" %s" % self.pipeline.merge_failure_actions)
|
||||||
|
|
||||||
def getSubmitAllowNeeds(self):
|
def getSubmitAllowNeeds(self):
|
||||||
# Get a list of code review labels that are allowed to be
|
# Get a list of code review labels that are allowed to be
|
||||||
|
@ -1334,10 +1346,7 @@ class BasePipelineManager(object):
|
||||||
build_set.commit = item.change.newrev
|
build_set.commit = item.change.newrev
|
||||||
if not build_set.commit:
|
if not build_set.commit:
|
||||||
self.log.info("Unable to merge change %s" % item.change)
|
self.log.info("Unable to merge change %s" % item.change)
|
||||||
msg = ("This change was unable to be automatically merged "
|
self.pipeline.setUnableToMerge(item)
|
||||||
"with the current state of the repository. Please "
|
|
||||||
"rebase your change and upload a new patchset.")
|
|
||||||
self.pipeline.setUnableToMerge(item, msg)
|
|
||||||
|
|
||||||
def reportItem(self, item):
|
def reportItem(self, item):
|
||||||
if item.reported:
|
if item.reported:
|
||||||
|
@ -1370,10 +1379,12 @@ class BasePipelineManager(object):
|
||||||
self.log.debug("Reporting change %s" % item.change)
|
self.log.debug("Reporting change %s" % item.change)
|
||||||
ret = True # Means error as returned by trigger.report
|
ret = True # Means error as returned by trigger.report
|
||||||
if self.pipeline.didAllJobsSucceed(item):
|
if self.pipeline.didAllJobsSucceed(item):
|
||||||
self.log.debug("success %s %s" % (self.pipeline.success_actions,
|
self.log.debug("success %s" % (self.pipeline.success_actions))
|
||||||
self.pipeline.failure_actions))
|
|
||||||
actions = self.pipeline.success_actions
|
actions = self.pipeline.success_actions
|
||||||
item.setReportedResult('SUCCESS')
|
item.setReportedResult('SUCCESS')
|
||||||
|
elif not self.pipeline.didMergerSucceed(item):
|
||||||
|
actions = self.pipeline.merge_failure_actions
|
||||||
|
item.setReportedResult('MERGER_FAILURE')
|
||||||
else:
|
else:
|
||||||
actions = self.pipeline.failure_actions
|
actions = self.pipeline.failure_actions
|
||||||
item.setReportedResult('FAILURE')
|
item.setReportedResult('FAILURE')
|
||||||
|
@ -1395,20 +1406,26 @@ class BasePipelineManager(object):
|
||||||
|
|
||||||
def formatReport(self, item):
|
def formatReport(self, item):
|
||||||
ret = ''
|
ret = ''
|
||||||
|
|
||||||
|
if not self.pipeline.didMergerSucceed(item):
|
||||||
|
ret += self.pipeline.merge_failure_message
|
||||||
|
if item.dequeued_needing_change:
|
||||||
|
ret += ('\n\nThis change depends on a change that failed to '
|
||||||
|
'merge.')
|
||||||
|
if self.pipeline.footer_message:
|
||||||
|
ret += '\n\n' + self.pipeline.footer_message
|
||||||
|
return ret
|
||||||
|
|
||||||
if self.pipeline.didAllJobsSucceed(item):
|
if self.pipeline.didAllJobsSucceed(item):
|
||||||
ret += self.pipeline.success_message + '\n\n'
|
ret += self.pipeline.success_message + '\n\n'
|
||||||
else:
|
else:
|
||||||
ret += self.pipeline.failure_message + '\n\n'
|
ret += self.pipeline.failure_message + '\n\n'
|
||||||
|
|
||||||
if item.dequeued_needing_change:
|
|
||||||
ret += "This change depends on a change that failed to merge."
|
|
||||||
elif item.current_build_set.unable_to_merge_message:
|
|
||||||
ret += item.current_build_set.unable_to_merge_message
|
|
||||||
else:
|
|
||||||
if self.sched.config.has_option('zuul', 'url_pattern'):
|
if self.sched.config.has_option('zuul', 'url_pattern'):
|
||||||
url_pattern = self.sched.config.get('zuul', 'url_pattern')
|
url_pattern = self.sched.config.get('zuul', 'url_pattern')
|
||||||
else:
|
else:
|
||||||
url_pattern = None
|
url_pattern = None
|
||||||
|
|
||||||
for job in self.pipeline.getJobs(item.change):
|
for job in self.pipeline.getJobs(item.change):
|
||||||
build = item.current_build_set.getBuild(job.name)
|
build = item.current_build_set.getBuild(job.name)
|
||||||
result = build.result
|
result = build.result
|
||||||
|
@ -1453,8 +1470,8 @@ class BasePipelineManager(object):
|
||||||
name = job.name + ' '
|
name = job.name + ' '
|
||||||
ret += '- %s%s : %s%s%s\n' % (name, url, result, elapsed,
|
ret += '- %s%s : %s%s%s\n' % (name, url, result, elapsed,
|
||||||
voting)
|
voting)
|
||||||
ret += '\n'
|
if self.pipeline.footer_message:
|
||||||
ret += self.pipeline.footer_message
|
ret += '\n' + self.pipeline.footer_message
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def formatDescription(self, build):
|
def formatDescription(self, build):
|
||||||
|
|
Loading…
Reference in New Issue