[JENKINS-49779] Feature/sequentiel add more tests (#1779)

* [JENKINS-49779] Feature/sequentiel add more tests

Signed-off-by: olivier lamy <olamy@apache.org>
This commit is contained in:
Olivier Lamy 2018-08-08 05:26:48 +10:00 committed by GitHub
parent 64ee5d460e
commit 170bd6480f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 19 deletions

View File

@ -260,15 +260,19 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
}else{
if(parallelNestedStages) {
String endId = parallelBranchEndNodes.peek().getId();
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endId);
if(stack==null){
stack=new Stack<>();
stackPerEnd.put(endId, stack);
if(parallelBranchEndNodes.isEmpty()){
logger.debug("skip parsing stage {} but parallelBranchEndNodes is empty", stage);
} else {
String endId = parallelBranchEndNodes.peek().getId();
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endId);
if(stack==null){
stack=new Stack<>();
stackPerEnd.put(endId, stack);
}
stack.add(stage);
}
stack.add(stage);
}
if(nextStage != null) {
if(nextStage != null&&!parallelNestedStages) {
nextStage.addParent(stage);
stage.addEdge(nextStage);
}
@ -349,7 +353,7 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
}
branch.setPipelineActions(branchActions);
// do we have nested sequential stages for this parallel branch?
// do we have sequential stages for this parallel branch?
Stack<FlowNodeWrapper> stack = stackPerEnd.get(endNode.getId());
if(stack!=null&&!stack.isEmpty()){
// yes so we can rebuild the graph here
@ -373,11 +377,15 @@ public class PipelineNodeGraphVisitor extends StandardChunkVisitor implements No
branch.addEdge(flowNodeWrapper);
flowNodeWrapper.addParent(branch);
nodes.add(flowNodeWrapper);
stack.stream().forEach(nodeWrapper->{
while(!stack.isEmpty()){
FlowNodeWrapper nodeWrapper = stack.pop();
nodes.peekLast().addEdge(nodeWrapper);
nodeWrapper.addParent(nodes.peekLast());
nodes.add( nodeWrapper );
});
if(stack.isEmpty()&&nextStage!=null){
nodeWrapper.addEdge(nextStage);
}
}
}
else if(nextStage!=null) {
branch.addEdge(nextStage);

View File

@ -1,5 +1,6 @@
package io.jenkins.blueocean.rest.impl.pipeline;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.Action;
import hudson.model.Queue;
import io.jenkins.blueocean.commons.JsonConverter;
@ -34,13 +35,16 @@ import org.kohsuke.stapler.export.Exported;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.CheckForNull;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
/**
* Implementation of {@link BluePipelineNode}.
@ -93,6 +97,12 @@ public class PipelineNodeImpl extends BluePipelineNode {
return status.getState();
}
@Override
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public @CheckForNull String getFirstParent() {
return node.getFirstParent() == null ? null : node.getFirstParent().getId();
}
@Override
public Date getStartTime() {
long nodeTime = node.getTiming().getStartTimeMillis();
@ -174,9 +184,9 @@ public class PipelineNodeImpl extends BluePipelineNode {
public boolean isRestartable() {
RestartDeclarativePipelineAction restartDeclarativePipelineAction =
this.run.getAction( RestartDeclarativePipelineAction.class );
if ( restartDeclarativePipelineAction != null ) {
if (restartDeclarativePipelineAction != null) {
List<String> restartableStages = restartDeclarativePipelineAction.getRestartableStages();
if ( restartableStages != null ) {
if (restartableStages != null) {
return restartableStages.contains(this.getDisplayName())
&& this.getStateObj() == BlueRun.BlueRunState.FINISHED;
}
@ -254,13 +264,9 @@ public class PipelineNodeImpl extends BluePipelineNode {
}
private List<Edge> buildEdges(List<FlowNodeWrapper> nodes) {
List<Edge> edges = new ArrayList<>();
if (!nodes.isEmpty()) {
for (FlowNodeWrapper edge : nodes) {
edges.add(new EdgeImpl(edge));
}
}
return edges;
return nodes.isEmpty()? Collections.emptyList():
nodes.stream().map( nodeWrapper -> new EdgeImpl( nodeWrapper ) ).collect( Collectors.toList() );
}
FlowNodeWrapper getFlowNodeWrapper() {

View File

@ -47,6 +47,7 @@ import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import static io.jenkins.blueocean.rest.impl.pipeline.PipelineStepImpl.PARAMETERS_ELEMENT;
@ -2397,6 +2398,106 @@ public class PipelineNodeTest extends PipelineBaseTest {
assertEquals(7, nodes.size());
}
@Test
@Issue("JENKINS-49050")
public void sequentialParallelStages() throws Exception {
WorkflowJob p = createWorkflowJobWithJenkinsfile( getClass(), "sequentialParallel.jenkinsfile");
Slave s = j.createOnlineSlave();
s.setNumExecutors(2);
// Run until completed
WorkflowRun run = p.scheduleBuild2( 0).waitForStart();
j.waitForCompletion( run );
PipelineNodeGraphVisitor pipelineNodeGraphVisitor = new PipelineNodeGraphVisitor( run );
assertTrue( pipelineNodeGraphVisitor.isDeclarative() );
List<FlowNodeWrapper> wrappers = pipelineNodeGraphVisitor.getPipelineNodes();
assertEquals(9, wrappers.size());
Optional<FlowNodeWrapper> optionalFlowNodeWrapper =
wrappers.stream().filter( nodeWrapper -> nodeWrapper.getDisplayName().equals( "first-sequential-stage" ) )
.findFirst();
// we ensure "multiple-stages" is parent of "first-sequential-stage"
assertTrue( optionalFlowNodeWrapper.isPresent() );
assertEquals( 1, optionalFlowNodeWrapper.get().edges.size() );
assertEquals( "second-sequential-stage", optionalFlowNodeWrapper.get().edges.get( 0 ).getDisplayName() );
final String parentId = optionalFlowNodeWrapper.get().getFirstParent().getId();
optionalFlowNodeWrapper =
wrappers.stream().filter( nodeWrapper -> nodeWrapper.getId().equals( parentId ) )
.findFirst();
assertTrue( optionalFlowNodeWrapper.isPresent() );
assertEquals( "multiple-stages", optionalFlowNodeWrapper.get().getDisplayName() );
assertEquals( 1, optionalFlowNodeWrapper.get().edges.size() );
optionalFlowNodeWrapper.get().edges.stream()
.filter( nodeWrapper -> nodeWrapper.getDisplayName().equals( "first-sequential-stage" ) )
.findFirst();
assertTrue( optionalFlowNodeWrapper.isPresent() );
optionalFlowNodeWrapper =
wrappers.stream().filter( nodeWrapper -> nodeWrapper.getDisplayName().equals( "other-single-stage" ) )
.findFirst();
assertTrue( optionalFlowNodeWrapper.isPresent() );
final String otherParentId = optionalFlowNodeWrapper.get().getFirstParent().getId();
optionalFlowNodeWrapper =
wrappers.stream().filter( nodeWrapper -> nodeWrapper.getId().equals( otherParentId ) )
.findFirst();
assertTrue( optionalFlowNodeWrapper.isPresent() );
assertEquals( "parent", optionalFlowNodeWrapper.get().getDisplayName() );
assertEquals( 3, optionalFlowNodeWrapper.get().edges.size() );
optionalFlowNodeWrapper =
wrappers.stream().filter( nodeWrapper -> nodeWrapper.getDisplayName().equals( "second-sequential-stage" ) )
.findFirst();
assertTrue( optionalFlowNodeWrapper.isPresent() );
assertEquals(1, optionalFlowNodeWrapper.get().edges.size() );
assertEquals( "third-sequential-stage", optionalFlowNodeWrapper.get().edges.get( 0 ).getDisplayName() );
optionalFlowNodeWrapper =
wrappers.stream().filter( nodeWrapper -> nodeWrapper.getDisplayName().equals( "third-sequential-stage" ) )
.findFirst();
assertTrue( optionalFlowNodeWrapper.isPresent() );
assertEquals(1, optionalFlowNodeWrapper.get().edges.size() );
assertEquals( "second-solo", optionalFlowNodeWrapper.get().edges.get( 0 ).getDisplayName() );
List<Map> nodes = get("/organizations/jenkins/pipelines/" + p.getName() + "/runs/1/nodes/", List.class);
assertEquals(9, nodes.size());
Optional<Map> firstSeqStage = nodes.stream()
.filter( map -> map.get( "displayName" )
.equals( "first-sequential-stage" ) ).findFirst();
assertTrue( firstSeqStage.isPresent() );
String firstParentId = (String) firstSeqStage.get().get( "firstParent" );
Optional<Map> parentStage = nodes.stream()
.filter( map -> map.get( "id" )
.equals( firstParentId ) ).findFirst();
assertTrue( parentStage.isPresent() );
assertEquals( "multiple-stages", parentStage.get().get( "displayName" ) );
// ensure no issue getting steps for each node
for(Map<String,String> node:nodes){
String id = node.get( "id" );
List<Map> steps =
get( "/organizations/jenkins/pipelines/" + p.getName() + "/runs/1/nodes/" + id +"/steps/", List.class );
assertFalse( steps.get( 0 ).isEmpty() );
}
}
@Test
public void nestedStagesGroups() throws Exception {

View File

@ -0,0 +1,50 @@
pipeline {
agent any
stages {
stage('first-solo') {
steps {
sh 'echo \'dummy text first-solo\''
}
}
stage('parent') {
parallel {
stage('single-stage') {
steps {
sh 'echo \'dummy text single-stage\''
}
}
stage('multiple-stages') {
stages {
stage('first-sequential-stage') {
steps {
sh 'echo \'dummy text first-sequential-stage\''
}
}
stage('second-sequential-stage') {
steps {
sh 'echo \'dummy text second-sequential-stage\''
}
}
stage('third-sequential-stage') {
steps {
sh 'echo \'dummy text third-sequential-stage\''
}
}
}
}
stage('other-single-stage') {
steps {
sh 'echo \'dummy text other-single-stage\''
}
}
}
}
stage('second-solo') {
steps {
sh 'echo \'dummy text second-solo\''
}
}
}
}

View File

@ -9,6 +9,7 @@ import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.verb.POST;
import javax.annotation.CheckForNull;
import java.util.List;
import static io.jenkins.blueocean.rest.model.KnownCapabilities.BLUE_PIPELINE_NODE;
@ -79,6 +80,12 @@ public abstract class BluePipelineNode extends BluePipelineStep {
@Exported
public abstract boolean isRestartable();
/**
* @return the id of the first immediate parent
*/
@Exported
public @CheckForNull abstract String getFirstParent();
/**
*
* @param request To restart the content must be simple json body with a field <code>restart</code> will value <code>true</code>