Skip to content
Next Next commit
support map with EnvStep
  • Loading branch information
jetersen committed Jan 27, 2020
commit 21ab92d4437daa4d615e7d17b519b83ebe456f3b
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package org.jenkinsci.plugins.workflow.steps;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.EnvVars;
import hudson.Extension;
Expand All @@ -34,7 +35,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import net.sf.json.JSONObject;
import org.jenkinsci.plugins.structs.describable.CustomDescribableModel;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

Expand Down Expand Up @@ -103,7 +106,7 @@ private static final class ExpanderImpl extends EnvironmentExpander {
}
}

@Extension public static class DescriptorImpl extends StepDescriptor {
@Extension public static class DescriptorImpl extends StepDescriptor implements CustomDescribableModel {

@Override public String getFunctionName() {
return "withEnv";
Expand Down Expand Up @@ -155,6 +158,25 @@ private static final class ExpanderImpl extends EnvironmentExpander {
}
}

@NonNull
@Override
public Map<String, Object> customInstantiate(@NonNull Map<String, Object> arguments) {
Map<String, Object> r = new HashMap<>(arguments);
Object overrides = r.get("overrides");
if (overrides instanceof Map) {
r.put("overrides", toKeyValueList((Map<?, ?>) overrides));
}
return r;
}

private static List<String> toKeyValueList(Map<?, ?> map) {
return map.entrySet().stream()
.filter(e -> e.getKey() instanceof String)
.collect(Collectors.toMap(e -> (String)e.getKey(), e -> e.getValue() == null ? "" : (String) e.getValue()))
.entrySet().stream()
.map(m -> m.getKey() + "=" + m.getValue())
.collect(Collectors.toList());
}
}

}
105 changes: 105 additions & 0 deletions src/test/java/org/jenkinsci/plugins/workflow/steps/EnvStepTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ public class EnvStepTest {
});
}

@Test public void mapOverriding() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"env.CUSTOM = 'initial'\n" +
"env.FOOPATH = node {isUnix() ? '/opt/foos' : 'C:\\\\foos'}\n" +
"env.NULLED = 'outside'\n" +
"node {\n" +
" withEnv([CUSTOM: 'override', NOVEL: 'val', BUILD_TAG: 'custom', NULLED: null, 'FOOPATH+BALL': isUnix() ? '/opt/ball' : 'C:\\\\ball']) {\n" +
" isUnix() ? sh('echo inside CUSTOM=$CUSTOM NOVEL=$NOVEL BUILD_TAG=$BUILD_TAG NULLED=$NULLED FOOPATH=$FOOPATH:') : bat('echo inside CUSTOM=%CUSTOM% NOVEL=%NOVEL% BUILD_TAG=%BUILD_TAG% NULLED=%NULLED% FOOPATH=%FOOPATH%;')\n" +
" echo \"groovy NULLED=${env.NULLED}\"\n" +
" }\n" +
" isUnix() ? sh('echo outside CUSTOM=$CUSTOM NOVEL=$NOVEL NULLED=outside') : bat('echo outside CUSTOM=%CUSTOM% NOVEL=%NOVEL% NULLED=outside')\n" +
"}", true));
WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
story.j.assertLogContains(Functions.isWindows() ? "inside CUSTOM=override NOVEL=val BUILD_TAG=custom NULLED= FOOPATH=C:\\ball;C:\\foos;" : "inside CUSTOM=override NOVEL=val BUILD_TAG=custom NULLED= FOOPATH=/opt/ball:/opt/foos:", b);
story.j.assertLogContains("groovy NULLED=null", b);
story.j.assertLogContains("outside CUSTOM=initial NOVEL= NULLED=outside", b);
List<FlowNode> coreStepNodes = new DepthFirstScanner().filteredNodes(
b.getExecution(),
Predicates.and(
new NodeStepTypePredicate("withEnv"),
n -> n instanceof StepStartNode && !((StepStartNode) n).isBody()));
assertThat(coreStepNodes, Matchers.hasSize(1));
assertEquals("CUSTOM, NOVEL, BUILD_TAG, NULLED, FOOPATH+BALL", ArgumentsAction.getStepArgumentsAsString(coreStepNodes.get(0)));
}
});
}

@Test public void parallel() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
Expand All @@ -105,6 +135,28 @@ public class EnvStepTest {
});
}

@Test public void mapParallel() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"parallel a: {\n" +
" node {withEnv([TOOL: 'aloc']) {semaphore 'a'; isUnix() ? sh('echo a TOOL=$TOOL') : bat('echo a TOOL=%TOOL%')}}\n" +
"}, b: {\n" +
" node {withEnv([TOOL: 'bloc']) {semaphore 'b'; isUnix() ? sh('echo b TOOL=$TOOL') : bat('echo b TOOL=%TOOL%')}}\n" +
"}", true));
WorkflowRun b = p.scheduleBuild2(0).getStartCondition().get();
SemaphoreStep.waitForStart("a/1", b);
SemaphoreStep.waitForStart("b/1", b);
SemaphoreStep.success("a/1", null);
SemaphoreStep.success("b/1", null);
story.j.assertBuildStatusSuccess(story.j.waitForCompletion(b));
story.j.assertLogContains("a TOOL=aloc", b);
story.j.assertLogContains("b TOOL=bloc", b);
}
});
}

@Test public void restarting() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
Expand Down Expand Up @@ -140,6 +192,41 @@ public class EnvStepTest {
});
}

@Test public void mapRestarting() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"def show(which) {\n" +
" echo \"groovy ${which} ${env.TESTVAR}:\"\n" +
" isUnix() ? sh(\"echo shell ${which} \\$TESTVAR:\") : bat(\"echo shell ${which} %TESTVAR%:\")\n" +
"}\n" +
"node {\n" +
" withEnv([TESTVAR: 'val']) {\n" +
" show 'before'\n" +
" semaphore 'restarting'\n" +
" show 'after'\n" +
" }\n" +
" show 'outside'\n" +
"}", true));
WorkflowRun b = p.scheduleBuild2(0).getStartCondition().get();
SemaphoreStep.waitForStart("restarting/1", b);
}
});
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
SemaphoreStep.success("restarting/1", null);
WorkflowRun b = story.j.assertBuildStatusSuccess(story.j.waitForCompletion(story.j.jenkins.getItemByFullName("p", WorkflowJob.class).getLastBuild()));
story.j.assertLogContains("groovy before val:", b);
story.j.assertLogContains("shell before val:", b);
story.j.assertLogContains("groovy after val:", b);
story.j.assertLogContains("shell after val:", b);
story.j.assertLogContains("groovy outside null:", b);
story.j.assertLogContains("shell outside :", b);
}
});
}

@Test public void nested() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
Expand All @@ -158,6 +245,24 @@ public class EnvStepTest {
});
}

@Test public void mapNested() {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
" withEnv([A: 'one']) {\n" +
" withEnv([B: 'two']) {\n" +
" isUnix() ? sh('echo A=$A B=$B') : bat('echo A=%A% B=%B%')\n" +
" }\n" +
" }\n" +
"}", true));
WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
story.j.assertLogContains("A=one B=two", b);
}
});
}

@Test public void configRoundTrip() throws Exception {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
Expand Down