diff --git a/pom.xml b/pom.xml
index 2d89b2c..525499d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,18 +62,6 @@
org.jenkins-ci.plugins.workflow
workflow-step-api
-
- org.jenkins-ci.plugins.workflow
- workflow-step-api
- tests
- test
-
-
- org.jenkins-ci.plugins.workflow
- workflow-support
- tests
- test
-
org.jenkins-ci.plugins.workflow
workflow-cps
@@ -88,17 +76,6 @@
org.jenkins-ci.plugins.workflow
workflow-basic-steps
test
-
-
- javax.mail
- mail
-
-
-
-
- org.jenkins-ci.plugins.workflow
- workflow-durable-task-step
- test
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStep.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStep.java
index 35fc52b..4ce4d7f 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStep.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStep.java
@@ -26,10 +26,9 @@
import com.google.common.collect.ImmutableSet;
import hudson.Extension;
-import hudson.model.Run;
-import hudson.model.TaskListener;
import java.util.Set;
import edu.umd.cs.findbugs.annotations.CheckForNull;
+import hudson.model.TaskListener;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
@@ -40,15 +39,12 @@
/**
* A named block.
- *
Former (deprecated) behavior:
- * Marks a flow build as entering a gated “stage”, like a stage in a pipeline.
- * Each job has a set of named stages, each of which acts like a semaphore with an initial permit count,
- * but with the special behavior that only one build may be waiting at any time: the newest.
- * Credit goes to @jtnord for implementing the {@code block} operator in {@code buildflow-extensions}, which inspired this.
*/
public final class StageStep extends Step {
public final String name;
+
+ @Deprecated
@DataBoundSetter public @CheckForNull Integer concurrency;
@DataBoundConstructor public StageStep(String name) {
@@ -77,7 +73,7 @@ public final class StageStep extends Step {
}
@Override public Set extends Class>> getRequiredContext() {
- return ImmutableSet.of(TaskListener.class, Run.class, FlowNode.class);
+ return ImmutableSet.of(TaskListener.class, FlowNode.class);
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStepExecution.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStepExecution.java
index cf1ef1b..984e42a 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStepExecution.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/StageStepExecution.java
@@ -1,42 +1,18 @@
package org.jenkinsci.plugins.workflow.support.steps;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
-import hudson.Extension;
-import hudson.XmlFile;
import hudson.model.InvisibleAction;
-import hudson.model.Job;
-import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
-import hudson.model.listeners.RunListener;
-import java.io.File;
-import java.io.IOException;
import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.logging.Level;
-import static java.util.logging.Level.WARNING;
import java.util.logging.Logger;
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-import edu.umd.cs.findbugs.annotations.Nullable;
import jenkins.model.CauseOfInterruption;
-import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.actions.LabelAction;
-import org.jenkinsci.plugins.workflow.actions.StageAction;
-import org.jenkinsci.plugins.workflow.actions.ThreadNameAction;
-import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
-import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import org.jenkinsci.plugins.workflow.graph.FlowStartNode;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
-import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.support.steps.stage.Messages;
@@ -51,7 +27,9 @@ public class StageStepExecution extends AbstractStepExecutionImpl {
this.step = step;
}
- private static final class StageActionImpl extends InvisibleAction implements StageAction {
+ /** @deprecated only to be able to load old builds, and for a deprecated run mode */
+ @Deprecated
+ private static final class StageActionImpl extends InvisibleAction implements org.jenkinsci.plugins.workflow.actions.StageAction {
private final String stageName;
StageActionImpl(String stageName) {
this.stageName = stageName;
@@ -61,12 +39,13 @@ private static final class StageActionImpl extends InvisibleAction implements St
}
}
+ @SuppressWarnings("deprecation")
@Override
public boolean start() throws Exception {
+ if (step.concurrency != null) {
+ throw new AbortException(Messages.StageStepExecution_concurrency_not_supported());
+ }
if (getContext().hasBody()) { // recommended mode
- if (step.concurrency != null) {
- throw new AbortException(Messages.StageStepExecution_concurrency_not_supported_in_block_mode());
- }
getContext().newBodyInvoker()
.withContexts(EnvironmentExpander.merge(getContext().get(EnvironmentExpander.class),
// NOTE: Other plugins should not be pulling from the environment to determine stage name.
@@ -80,224 +59,23 @@ public boolean start() throws Exception {
}
getContext().get(TaskListener.class).getLogger().println(Messages.StageStepExecution_non_block_mode_deprecated());
FlowNode node = getContext().get(FlowNode.class);
- if (isInsideParallel(node)) {
- throw new AbortException(Messages.StageStepExecution_the_stage_step_must_not_be_used_inside_a());
- }
node.addAction(new LabelAction(step.name));
node.addAction(new StageActionImpl(step.name));
- enter(getContext(), step.name, step.concurrency);
- return false; // execute asynchronously
- }
-
- // TODO switch to FlowNodeSerialWalker or equivalent from https://github.com/jenkinsci/workflow-api-plugin/pull/2
- private static boolean isInsideParallel(FlowNode n) {
- while (true) {
- if (n instanceof BlockEndNode) {
- n = ((BlockEndNode) n).getStartNode();
- }
- if (n.getAction(ThreadNameAction.class) != null) {
- return true;
- }
- List parents = n.getParents();
- if (parents.isEmpty()) {
- assert n instanceof FlowStartNode;
- return false;
- }
- assert parents.size() == 1;
- n = parents.get(0);
- }
- }
-
- private static XmlFile getConfigFile() throws IOException {
- Jenkins j = Jenkins.getInstanceOrNull();
- if (j == null) {
- throw new IOException("Jenkins is not running");
- }
- return new XmlFile(new File(j.getRootDir(), StageStep.class.getName() + ".xml"));
- }
-
- // TODO can this be replaced with StepExecutionIterator?
- private static Map> stagesByNameByJob;
-
- // TODO or delete and make this an instance field in DescriptorImpl
- public static void clear() {
- stagesByNameByJob = null;
- }
-
- @SuppressWarnings("unchecked")
- private static synchronized void load() {
- if (stagesByNameByJob == null) {
- stagesByNameByJob = new TreeMap<>();
- try {
- XmlFile configFile = getConfigFile();
- if (configFile.exists()) {
- stagesByNameByJob = (Map>) configFile.read();
- }
- } catch (IOException x) {
- LOGGER.log(WARNING, null, x);
- }
- LOGGER.log(Level.FINE, "load: {0}", stagesByNameByJob);
- }
- }
-
- private static synchronized void save() {
- try {
- getConfigFile().write(stagesByNameByJob);
- } catch (IOException x) {
- LOGGER.log(WARNING, null, x);
- }
- LOGGER.log(Level.FINE, "save: {0}", stagesByNameByJob);
- }
-
- private static synchronized void enter(StepContext context, String name, Integer concurrency) throws IOException, InterruptedException {
- Run,?> r = context.get(Run.class);
- LOGGER.log(Level.FINE, "enter {0} {1}", new Object[] {r, name});
- println(context, "Entering stage " + name);
- load();
- Job,?> job = r.getParent();
- String jobName = job.getFullName();
- Map stagesByName = stagesByNameByJob.get(jobName);
- if (stagesByName == null) {
- stagesByName = new TreeMap<>();
- stagesByNameByJob.put(jobName, stagesByName);
- }
- Stage stage = stagesByName.get(name);
- if (stage == null) {
- stage = new Stage();
- stagesByName.put(name, stage);
- }
- stage.concurrency = concurrency;
- int build = r.number;
- if (stage.waitingContext != null) {
- // Someone has got to give up.
- if (stage.waitingBuild < build) {
- // Cancel the older one.
- try {
- cancel(stage.waitingContext, context);
- } catch (Exception x) {
- LOGGER.log(WARNING, "could not cancel an older flow (perhaps since deleted?)", x);
- }
- } else if (stage.waitingBuild > build) {
- // Cancel this one. And work with the older one below, instead of the one initiating this call.
- try {
- cancel(context, stage.waitingContext);
- } catch (Exception x) {
- LOGGER.log(WARNING, "could not cancel the current flow", x);
- }
- build = stage.waitingBuild;
- context = stage.waitingContext;
- } else {
- throw new IllegalStateException("the same flow is trying to reënter the stage " + name); // see 'e' with two dots, that's Jesse Glick for you! - KK
- }
- }
- for (Map.Entry entry : stagesByName.entrySet()) {
- if (entry.getKey().equals(name)) {
- continue;
- }
- Stage stage2 = entry.getValue();
- // If we were holding another stage in the same job, release it, unlocking its waiter to proceed.
- if (stage2.holding.remove(build)) {
- if (stage2.waitingContext != null) {
- stage2.unblock("Unblocked since " + r.getDisplayName() + " is moving into stage " + name);
- }
- }
- }
- stage.waitingBuild = build;
- stage.waitingContext = context;
- if (stage.concurrency == null || stage.holding.size() < stage.concurrency) {
- stage.unblock("Proceeding");
- } else {
- println(context, "Waiting for builds " + stage.holding);
- }
- cleanUp(job, jobName);
- save();
- }
-
- private static synchronized void exit(Run,?> r) {
- load();
- LOGGER.log(Level.FINE, "exit {0}: {1}", new Object[] {r, stagesByNameByJob});
- Job,?> job = r.getParent();
- String jobName = job.getFullName();
- Map stagesByName = stagesByNameByJob.get(jobName);
- if (stagesByName == null) {
- return;
- }
- boolean modified = false;
- for (Stage stage : stagesByName.values()) {
- if (stage.holding.contains(r.number)) {
- stage.holding.remove(r.number); // XSTR-757: do not rely on return value of TreeSet.remove(Object)
- modified = true;
- if (stage.waitingContext != null) {
- stage.unblock("Unblocked since " + r.getDisplayName() + " finished");
- }
- }
- }
- if (modified) {
- cleanUp(job, jobName);
- save();
- }
+ getContext().onSuccess(null);
+ return true;
}
- private static void cleanUp(Job,?> job, String jobName) {
- Map stagesByName = stagesByNameByJob.get(jobName);
- assert stagesByName != null;
- Iterator> it = stagesByName.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry entry = it.next();
- Set holding = entry.getValue().holding;
- Iterator it2 = holding.iterator();
- while (it2.hasNext()) {
- Integer number = it2.next();
- if (job.getBuildByNumber(number) == null) {
- // Deleted at some point but did not properly clean up from exit(…).
- LOGGER.log(WARNING, "Cleaning up apparently deleted {0}#{1}", new Object[] {jobName, number});
- it2.remove();
- }
- }
- if (holding.isEmpty()) {
- assert entry.getValue().waitingContext == null : entry;
- it.remove();
- }
- }
- if (stagesByName.isEmpty()) {
- stagesByNameByJob.remove(jobName);
- }
- }
-
- private static void println(StepContext context, String message) {
- if (!context.isReady()) {
- LOGGER.log(Level.FINE, "cannot print message ‘{0}’ to dead {1}", new Object[] {message, context});
- return;
- }
- try {
- context.get(TaskListener.class).getLogger().println(message);
- } catch (Exception x) {
- LOGGER.log(WARNING, "failed to print message to dead " + context, x);
- }
- }
-
- // TODO record the stage it got to and display that
- private static void cancel(StepContext context, StepContext newer) throws IOException, InterruptedException {
- if (context.isReady() && newer.isReady()) {
- println(context, "Canceled since " + newer.get(Run.class).getDisplayName() + " got here");
- println(newer, "Canceling older " + context.get(Run.class).getDisplayName());
- context.onFailure(new FlowInterruptedException(Result.NOT_BUILT, new CanceledCause(newer.get(Run.class))));
- } else {
- LOGGER.log(WARNING, "cannot cancel dead {0} or {1}", new Object[] {context, newer});
- }
- }
-
- /**
- * Records that a flow was canceled while waiting in a stage step because a newer flow entered that stage instead.
- */
+ /** @deprecated only to be able to load old builds */
+ @Deprecated
public static final class CanceledCause extends CauseOfInterruption {
private static final long serialVersionUID = 1;
+ @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "correct")
private final String newerBuild;
- CanceledCause(Run,?> newerBuild) {
- this.newerBuild = newerBuild.getExternalizableId();
+ private CanceledCause() {
+ throw new AssertionError("no longer constructed");
}
public Run,?> getNewerBuild() {
@@ -310,52 +88,6 @@ public Run,?> getNewerBuild() {
}
- private static final class Stage {
- /** Numbers of builds currently running in this stage. */
- final Set holding = new TreeSet<>();
- /** Maximum permitted size of {@link #holding}, or null for unbounded. */
- @CheckForNull
- Integer concurrency;
- /** Context of the build currently waiting to enter this stage, if any. */
- @CheckForNull StepContext waitingContext;
- /** Number of the build corresponding to {@link #waitingContext}, if any. */
- @Nullable
- Integer waitingBuild;
- @Override public String toString() {
- return "Stage[holding=" + holding + ",waitingBuild=" + waitingBuild + ",concurrency=" + concurrency + "]";
- }
- /**
- * Unblocks the build currently waiting.
- * @param message a message to print to the log of the unblocked build
- */
- void unblock(String message) {
- assert Thread.holdsLock(StageStepExecution.class);
- assert waitingContext != null : message;
- assert waitingBuild != null : message;
- if (holding.contains(waitingBuild)) {
- LOGGER.log(WARNING, "{0}: {1} already in {2}", new Object[] {message, waitingBuild, holding});
- }
- /* Not necessarily true, since a later build could reduce the concurrency of an existing stage; could perhaps adjust semantics to skip unblocking in this special case:
- assert concurrency == null || holding.size() < concurrency;
- */
- println(waitingContext, message);
- waitingContext.onSuccess(null);
- holding.add(waitingBuild);
- waitingContext = null;
- waitingBuild = null;
- }
- }
-
- @Extension
- public static final class Listener extends RunListener> {
- @Override public void onCompleted(Run,?> r, TaskListener listener) {
- if (!(r instanceof FlowExecutionOwner.Executable) || ((FlowExecutionOwner.Executable) r).asFlowExecutionOwner() == null) {
- return;
- }
- exit(r);
- }
- }
-
private static final long serialVersionUID = 1L;
}
diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/StageStep/help.html b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/StageStep/help.html
index 764f52b..61eb0d5 100644
--- a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/StageStep/help.html
+++ b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/StageStep/help.html
@@ -1,5 +1,3 @@
Creates a labeled block.
-
- An older, deprecated mode of this step did not take a block argument, and accepted a concurrency parameter.
diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/stage/Messages.properties b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/stage/Messages.properties
index 5137122..b6a3131 100644
--- a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/stage/Messages.properties
+++ b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/stage/Messages.properties
@@ -1,3 +1,2 @@
-StageStepExecution.the_stage_step_must_not_be_used_inside_a=The \u2018stage\u2019 step must not be used inside a \u2018parallel\u2019 block.
-StageStepExecution.concurrency_not_supported_in_block_mode=The \u2018concurrency\u2019 parameter is not supported in block-mode \u2018stage\u2019; try \u2018lock\u2019 and/or \u2018milestone\u2019 steps instead
+StageStepExecution.concurrency_not_supported=The \u2018concurrency\u2019 parameter is no longer supported in \u2018stage\u2019; try \u2018lock\u2019 and/or \u2018milestone\u2019 steps instead.
StageStepExecution.non_block_mode_deprecated=Using the \u2018stage\u2019 step without a block argument is deprecated
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/StageStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/StageStepTest.java
index 9ad3320..b63624c 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/StageStepTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/StageStepTest.java
@@ -25,242 +25,49 @@
package org.jenkinsci.plugins.workflow.support.steps;
import hudson.model.Result;
-import java.util.List;
-import jenkins.model.CauseOfInterruption;
-import jenkins.model.InterruptedBuildAction;
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.support.steps.stage.Messages;
-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;
import org.junit.Test;
-import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
-import org.jvnet.hudson.test.RestartableJenkinsRule;
+import org.jvnet.hudson.test.JenkinsRule;
public class StageStepTest {
@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
- @Rule public RestartableJenkinsRule story = new RestartableJenkinsRule();
-
- @Before public void clear() {
- StageStepExecution.clear();
- }
+ @Rule public JenkinsRule r = new JenkinsRule();
@Issue("JENKINS-26107")
@Test public void blockMode() throws Exception {
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
- p.setDefinition(new CpsFlowDefinition("stage('hello there') {echo 'yes I ran'}", true));
- WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
- story.j.assertLogContains("hello there", b);
- story.j.assertLogContains("yes I ran", b);
- story.j.assertLogNotContains(Messages.StageStepExecution_non_block_mode_deprecated(), b);
- }
- });
+ WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
+ p.setDefinition(new CpsFlowDefinition("stage('hello there') {echo 'yes I ran'}", true));
+ WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ r.assertLogContains("hello there", b);
+ r.assertLogContains("yes I ran", b);
+ r.assertLogNotContains(Messages.StageStepExecution_non_block_mode_deprecated(), b);
}
@Issue("JENKINS-44456")
@Test public void stageNameInEnv() throws Exception {
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
- p.setDefinition(new CpsFlowDefinition("stage('foo-stage') {echo \"STAGE_NAME is ${STAGE_NAME}\"}", true));
- WorkflowRun b = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
- story.j.assertLogContains("STAGE_NAME is foo-stage", b);
- story.j.assertLogNotContains(Messages.StageStepExecution_non_block_mode_deprecated(), b);
- }
- });
- }
-
- @Test public void basics() throws Exception {
- // Timeline (A has concurrency 2, B 1):
- // #1 o-A--------------B-----------------o
- // #2 o-A-------------B x
- // #3 o-A ------------B -------o
- // ^ ^ ^ ^ ^
- // B/1 B/2 B/3 ^ X/1 X/2
- // (restart)
- // (above are the semaphores we must signal)
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "demo");
- p.setDefinition(new CpsFlowDefinition(
- "stage(name: 'A', concurrency: 2);\n" +
- "echo('in A');\n" +
- "semaphore('B');\n" +
- "stage(name: 'B', concurrency: 1);\n" +
- "echo('in B');\n" +
- "semaphore('X');\n" +
- "echo('done')", true));
- WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("in A", b1);
- assertTrue(b1.isBuilding());
- WorkflowRun b2 = p.scheduleBuild2(0).waitForStart();
- story.j.assertLogNotContains("in B", b1);
- story.j.waitForMessage("in A", b2);
- assertTrue(b2.isBuilding());
- WorkflowRun b3 = p.scheduleBuild2(0).waitForStart();
- assertTrue(b3.isBuilding());
- story.j.assertLogNotContains("in B", b2);
- story.j.assertLogNotContains("in A", b3);
- SemaphoreStep.success("B/1", null);
- assertTrue(b1.isBuilding());
- assertTrue(b2.isBuilding());
- assertTrue(b3.isBuilding());
- story.j.waitForMessage("in B", b1);
- story.j.assertLogNotContains("done", b1);
- story.j.assertLogNotContains("in B", b2);
- story.j.waitForMessage("in A", b3);
- story.j.assertLogNotContains("in B", b3);
- SemaphoreStep.success("B/2", null);
- assertTrue(b1.isBuilding());
- assertTrue(b2.isBuilding());
- assertTrue(b3.isBuilding());
- story.j.assertLogNotContains("done", b1);
- story.j.assertLogNotContains("in B", b2);
- story.j.assertLogNotContains("in B", b3);
- SemaphoreStep.success("B/3", null);
- assertTrue(b1.isBuilding());
- story.j.assertBuildStatus(Result.NOT_BUILT, story.j.waitForCompletion(b2));
- story.j.waitForMessage(Messages.StageStepExecution_non_block_mode_deprecated(), b2);
- InterruptedBuildAction iba = b2.getAction(InterruptedBuildAction.class);
- assertNotNull(iba);
- List causes = iba.getCauses();
- assertEquals(1, causes.size());
- assertEquals(StageStepExecution.CanceledCause.class, causes.get(0).getClass());
- assertEquals(b3, ((StageStepExecution.CanceledCause) causes.get(0)).getNewerBuild());
- assertTrue(b3.isBuilding());
- story.j.assertLogNotContains("done", b1);
- story.j.assertLogNotContains("in B", b2);
- story.j.assertLogNotContains("in B", b3);
- }
- });
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- StageStepExecution.clear();
- WorkflowJob p = story.j.jenkins.getItemByFullName("demo", WorkflowJob.class);
- WorkflowRun b1 = p.getBuildByNumber(1);
- WorkflowRun b3 = p.getBuildByNumber(3);
- assertTrue(b1.isBuilding());
- story.j.assertLogNotContains("done", b1);
- assertTrue(b3.isBuilding());
- story.j.assertLogNotContains("in B", b3);
- SemaphoreStep.success("X/1", null);
- story.j.waitForCompletion(b1);
- assertEquals(Result.SUCCESS, b1.getResult());
- story.j.waitForMessage(Messages.StageStepExecution_non_block_mode_deprecated(), b1);
- assertTrue(b3.isBuilding());
- story.j.waitForMessage("done", b1);
- story.j.waitForMessage("in B", b3);
- story.j.assertLogNotContains("done", b3);
- SemaphoreStep.success("X/2", null);
- story.j.waitForCompletion(b3);
- assertEquals(Result.SUCCESS, b3.getResult());
- story.j.waitForMessage("done", b3);
- story.j.waitForMessage(Messages.StageStepExecution_non_block_mode_deprecated(), b3);
- }
- });
- }
-
- @SuppressWarnings("SleepWhileInLoop")
- @Test public void serializability() throws Exception {
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
- p.setDefinition(new CpsFlowDefinition(
- "try {\n" +
- " stage name: 'S', concurrency: 1\n" +
- " echo 'in A'\n" +
- " semaphore 'serializability'\n" +
- "} finally {\n" +
- " node {\n" +
- " echo 'in finally'\n" +
- " }\n" +
- "}", true));
- WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
- SemaphoreStep.waitForStart("serializability/1", b1);
- WorkflowRun b2 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("Waiting for builds [1]", b2);
- WorkflowRun b3 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("Waiting for builds [1]", b3);
- SemaphoreStep.success("serializability/1", null); // b1
- story.j.assertBuildStatusSuccess(story.j.waitForCompletion(b1));
- SemaphoreStep.success("serializability/2", null); // b3
- story.j.assertBuildStatusSuccess(story.j.waitForCompletion(b3));
- story.j.assertBuildStatus(Result.NOT_BUILT, b2);
- story.j.assertLogContains("Canceled since #3 got here", b2);
- story.j.assertLogContains("in finally", b2);
- }
- });
- }
-
- @Issue("JENKINS-27052")
- @Test public void holdingAfterUnblock() throws Exception {
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
- p.setDefinition(new CpsFlowDefinition(
- "stage name: 'A', concurrency: 1\n" +
- "semaphore 'holdingAfterUnblockA'\n" +
- "stage name: 'B', concurrency: 1\n" +
- "semaphore 'holdingAfterUnblockB'\n" +
- "", true));
- WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
- SemaphoreStep.waitForStart("holdingAfterUnblockA/1", b1); // about to leave A
- WorkflowRun b2 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("Waiting for builds [1]", b2);
- SemaphoreStep.success("holdingAfterUnblockA/1", null);
- SemaphoreStep.waitForStart("holdingAfterUnblockB/1", b1); // now in B
- SemaphoreStep.waitForStart("holdingAfterUnblockA/2", b2); // b2 unblocked, now in A
- WorkflowRun b3 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("Waiting for builds [2]", b3);
- }
- });
- }
-
- @Issue("JENKINS-27052")
- @Test public void holdingAfterExitUnblock() throws Exception {
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
- p.setDefinition(new CpsFlowDefinition(
- "stage name: 'A', concurrency: 1\n" +
- "semaphore 'holdingAfterExitUnblock'\n" +
- "", true));
- WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
- SemaphoreStep.waitForStart("holdingAfterExitUnblock/1", b1); // about to leave
- WorkflowRun b2 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("Waiting for builds [1]", b2);
- SemaphoreStep.success("holdingAfterExitUnblock/1", null);
- story.j.waitForCompletion(b1);
- SemaphoreStep.waitForStart("holdingAfterExitUnblock/2", b2); // b2 unblocked
- WorkflowRun b3 = p.scheduleBuild2(0).waitForStart();
- story.j.waitForMessage("Waiting for builds [2]", b3);
- }
- });
+ WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
+ p.setDefinition(new CpsFlowDefinition("stage('foo-stage') {echo \"STAGE_NAME is ${STAGE_NAME}\"}", true));
+ WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
+ r.assertLogContains("STAGE_NAME is foo-stage", b);
+ r.assertLogNotContains(Messages.StageStepExecution_non_block_mode_deprecated(), b);
}
- @Test public void notInsideParallel() throws Exception {
- story.addStep(new Statement() {
- @Override public void evaluate() throws Throwable {
- WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
- p.setDefinition(new CpsFlowDefinition("stage 'one'; parallel one: {}, two: {}; stage 'two'", true));
- story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
- p.setDefinition(new CpsFlowDefinition("parallel one: {stage 'one'}, two: {stage 'two'}", true));
- story.j.assertLogContains(Messages.StageStepExecution_the_stage_step_must_not_be_used_inside_a(), story.j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()));
- p.setDefinition(new CpsFlowDefinition("parallel x: {node {echo 'ok'; dir('subdir') {stage 'oops'}}}", true));
- story.j.assertLogContains(Messages.StageStepExecution_the_stage_step_must_not_be_used_inside_a(), story.j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()));
- p.setDefinition(new CpsFlowDefinition("node {echo 'ok'; stage 'one'}; node {echo 'still ok'; stage 'two'}", true));
- story.j.assertBuildStatusSuccess(p.scheduleBuild2(0));
- }
- });
+ @Test public void deprecatedModes() throws Exception {
+ WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
+ p.setDefinition(new CpsFlowDefinition("stage(name: 'x', concurrency: 1) {}", true));
+ r.assertLogContains(Messages.StageStepExecution_concurrency_not_supported(), r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0)));
+ p.setDefinition(new CpsFlowDefinition("stage name: 'x', concurrency: 1", true));
+ r.assertLogContains(Messages.StageStepExecution_concurrency_not_supported(), r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0)));
+ p.setDefinition(new CpsFlowDefinition("stage 'x'", true));
+ r.assertLogContains(Messages.StageStepExecution_non_block_mode_deprecated(), r.buildAndAssertSuccess(p));
}
}