diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java index 35126cfc..6946eab7 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java @@ -81,7 +81,7 @@ public boolean start() throws Exception { } List actions = new ArrayList<>(); - actions.add(new CauseAction(new Cause.UpstreamCause(invokingRun))); + actions.add(new CauseAction(new BuildUpstreamCause(node, invokingRun))); actions.add(new BuildUpstreamNodeAction(node, invokingRun)); if (item instanceof ParameterizedJobMixIn.ParameterizedJob) { diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildUpstreamCause.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildUpstreamCause.java new file mode 100644 index 00000000..f3aeb623 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildUpstreamCause.java @@ -0,0 +1,33 @@ +package org.jenkinsci.plugins.workflow.support.steps.build; + +import hudson.model.Cause; +import hudson.model.Run; +import java.util.Objects; +import org.jenkinsci.plugins.workflow.graph.FlowNode; + +public class BuildUpstreamCause extends Cause.UpstreamCause { + private final String nodeId; + + public BuildUpstreamCause(FlowNode node, Run invokingRun) { + super(invokingRun); + this.nodeId = node.getId(); + } + + public String getNodeId() { + return nodeId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + BuildUpstreamCause that = (BuildUpstreamCause) o; + return Objects.equals(nodeId, that.nodeId); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), nodeId); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java index 877c5783..47abb1ad 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java @@ -35,9 +35,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; import jenkins.branch.MultiBranchProjectFactory; import jenkins.branch.MultiBranchProjectFactoryDescriptor; import jenkins.branch.OrganizationFolder; @@ -49,17 +52,21 @@ import jenkins.scm.impl.mock.MockSCMNavigator; import jenkins.security.QueueItemAuthenticatorConfiguration; import org.apache.commons.lang.StringUtils; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution; import org.jenkinsci.plugins.workflow.cps.SnippetizerTester; +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.jenkinsci.plugins.workflow.test.steps.SemaphoreStep; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; @@ -80,6 +87,8 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assume.assumeThat; public class BuildTriggerStepTest { @@ -115,6 +124,57 @@ public class BuildTriggerStepTest { j.assertLogContains("ds.result=FAILURE", j.buildAndAssertSuccess(us)); } + @Issue("JENKINS-60995") + @Test public void upstreamCause() throws Exception { + FreeStyleProject downstream = j.createFreeStyleProject("downstream"); + WorkflowJob upstream = j.jenkins.createProject(WorkflowJob.class, "upstream"); + + upstream.setDefinition(new CpsFlowDefinition("build 'downstream'", true)); + int numberOfUpstreamBuilds = 3; + for (int i = 0; i < numberOfUpstreamBuilds; i++) { + upstream.scheduleBuild2(0); + // Wait a bit before scheduling next one + Thread.sleep(100); + } + for (WorkflowRun upstreamRun : upstream.getBuilds()) { + j.waitForCompletion(upstreamRun); + j.assertBuildStatus(Result.SUCCESS, upstreamRun); + } + // Wait for all downstream builds to complete + j.waitUntilNoActivity(); + + List upstreamCauses = new ArrayList<>(); + for (Run downstreamRun : downstream.getBuilds()) { + upstreamCauses.addAll(downstreamRun.getCauses().stream() + .filter(BuildUpstreamCause.class::isInstance) + .map(BuildUpstreamCause.class::cast) + .collect(Collectors.toList())); + } + assertThat("There should be as many upstream causes as upstream builds", upstreamCauses, hasSize(numberOfUpstreamBuilds)); + + Set ups = new HashSet<>(); + for (BuildUpstreamCause up : upstreamCauses) { + WorkflowRun upstreamRun = (WorkflowRun) up.getUpstreamRun(); + ups.add(upstreamRun); + FlowExecution execution = upstreamRun.getExecution(); + FlowNode buildTriggerNode = findFirstNodeWithDescriptor(execution, BuildTriggerStep.DescriptorImpl.class); + assertEquals("node id should be build trigger node", buildTriggerNode, execution.getNode(up.getNodeId())); + } + assertEquals("There should be as many upstream causes as referenced upstream builds", numberOfUpstreamBuilds, ups.size()); + } + + 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; + } + @Issue("JENKINS-38339") @Test public void upstreamNodeAction() throws Exception { FreeStyleProject downstream = j.createFreeStyleProject("downstream"); @@ -127,16 +187,15 @@ public class BuildTriggerStepTest { FreeStyleBuild lastDownstreamRun = downstream.getLastBuild(); final FlowExecution execution = lastUpstreamRun.getExecution(); - List nodes = execution.getCurrentHeads(); - assertEquals("node count", 1, nodes.size()); - FlowNode headNode = nodes.get(0); + FlowNode buildTriggerNode = findFirstNodeWithDescriptor(execution, BuildTriggerStep.DescriptorImpl.class); + assertNotNull(buildTriggerNode); List actions = lastDownstreamRun.getActions(BuildUpstreamNodeAction.class); assertEquals("action count", 1, actions.size()); BuildUpstreamNodeAction action = actions.get(0); assertEquals("correct upstreamRunId", action.getUpstreamRunId(), lastUpstreamRun.getExternalizableId()); - assertNotNull("valid upstreamNodeId", execution.getNode(action.getUpstreamNodeId())); + assertEquals("valid upstreamNodeId", buildTriggerNode, execution.getNode(action.getUpstreamNodeId())); } @SuppressWarnings("deprecation")