/*
* The MIT License
*
* Copyright 2014 Jesse Glick.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.workflow;
import hudson.model.Result;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import static org.hamcrest.Matchers.containsString;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.steps.SynchronousStepExecution;
import static org.junit.Assert.*;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* Verifies general DSL functionality.
*/
public class DSLTest {
@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule public JenkinsRule r = new JenkinsRule();
@Test public void overrideFunction() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("echo 'this came from a step'"));
r.assertLogContains("this came from a step", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
p.setDefinition(new CpsFlowDefinition("def echo(s) {println s.toUpperCase()}\necho 'this came from my own function'\nsteps.echo 'but this is still from a step'", true));
WorkflowRun b2 = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("THIS CAME FROM MY OWN FUNCTION", b2);
r.assertLogContains("but this is still from a step", b2);
}
@Issue("JENKINS-43934")
@Test public void flattenGString() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("def message = myJoin(['the', /${'message'.toLowerCase(Locale.ENGLISH)}/]); echo(/What is $message?/)", true));
r.assertLogContains("What is the message?", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
}
public static class MyJoinStep extends Step {
public final String args;
@DataBoundConstructor public MyJoinStep(String args) {this.args = args;}
@Override public StepExecution start(StepContext context) throws Exception {
return new Exec(context, args);
}
private static class Exec extends SynchronousStepExecution<String> {
final String args;
Exec(StepContext context, String args) {
super(context);
this.args = args;
}
@Override protected String run() throws Exception {
return args;
}
}
@TestExtension("flattenGString") public static class DescriptorImpl extends StepDescriptor {
@Override public String getFunctionName() {
return "myJoin";
}
@Override public Set<? extends Class<?>> getRequiredContext() {
return Collections.emptySet();
}
@Override public Step newInstance(Map<String, Object> arguments) throws Exception {
List<?> args = (List<?>) arguments.get("args");
StringBuilder b = new StringBuilder();
for (Object arg : args) {
if (b.length() > 0) {
b.append(' ');
}
b.append((String) arg);
}
return new MyJoinStep(b.toString());
}
}
}
/**
* Tests the ability to execute meta-step with clean syntax
*/
@Issue("JENKINS-29922")
@Test
public void dollar_class_must_die() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "die1");
p.setDefinition(new CpsFlowDefinition("california ocean:'pacific', mountain:'sierra'"));
r.assertLogContains("California from pacific to sierra", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
}
/**
* Split arguments between meta step and state
*/
@Issue("JENKINS-29922")
@Test
public void dollar_class_must_die2() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "die2");
p.setDefinition(new CpsFlowDefinition("california ocean:'pacific', mountain:'sierra', moderate:true"));
assertThat(JenkinsRule.getLog(r.assertBuildStatusSuccess(p.scheduleBuild2(0))).replace("\r\n", "\n"), containsString("Introducing california\nCalifornia from pacific to sierra"));
}
/**
* Split arguments between meta step and state
*/
@Issue("JENKINS-29922")
@Test
public void dollar_class_must_die3() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "die3");
p.setDefinition(new CpsFlowDefinition("nevada()"));
r.assertLogContains("All For Our Country", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
}
/**
* Split arguments between meta step and state, when argument is colliding
*/
@Issue("JENKINS-29922")
@Test
public void dollar_class_must_die_colliding_argument() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "die5");
p.setDefinition(new CpsFlowDefinition("newYork motto:'Empire', moderate:true"));
WorkflowRun run = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
assertThat(JenkinsRule.getLog(run).replace("\r\n", "\n"), containsString("Introducing newYork\nThe Empire State"));
r.assertLogNotContains("New York can be moderate in spring or fall", run);
}
/**
* Single argument state
*/
@Issue("JENKINS-29922")
@Test
public void dollar_class_must_die_onearg() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "die4");
p.setDefinition(new CpsFlowDefinition("newYork 'Empire'"));
r.assertLogContains("The Empire State", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
}
@Issue("JENKINS-29922")
@Test
public void nonexistentFunctions() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("nonexistent()"));
WorkflowRun b = r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0));
r.assertLogContains("nonexistent", b);
r.assertLogContains("wrapInCurve", b);
r.assertLogContains("polygon", b);
}
@Issue("JENKINS-29922")
@Test public void runMetaBlockStep() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("circle {echo 'interior is a disk'}", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("wrapping in a circle", b);
r.assertLogContains("interior is a disk", b);
p.setDefinition(new CpsFlowDefinition("polygon(17) {echo 'constructible with compass and straightedge'}", true));
b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("wrapping in a 17-gon", b);
r.assertLogContains("constructible with compass and straightedge", b);
}
/**
* Tests the ability to execute a step with an unnamed monomorphic describable argument.
*/
@Issue("JENKINS-29711")
@Test
public void monomorphic() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "mon");
p.setDefinition(new CpsFlowDefinition("monomorphStep([firstArg:'one', secondArg:'two'])", true));
r.assertLogContains("First arg: one, second arg: two", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
}
@Issue("JENKINS-29711")
@Test
public void monomorphicSymbol() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "monSymbol");
p.setDefinition(new CpsFlowDefinition("monomorphWithSymbolStep monomorphSymbol(firstArg: 'one', secondArg: 'two')", true));
r.assertLogContains("First arg: one, second arg: two", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
}
/**
* Tests the ability to execute a step with an unnamed monomorphic list argument.
*/
@Issue("JENKINS-29711")
@Test
public void monomorphicList() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "monList");
p.setDefinition(new CpsFlowDefinition("monomorphListStep([[firstArg:'one', secondArg:'two'], [firstArg:'three', secondArg:'four']])", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("First arg: one, second arg: two", b);
r.assertLogContains("First arg: three, second arg: four", b);
}
@Issue("JENKINS-29711")
@Test
public void monomorphicListWithSymbol() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "monListSymbol");
p.setDefinition(new CpsFlowDefinition("monomorphListSymbolStep([monomorphSymbol(firstArg: 'one', secondArg: 'two'), monomorphSymbol(firstArg: 'three', secondArg: 'four')])", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("First arg: one, second arg: two", b);
r.assertLogContains("First arg: three, second arg: four", b);
}
@Issue("JENKINS-38037")
@Test
public void metaStepSyntaxForDataBoundSetters() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "metaStepSyntaxForDataBoundSetters");
p.setDefinition(new CpsFlowDefinition("multiShape(count: 2, name: 'pentagon') { echo 'Multiple shapes' }", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("wrapping in a group of 2 instances of pentagon", b);
r.assertLogContains("Multiple shapes", b);
}
@Issue("JENKINS-38169")
@Test
public void namedSoleParamForStep() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "namedSoleParamForStep");
p.setDefinition(new CpsFlowDefinition("echo message:'Hello world'", true));
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Hello world", b);
}
@Test public void contextClassLoader() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("try {def c = classLoad(getClass().name); error(/did not expect to be able to load ${c} from ${c.classLoader}/)} catch (ClassNotFoundException x) {echo(/good, got ${x}/)}", false));
r.assertBuildStatusSuccess(p.scheduleBuild2(0));
}
public static class CLStep extends AbstractStepImpl {
public final String name;
@DataBoundConstructor public CLStep(String name) {this.name = name;}
public static class Execution extends AbstractSynchronousStepExecution<Class<?>> {
@Inject CLStep step;
protected Class<?> run() throws Exception {
return Thread.currentThread().getContextClassLoader().loadClass(step.name);
}
}
@TestExtension("contextClassLoader") public static class DescriptorImpl extends AbstractStepDescriptorImpl {
public DescriptorImpl() {super(Execution.class);}
@Override public String getFunctionName() {
return "classLoad";
}
}
}
}