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> 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)); } }