From 1fec530da85897f21337ea46be557adf72bb416f Mon Sep 17 00:00:00 2001 From: Stuart Rowe Date: Thu, 27 Apr 2023 20:36:35 -0700 Subject: [PATCH] Mark pipeline waitForBuild step with the downstream build result (#112) * Add a WarningAction to the waitForBuild step flownode for unsuccessful builds * Log the result of the completed build * Only add WarningAction when propagate: true Update test * Fix spotbugs error NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE --------- Co-authored-by: Olivier Lamy --- .../steps/build/WaitForBuildListener.java | 22 +++++++-- .../steps/build/WaitForBuildStepTest.java | 48 ++++++++++++++++++- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildListener.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildListener.java index aceb333e..4e0ce047 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildListener.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildListener.java @@ -2,12 +2,15 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.console.ModelHyperlinkNote; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; import java.util.logging.Level; import java.util.logging.Logger; +import org.jenkinsci.plugins.workflow.actions.WarningAction; +import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException; import org.jenkinsci.plugins.workflow.steps.StepContext; @@ -21,11 +24,24 @@ public void onCompleted(Run run, @NonNull TaskListener listener) { for (WaitForBuildAction action : run.getActions(WaitForBuildAction.class)) { StepContext context = action.context; LOGGER.log(Level.FINE, "completing {0} for {1}", new Object[] {run, context}); - if (!action.propagate || run.getResult() == Result.SUCCESS) { + + Result result = run.getResult(); + if (result == null) { /* probably impossible */ + result = Result.FAILURE; + } + try { + context.get(TaskListener.class).getLogger().println("Build " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName()) + " completed: " + result.toString()); + if (action.propagate && result.isWorseThan(Result.SUCCESS)) { + context.get(FlowNode.class).addOrReplaceAction(new WarningAction(result)); + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, null, e); + } + + if (!action.propagate || result == Result.SUCCESS) { context.onSuccess(new RunWrapper(run, false)); } else { - Result result = run.getResult(); - context.onFailure(new FlowInterruptedException(result != null ? result : /* probably impossible */ Result.FAILURE, false, new DownstreamFailureCause(run))); + context.onFailure(new FlowInterruptedException(result, false, new DownstreamFailureCause(run))); } } run.removeActions(WaitForBuildAction.class); diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildStepTest.java index 0f5176b3..4b0aa9ec 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/WaitForBuildStepTest.java @@ -6,18 +6,27 @@ import hudson.model.Run; import hudson.tasks.Builder; import hudson.util.DescribableList; +import org.jenkinsci.plugins.workflow.actions.WarningAction; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode; +import org.jenkinsci.plugins.workflow.flow.FlowExecution; +import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker; +import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.FailureBuilder; -import org.jvnet.hudson.test.SleepBuilder; -import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.SleepBuilder; +import org.jvnet.hudson.test.UnstableBuilder; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class WaitForBuildStepTest { @@ -63,4 +72,39 @@ public class WaitForBuildStepTest { us.setDefinition(new CpsFlowDefinition("waitForBuild runId: 'ds#1'", true)); j.assertLogContains("is already complete", j.buildAndAssertSuccess(us)); } + + @Issue("JENKINS-70983") + @Test public void waitForUnstableBuildWithWarningAction() throws Exception { + FreeStyleProject ds = j.createFreeStyleProject("ds"); + DescribableList> buildersList = ds.getBuildersList(); + buildersList.add(new SleepBuilder(500)); + buildersList.add(new UnstableBuilder()); + WorkflowJob us = j.jenkins.createProject(WorkflowJob.class, "us"); + us.setDefinition(new CpsFlowDefinition( + "def ds = build job: 'ds', waitForStart: true\n" + + "def dsRunId = \"${ds.getFullProjectName()}#${ds.getNumber()}\"\n" + + "try {\n" + + " waitForBuild runId: dsRunId, propagate: true\n" + + "} finally {\n" + + " echo \"'ds' completed with status ${ds.getResult()}\"\n" + + "}", true)); + j.assertLogContains("'ds' completed with status UNSTABLE", j.buildAndAssertStatus(Result.UNSTABLE, us)); + WorkflowRun lastUpstreamRun = us.getLastBuild(); + FlowNode buildTriggerNode = findFirstNodeWithDescriptor(lastUpstreamRun.getExecution(), WaitForBuildStep.DescriptorImpl.class); + WarningAction action = buildTriggerNode.getAction(WarningAction.class); + assertNotNull(action); + assertEquals(action.getResult(), Result.UNSTABLE); + } + + private static FlowNode findFirstNodeWithDescriptor(FlowExecution execution, Class cls) { + for (FlowNode node : new FlowGraphWalker(execution)) { + if (node instanceof StepAtomNode) { + StepAtomNode stepAtomNode = (StepAtomNode) node; + if (cls.isInstance(stepAtomNode.getDescriptor())) { + return stepAtomNode; + } + } + } + return null; + } }